This commit is contained in:
@@ -106,8 +106,7 @@
|
|||||||
<span>角色</span>
|
<span>角色</span>
|
||||||
<select v-model="uploadRole">
|
<select v-model="uploadRole">
|
||||||
<option :value="null">— 自动 / 通用图 —</option>
|
<option :value="null">— 自动 / 通用图 —</option>
|
||||||
<option value="chord_letters">和弦谱(字母 G/Em/C)</option>
|
<option value="chord">和弦谱(数字级数 1/4/5)</option>
|
||||||
<option value="chord_functional">简谱(数字 1/4/5)</option>
|
|
||||||
<option value="numbered">简谱(手动上传)</option>
|
<option value="numbered">简谱(手动上传)</option>
|
||||||
<option value="staff">五线谱</option>
|
<option value="staff">五线谱</option>
|
||||||
</select>
|
</select>
|
||||||
@@ -339,8 +338,6 @@ function kindLabel(k) {
|
|||||||
function roleLabel(r) {
|
function roleLabel(r) {
|
||||||
return ({
|
return ({
|
||||||
chord: '和弦谱',
|
chord: '和弦谱',
|
||||||
chord_letters: '和弦谱',
|
|
||||||
chord_functional: '简谱',
|
|
||||||
numbered: '简谱',
|
numbered: '简谱',
|
||||||
staff: '五线谱',
|
staff: '五线谱',
|
||||||
})[r] || r
|
})[r] || r
|
||||||
|
|||||||
@@ -131,10 +131,10 @@
|
|||||||
>{{ line.text }}</div>
|
>{{ line.text }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 谱面:弹唱谱 / 功能谱 共用渲染 -->
|
<!-- 和弦谱 -->
|
||||||
<div v-show="['chord', 'functional'].includes(activeTab)" class="sheet-box">
|
<div v-show="activeTab === 'chord'" class="sheet-box">
|
||||||
<img
|
<img
|
||||||
v-for="att in chordTabAttachments(activeTab)"
|
v-for="att in chordTabAttachments('chord')"
|
||||||
:key="att.id"
|
:key="att.id"
|
||||||
:src="attachmentUrl(att.id)"
|
:src="attachmentUrl(att.id)"
|
||||||
:alt="att.filename"
|
:alt="att.filename"
|
||||||
@@ -142,34 +142,33 @@
|
|||||||
@click="fullscreenSrc = attachmentUrl(att.id)"
|
@click="fullscreenSrc = attachmentUrl(att.id)"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="chordTabAttachments(activeTab).length === 0"
|
v-if="chordTabAttachments('chord').length === 0"
|
||||||
class="auto-fetch"
|
class="auto-fetch"
|
||||||
>
|
>
|
||||||
<p v-if="chordStateOf(activeTab) === 'idle'" class="hint-line">
|
<p v-if="chordStateOf('chord') === 'idle'" class="hint-line">
|
||||||
<span v-if="activeTab === 'chord'">从 yopu.co 抓 <b>和弦谱(字母 G/Em/C + 六线谱)</b>。</span>
|
从 yopu.co 抓 <b>和弦谱(歌词 + 数字级数 1/4/5/6m)</b>。
|
||||||
<span v-else>从 yopu.co 抓 <b>简谱(数字 1/4/5/6m 级数)</b>。</span>
|
|
||||||
</p>
|
</p>
|
||||||
<p v-else-if="['pending','processing'].includes(chordStateOf(activeTab))" class="hint-line">
|
<p v-else-if="['pending','processing'].includes(chordStateOf('chord'))" class="hint-line">
|
||||||
正在抓取,约 30-60s…
|
正在抓取,约 30-60s…
|
||||||
</p>
|
</p>
|
||||||
<p v-else-if="chordStateOf(activeTab) === 'failed'" class="hint-line err">
|
<p v-else-if="chordStateOf('chord') === 'failed'" class="hint-line err">
|
||||||
抓取失败:{{ chordErrors[modeForTab(activeTab)] }}
|
抓取失败:{{ chordErrors.chord }}
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
class="btn-fetch"
|
class="btn-fetch"
|
||||||
:disabled="['pending','processing'].includes(chordStateOf(activeTab))"
|
:disabled="['pending','processing'].includes(chordStateOf('chord'))"
|
||||||
@click="startChordFetch(modeForTab(activeTab))"
|
@click="startChordFetch('chord')"
|
||||||
>
|
>
|
||||||
<span v-if="['pending','processing'].includes(chordStateOf(activeTab))" class="spin">⏳</span>
|
<span v-if="['pending','processing'].includes(chordStateOf('chord'))" class="spin">⏳</span>
|
||||||
<span v-else>🎸 自动抓取{{ activeTab === 'chord' ? '和弦谱' : '简谱' }}</span>
|
<span v-else>🎸 自动抓取和弦谱</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 五线谱(手动上传的图) -->
|
<!-- 简谱(手动上传的图) / 五线谱 -->
|
||||||
<div v-show="activeTab === 'staff'" class="sheet-box">
|
<div v-show="['numbered', 'staff'].includes(activeTab)" class="sheet-box">
|
||||||
<img
|
<img
|
||||||
v-for="att in roleAttachments('staff')"
|
v-for="att in roleAttachments(activeTab)"
|
||||||
:key="att.id"
|
:key="att.id"
|
||||||
:src="attachmentUrl(att.id)"
|
:src="attachmentUrl(att.id)"
|
||||||
:alt="att.filename"
|
:alt="att.filename"
|
||||||
@@ -480,11 +479,11 @@ async function runInspire() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// chord —— 两个 mode 各自独立 state
|
// chord —— 单 mode(只抓 yopu 默认数字级数版)
|
||||||
const chordStates = ref({ letters: 'idle', functional: 'idle' })
|
const chordStates = ref({ chord: 'idle' })
|
||||||
const chordErrors = ref({ letters: '', functional: '' })
|
const chordErrors = ref({ chord: '' })
|
||||||
const chordPollTimers = { letters: null, functional: null }
|
const chordPollTimers = { chord: null }
|
||||||
const chordPollStarted = { letters: 0, functional: 0 }
|
const chordPollStarted = { chord: 0 }
|
||||||
function chordStateOf(tab) {
|
function chordStateOf(tab) {
|
||||||
return chordStates.value[modeForTab(tab)] || 'idle'
|
return chordStates.value[modeForTab(tab)] || 'idle'
|
||||||
}
|
}
|
||||||
@@ -524,19 +523,15 @@ function roleAttachments(role) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 和弦谱 tab = 字母版 (chord_letters + 历史 chord)
|
// 和弦谱 tab = role='chord'(数字级数版,yopu 自动抓的就这种)
|
||||||
// 简谱 tab = 数字级数版 (chord_functional) + 用户手动上传 numbered
|
|
||||||
function chordTabAttachments(tab) {
|
function chordTabAttachments(tab) {
|
||||||
const set = tab === 'chord'
|
|
||||||
? new Set(['chord', 'chord_letters'])
|
|
||||||
: new Set(['chord_functional', 'numbered'])
|
|
||||||
return (selected.value?.attachments || []).filter(
|
return (selected.value?.attachments || []).filter(
|
||||||
a => a.kind === 'image' && set.has(a.role),
|
a => a.kind === 'image' && a.role === 'chord',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function modeForTab(tab) {
|
function modeForTab(_tab) {
|
||||||
return tab === 'functional' ? 'functional' : 'letters'
|
return 'chord' // 唯一抓取模式
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabs = computed(() => {
|
const tabs = computed(() => {
|
||||||
@@ -544,7 +539,8 @@ const tabs = computed(() => {
|
|||||||
const list = []
|
const list = []
|
||||||
if (selected.value.lyrics) list.push({ key: 'lyrics', label: '歌词', count: 0 })
|
if (selected.value.lyrics) list.push({ key: 'lyrics', label: '歌词', count: 0 })
|
||||||
list.push({ key: 'chord', label: '和弦谱', count: chordTabAttachments('chord').length })
|
list.push({ key: 'chord', label: '和弦谱', count: chordTabAttachments('chord').length })
|
||||||
list.push({ key: 'functional', label: '简谱', count: chordTabAttachments('functional').length })
|
const num = roleAttachments('numbered').length
|
||||||
|
if (num) list.push({ key: 'numbered', label: '简谱(自传)', count: num })
|
||||||
const staff = roleAttachments('staff').length
|
const staff = roleAttachments('staff').length
|
||||||
if (staff) list.push({ key: 'staff', label: '五线谱', count: staff })
|
if (staff) list.push({ key: 'staff', label: '五线谱', count: staff })
|
||||||
if (pdfAttachments.value.length) list.push({ key: 'pdf', label: '乐谱 PDF', count: pdfAttachments.value.length })
|
if (pdfAttachments.value.length) list.push({ key: 'pdf', label: '乐谱 PDF', count: pdfAttachments.value.length })
|
||||||
@@ -662,9 +658,9 @@ async function promptNewPlaylist() {
|
|||||||
async function loadPiece(id) {
|
async function loadPiece(id) {
|
||||||
selected.value = null
|
selected.value = null
|
||||||
notesDraft.value = ''
|
notesDraft.value = ''
|
||||||
stopChordPoll('letters'); stopChordPoll('functional')
|
stopChordPoll('chord')
|
||||||
chordStates.value = { letters: 'idle', functional: 'idle' }
|
chordStates.value = { chord: 'idle' }
|
||||||
chordErrors.value = { letters: '', functional: '' }
|
chordErrors.value = { chord: '' }
|
||||||
abortChat()
|
abortChat()
|
||||||
chatMessages.value = []
|
chatMessages.value = []
|
||||||
chatStreamText.value = ''
|
chatStreamText.value = ''
|
||||||
@@ -1002,7 +998,7 @@ onMounted(async () => {
|
|||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
document.removeEventListener('keydown', onKeyDown)
|
document.removeEventListener('keydown', onKeyDown)
|
||||||
if (notesTimer) clearTimeout(notesTimer)
|
if (notesTimer) clearTimeout(notesTimer)
|
||||||
stopChordPoll('letters'); stopChordPoll('functional')
|
stopChordPoll('chord')
|
||||||
abortChat()
|
abortChat()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1549,17 +1549,10 @@ async fn upload_attachments(
|
|||||||
) -> Result<JsonResp<Value>, AppError> {
|
) -> Result<JsonResp<Value>, AppError> {
|
||||||
let role = match q.role.as_deref().map(str::trim).filter(|s| !s.is_empty()) {
|
let role = match q.role.as_deref().map(str::trim).filter(|s| !s.is_empty()) {
|
||||||
None => None,
|
None => None,
|
||||||
Some(r)
|
Some(r) if matches!(r, "chord" | "jianpu" | "numbered" | "staff") => Some(r.to_string()),
|
||||||
if matches!(
|
|
||||||
r,
|
|
||||||
"chord" | "chord_letters" | "chord_functional" | "numbered" | "staff"
|
|
||||||
) =>
|
|
||||||
{
|
|
||||||
Some(r.to_string())
|
|
||||||
}
|
|
||||||
Some(other) => {
|
Some(other) => {
|
||||||
return Err(AppError::bad_request(format!(
|
return Err(AppError::bad_request(format!(
|
||||||
"unsupported role '{other}', expect one of: chord / chord_letters / chord_functional / numbered / staff"
|
"unsupported role '{other}', expect one of: chord / jianpu / numbered / staff"
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user