music: 新建 music app,替换 piano-sheet
听歌 + 练琴曲目管理: - 数据:piece (title/artist/category/lyrics/play_count/notes) + attachments (audio/video/pdf/image; image 带 role=chord/numbered/staff) - 后端 axum + sqlite,附件流式落 PVC,ServeFile 支持 Range(视频拖动) - 前端 guitar 风格 player:左 sidebar + tabs(歌词/吉他谱/简谱/五线谱/PDF/视频),LRC 同步、快捷键、笔记自动保存 - ns cube-music + music.famzheng.me + bodylimit 5GiB - scripts/import_guitar.py 用于把 oci /data/guitar/ 旧曲库导入
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
// 薄薄一层 fetch 封装。错误统一抛 Error(message)。
|
||||
|
||||
async function jsonOrThrow(res) {
|
||||
if (!res.ok) {
|
||||
const text = await res.text().catch(() => '')
|
||||
throw new Error(text || `${res.status} ${res.statusText}`)
|
||||
}
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export function listPieces() {
|
||||
return fetch('/api/pieces').then(jsonOrThrow)
|
||||
}
|
||||
|
||||
export function getPiece(id) {
|
||||
return fetch(`/api/pieces/${id}`).then(jsonOrThrow)
|
||||
}
|
||||
|
||||
export function createPiece(body) {
|
||||
return fetch('/api/pieces', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
}).then(jsonOrThrow)
|
||||
}
|
||||
|
||||
export function patchPiece(id, body) {
|
||||
return fetch(`/api/pieces/${id}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
}).then(jsonOrThrow)
|
||||
}
|
||||
|
||||
export function deletePiece(id) {
|
||||
return fetch(`/api/pieces/${id}`, { method: 'DELETE' }).then(jsonOrThrow)
|
||||
}
|
||||
|
||||
export function recordPlay(id) {
|
||||
return fetch(`/api/pieces/${id}/play`, { method: 'POST' }).then(jsonOrThrow)
|
||||
}
|
||||
|
||||
// `role`: null | 'chord' | 'numbered' | 'staff'
|
||||
export function uploadAttachments(pieceId, files, role) {
|
||||
const fd = new FormData()
|
||||
for (const f of files) fd.append('files', f, f.name)
|
||||
const url = role
|
||||
? `/api/pieces/${pieceId}/attachments?role=${encodeURIComponent(role)}`
|
||||
: `/api/pieces/${pieceId}/attachments`
|
||||
return fetch(url, { method: 'POST', body: fd }).then(jsonOrThrow)
|
||||
}
|
||||
|
||||
export function deleteAttachment(id) {
|
||||
return fetch(`/api/attachments/${id}`, { method: 'DELETE' }).then(jsonOrThrow)
|
||||
}
|
||||
|
||||
export function attachmentUrl(id) {
|
||||
return `/api/attachments/${id}`
|
||||
}
|
||||
Reference in New Issue
Block a user