Initial commit: ap_ds Code Repository System v1.0.0
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}404 - 页面未找到{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="repo-container repo-text-center repo-py-10">
|
||||
<span style="font-size: 6rem;">🔍</span>
|
||||
<h1 class="repo-text-2xl repo-font-bold repo-mt-4">404</h1>
|
||||
<p class="repo-text-secondary repo-mt-2">页面未找到</p>
|
||||
<a href="{{ url_for('index') }}" class="repo-btn repo-btn--primary repo-mt-6">返回首页</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,12 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}500 - 服务器错误{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="repo-container repo-text-center repo-py-10">
|
||||
<span style="font-size: 6rem;">⚠️</span>
|
||||
<h1 class="repo-text-2xl repo-font-bold repo-mt-4">500</h1>
|
||||
<p class="repo-text-secondary repo-mt-2">服务器内部错误</p>
|
||||
<a href="{{ url_for('index') }}" class="repo-btn repo-btn--primary repo-mt-6">返回首页</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,90 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}分支管理 - ap_ds{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="repo-container">
|
||||
<div class="repo-flex repo-justify-between repo-items-center repo-mb-6">
|
||||
<h1 class="repo-text-2xl repo-font-bold">分支与版本管理</h1>
|
||||
<a href="{{ url_for('admin_dashboard') }}" class="repo-btn repo-btn--ghost">返回仪表板</a>
|
||||
</div>
|
||||
|
||||
<!-- 创建分支 -->
|
||||
<div class="repo-card repo-mb-6">
|
||||
<div class="repo-card__header">
|
||||
<h3 class="repo-card__title">创建新分支</h3>
|
||||
</div>
|
||||
<div class="repo-card__body">
|
||||
<form method="post" action="{{ url_for('admin_branch_create') }}" class="repo-flex repo-gap-3">
|
||||
<input type="text" name="branch_name" class="repo-input" placeholder="分支名称 (如: LFV)" required>
|
||||
<button type="submit" class="repo-btn repo-btn--primary">创建分支</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 上传新版本 -->
|
||||
<div class="repo-card repo-mb-6">
|
||||
<div class="repo-card__header">
|
||||
<h3 class="repo-card__title">上传新版本</h3>
|
||||
</div>
|
||||
<div class="repo-card__body">
|
||||
<form method="post" action="{{ url_for('admin_version_upload') }}" enctype="multipart/form-data">
|
||||
<div class="repo-row">
|
||||
<div class="repo-col">
|
||||
<select name="branch" class="repo-select__trigger repo-w-full" required>
|
||||
<option value="">选择分支</option>
|
||||
{% for branch_name in branches.keys() %}
|
||||
<option value="{{ branch_name }}">{{ branch_name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="repo-col">
|
||||
<input type="text" name="version" class="repo-input" placeholder="版本号 (如: v2.5.0)" required>
|
||||
</div>
|
||||
<div class="repo-col">
|
||||
<input type="file" name="files" class="repo-input" multiple>
|
||||
</div>
|
||||
<div class="repo-col">
|
||||
<button type="submit" class="repo-btn repo-btn--primary">上传版本</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分支列表 -->
|
||||
{% for branch_name, versions in branches.items() %}
|
||||
<div class="repo-card repo-mb-6">
|
||||
<div class="repo-card__header">
|
||||
<h3 class="repo-card__title">📁 {{ branch_name }}</h3>
|
||||
</div>
|
||||
<div class="repo-card__body">
|
||||
<table class="repo-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>版本</th>
|
||||
<th>创建时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for version in versions %}
|
||||
<tr>
|
||||
<td>{{ version.name }}</td>
|
||||
<td class="repo-text-secondary">{{ version.created }}</td>
|
||||
<td>
|
||||
<a href="{{ url_for('admin_version_delete', branch=branch_name, version=version.name) }}"
|
||||
class="repo-btn repo-btn--sm repo-btn--danger"
|
||||
onclick="return confirm('确定要删除版本 {{ version.name }} 吗?')">
|
||||
删除
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,117 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}管理仪表板 - ap_ds{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="repo-container">
|
||||
<div class="repo-flex repo-justify-between repo-items-center repo-mb-6">
|
||||
<h1 class="repo-text-2xl repo-font-bold">管理仪表板</h1>
|
||||
<a href="{{ url_for('admin_logout') }}" class="repo-btn repo-btn--danger">登出</a>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<div class="repo-row repo-mb-6">
|
||||
<div class="repo-col">
|
||||
<div class="repo-card">
|
||||
<div class="repo-card__body repo-text-center">
|
||||
<div class="repo-text-3xl repo-font-bold" style="color: var(--repo-color-primary);">{{ stats.branches }}</div>
|
||||
<div class="repo-text-secondary">分支</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="repo-col">
|
||||
<div class="repo-card">
|
||||
<div class="repo-card__body repo-text-center">
|
||||
<div class="repo-text-3xl repo-font-bold" style="color: var(--repo-color-success);">{{ stats.versions }}</div>
|
||||
<div class="repo-text-secondary">版本</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="repo-col">
|
||||
<div class="repo-card">
|
||||
<div class="repo-card__body repo-text-center">
|
||||
<div class="repo-text-3xl repo-font-bold" style="color: var(--repo-color-warning);">{{ stats.feedbacks }}</div>
|
||||
<div class="repo-text-secondary">反馈总数</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="repo-col">
|
||||
<div class="repo-card">
|
||||
<div class="repo-card__body repo-text-center">
|
||||
<div class="repo-text-3xl repo-font-bold" style="color: var(--repo-color-danger);">{{ stats.unread_feedbacks }}</div>
|
||||
<div class="repo-text-secondary">未读反馈</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 快速操作 -->
|
||||
<div class="repo-row">
|
||||
<div class="repo-col">
|
||||
<div class="repo-card">
|
||||
<div class="repo-card__header">
|
||||
<h3 class="repo-card__title">上传 WHL 文件</h3>
|
||||
</div>
|
||||
<div class="repo-card__body">
|
||||
<form method="post" action="{{ url_for('admin_whl_upload') }}" enctype="multipart/form-data">
|
||||
<input type="file" name="whl_file" accept=".whl" class="repo-input repo-mb-3" required>
|
||||
<button type="submit" class="repo-btn repo-btn--primary">上传</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="repo-col">
|
||||
<div class="repo-card">
|
||||
<div class="repo-card__header">
|
||||
<h3 class="repo-card__title">管理链接</h3>
|
||||
</div>
|
||||
<div class="repo-card__body">
|
||||
<div class="repo-flex repo-gap-3">
|
||||
<a href="{{ url_for('admin_branches') }}" class="repo-btn repo-btn--primary">分支管理</a>
|
||||
<a href="{{ url_for('admin_feedbacks') }}" class="repo-btn repo-btn--secondary">反馈管理</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 最近反馈 -->
|
||||
<div class="repo-card repo-mt-6">
|
||||
<div class="repo-card__header">
|
||||
<h3 class="repo-card__title">最近反馈</h3>
|
||||
</div>
|
||||
<div class="repo-card__body">
|
||||
{% if recent_feedbacks %}
|
||||
<table class="repo-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>时间</th>
|
||||
<th>邮箱</th>
|
||||
<th>内容</th>
|
||||
<th>状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for f in recent_feedbacks %}
|
||||
<tr>
|
||||
<td class="repo-text-sm">{{ f.created }}</td>
|
||||
<td class="repo-text-sm">{{ f.email }}</td>
|
||||
<td class="repo-text-sm repo-truncate" style="max-width: 300px;">{{ f.content[:50] }}...</td>
|
||||
<td>
|
||||
{% if f.status == 'unread' %}
|
||||
<span class="repo-tag repo-tag--danger">未读</span>
|
||||
{% else %}
|
||||
<span class="repo-tag repo-tag--default">已读</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="repo-text-secondary repo-text-center">暂无反馈</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,62 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}反馈管理 - ap_ds{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="repo-container">
|
||||
<div class="repo-flex repo-justify-between repo-items-center repo-mb-6">
|
||||
<h1 class="repo-text-2xl repo-font-bold">反馈管理</h1>
|
||||
<a href="{{ url_for('admin_dashboard') }}" class="repo-btn repo-btn--ghost">返回仪表板</a>
|
||||
</div>
|
||||
|
||||
<div class="repo-card">
|
||||
<div class="repo-card__body">
|
||||
{% if feedbacks %}
|
||||
<table class="repo-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>时间</th>
|
||||
<th>邮箱</th>
|
||||
<th>内容</th>
|
||||
<th>状态</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for f in feedbacks|reverse %}
|
||||
<tr>
|
||||
<td>{{ f.id }}</td>
|
||||
<td class="repo-text-sm">{{ f.created }}</td>
|
||||
<td class="repo-text-sm">{{ f.email }}</td>
|
||||
<td class="repo-text-sm" style="max-width: 400px;">
|
||||
<div class="repo-line-clamp-2">{{ f.content }}</div>
|
||||
</td>
|
||||
<td>
|
||||
{% if f.status == 'unread' %}
|
||||
<span class="repo-tag repo-tag--danger">未读</span>
|
||||
{% else %}
|
||||
<span class="repo-tag repo-tag--default">已读</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<div class="repo-flex repo-gap-2">
|
||||
{% if f.status == 'unread' %}
|
||||
<a href="{{ url_for('admin_feedback_mark_read', feedback_id=f.id) }}" class="repo-btn repo-btn--sm repo-btn--primary">标记已读</a>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('admin_feedback_delete', feedback_id=f.id) }}"
|
||||
class="repo-btn repo-btn--sm repo-btn--danger"
|
||||
onclick="return confirm('确定要删除这条反馈吗?')">删除</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="repo-text-secondary repo-text-center">暂无反馈</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>管理员登录 - ap_ds</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css.css') }}">
|
||||
</head>
|
||||
<body style="background: var(--repo-color-bg-secondary); min-height: 100vh; display: flex; align-items: center; justify-content: center;">
|
||||
<div class="repo-card" style="width: 100%; max-width: 400px;">
|
||||
<div class="repo-card__header">
|
||||
<h2 class="repo-card__title">管理员登录</h2>
|
||||
</div>
|
||||
<div class="repo-card__body">
|
||||
{% if error %}
|
||||
<div class="repo-message repo-message--error" style="position: static; transform: none; margin-bottom: 1rem;">
|
||||
{{ error }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<form method="post">
|
||||
<div class="repo-mb-4">
|
||||
<label class="repo-text-sm repo-font-medium repo-mb-2 repo-block">密码</label>
|
||||
<input type="password" name="password" class="repo-input" required autofocus>
|
||||
</div>
|
||||
<button type="submit" class="repo-btn repo-btn--primary repo-w-full">登录</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,581 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}ap_ds · 轻量级Python音频库{% endblock %}</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css.css') }}">
|
||||
<!-- highlight.js 主题 (浅色) -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/python.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<style>
|
||||
/* 浅色主题覆盖 - 基于 css.css 变量 */
|
||||
:root {
|
||||
--repo-color-primary: #3b82f6;
|
||||
--repo-color-primary-hover: #2563eb;
|
||||
--repo-color-text: #1f2937;
|
||||
--repo-color-text-secondary: #4b5563;
|
||||
--repo-color-text-tertiary: #9ca3af;
|
||||
--repo-color-bg: #ffffff;
|
||||
--repo-color-bg-secondary: #f9fafb;
|
||||
--repo-color-bg-tertiary: #f3f4f6;
|
||||
--repo-color-border: #e5e7eb;
|
||||
--repo-color-border-hover: #d1d5db;
|
||||
}
|
||||
|
||||
/* 全局样式 */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--repo-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif);
|
||||
font-size: var(--repo-font-size-base, 14px);
|
||||
line-height: var(--repo-line-height-base, 1.5);
|
||||
color: var(--repo-color-text);
|
||||
background-color: var(--repo-color-bg-secondary);
|
||||
}
|
||||
|
||||
/* 导航栏 - 使用 css.css 变量 */
|
||||
.header {
|
||||
background-color: var(--repo-color-bg);
|
||||
border-bottom: 1px solid var(--repo-color-border);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: var(--repo-z-sticky, 100);
|
||||
}
|
||||
|
||||
.header-inner {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 0 var(--repo-spacing-6, 16px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: var(--repo-header-height, 60px);
|
||||
}
|
||||
|
||||
.logo-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--repo-spacing-4, 16px);
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: var(--repo-font-size-xl, 18px);
|
||||
font-weight: 600;
|
||||
color: var(--repo-color-text);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.logo span {
|
||||
color: var(--repo-color-primary);
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: flex;
|
||||
gap: var(--repo-spacing-4, 16px);
|
||||
}
|
||||
|
||||
.nav-links a {
|
||||
color: var(--repo-color-text-secondary);
|
||||
text-decoration: none;
|
||||
font-size: var(--repo-font-size-base, 14px);
|
||||
font-weight: 500;
|
||||
padding: var(--repo-spacing-2, 8px) 0;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-links a:hover {
|
||||
color: var(--repo-color-primary);
|
||||
}
|
||||
|
||||
.repo-badge {
|
||||
background-color: var(--repo-color-bg-tertiary);
|
||||
padding: 4px 10px;
|
||||
border-radius: var(--repo-radius-full, 20px);
|
||||
font-size: var(--repo-font-size-xs, 12px);
|
||||
font-family: var(--repo-font-family-mono, monospace);
|
||||
color: var(--repo-color-text-secondary);
|
||||
}
|
||||
|
||||
/* 主容器 */
|
||||
.main-container {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: var(--repo-spacing-6, 24px) var(--repo-spacing-4, 16px);
|
||||
}
|
||||
|
||||
/* 仓库头部 */
|
||||
.repo-header {
|
||||
margin-bottom: var(--repo-spacing-6, 24px);
|
||||
}
|
||||
|
||||
.repo-path {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--repo-spacing-2, 8px);
|
||||
font-size: var(--repo-font-size-xl, 18px);
|
||||
font-weight: 600;
|
||||
margin-bottom: var(--repo-spacing-4, 16px);
|
||||
}
|
||||
|
||||
.repo-path a {
|
||||
color: var(--repo-color-primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.repo-path a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.repo-path .separator {
|
||||
color: var(--repo-color-text-tertiary);
|
||||
}
|
||||
|
||||
/* 两栏布局 */
|
||||
.two-column {
|
||||
display: flex;
|
||||
gap: var(--repo-spacing-8, 32px);
|
||||
}
|
||||
|
||||
/* 左侧边栏 */
|
||||
.sidebar {
|
||||
width: 260px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar-section {
|
||||
margin-bottom: var(--repo-spacing-6, 24px);
|
||||
}
|
||||
|
||||
.sidebar-title {
|
||||
font-size: var(--repo-font-size-xs, 12px);
|
||||
font-weight: 600;
|
||||
color: var(--repo-color-text-tertiary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: var(--repo-spacing-3, 12px);
|
||||
}
|
||||
|
||||
.branch-selector {
|
||||
background-color: var(--repo-color-bg);
|
||||
border: 1px solid var(--repo-color-border);
|
||||
border-radius: var(--repo-radius-md, 6px);
|
||||
padding: var(--repo-spacing-2, 8px) var(--repo-spacing-3, 12px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
margin-bottom: var(--repo-spacing-4, 16px);
|
||||
color: var(--repo-color-text);
|
||||
}
|
||||
|
||||
.branch-selector:hover {
|
||||
background-color: var(--repo-color-bg-hover);
|
||||
border-color: var(--repo-color-border-hover);
|
||||
}
|
||||
|
||||
/* 右侧主内容 */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* 文件列表表格 */
|
||||
.file-list {
|
||||
border: 1px solid var(--repo-color-border);
|
||||
border-radius: var(--repo-radius-lg, 8px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.file-list-header {
|
||||
background-color: var(--repo-color-bg-secondary);
|
||||
padding: var(--repo-spacing-3, 12px) var(--repo-spacing-4, 16px);
|
||||
border-bottom: 1px solid var(--repo-color-border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: var(--repo-font-size-xs, 12px);
|
||||
color: var(--repo-color-text-tertiary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.file-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: var(--repo-spacing-2, 8px) var(--repo-spacing-4, 16px);
|
||||
border-bottom: 1px solid var(--repo-color-border);
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.file-row:hover {
|
||||
background-color: var(--repo-color-bg-hover);
|
||||
}
|
||||
|
||||
.file-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--repo-spacing-2, 8px);
|
||||
}
|
||||
|
||||
.file-name a {
|
||||
color: var(--repo-color-primary);
|
||||
text-decoration: none;
|
||||
font-family: var(--repo-font-family-mono, monospace);
|
||||
font-size: var(--repo-font-size-sm, 13px);
|
||||
}
|
||||
|
||||
.file-name a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.file-message {
|
||||
flex: 2;
|
||||
font-size: var(--repo-font-size-xs, 12px);
|
||||
color: var(--repo-color-text-tertiary);
|
||||
}
|
||||
|
||||
.file-time {
|
||||
width: 100px;
|
||||
font-size: var(--repo-font-size-xs, 12px);
|
||||
color: var(--repo-color-text-tertiary);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* README 区域 */
|
||||
.readme-section {
|
||||
margin-top: var(--repo-spacing-8, 32px);
|
||||
border: 1px solid var(--repo-color-border);
|
||||
border-radius: var(--repo-radius-lg, 8px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.readme-header {
|
||||
background-color: var(--repo-color-bg-secondary);
|
||||
padding: var(--repo-spacing-3, 12px) var(--repo-spacing-4, 16px);
|
||||
border-bottom: 1px solid var(--repo-color-border);
|
||||
font-weight: 600;
|
||||
font-size: var(--repo-font-size-base, 14px);
|
||||
color: var(--repo-color-text);
|
||||
}
|
||||
|
||||
.readme-content {
|
||||
padding: var(--repo-spacing-8, 32px);
|
||||
background-color: var(--repo-color-bg);
|
||||
}
|
||||
|
||||
.readme-content h1, .readme-content h2, .readme-content h3 {
|
||||
margin-top: var(--repo-spacing-6, 24px);
|
||||
margin-bottom: var(--repo-spacing-4, 16px);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.readme-content h1 { font-size: 2em; border-bottom: 1px solid var(--repo-color-border); padding-bottom: 0.3em; }
|
||||
.readme-content h2 { font-size: 1.5em; border-bottom: 1px solid var(--repo-color-border); padding-bottom: 0.3em; }
|
||||
.readme-content h3 { font-size: 1.25em; }
|
||||
.readme-content p { margin-bottom: var(--repo-spacing-4, 16px); line-height: 1.6; }
|
||||
.readme-content code {
|
||||
background-color: var(--repo-color-bg-tertiary);
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: var(--repo-radius-sm, 4px);
|
||||
font-family: var(--repo-font-family-mono, monospace);
|
||||
font-size: 85%;
|
||||
}
|
||||
.readme-content pre {
|
||||
background-color: var(--repo-color-bg-tertiary);
|
||||
padding: var(--repo-spacing-4, 16px);
|
||||
border-radius: var(--repo-radius-md, 6px);
|
||||
overflow-x: auto;
|
||||
margin-bottom: var(--repo-spacing-4, 16px);
|
||||
}
|
||||
.readme-content pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
.readme-content ul, .readme-content ol {
|
||||
padding-left: 2em;
|
||||
margin-bottom: var(--repo-spacing-4, 16px);
|
||||
}
|
||||
.readme-content blockquote {
|
||||
border-left: 4px solid var(--repo-color-border);
|
||||
padding-left: var(--repo-spacing-4, 16px);
|
||||
color: var(--repo-color-text-tertiary);
|
||||
margin-bottom: var(--repo-spacing-4, 16px);
|
||||
}
|
||||
.readme-content table {
|
||||
border-collapse: collapse;
|
||||
margin-bottom: var(--repo-spacing-4, 16px);
|
||||
width: 100%;
|
||||
}
|
||||
.readme-content th, .readme-content td {
|
||||
border: 1px solid var(--repo-color-border);
|
||||
padding: var(--repo-spacing-2, 8px) var(--repo-spacing-3, 12px);
|
||||
}
|
||||
.readme-content th {
|
||||
background-color: var(--repo-color-bg-secondary);
|
||||
}
|
||||
|
||||
/* 代码查看器 */
|
||||
.code-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: var(--repo-spacing-3, 12px) var(--repo-spacing-4, 16px);
|
||||
background-color: var(--repo-color-bg-secondary);
|
||||
border: 1px solid var(--repo-color-border);
|
||||
border-radius: var(--repo-radius-lg, 8px) var(--repo-radius-lg, 8px) 0 0;
|
||||
}
|
||||
|
||||
.code-content {
|
||||
border: 1px solid var(--repo-color-border);
|
||||
border-top: none;
|
||||
border-radius: 0 0 var(--repo-radius-lg, 8px) var(--repo-radius-lg, 8px);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.code-content pre {
|
||||
margin: 0;
|
||||
padding: var(--repo-spacing-4, 16px);
|
||||
background-color: var(--repo-color-bg);
|
||||
}
|
||||
|
||||
.code-content code {
|
||||
font-family: var(--repo-font-family-mono, "SF Mono", Monaco, monospace);
|
||||
font-size: var(--repo-font-size-sm, 13px);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 按钮 - 使用 css.css 样式 */
|
||||
.copy-btn {
|
||||
background-color: var(--repo-color-bg);
|
||||
border: 1px solid var(--repo-color-border);
|
||||
color: var(--repo-color-text-secondary);
|
||||
padding: var(--repo-spacing-1, 4px) var(--repo-spacing-3, 12px);
|
||||
border-radius: var(--repo-radius-md, 6px);
|
||||
font-size: var(--repo-font-size-xs, 12px);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
background-color: var(--repo-color-bg-hover);
|
||||
border-color: var(--repo-color-border-hover);
|
||||
color: var(--repo-color-text);
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--repo-spacing-1, 4px);
|
||||
padding: var(--repo-spacing-1, 4px) var(--repo-spacing-3, 12px);
|
||||
font-size: var(--repo-font-size-xs, 12px);
|
||||
font-weight: 500;
|
||||
border-radius: var(--repo-radius-md, 6px);
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--repo-color-primary);
|
||||
border: 1px solid transparent;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: var(--repo-color-primary-hover);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: var(--repo-color-bg);
|
||||
border: 1px solid var(--repo-color-border);
|
||||
color: var(--repo-color-text);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: var(--repo-color-bg-hover);
|
||||
border-color: var(--repo-color-border-hover);
|
||||
}
|
||||
|
||||
/* 搜索框 */
|
||||
.search-box {
|
||||
display: flex;
|
||||
gap: var(--repo-spacing-2, 8px);
|
||||
margin-bottom: var(--repo-spacing-6, 24px);
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
background-color: var(--repo-color-bg);
|
||||
border: 1px solid var(--repo-color-border);
|
||||
border-radius: var(--repo-radius-md, 6px);
|
||||
padding: var(--repo-spacing-2, 8px) var(--repo-spacing-3, 12px);
|
||||
color: var(--repo-color-text);
|
||||
font-size: var(--repo-font-size-base, 14px);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.search-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--repo-color-primary);
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
/* 卡片 */
|
||||
.repo-card-custom {
|
||||
background-color: var(--repo-color-bg);
|
||||
border: 1px solid var(--repo-color-border);
|
||||
border-radius: var(--repo-radius-lg, 8px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media (max-width: 768px) {
|
||||
.two-column {
|
||||
flex-direction: column;
|
||||
}
|
||||
.sidebar {
|
||||
width: 100%;
|
||||
}
|
||||
.file-message {
|
||||
display: none;
|
||||
}
|
||||
.header-inner {
|
||||
padding: 0 var(--repo-spacing-4, 16px);
|
||||
}
|
||||
.nav-links {
|
||||
gap: var(--repo-spacing-3, 12px);
|
||||
}
|
||||
.nav-links a {
|
||||
font-size: var(--repo-font-size-sm, 13px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 图标占位 */
|
||||
.icon {
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 加载动画 */
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: var(--repo-spacing-8, 32px);
|
||||
color: var(--repo-color-text-tertiary);
|
||||
}
|
||||
|
||||
/* 提示信息 */
|
||||
.info-message {
|
||||
background-color: var(--repo-color-bg-secondary);
|
||||
border-left: 4px solid var(--repo-color-primary);
|
||||
padding: var(--repo-spacing-4, 16px);
|
||||
border-radius: var(--repo-radius-md, 6px);
|
||||
margin-bottom: var(--repo-spacing-4, 16px);
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background-color: #fef2f2;
|
||||
border-left: 4px solid var(--repo-color-danger, #ef4444);
|
||||
padding: var(--repo-spacing-4, 16px);
|
||||
border-radius: var(--repo-radius-md, 6px);
|
||||
margin-bottom: var(--repo-spacing-4, 16px);
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.success-message {
|
||||
background-color: #f0fdf4;
|
||||
border-left: 4px solid var(--repo-color-success, #10b981);
|
||||
padding: var(--repo-spacing-4, 16px);
|
||||
border-radius: var(--repo-radius-md, 6px);
|
||||
margin-bottom: var(--repo-spacing-4, 16px);
|
||||
color: #047857;
|
||||
}
|
||||
|
||||
/* 分支菜单下拉 */
|
||||
.branch-menu-dropdown {
|
||||
position: absolute;
|
||||
background-color: var(--repo-color-bg);
|
||||
border: 1px solid var(--repo-color-border);
|
||||
border-radius: var(--repo-radius-md, 6px);
|
||||
width: 200px;
|
||||
z-index: var(--repo-z-dropdown, 100);
|
||||
box-shadow: var(--repo-shadow-lg, 0 10px 15px -3px rgba(0,0,0,0.1));
|
||||
}
|
||||
|
||||
.branch-menu-item {
|
||||
padding: var(--repo-spacing-2, 8px) var(--repo-spacing-3, 12px);
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.branch-menu-item:hover {
|
||||
background-color: var(--repo-color-bg-hover);
|
||||
}
|
||||
</style>
|
||||
{% block extra_head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<div class="header-inner">
|
||||
<div class="logo-area">
|
||||
<a href="/" class="logo">ap_<span>ds</span></a>
|
||||
<div class="nav-links">
|
||||
<a href="{{ url_for('index') }}">首页</a>
|
||||
<a href="{{ url_for('code_browser') }}">代码</a>
|
||||
<a href="{{ url_for('feedback') }}">提问/建议</a>
|
||||
<a href="{{ url_for('releases') }}">发行版</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="repo-badge">
|
||||
v3.0.0
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main-container">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 初始化 highlight.js
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.querySelectorAll('pre code').forEach(function(block) {
|
||||
hljs.highlightElement(block);
|
||||
});
|
||||
});
|
||||
|
||||
// 复制代码功能
|
||||
function copyCode(btn) {
|
||||
var pre = btn.closest('.code-header')?.nextElementSibling;
|
||||
var codeBlock = pre?.querySelector('code');
|
||||
if (!codeBlock) return;
|
||||
var code = codeBlock.innerText;
|
||||
navigator.clipboard.writeText(code).then(function() {
|
||||
var originalText = btn.innerText;
|
||||
btn.innerText = '已复制!';
|
||||
setTimeout(function() { btn.innerText = originalText; }, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
// 全局分支/版本选择辅助函数
|
||||
function selectBranch(branch, baseUrl) {
|
||||
window.location.href = baseUrl + '?branch=' + encodeURIComponent(branch);
|
||||
}
|
||||
|
||||
function selectVersion(version, baseUrl, branch) {
|
||||
window.location.href = baseUrl + '?branch=' + encodeURIComponent(branch) + '&version=' + encodeURIComponent(version);
|
||||
}
|
||||
</script>
|
||||
{% block extra_scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,381 @@
|
||||
|
||||
|
||||
<!-- 英文 SEO 标签 -->
|
||||
<meta name="description" content="ap_ds official code repository. Browse branches (APDSLTS001), tags (v3.0.0), view README/setup.py with syntax highlighting, search files, and download ZIP/TAR.GZ/WHL. Self-hosted, never offline.">
|
||||
<meta name="keywords" content="ap_ds source code, Python audio library code, browse repository, download WHL, self-hosted git, APDSLTS001, v3.0.0 source">
|
||||
<meta property="og:title" content="ap_ds Code Repository – Browse & Download Source">
|
||||
<meta property="og:description" content="Official self-hosted Git-style code browser for ap_ds. Syntax-highlighted Python files, Markdown rendering, multi-format downloads.">
|
||||
<meta property="og:type" content="software">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="ap_ds Code Repository">
|
||||
|
||||
<!-- 中文 SEO 标签 -->
|
||||
<meta name="description" lang="zh-CN" content="ap_ds 官方代码仓库。浏览分支(APDSLTS001)和版本(v3.0.0)下的所有源文件,语法高亮预览 README/setup.py,文件搜索,一键下载 ZIP/TAR.GZ/WHL。自主托管,永不掉线。">
|
||||
<meta name="keywords" lang="zh-CN" content="ap_ds 源代码, Python音频库代码, 浏览仓库, 下载WHL, 自建Git仓库, APDSLTS001, v3.0.0源码">
|
||||
<meta property="og:title" lang="zh-CN" content="ap_ds 代码仓库 – 浏览与下载源码">
|
||||
<meta property="og:description" lang="zh-CN" content="ap_ds 官方自建代码仓库。支持语法高亮的 Python 文件预览、Markdown 渲染、多格式源码下载。">{% extends "base.html" %}
|
||||
{% block content %}
|
||||
|
||||
{% block title %}ap_ds Code Repository – Official Self-Hosted Git Source Code & Downloads{% endblock %}<!-- 仓库头部 -->
|
||||
<div class="repo-header">
|
||||
<div class="repo-path">
|
||||
<a href="/code">ap_ds</a>
|
||||
<span class="separator">/</span>
|
||||
<span>{{ current_branch if current_branch else '选择分支' }}</span>
|
||||
{% if current_version %}
|
||||
<span class="separator">/</span>
|
||||
<span>{{ current_version }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<div class="search-box">
|
||||
<form action="{{ url_for('search') }}" method="get" style="display: flex; gap: 8px; width: 100%;">
|
||||
<input type="hidden" name="branch" value="{{ current_branch }}">
|
||||
<input type="hidden" name="version" value="{{ current_version }}">
|
||||
<input type="text" name="q" class="search-input" placeholder="查找文件..." value="{{ search_query or '' }}">
|
||||
<button type="submit" class="btn btn-primary">搜索</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="two-column">
|
||||
<!-- 左侧边栏 -->
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-title">📌 分支</div>
|
||||
<div class="branch-selector" id="branch-selector">
|
||||
<span>{{ current_branch if current_branch else '选择分支' }}</span>
|
||||
<span>▼</span>
|
||||
</div>
|
||||
<div id="branch-menu" class="branch-menu-dropdown" style="display: none;">
|
||||
{% for branch_name in branches.keys() %}
|
||||
<div class="branch-menu-item" data-branch="{{ branch_name }}">{{ branch_name }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-title">🏷️ 版本</div>
|
||||
<div class="branch-selector" id="version-selector">
|
||||
<span>{{ current_version if current_version else '选择版本' }}</span>
|
||||
<span>▼</span>
|
||||
</div>
|
||||
<div id="version-menu" class="branch-menu-dropdown" style="display: none;">
|
||||
{% if current_branch and branches[current_branch] %}
|
||||
{% for version in branches[current_branch] %}
|
||||
<div class="branch-menu-item" data-version="{{ version.name }}">{{ version.name }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-title">📦 下载</div>
|
||||
<div style="display: flex; flex-direction: column; gap: 8px;">
|
||||
<a href="{{ url_for('download_zip', branch=current_branch, version=current_version) if current_branch and current_version else '#' }}" class="btn btn-secondary {% if not current_branch or not current_version %}disabled{% endif %}" {% if not current_branch or not current_version %}style="opacity:0.5; pointer-events:none;"{% endif %}>
|
||||
📥 下载 ZIP
|
||||
</a>
|
||||
<a href="{{ url_for('download_tar', branch=current_branch, version=current_version) if current_branch and current_version else '#' }}" class="btn btn-secondary {% if not current_branch or not current_version %}disabled{% endif %}" {% if not current_branch or not current_version %}style="opacity:0.5; pointer-events:none;"{% endif %}>
|
||||
📥 下载 TAR.GZ
|
||||
</a>
|
||||
<a href="{{ url_for('download_whl', version=current_version) if current_version else '#' }}" class="btn btn-secondary {% if not current_version %}disabled{% endif %}" {% if not current_version %}style="opacity:0.5; pointer-events:none;"{% endif %}>
|
||||
📥 下载 WHL
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-title">🔗 快速链接</div>
|
||||
<div style="display: flex; flex-direction: column; gap: 6px;">
|
||||
<a href="/releases" class="btn btn-secondary" style="text-align: center;">查看发行版</a>
|
||||
<a href="/feedback" class="btn btn-secondary" style="text-align: center;">提交反馈</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧内容 -->
|
||||
<div class="main-content">
|
||||
<!-- 搜索结果 -->
|
||||
{% if search_results %}
|
||||
<div class="file-list">
|
||||
<div class="file-list-header">
|
||||
<span>🔍 搜索结果 ({{ search_results|length }})</span>
|
||||
<span></span>
|
||||
</div>
|
||||
{% for result in search_results %}
|
||||
<div class="file-row">
|
||||
<div class="file-name">
|
||||
<span class="icon">📄</span>
|
||||
{% set relative_path = result.path.replace(current_branch + '/' + current_version + '/', '') %}
|
||||
<a href="{{ url_for('view_file', file_path=relative_path, branch=current_branch, version=current_version) }}">{{ result.name }}</a>
|
||||
</div>
|
||||
<div class="file-message">{{ result.path }}</div>
|
||||
<div class="file-time">
|
||||
{% if result.size < 1024 %}
|
||||
{{ result.size }} B
|
||||
{% elif result.size < 1024*1024 %}
|
||||
{{ (result.size/1024)|round(1) }} KB
|
||||
{% else %}
|
||||
{{ (result.size/(1024*1024))|round(1) }} MB
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- 文件列表 -->
|
||||
{% if not search_results %}
|
||||
<div class="file-list">
|
||||
<div class="file-list-header">
|
||||
<span>📁 文件列表</span>
|
||||
<span></span>
|
||||
<span>大小</span>
|
||||
</div>
|
||||
<!-- 文件列表中的链接改为使用完整路径 -->
|
||||
{% for item in file_tree %}
|
||||
<div class="file-row">
|
||||
<div class="file-name">
|
||||
<span class="icon">{% if item.type == 'dir' %}📁{% else %}📄{% endif %}</span>
|
||||
{% if item.type == 'dir' %}
|
||||
<a href="{{ url_for('browse_directory', dir_path=item.path, branch=current_branch, version=current_version) }}">{{ item.name }}</a>
|
||||
{% else %}
|
||||
<!-- 直接使用完整路径,不加替换 -->
|
||||
<a href="{{ url_for('view_file', file_path=item.path, branch=current_branch, version=current_version) }}">{{ item.name }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="file-message">
|
||||
{% if item.type == 'dir' %}目录{% else %}{{ item.name }}{% endif %}
|
||||
</div>
|
||||
<div class="file-time">
|
||||
{% if item.type == 'file' %}
|
||||
{% if item.size < 1024 %}
|
||||
{{ item.size }} B
|
||||
{% elif item.size < 1024*1024 %}
|
||||
{{ (item.size/1024)|round(1) }} KB
|
||||
{% else %}
|
||||
{{ (item.size/(1024*1024))|round(1) }} MB
|
||||
{% endif %}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<div class="file-row">
|
||||
<div class="file-name" style="justify-content: center; color: var(--repo-color-text-tertiary);">
|
||||
此目录为空
|
||||
</div>
|
||||
<div class="file-message"></div>
|
||||
<div class="file-time"></div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- README 区域 -->
|
||||
<div class="readme-section">
|
||||
<div class="readme-header">
|
||||
📖 README.md
|
||||
</div>
|
||||
<div class="readme-content" id="readme-content">
|
||||
<div class="loading">加载中...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 获取当前 URL 基础路径
|
||||
var currentUrl = "{{ url_for('code_browser') }}";
|
||||
var currentBranch = "{{ current_branch }}";
|
||||
var currentVersion = "{{ current_version }}";
|
||||
|
||||
// ========== 分支/版本选择器 ==========
|
||||
var branchSelector = document.getElementById('branch-selector');
|
||||
var branchMenu = document.getElementById('branch-menu');
|
||||
var versionSelector = document.getElementById('version-selector');
|
||||
var versionMenu = document.getElementById('version-menu');
|
||||
|
||||
if (branchSelector) {
|
||||
branchSelector.onclick = function(e) {
|
||||
e.stopPropagation();
|
||||
branchMenu.style.display = branchMenu.style.display === 'none' ? 'block' : 'none';
|
||||
if (versionMenu) versionMenu.style.display = 'none';
|
||||
};
|
||||
}
|
||||
|
||||
if (versionSelector) {
|
||||
versionSelector.onclick = function(e) {
|
||||
e.stopPropagation();
|
||||
versionMenu.style.display = versionMenu.style.display === 'none' ? 'block' : 'none';
|
||||
if (branchMenu) branchMenu.style.display = 'none';
|
||||
};
|
||||
}
|
||||
|
||||
document.onclick = function() {
|
||||
if (branchMenu) branchMenu.style.display = 'none';
|
||||
if (versionMenu) versionMenu.style.display = 'none';
|
||||
};
|
||||
|
||||
var branchItems = document.querySelectorAll('#branch-menu .branch-menu-item');
|
||||
branchItems.forEach(function(item) {
|
||||
item.onclick = function(e) {
|
||||
e.stopPropagation();
|
||||
var branch = this.getAttribute('data-branch');
|
||||
if (branch) {
|
||||
window.location.href = currentUrl + '?branch=' + encodeURIComponent(branch);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
var versionItems = document.querySelectorAll('#version-menu .branch-menu-item');
|
||||
versionItems.forEach(function(item) {
|
||||
item.onclick = function(e) {
|
||||
e.stopPropagation();
|
||||
var version = this.getAttribute('data-version');
|
||||
if (version && currentBranch) {
|
||||
window.location.href = currentUrl + '?branch=' + encodeURIComponent(currentBranch) + '&version=' + encodeURIComponent(version);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// ========== 加载 README ==========
|
||||
function loadReadme() {
|
||||
if (!currentBranch || !currentVersion) {
|
||||
document.getElementById('readme-content').innerHTML = '<div class="info-message" style="text-align: center;">请选择分支和版本以查看 README</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
var readmeUrl = '/view/' + currentBranch + '/' + currentVersion + '/README.md?branch=' + currentBranch + '&version=' + currentVersion;
|
||||
|
||||
fetch(readmeUrl)
|
||||
.then(function(res) {
|
||||
if (res.ok) {
|
||||
return res.text();
|
||||
}
|
||||
throw new Error('README.md 不存在');
|
||||
})
|
||||
.then(function(html) {
|
||||
var container = document.getElementById('readme-content');
|
||||
|
||||
if (html.indexOf('<div class="readme-content">') !== -1) {
|
||||
var match = html.match(/<div class="readme-content">([\s\S]*?)<\/div>/);
|
||||
if (match && match[1]) {
|
||||
container.innerHTML = match[1];
|
||||
} else {
|
||||
container.innerHTML = html;
|
||||
}
|
||||
} else {
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
var codeBlocks = container.querySelectorAll('pre code');
|
||||
codeBlocks.forEach(function(block) {
|
||||
if (typeof hljs !== 'undefined') {
|
||||
hljs.highlightElement(block);
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(function(err) {
|
||||
document.getElementById('readme-content').innerHTML = '<div class="info-message" style="text-align: center;">📄 暂无 README.md 文件</div>';
|
||||
});
|
||||
}
|
||||
|
||||
loadReadme();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.branch-menu-dropdown {
|
||||
position: absolute;
|
||||
background-color: var(--repo-color-bg);
|
||||
border: 1px solid var(--repo-color-border);
|
||||
border-radius: var(--repo-radius-md, 6px);
|
||||
width: 200px;
|
||||
z-index: var(--repo-z-dropdown, 100);
|
||||
box-shadow: var(--repo-shadow-md);
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.branch-menu-item {
|
||||
padding: var(--repo-spacing-2, 8px) var(--repo-spacing-3, 12px);
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
color: var(--repo-color-text);
|
||||
}
|
||||
|
||||
.branch-menu-item:hover {
|
||||
background-color: var(--repo-color-bg-hover);
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: var(--repo-spacing-8, 32px);
|
||||
color: var(--repo-color-text-tertiary);
|
||||
}
|
||||
|
||||
.info-message {
|
||||
text-align: center;
|
||||
padding: var(--repo-spacing-8, 32px);
|
||||
color: var(--repo-color-text-tertiary);
|
||||
}
|
||||
|
||||
/* README 内容样式 */
|
||||
#readme-content h1, #readme-content h2, #readme-content h3 {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
#readme-content h1 { font-size: 2em; border-bottom: 1px solid var(--repo-color-border); padding-bottom: 0.3em; }
|
||||
#readme-content h2 { font-size: 1.5em; border-bottom: 1px solid var(--repo-color-border); padding-bottom: 0.3em; }
|
||||
#readme-content p { margin-bottom: 16px; line-height: 1.6; }
|
||||
#readme-content code {
|
||||
background-color: var(--repo-color-bg-tertiary);
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
}
|
||||
#readme-content pre {
|
||||
background-color: var(--repo-color-bg-tertiary);
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
#readme-content pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
#readme-content ul, #readme-content ol {
|
||||
padding-left: 2em;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
#readme-content blockquote {
|
||||
border-left: 4px solid var(--repo-color-border);
|
||||
padding-left: 16px;
|
||||
color: var(--repo-color-text-tertiary);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
#readme-content table {
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 16px;
|
||||
width: 100%;
|
||||
}
|
||||
#readme-content th, #readme-content td {
|
||||
border: 1px solid var(--repo-color-border);
|
||||
padding: 8px 12px;
|
||||
}
|
||||
#readme-content th {
|
||||
background-color: var(--repo-color-bg-secondary);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,81 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}目录浏览 - ap_ds{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="repo-container">
|
||||
<!-- 面包屑导航 -->
|
||||
<div class="repo-breadcrumb repo-mb-4">
|
||||
<a href="{{ url_for('code_browser') }}?branch={{ branch }}&version={{ version }}" class="repo-breadcrumb__link">根目录</a>
|
||||
{% for crumb in breadcrumbs %}
|
||||
<span class="repo-breadcrumb__separator">/</span>
|
||||
<a href="{{ url_for('browse_directory', dir_path=crumb.path, branch=branch, version=version) }}" class="repo-breadcrumb__link">
|
||||
{{ crumb.name }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="repo-card">
|
||||
<div class="repo-card__body">
|
||||
<table class="repo-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>名称</th>
|
||||
<th>大小</th>
|
||||
<th>修改时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if current_path %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ url_for('code_browser') }}?branch={{ branch }}&version={{ version }}" class="repo-text-link">..</a>
|
||||
</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% for file in files %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if file.type == 'dir' %}
|
||||
<span class="icon-folder repo-mr-2"></span>
|
||||
<a href="{{ url_for('browse_directory', dir_path=file.path, branch=branch, version=version) }}" class="repo-text-link">
|
||||
{{ file.name }}
|
||||
</a>
|
||||
{% else %}
|
||||
<span class="icon-file repo-mr-2"></span>
|
||||
<a href="{{ url_for('view_file', file_path=file.path, branch=branch, version=version) }}" class="repo-text-link">
|
||||
{{ file.name }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="repo-text-secondary repo-text-sm">
|
||||
{% if file.type == 'file' %}
|
||||
{% if file.size < 1024 %}
|
||||
{{ file.size }} B
|
||||
{% elif file.size < 1024*1024 %}
|
||||
{{ (file.size/1024)|round(1) }} KB
|
||||
{% else %}
|
||||
{{ (file.size/(1024*1024))|round(1) }} MB
|
||||
{% endif %}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="repo-text-secondary repo-text-sm">{{ file.modified }}</td>
|
||||
<td>
|
||||
{% if file.type == 'file' %}
|
||||
<a href="{{ url_for('download_file', file_path=file.path) }}" class="repo-btn repo-btn--sm repo-btn--ghost">下载</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,77 @@
|
||||
|
||||
<!-- 英文 SEO 标签 -->
|
||||
<meta name="description" content="Official feedback channel for ap_ds. Submit bug reports, feature requests, or general inquiries via form. Includes FAQ: supported formats (MP3/FLAC/OGG/WAV), DAP playback logs. Direct email replies from maintainer.">
|
||||
<meta name="keywords" content="ap_ds feedback, bug report Python audio, feature request, ask question, DvsXT contact, MP3 FLAC OGG WAV support, Python audio library help">
|
||||
<meta property="og:title" content="ap_ds Feedback – Ask Questions & Report Issues">
|
||||
<meta property="og:description" content="Direct line to ap_ds maintainer. Submit feedback, get answers via email. Read FAQ before posting.">
|
||||
<meta property="og:type" content="website">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="ap_ds Feedback Form">
|
||||
|
||||
<!-- 中文 SEO 标签 -->
|
||||
<meta name="description" lang="zh-CN" content="ap_ds 官方反馈通道。提交 Bug 报告、功能建议或一般咨询。附常见问题:支持的音频格式(MP3/FLAC/OGG/WAV)、DAP 播放记录获取方法。维护者通过邮件直接回复。">
|
||||
<meta name="keywords" lang="zh-CN" content="ap_ds 反馈, 提交Bug, 功能建议, 提问, DvsXT 联系, MP3 FLAC OGG WAV支持, Python音频库帮助">
|
||||
<meta property="og:title" lang="zh-CN" content="ap_ds 反馈 – 提问与建议">
|
||||
<meta property="og:description" lang="zh-CN" content="直连 ap_ds 维护者。提交反馈,邮件获取回复。提交前可查阅常见问题。">{% extends "base.html" %}
|
||||
|
||||
{% block content %}{% block title %}ap_ds Feedback – Submit Questions, Bug Reports & Feature Requests{% endblock %}
|
||||
|
||||
<div class="repo-container" style="max-width: 800px;">
|
||||
<div class="repo-card">
|
||||
<div class="repo-card__header">
|
||||
<h2 class="repo-card__title">💬 提问与建议</h2>
|
||||
<p class="repo-text-secondary repo-text-sm repo-mt-2">
|
||||
欢迎向我们提出任何问题或建议,我们会认真对待每一条反馈。
|
||||
</p>
|
||||
</div>
|
||||
<div class="repo-card__body">
|
||||
{% if error %}
|
||||
<div class="repo-message repo-message--error" style="position: static; transform: none; margin-bottom: 1rem;">
|
||||
{{ error }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if success %}
|
||||
<div class="repo-message repo-message--success" style="position: static; transform: none; margin-bottom: 1rem;">
|
||||
{{ success }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post">
|
||||
<div class="repo-mb-4">
|
||||
<label class="repo-text-sm repo-font-medium repo-mb-2 repo-block">邮箱地址</label>
|
||||
<input type="email" name="email" class="repo-input" required placeholder="your@email.com">
|
||||
<p class="repo-text-xs repo-text-tertiary repo-mt-1">我们会将回复发送到此邮箱</p>
|
||||
</div>
|
||||
<div class="repo-mb-4">
|
||||
<label class="repo-text-sm repo-font-medium repo-mb-2 repo-block">反馈内容</label>
|
||||
<textarea name="content" class="repo-textarea repo-input" rows="8" required placeholder="请详细描述您的问题或建议..."></textarea>
|
||||
</div>
|
||||
<div class="repo-flex repo-gap-3">
|
||||
<button type="submit" class="repo-btn repo-btn--primary">提交反馈</button>
|
||||
<button type="reset" class="repo-btn repo-btn--secondary">清空</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="repo-card repo-mt-6">
|
||||
<div class="repo-card__body">
|
||||
<h3 class="repo-font-semibold repo-mb-3">常见问题</h3>
|
||||
<div class="repo-space-y-3">
|
||||
<div>
|
||||
<div class="repo-font-medium">Q: ap_ds 支持哪些音频格式?</div>
|
||||
<p class="repo-text-secondary repo-text-sm">A: 支持 MP3、FLAC、OGG、WAV 四种主流格式。</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="repo-font-medium">Q: 遇到 SSL 证书错误怎么办?</div>
|
||||
<p class="repo-text-secondary repo-text-sm">A: ap_ds 内置了多层下载策略,会自动处理 SSL 问题。如仍遇到问题,请手动下载 SDL2 库。</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="repo-font-medium">Q: 如何获取 DAP 播放记录?</div>
|
||||
<p class="repo-text-secondary repo-text-sm">A: 使用 get_dap_recordings() 方法获取,或使用 save_dap_to_json() 保存为文件。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,679 @@
|
||||
<!-- 英文 SEO 标签 -->
|
||||
<meta name="description" content="ap_ds is an ultra-lightweight Python audio library (2.5MB) for high-quality playback & precise metadata parsing of MP3, FLAC, OGG, WAV. Zero external dependencies, non-blocking architecture. Created by Dvs (DvsXT), released 2026-03-01. A precise revolution in Python audio.">
|
||||
<meta name="keywords" content="Python audio library, lightweight audio player, MP3, FLAC, OGG, WAV metadata, non-blocking playback, ap_ds, DvsXT, zero dependencies, 2026 Python ecosystem">
|
||||
<meta property="og:title" content="ap_ds: Ultra-Lightweight Python Audio Library (2.5MB)">
|
||||
<meta property="og:description" content="Zero external deps, non-blocking playback, precise metadata parsing for MP3/FLAC/OGG/WAV. Released 2026-03-01 by Dvs (DvsXT).">
|
||||
<meta property="og:type" content="software">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="ap_ds - Python Audio Library, 2.5MB">
|
||||
<meta name="twitter:description" content="Solve the lightweight vs completeness contradiction in Python audio. Non-blocking, zero deps, 4 major formats.">
|
||||
|
||||
<!-- 中文 SEO 标签 -->
|
||||
<meta name="description" lang="zh-CN" content="ap_ds 是一个超轻量级 Python 音频库(仅 2.5MB),支持 MP3、FLAC、OGG、WAV 四种主流格式的高质量播放和精确元数据解析。零外部 Python 依赖,非阻塞播放架构让 GUI 应用流畅运行。由 Dvs (DvsXT) 创作,发布于 2026-03-01,是 Python 音频领域的精准革命。">
|
||||
<meta name="keywords" lang="zh-CN" content="Python音频库, 轻量级音频播放, MP3, FLAC, OGG, WAV元数据解析, 非阻塞播放, ap_ds, DvsXT, 零依赖, 2026 Python生态">
|
||||
<meta property="og:title" content="ap_ds:超轻量级 Python 音频库 (2.5MB)">
|
||||
<meta property="og:description" lang="zh-CN" content="零外部依赖,非阻塞播放,精准解析 MP3/FLAC/OGG/WAV。2026-03-01 由 Dvs (DvsXT) 发布。终结 Python 音频库的「完整性与轻量化」矛盾。">
|
||||
|
||||
<!-- 新增:官方自建代码仓库说明 -->
|
||||
<meta name="description" content="This domain is the official self-hosted code repository for ap_ds. Direct source, stable releases, and authoritative documentation.">
|
||||
<meta name="description" lang="zh-CN" content="本域名是 ap_ds 官方自建代码仓库。提供直接源代码、稳定版本及权威文档。"> {% extends "base.html" %}
|
||||
|
||||
{% block title %}ap_ds · lightweight Python audio solution ap_ds · 轻量级Python音频库{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
<link rel="canonical" href="https://apds.top" />
|
||||
<link rel="me" href="https://gitee.com/dssxt/ap_ds" />
|
||||
<link rel="me" href="https://gitcode.com/dvsxt/ap_ds" />
|
||||
<link rel="nofollow" href="https://github.com/dvs-web/ap_ds" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-python.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-bash.min.js"></script>
|
||||
<style>
|
||||
/* 首页自定义样式 - 基于 css.css 变量 */
|
||||
.home-container {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: var(--repo-spacing-6) var(--repo-spacing-4);
|
||||
}
|
||||
|
||||
/* Hero 区域 */
|
||||
.hero-section {
|
||||
text-align: center;
|
||||
padding: var(--repo-spacing-10) 0 var(--repo-spacing-8);
|
||||
background: linear-gradient(135deg, var(--repo-color-bg) 0%, var(--repo-color-bg-secondary) 100%);
|
||||
border-radius: var(--repo-radius-xl);
|
||||
margin-bottom: var(--repo-spacing-8);
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: var(--repo-spacing-4);
|
||||
background: linear-gradient(135deg, var(--repo-color-primary) 0%, #6366f1 100%);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: var(--repo-font-size-xl);
|
||||
color: var(--repo-color-text-secondary);
|
||||
margin-bottom: var(--repo-spacing-6);
|
||||
}
|
||||
|
||||
.version-badges {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: var(--repo-spacing-3);
|
||||
flex-wrap: wrap;
|
||||
margin-top: var(--repo-spacing-4);
|
||||
}
|
||||
|
||||
/* 目录 */
|
||||
.toc-section {
|
||||
background: var(--repo-color-bg-secondary);
|
||||
border-radius: var(--repo-radius-lg);
|
||||
padding: var(--repo-spacing-6);
|
||||
margin-bottom: var(--repo-spacing-8);
|
||||
border-left: 4px solid var(--repo-color-primary);
|
||||
}
|
||||
|
||||
.toc-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: var(--repo-spacing-2);
|
||||
margin-top: var(--repo-spacing-4);
|
||||
}
|
||||
|
||||
.toc-link {
|
||||
color: var(--repo-color-text-secondary);
|
||||
text-decoration: none;
|
||||
font-size: var(--repo-font-size-sm);
|
||||
padding: var(--repo-spacing-1) 0;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.toc-link:hover {
|
||||
color: var(--repo-color-primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* 内容区块 */
|
||||
.content-card {
|
||||
background: var(--repo-color-bg);
|
||||
border: 1px solid var(--repo-color-border);
|
||||
border-radius: var(--repo-radius-lg);
|
||||
padding: var(--repo-spacing-8);
|
||||
margin-bottom: var(--repo-spacing-8);
|
||||
box-shadow: var(--repo-shadow-sm);
|
||||
}
|
||||
|
||||
.content-card h2 {
|
||||
font-size: var(--repo-font-size-2xl);
|
||||
font-weight: 600;
|
||||
margin-bottom: var(--repo-spacing-6);
|
||||
padding-bottom: var(--repo-spacing-3);
|
||||
border-bottom: 2px solid var(--repo-color-border);
|
||||
color: var(--repo-color-text);
|
||||
}
|
||||
|
||||
.content-card h3 {
|
||||
font-size: var(--repo-font-size-xl);
|
||||
font-weight: 600;
|
||||
margin-top: var(--repo-spacing-6);
|
||||
margin-bottom: var(--repo-spacing-4);
|
||||
color: var(--repo-color-text);
|
||||
}
|
||||
|
||||
.content-card h4 {
|
||||
font-size: var(--repo-font-size-lg);
|
||||
font-weight: 600;
|
||||
margin-top: var(--repo-spacing-4);
|
||||
margin-bottom: var(--repo-spacing-3);
|
||||
color: var(--repo-color-text);
|
||||
}
|
||||
|
||||
/* 特性网格 */
|
||||
.feature-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: var(--repo-spacing-5);
|
||||
margin-bottom: var(--repo-spacing-8);
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
background: var(--repo-color-bg-secondary);
|
||||
border-radius: var(--repo-radius-lg);
|
||||
padding: var(--repo-spacing-5);
|
||||
border: 1px solid var(--repo-color-border);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--repo-shadow-md);
|
||||
border-color: var(--repo-color-primary);
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
font-size: 2rem;
|
||||
margin-bottom: var(--repo-spacing-3);
|
||||
}
|
||||
|
||||
.feature-title {
|
||||
font-weight: 600;
|
||||
font-size: var(--repo-font-size-lg);
|
||||
margin-bottom: var(--repo-spacing-2);
|
||||
color: var(--repo-color-text);
|
||||
}
|
||||
|
||||
.feature-desc {
|
||||
color: var(--repo-color-text-secondary);
|
||||
font-size: var(--repo-font-size-sm);
|
||||
}
|
||||
|
||||
/* 对比表格 */
|
||||
.comparison-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: var(--repo-spacing-4) 0;
|
||||
}
|
||||
|
||||
.comparison-table th,
|
||||
.comparison-table td {
|
||||
border: 1px solid var(--repo-color-border);
|
||||
padding: var(--repo-spacing-3) var(--repo-spacing-4);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.comparison-table th {
|
||||
background: var(--repo-color-bg-secondary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 提示框 */
|
||||
.note-box {
|
||||
background: #eef2ff;
|
||||
border-left: 4px solid var(--repo-color-primary);
|
||||
border-radius: var(--repo-radius-md);
|
||||
padding: var(--repo-spacing-4) var(--repo-spacing-6);
|
||||
margin: var(--repo-spacing-4) 0;
|
||||
color: #1e40af;
|
||||
}
|
||||
|
||||
.warning-box {
|
||||
background: #fffbeb;
|
||||
border-left: 4px solid #f59e0b;
|
||||
border-radius: var(--repo-radius-md);
|
||||
padding: var(--repo-spacing-4) var(--repo-spacing-6);
|
||||
margin: var(--repo-spacing-4) 0;
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
.success-box {
|
||||
background: #ecfdf5;
|
||||
border-left: 4px solid #10b981;
|
||||
border-radius: var(--repo-radius-md);
|
||||
padding: var(--repo-spacing-4) var(--repo-spacing-6);
|
||||
margin: var(--repo-spacing-4) 0;
|
||||
color: #065f46;
|
||||
}
|
||||
|
||||
/* 代码块 */
|
||||
.code-block {
|
||||
position: relative;
|
||||
background: #0f172a;
|
||||
border-radius: var(--repo-radius-lg);
|
||||
margin: var(--repo-spacing-4) 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.code-block pre {
|
||||
margin: 0;
|
||||
padding: var(--repo-spacing-5);
|
||||
overflow-x: auto;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.code-block code {
|
||||
font-family: var(--repo-font-family-mono);
|
||||
font-size: var(--repo-font-size-sm);
|
||||
color: #f8fafc;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 16px;
|
||||
background: #334155;
|
||||
color: #f1f5f9;
|
||||
border: none;
|
||||
border-radius: var(--repo-radius-full);
|
||||
padding: 4px 14px;
|
||||
font-size: var(--repo-font-size-xs);
|
||||
cursor: pointer;
|
||||
transition: 0.2s;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
opacity: 1;
|
||||
background: #475569;
|
||||
}
|
||||
|
||||
.copy-btn.copied {
|
||||
background: #059669;
|
||||
}
|
||||
|
||||
/* 许可证区域 */
|
||||
.license-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: var(--repo-spacing-4);
|
||||
margin-top: var(--repo-spacing-6);
|
||||
}
|
||||
|
||||
.license-card {
|
||||
background: var(--repo-color-bg-secondary);
|
||||
border-radius: var(--repo-radius-lg);
|
||||
padding: var(--repo-spacing-5);
|
||||
border: 1px solid var(--repo-color-border);
|
||||
}
|
||||
|
||||
.license-card h4 {
|
||||
margin-top: 0;
|
||||
margin-bottom: var(--repo-spacing-3);
|
||||
}
|
||||
|
||||
/* 公告区域 */
|
||||
.announcement-content {
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.announcement-content h1,
|
||||
.announcement-content h2,
|
||||
.announcement-content h3 {
|
||||
margin-top: var(--repo-spacing-5);
|
||||
margin-bottom: var(--repo-spacing-3);
|
||||
}
|
||||
|
||||
.announcement-content h1 { font-size: var(--repo-font-size-xl); border-bottom: 1px solid var(--repo-color-border); padding-bottom: var(--repo-spacing-2); }
|
||||
.announcement-content h2 { font-size: var(--repo-font-size-lg); border-bottom: 1px solid var(--repo-color-border); padding-bottom: var(--repo-spacing-2); }
|
||||
.announcement-content h3 { font-size: var(--repo-font-size-base); }
|
||||
.announcement-content p { margin-bottom: var(--repo-spacing-4); }
|
||||
.announcement-content ul, .announcement-content ol { padding-left: var(--repo-spacing-6); margin-bottom: var(--repo-spacing-4); }
|
||||
.announcement-content code {
|
||||
background: var(--repo-color-bg-tertiary);
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: var(--repo-radius-sm);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.announcement-content pre {
|
||||
background: #0f172a;
|
||||
padding: var(--repo-spacing-4);
|
||||
border-radius: var(--repo-radius-md);
|
||||
overflow-x: auto;
|
||||
margin: var(--repo-spacing-4) 0;
|
||||
}
|
||||
.announcement-content pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
color: #f8fafc;
|
||||
}
|
||||
.announcement-content blockquote {
|
||||
border-left: 4px solid var(--repo-color-border);
|
||||
padding-left: var(--repo-spacing-4);
|
||||
margin: var(--repo-spacing-4) 0;
|
||||
color: var(--repo-color-text-secondary);
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media (max-width: 768px) {
|
||||
.hero-title {
|
||||
font-size: 2rem;
|
||||
}
|
||||
.content-card {
|
||||
padding: var(--repo-spacing-5);
|
||||
}
|
||||
.feature-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="home-container">
|
||||
<!-- Hero 区域 -->
|
||||
<div class="hero-section">
|
||||
<h1 class="hero-title" id="hero-title">ap_ds: 轻量级 Python 音频革命</h1>
|
||||
<p class="hero-subtitle" id="hero-subtitle">2.5MB 终结 160MB 臃肿时代</p>
|
||||
<div class="version-badges" id="version-badges">
|
||||
<span class="repo-tag repo-tag--primary">v3.0.0 LTS</span>
|
||||
<span class="repo-tag repo-tag--success">仅 2.5MB</span>
|
||||
<span class="repo-tag repo-tag--warning">跨平台 W/M/L</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 目录 -->
|
||||
<div class="toc-section">
|
||||
<h3 class="repo-text-lg repo-font-semibold repo-mb-4"><i class="fa fa-list-ul" style="margin-right: 8px; color: var(--repo-color-primary);"></i>目录</h3>
|
||||
<div class="toc-grid">
|
||||
<a href="#introduction" class="toc-link">1. 项目介绍</a>
|
||||
<a href="#features" class="toc-link">2. 核心特性</a>
|
||||
<a href="#comparison" class="toc-link">3. 技术对比</a>
|
||||
<a href="#installation" class="toc-link">4. 安装与使用</a>
|
||||
<a href="#license" class="toc-link">5. 开源许可证</a>
|
||||
<a href="#faq" class="toc-link">6. 常见问题</a>
|
||||
<a href="#contact" class="toc-link">7. 联系与支持</a>
|
||||
<a href="#announcement" class="toc-link">8. 最新公告</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 1. 项目介绍 -->
|
||||
<div class="content-card" id="introduction">
|
||||
<h2>1. 项目介绍</h2>
|
||||
<div class="note-box">
|
||||
<p><strong>📋 官方信息</strong></p>
|
||||
<p>项目名称:ap_ds</p>
|
||||
<p>官方网站:<a href="https://apds.top" style="color: var(--repo-color-primary);">https://apds.top</a></p>
|
||||
<p>PyPI:<a href="https://pypi.org/project/ap_ds/" style="color: var(--repo-color-primary);">https://pypi.org/project/ap_ds/</a></p>
|
||||
<p>开发者:Dvs (DvsXT)</p>
|
||||
</div>
|
||||
<p>ap_ds 是一个极轻量级(2.5MB)的 Python 音频库,用于播放和精确解析 MP3、FLAC、OGG 和 WAV 文件。它没有外部 Python 依赖,仅使用 Python 标准库,为非阻塞播放提供平滑的 GUI 应用程序体验。</p>
|
||||
<div class="success-box">
|
||||
<p><strong>🎉 v3.0.0 LTS 长期支持版本</strong></p>
|
||||
<p>首个长期支持版本,提供 <strong>永久免费技术支持</strong>,哈希验证下载,确定性资源清理。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. 核心特性 -->
|
||||
<div class="content-card" id="features">
|
||||
<h2>2. 核心特性</h2>
|
||||
<div class="feature-grid">
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">⚡</div>
|
||||
<div class="feature-title">极致轻量</div>
|
||||
<div class="feature-desc">Windows 2.5MB · macOS 3.36MB · Linux 就绪</div>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">🎵</div>
|
||||
<div class="feature-title">四种格式</div>
|
||||
<div class="feature-desc">MP3 / FLAC / OGG / WAV</div>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">📊</div>
|
||||
<div class="feature-title">精确元数据</div>
|
||||
<div class="feature-desc">WAV/FLAC 100% 准确</div>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">▶️</div>
|
||||
<div class="feature-title">非阻塞播放</div>
|
||||
<div class="feature-desc">完美适配 GUI 应用</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid" style="display: grid; grid-template-columns: 1fr 1fr; gap: var(--repo-spacing-8);">
|
||||
<div>
|
||||
<h3>📦 体积对比</h3>
|
||||
<ul style="list-style: none; padding-left: 0;">
|
||||
<li style="margin-bottom: var(--repo-spacing-2);">🟢 <strong>ap_ds</strong> Windows: 2.5MB</li>
|
||||
<li style="margin-bottom: var(--repo-spacing-2);">🍎 macOS: 3.36MB</li>
|
||||
<li style="margin-bottom: var(--repo-spacing-2);">🐧 Linux: 动态链接 ~50KB</li>
|
||||
<li style="margin-bottom: var(--repo-spacing-2);">🔴 FFmpeg: ≥160MB</li>
|
||||
<li>🟡 Pygame + 解析库: 臃肿且功能不全</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3>⚙️ 为什么这么小?</h3>
|
||||
<ul>
|
||||
<li>专注:仅播放和解析,无编辑/转码</li>
|
||||
<li>基于工业级 SDL2</li>
|
||||
<li>逐字节优化</li>
|
||||
<li>跨平台 W/M/L</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. 技术对比 -->
|
||||
<div class="content-card" id="comparison">
|
||||
<h2>3. 技术对比</h2>
|
||||
|
||||
<h3>第一章:Pygame —— 游戏引擎的"音频残疾"</h3>
|
||||
<div class="code-block">
|
||||
<pre class="language-python"><code># Pygame 不可靠的音频时长
|
||||
my_sound = pygame.mixer.Sound('my_song.mp3')
|
||||
total_length = my_sound.get_length() # 仅对 WAV 可靠!</code></pre>
|
||||
<button class="copy-btn" onclick="copyCode(this)"><i class="fa fa-clipboard"></i> 复制</button>
|
||||
</div>
|
||||
<div class="warning-box">
|
||||
⚠️ Pygame 的 <code>Sound.get_length()</code> 仅对 WAV 格式正确,MP3/OGG/FLAC 返回无效值。
|
||||
</div>
|
||||
|
||||
<h3>第二章:FFmpeg 套件 —— 160MB 的"至少"之痛</h3>
|
||||
<table class="comparison-table">
|
||||
<thead><tr><th>组件</th><th>功能</th><th>大小</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>ffplay.exe</td><td>播放核心</td><td>≈80MB</td></tr>
|
||||
<tr><td>ffprobe.exe</td><td>元数据探测</td><td>≈80MB</td></tr>
|
||||
<tr><td>ffmpeg.exe</td><td>转换器</td><td>≈80MB</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="note-box">🔍 三个独立可执行文件,无 Python 封装,总计 ≥160MB。</div>
|
||||
|
||||
<h3>第三章:ap_ds 革命</h3>
|
||||
<div class="success-box">
|
||||
<p><strong>七大技术突破</strong></p>
|
||||
<ul>
|
||||
<li>✓ 2.5MB 解决 160MB 问题</li>
|
||||
<li>✓ 非阻塞消除 GUI 卡顿</li>
|
||||
<li>✓ 精确进度查询</li>
|
||||
<li>✓ 零依赖部署</li>
|
||||
<li>✓ AID 多音频管理</li>
|
||||
<li>✓ SDL2 性能</li>
|
||||
<li>✓ 智能 C 依赖跨平台</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 4. 安装与使用 -->
|
||||
<div class="content-card" id="installation">
|
||||
<h2>4. 安装与使用</h2>
|
||||
|
||||
<div class="code-block">
|
||||
<pre class="language-bash"><code>pip install ap_ds</code></pre>
|
||||
<button class="copy-btn" onclick="copyCode(this)"><i class="fa fa-clipboard"></i> 复制</button>
|
||||
</div>
|
||||
|
||||
<h3>基础示例</h3>
|
||||
<div class="code-block">
|
||||
<pre class="language-python"><code>from ap_ds import AudioLibrary
|
||||
|
||||
lib = AudioLibrary()
|
||||
aid = lib.play_from_file("music.mp3")
|
||||
lib.pause_audio(aid)
|
||||
lib.seek_audio(aid, 30.5)
|
||||
duration = lib.stop_audio(aid)</code></pre>
|
||||
<button class="copy-btn" onclick="copyCode(this)"><i class="fa fa-clipboard"></i> 复制</button>
|
||||
</div>
|
||||
|
||||
<h3>DAP 播放列表 (v2.3.0+)</h3>
|
||||
<div class="code-block">
|
||||
<pre class="language-python"><code>aid1 = lib.play_from_file("song1.mp3")
|
||||
recordings = lib.get_dap_recordings()
|
||||
lib.save_dap_to_json("my_playlist.ap-ds-dap")</code></pre>
|
||||
<button class="copy-btn" onclick="copyCode(this)"><i class="fa fa-clipboard"></i> 复制</button>
|
||||
</div>
|
||||
|
||||
<h3>淡入淡出效果 (v2.4.0+)</h3>
|
||||
<div class="code-block">
|
||||
<pre class="language-python"><code># 2秒淡入
|
||||
lib.fadein_music(aid, loops=-1, ms=2000)
|
||||
|
||||
# 3秒淡出
|
||||
lib.fadeout_music(ms=3000)
|
||||
|
||||
# 检查播放状态
|
||||
if lib.is_music_playing():
|
||||
print("音乐播放中")</code></pre>
|
||||
<button class="copy-btn" onclick="copyCode(this)"><i class="fa fa-clipboard"></i> 复制</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 5. 开源许可证 -->
|
||||
<div class="content-card" id="license">
|
||||
<h2>5. 开源许可证</h2>
|
||||
<div class="license-grid">
|
||||
<div class="license-card">
|
||||
<h4>✅ 您可以</h4>
|
||||
<ul>
|
||||
<li>商业使用</li>
|
||||
<li>修改代码</li>
|
||||
<li>闭源集成</li>
|
||||
<li>销售解决方案</li>
|
||||
<li>云服务部署</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="license-card">
|
||||
<h4>❌ 您不可以</h4>
|
||||
<ul>
|
||||
<li>使用原始品牌名称</li>
|
||||
<li>删除版权声明</li>
|
||||
<li>用于非法用途</li>
|
||||
<li>专利侵权诉讼</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="license-card">
|
||||
<h4>👍 您应该</h4>
|
||||
<ul>
|
||||
<li>保留版权声明</li>
|
||||
<li>标注来源归属</li>
|
||||
<li>报告安全问题</li>
|
||||
<li>修改版本用独立品牌</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<p class="repo-text-secondary repo-text-sm repo-mt-4">详细许可证请查看 <code>AP_DS_LICENSE.MD</code> 文件。</p>
|
||||
</div>
|
||||
|
||||
<!-- 6. 常见问题 -->
|
||||
<div class="content-card" id="faq">
|
||||
<h2>6. 常见问题</h2>
|
||||
|
||||
<div><p class="repo-font-semibold">问:公司可以将其集成到商业产品中吗?</p><div class="success-box">✅ 完全可以,无需付费、无需通知、无需授权。</div></div>
|
||||
|
||||
<div><p class="repo-font-semibold repo-mt-4">问:我可以修改并重新分发吗?</p><div class="success-box">✅ 可以,但必须使用独立品牌、保留版权、标注来源、提供维护者声明。</div></div>
|
||||
|
||||
<div><p class="repo-font-semibold repo-mt-4">问:我必须开源我的修改吗?</p><div class="warning-box">❌ 不需要,您可以闭源分发。</div></div>
|
||||
|
||||
<div><p class="repo-font-semibold repo-mt-4">问:在云服务中使用需要付费吗?</p><div class="warning-box">❌ 不需要,但欢迎贡献代码。</div></div>
|
||||
|
||||
<div><p class="repo-font-semibold repo-mt-4">问:技术支持收费吗?</p><div class="success-box">✅ 永久免费技术支持,通过 GitCode Issues 或邮件联系。</div></div>
|
||||
</div>
|
||||
|
||||
<!-- 7. 联系与支持 -->
|
||||
<div class="content-card" id="contact">
|
||||
<h2>7. 联系与支持</h2>
|
||||
<div class="grid" style="display: grid; grid-template-columns: 1fr 1fr; gap: var(--repo-spacing-6);">
|
||||
<div style="background: var(--repo-color-bg-secondary); padding: var(--repo-spacing-5); border-radius: var(--repo-radius-lg);">
|
||||
<h4>📧 授权咨询</h4>
|
||||
<p>me@dvsyun.top 或 dvs6666@163.com</p>
|
||||
<p class="repo-text-sm repo-text-tertiary">7个工作日内回复</p>
|
||||
</div>
|
||||
<div style="background: var(--repo-color-bg-secondary); padding: var(--repo-spacing-5); border-radius: var(--repo-radius-lg);">
|
||||
<h4>🛠️ 技术支持</h4>
|
||||
<p>GitCode Issues · 官方文档 · 邮件支持</p>
|
||||
<p class="repo-text-sm repo-text-success">✅ 永久免费</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="repo-mt-6" style="background: var(--repo-color-bg-secondary); border-radius: var(--repo-radius-lg); padding: var(--repo-spacing-5);">
|
||||
<h3>🔗 官方仓库</h3>
|
||||
<div class="grid" style="display: grid; grid-template-columns: 1fr 1fr; gap: var(--repo-spacing-4); margin-top: var(--repo-spacing-3);">
|
||||
<div>
|
||||
<p>✅ <strong>主仓库:</strong><a href="https://gitcode.com/dvsxt/ap_ds" class="repo-text-link">GitCode</a></p>
|
||||
<p>✅ <strong>国内镜像:</strong><a href="https://gitee.com/dssxt/ap_ds" class="repo-text-link">Gitee</a></p>
|
||||
</div>
|
||||
<div>
|
||||
<p>❌ <strong>已废弃:</strong><a href="https://github.com/dvs-web/ap_ds" class="repo-text-danger">GitHub</a></p>
|
||||
<p>🔗 <strong>项目主页:</strong><a href="https://apds.top" class="repo-text-link">apds.top</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 8. 最新公告 -->
|
||||
<div class="content-card" id="announcement">
|
||||
<h2>8. 最新公告</h2>
|
||||
<div id="announcement-content" class="announcement-content">加载中...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 复制代码函数
|
||||
window.copyCode = function(btn) {
|
||||
const pre = btn.parentElement.querySelector('pre');
|
||||
const code = pre.innerText;
|
||||
navigator.clipboard.writeText(code).then(() => {
|
||||
btn.classList.add('copied');
|
||||
btn.innerHTML = '<i class="fa fa-check"></i> 已复制';
|
||||
setTimeout(() => {
|
||||
btn.classList.remove('copied');
|
||||
btn.innerHTML = '<i class="fa fa-clipboard"></i> 复制';
|
||||
}, 2000);
|
||||
});
|
||||
};
|
||||
|
||||
// 从后端获取版本信息
|
||||
fetch('/api/version-info')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.title) document.getElementById('hero-title').innerHTML = data.title;
|
||||
if (data.subtitle) document.getElementById('hero-subtitle').innerHTML = data.subtitle;
|
||||
const badgesDiv = document.getElementById('version-badges');
|
||||
if (data.version || data.size) {
|
||||
badgesDiv.innerHTML = `
|
||||
<span class="repo-tag repo-tag--primary">${data.version || 'v3.0.0 LTS'}</span>
|
||||
<span class="repo-tag repo-tag--success">仅 ${data.size || '2.5MB'}</span>
|
||||
<span class="repo-tag repo-tag--warning">跨平台 W/M/L</span>
|
||||
`;
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
|
||||
// 加载最新公告
|
||||
fetch('/api/latest-announcement')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
const container = document.getElementById('announcement-content');
|
||||
if (data.html) {
|
||||
container.innerHTML = data.html;
|
||||
// 高亮代码块
|
||||
if (typeof Prism !== 'undefined') {
|
||||
Prism.highlightAllUnder(container);
|
||||
}
|
||||
// 为代码块添加复制按钮
|
||||
container.querySelectorAll('pre').forEach(pre => {
|
||||
if (!pre.parentElement.classList.contains('code-block')) {
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'code-block';
|
||||
pre.parentNode.insertBefore(wrapper, pre);
|
||||
wrapper.appendChild(pre);
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'copy-btn';
|
||||
btn.innerHTML = '<i class="fa fa-clipboard"></i> 复制';
|
||||
btn.onclick = function() { window.copyCode(btn); };
|
||||
wrapper.appendChild(btn);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
container.innerHTML = '<p class="repo-text-tertiary">暂无公告</p>';
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
document.getElementById('announcement-content').innerHTML = '<p class="repo-text-tertiary">无法加载公告</p>';
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,297 @@
|
||||
|
||||
<!-- 英文 SEO 标签 -->
|
||||
<meta name="description" content="Official releases page for ap_ds. Full version list: v3.0.0 LTS (current stable), branch APDSLTS001, release date 2026-03-01. Each version includes ZIP/TAR.GZ/WHL downloads, detailed changelogs (deterministic cleanup, hash verification), upgrade guides, and version status labels (stable/withdrawn/incident).">
|
||||
<meta name="keywords" content="ap_ds releases, download v3.0.0, changelog, LTS version, Python audio library stable release, APDSLTS001, hash verification, deterministic cleanup, WHL download">
|
||||
<meta property="og:title" content="ap_ds Releases – v3.0.0 LTS & Version Archive">
|
||||
<meta property="og:description" content="Official ap_ds release history. Download stable builds, read detailed changelogs, check version status (stable/withdrawn/incident).">
|
||||
<meta property="og:type" content="software">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="ap_ds Releases & Changelogs">
|
||||
|
||||
<!-- 中文 SEO 标签 -->
|
||||
<meta name="description" lang="zh-CN" content="ap_ds 官方发行版页面。完整版本列表:v3.0.0 LTS(当前稳定版),分支 APDSLTS001,发布日期 2026-03-01。每个版本附 ZIP/TAR.GZ/WHL 下载、详细更新日志(确定性资源清理、哈希验证)、升级指南及版本状态标注(稳定版/已撤回/开发事故)。">
|
||||
<meta name="keywords" lang="zh-CN" content="ap_ds 发行版, 下载 v3.0.0, 更新日志, LTS版本, Python音频库稳定版, APDSLTS001, 哈希验证, 确定性清理, WHL下载">
|
||||
<meta property="og:title" lang="zh-CN" content="ap_ds 发行版 – v3.0.0 LTS 及版本归档">
|
||||
<meta property="og:description" lang="zh-CN" content="ap_ds 官方版本历史。下载稳定版,阅读详细更新日志,查看版本状态(稳定/已撤回/开发事故)。">{% extends "base.html" %}{% block content %}{% block title %}ap_ds Releases – Version History, Changelogs & Stable Downloads (v3.0.0 LTS){% endblock %}
|
||||
<div class="repo-header">
|
||||
<div class="repo-path">
|
||||
<span>📦 发行版</span>
|
||||
</div>
|
||||
<p style="color: var(--repo-color-text-tertiary); margin-top: 8px;">所有 ap_ds 版本下载</p>
|
||||
</div>
|
||||
|
||||
<!-- 版本列表 -->
|
||||
<div class="file-list">
|
||||
<div class="file-list-header">
|
||||
<span>版本</span>
|
||||
<span>分支</span>
|
||||
<span>发布日期</span>
|
||||
<span>下载</span>
|
||||
</div>
|
||||
{% for release in releases %}
|
||||
<div class="file-row">
|
||||
<div class="file-name" style="flex: 0.3;">
|
||||
<span class="icon">🏷️</span>
|
||||
<strong style="color: var(--repo-color-primary);">{{ release.version }}</strong>
|
||||
</div>
|
||||
<div class="file-message" style="flex: 0.3;">{{ release.branch }}</div>
|
||||
<div class="file-time" style="flex: 0.2;">{{ release.date }}</div>
|
||||
<div style="display: flex; gap: 8px; flex: 0.2;">
|
||||
<a href="{{ url_for('download_zip', branch=release.branch, version=release.version) }}" class="btn btn-secondary" style="padding: 4px 8px;">ZIP</a>
|
||||
<a href="{{ url_for('download_tar', branch=release.branch, version=release.version) }}" class="btn btn-secondary" style="padding: 4px 8px;">TAR.GZ</a>
|
||||
<a href="{{ url_for('download_whl', version=release.version) }}" class="btn btn-primary" style="padding: 4px 8px;">WHL</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- 版本详情 - 展开/收起 -->
|
||||
<div class="releases-details">
|
||||
{% for release in releases %}
|
||||
<div class="release-card" data-version="{{ release.version }}">
|
||||
<div class="release-header" onclick="toggleRelease('{{ release.version }}')">
|
||||
<div class="release-header-left">
|
||||
<span class="release-version">{{ release.version }}</span>
|
||||
<span class="release-branch">{{ release.branch }}</span>
|
||||
<span class="release-date">{{ release.date }}</span>
|
||||
</div>
|
||||
<div class="release-header-right">
|
||||
<span class="release-status">
|
||||
{% if release.has_whl %}
|
||||
<span class="repo-tag repo-tag--success">PyPI</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
<span class="release-toggle" id="toggle-{{ release.version }}">▼</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="release-body" id="release-{{ release.version }}" style="display: none;">
|
||||
<div class="release-content">
|
||||
{% if release.announcement %}
|
||||
{{ release.announcement|safe }}
|
||||
{% else %}
|
||||
<div class="info-message">
|
||||
📄 暂无版本说明文档。请查看 <a href="{{ url_for('view_file', file_path=release.branch + '/' + release.version + '/README.md', branch=release.branch, version=release.version) }}">README.md</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="release-downloads">
|
||||
<a href="{{ url_for('download_zip', branch=release.branch, version=release.version) }}" class="btn btn-secondary">📦 下载 ZIP</a>
|
||||
<a href="{{ url_for('download_tar', branch=release.branch, version=release.version) }}" class="btn btn-secondary">📦 下载 TAR.GZ</a>
|
||||
<a href="{{ url_for('download_whl', version=release.version) }}" class="btn btn-primary">📦 下载 WHL</a>
|
||||
<a href="{{ url_for('view_file', file_path=release.branch + '/' + release.version + '/README.md', branch=release.branch, version=release.version) }}" class="btn btn-secondary">📖 查看 README</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.releases-details {
|
||||
margin-top: 32px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.release-card {
|
||||
border: 1px solid var(--repo-color-border);
|
||||
border-radius: var(--repo-radius-lg, 8px);
|
||||
overflow: hidden;
|
||||
background-color: var(--repo-color-bg);
|
||||
}
|
||||
|
||||
.release-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
background-color: var(--repo-color-bg-secondary);
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.release-header:hover {
|
||||
background-color: var(--repo-color-bg-hover);
|
||||
}
|
||||
|
||||
.release-header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.release-version {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--repo-color-primary);
|
||||
}
|
||||
|
||||
.release-branch {
|
||||
font-size: 13px;
|
||||
color: var(--repo-color-text-secondary);
|
||||
background-color: var(--repo-color-bg-tertiary);
|
||||
padding: 2px 8px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.release-date {
|
||||
font-size: 13px;
|
||||
color: var(--repo-color-text-tertiary);
|
||||
}
|
||||
|
||||
.release-header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.release-toggle {
|
||||
font-size: 16px;
|
||||
color: var(--repo-color-text-tertiary);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.release-card.open .release-toggle {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.release-body {
|
||||
padding: 24px;
|
||||
border-top: 1px solid var(--repo-color-border);
|
||||
background-color: var(--repo-color-bg);
|
||||
}
|
||||
|
||||
.release-content {
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 24px;
|
||||
border-bottom: 1px solid var(--repo-color-border);
|
||||
}
|
||||
|
||||
/* README 内容样式 */
|
||||
.release-content h1, .release-content h2, .release-content h3 {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.release-content h1 { font-size: 1.8em; border-bottom: 1px solid var(--repo-color-border); padding-bottom: 0.3em; }
|
||||
.release-content h2 { font-size: 1.5em; border-bottom: 1px solid var(--repo-color-border); padding-bottom: 0.3em; }
|
||||
.release-content h3 { font-size: 1.25em; }
|
||||
.release-content h4 { font-size: 1.1em; }
|
||||
.release-content p { margin-bottom: 16px; line-height: 1.6; }
|
||||
.release-content code {
|
||||
background-color: var(--repo-color-bg-tertiary);
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 4px;
|
||||
font-family: var(--repo-font-family-mono, monospace);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.release-content pre {
|
||||
background-color: var(--repo-color-bg-tertiary);
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.release-content pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
.release-content ul, .release-content ol {
|
||||
padding-left: 2em;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.release-content blockquote {
|
||||
border-left: 4px solid var(--repo-color-border);
|
||||
padding-left: 16px;
|
||||
color: var(--repo-color-text-tertiary);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.release-content table {
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 16px;
|
||||
width: 100%;
|
||||
}
|
||||
.release-content th, .release-content td {
|
||||
border: 1px solid var(--repo-color-border);
|
||||
padding: 8px 12px;
|
||||
}
|
||||
.release-content th {
|
||||
background-color: var(--repo-color-bg-secondary);
|
||||
}
|
||||
.release-content hr {
|
||||
border: none;
|
||||
border-top: 1px solid var(--repo-color-border);
|
||||
margin: 24px 0;
|
||||
}
|
||||
.release-content img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.release-downloads {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.info-message {
|
||||
padding: 16px;
|
||||
background-color: var(--repo-color-bg-secondary);
|
||||
border-radius: var(--repo-radius-md, 6px);
|
||||
color: var(--repo-color-text-secondary);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.repo-tag--success {
|
||||
background-color: rgba(16, 185, 129, 0.1);
|
||||
color: #10b981;
|
||||
padding: 2px 8px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.release-header-left {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
.release-downloads {
|
||||
flex-direction: column;
|
||||
}
|
||||
.release-downloads .btn {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
function toggleRelease(version) {
|
||||
var body = document.getElementById('release-' + version);
|
||||
var card = document.querySelector('.release-card[data-version="' + version + '"]');
|
||||
|
||||
if (body.style.display === 'none') {
|
||||
body.style.display = 'block';
|
||||
card.classList.add('open');
|
||||
} else {
|
||||
body.style.display = 'none';
|
||||
card.classList.remove('open');
|
||||
}
|
||||
}
|
||||
|
||||
// 默认展开第一个版本
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var firstRelease = document.querySelector('.release-card');
|
||||
if (firstRelease) {
|
||||
var version = firstRelease.getAttribute('data-version');
|
||||
if (version) {
|
||||
var body = document.getElementById('release-' + version);
|
||||
if (body) {
|
||||
body.style.display = 'block';
|
||||
firstRelease.classList.add('open');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,59 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ metadata.name }} · ap_ds{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="repo-header">
|
||||
<div class="repo-path">
|
||||
<a href="/code">ap_ds</a>
|
||||
<span class="separator">/</span>
|
||||
<a href="{{ url_for('code_browser') }}?branch={{ branch }}">{{ branch }}</a>
|
||||
<span class="separator">/</span>
|
||||
<a href="{{ url_for('code_browser') }}?branch={{ branch }}&version={{ version }}">{{ version }}</a>
|
||||
{% if branch and version %}
|
||||
{# 将反斜杠替换为正斜杠 #}
|
||||
{% set normalized_path = metadata.path.replace('\\', '/') %}
|
||||
{% set relative_path = normalized_path.replace(branch + '/' + version + '/', '') %}
|
||||
{% if relative_path and '/' in relative_path %}
|
||||
{% set parts = relative_path.split('/') %}
|
||||
{% for part in parts[:-1] %}
|
||||
<span class="separator">/</span>
|
||||
<a href="#">{{ part }}</a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<span class="separator">/</span>
|
||||
<span>{{ metadata.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="code-header">
|
||||
<div>
|
||||
<span style="font-family: monospace;">{{ metadata.name }}</span>
|
||||
<span style="margin-left: 12px; font-size: 12px; color: #848d97;">{{ metadata.size }} 字节</span>
|
||||
</div>
|
||||
<div style="display: flex; gap: 8px;">
|
||||
<button class="copy-btn" onclick="copyCode(this)">复制代码</button>
|
||||
<a href="{{ url_for('download_file', file_path=metadata.path.replace('\\', '/')) }}" class="copy-btn" style="text-decoration: none;">下载文件</a>
|
||||
<a href="{{ url_for('code_browser') }}?branch={{ branch }}&version={{ version }}" class="copy-btn" style="text-decoration: none;">返回</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="code-content">
|
||||
<pre><code class="language-python" id="code-content">{{ content|escape }}</code></pre>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
hljs.highlightElement(document.querySelector('#code-content'));
|
||||
});
|
||||
|
||||
function copyCode(btn) {
|
||||
var code = document.getElementById('code-content').innerText;
|
||||
navigator.clipboard.writeText(code).then(function() {
|
||||
var originalText = btn.innerText;
|
||||
btn.innerText = '已复制!';
|
||||
setTimeout(function() { btn.innerText = originalText; }, 2000);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,40 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ metadata.name }} - ap_ds{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="repo-container">
|
||||
<div class="repo-card repo-mb-4">
|
||||
<div class="repo-card__body">
|
||||
<div class="repo-flex repo-justify-between repo-items-center">
|
||||
<div>
|
||||
<h2 class="repo-text-lg repo-font-semibold">{{ metadata.name }}</h2>
|
||||
<div class="repo-text-sm repo-text-tertiary repo-mt-1">
|
||||
{{ metadata.size }} 字节 · 最后修改 {{ metadata.modified }} · 类型: {{ metadata.extension }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="repo-flex repo-gap-2">
|
||||
<a href="{{ url_for('download_file', file_path=metadata.path) }}" class="repo-btn repo-btn--sm repo-btn--primary">下载文件</a>
|
||||
<a href="{{ url_for('code_browser') }}?branch={{ branch }}&version={{ version }}" class="repo-btn repo-btn--sm repo-btn--ghost">返回</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if is_text %}
|
||||
<div class="repo-card">
|
||||
<div class="repo-card__body">
|
||||
<pre style="background: var(--repo-color-bg-tertiary); padding: 1rem; border-radius: 8px; overflow-x: auto; font-family: monospace; font-size: 13px;">{{ content }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="repo-card repo-text-center repo-py-8">
|
||||
<div class="repo-card__body">
|
||||
<span class="icon-file" style="font-size: 4rem; color: var(--repo-color-text-tertiary);"></span>
|
||||
<p class="repo-text-secondary repo-mt-4">此文件类型无法在线预览</p>
|
||||
<a href="{{ url_for('download_file', file_path=metadata.path) }}" class="repo-btn repo-btn--primary repo-mt-4">下载文件</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,34 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ metadata.name }} · ap_ds{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="repo-header">
|
||||
<div class="repo-path">
|
||||
<a href="/code">ap_ds</a>
|
||||
<span class="separator">/</span>
|
||||
<a href="{{ url_for('code_browser') }}?branch={{ branch }}">{{ branch }}</a>
|
||||
<span class="separator">/</span>
|
||||
<a href="{{ url_for('code_browser') }}?branch={{ branch }}&version={{ version }}">{{ version }}</a>
|
||||
<span class="separator">/</span>
|
||||
<span>{{ metadata.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="code-header">
|
||||
<div>
|
||||
<span style="font-family: monospace;">{{ metadata.name }}</span>
|
||||
<span style="margin-left: 12px; font-size: 12px; color: var(--repo-color-text-tertiary);">{{ metadata.size }} 字节</span>
|
||||
<span style="margin-left: 12px; font-size: 12px; color: var(--repo-color-text-tertiary);">最后修改 {{ metadata.modified }}</span>
|
||||
</div>
|
||||
<div style="display: flex; gap: 8px;">
|
||||
<a href="{{ url_for('download_file', file_path=metadata.path) }}" class="copy-btn" style="text-decoration: none;">下载文件</a>
|
||||
<a href="{{ url_for('code_browser') }}?branch={{ branch }}&version={{ version }}" class="copy-btn" style="text-decoration: none;">返回</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 关键修改:使用 |safe 过滤器 -->
|
||||
<div class="readme-content" style="border: 1px solid var(--repo-color-border); border-top: none; border-radius: 0 0 var(--repo-radius-lg, 8px) var(--repo-radius-lg, 8px);">
|
||||
{{ html_content|safe }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user