133 行
6.9 KiB
Vue
133 行
6.9 KiB
Vue
<template>
|
||
<div class="ops" @click.stop @keydown.stop>
|
||
<div class="btns">
|
||
<el-button size="small" type="danger" plain :disabled="!can" @click="run('断电重启', { cmd: 'power_cycle' }, true)">断电重启</el-button>
|
||
<el-button size="small" plain :disabled="!can" @click="run('强制重启(长按电源 10 秒)', { cmd: 'key', keys: 'pwr', ms: 10000 }, true)">强制重启</el-button>
|
||
<el-dropdown trigger="click" :disabled="!r?.pr" @command="more">
|
||
<el-button size="small" plain>更多<el-icon class="el-icon--right"><ArrowDown /></el-icon></el-button>
|
||
<template #dropdown>
|
||
<el-dropdown-menu>
|
||
<el-dropdown-item command="tap">短按电源(亮屏 / 息屏)</el-dropdown-item>
|
||
<el-dropdown-item command="on">开机(长按电源 5 秒)</el-dropdown-item>
|
||
<el-dropdown-item command="fastboot" divided>进 Fastboot(音量下 + 电源)</el-dropdown-item>
|
||
<el-dropdown-item command="recovery">进 Recovery(音量上 + 电源)</el-dropdown-item>
|
||
<el-dropdown-item command="edl">进 EDL / 9008</el-dropdown-item>
|
||
<el-dropdown-item command="vbus" divided>{{ r?.vb ? '断开 USB' : '接通 USB' }}</el-dropdown-item>
|
||
<el-dropdown-item command="flash">{{ r?.chg > 0 ? '退出刷机模式' : '刷机模式(1 小时)…' }}</el-dropdown-item>
|
||
<el-dropdown-item v-if="r?.chg > 0" command="flash_add">刷机模式加钟 30 分钟</el-dropdown-item>
|
||
<el-dropdown-item command="power">{{ r?.en ? '断电(保持关闭)' : '上电' }}</el-dropdown-item>
|
||
<el-dropdown-item command="volt">调电压…</el-dropdown-item>
|
||
</el-dropdown-menu>
|
||
</template>
|
||
</el-dropdown>
|
||
</div>
|
||
<div class="ar">
|
||
<el-switch v-model="auto" size="small" :disabled="!r?.pr" @change="saveAuto" />
|
||
<span class="muted">自动恢复</span>
|
||
<el-tag v-if="recState && recState !== 'idle'" type="warning" size="small">{{ RECOVERY_ZH[recState] }}</el-tag>
|
||
</div>
|
||
|
||
<el-dialog v-model="voltOpen" title="调整输出电压" width="min(420px, 92vw)" append-to-body>
|
||
<p class="muted">第 {{ rail }} 路。范围 3.524 ~ 4.010 V(硬件决定,固件再限一次)。只是微调,不是保护手段。</p>
|
||
<el-slider v-model="mv" :min="3524" :max="4010" :step="1" show-input />
|
||
<template #footer>
|
||
<el-button @click="voltOpen = false">取消</el-button>
|
||
<el-button type="primary" @click="setVolt">下发 {{ (mv / 1000).toFixed(3) }} V</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
// 一路的全部操作(原 RailCard 的动作部分)。拓扑图里的元件点击也走这里:defineExpose 出 more()。
|
||
import { computed, ref, watch } from 'vue'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
import { ArrowDown } from '@element-plus/icons-vue'
|
||
import { api, rspText } from '../api'
|
||
import { RECOVERY_ZH } from '../util'
|
||
|
||
const props = defineProps({ board: Object, rail: Number, recState: String })
|
||
|
||
const r = computed(() => props.board?.status?.rails?.[props.rail - 1])
|
||
const phone = computed(() => props.board?.phones?.find(p => p.rail === props.rail) || {})
|
||
const online = computed(() => props.board?.online && !props.board?.stale)
|
||
// 在位、板子在线、执行器空闲才能下动作(忙的时候板子也会回 busy,这里只是少一次无用的往返)
|
||
const can = computed(() => !!(r.value?.pr && online.value && r.value.st === 'idle'))
|
||
|
||
const auto = ref(false)
|
||
watch(phone, p => { auto.value = !!p.auto_recover }, { immediate: true })
|
||
|
||
const who = () => `${props.board.name || props.board.id} #${props.rail}${phone.value.label ? `(${phone.value.label})` : ''}`
|
||
|
||
async function run(title, body, confirm) {
|
||
if (confirm) {
|
||
try {
|
||
await ElMessageBox.confirm(`对 ${who()} 执行「${title}」?`, '确认', { type: 'warning', confirmButtonText: '执行' })
|
||
} catch { return }
|
||
}
|
||
try {
|
||
const rsp = await api.cmd(props.board.id, { rail: props.rail, ...body })
|
||
ElMessage({ type: rsp.ok ? 'success' : 'error', message: `${title}:${rspText(rsp)}` })
|
||
} catch (e) {
|
||
ElMessage.error(`${title}:${e.message}`)
|
||
}
|
||
}
|
||
|
||
function more(c) {
|
||
const on = r.value
|
||
if (!on?.pr) return
|
||
switch (c) {
|
||
case 'tap': return run('短按电源', { cmd: 'key', keys: 'pwr', ms: 100 })
|
||
case 'on': return run('开机', { cmd: 'key', keys: 'pwr', ms: 5000 })
|
||
case 'fastboot': return run('进 Fastboot', { cmd: 'key', keys: 'vdn+pwr', ms: 10000 }, true)
|
||
case 'recovery': return run('进 Recovery', { cmd: 'key', keys: 'vup+pwr', ms: 10000 }, true)
|
||
case 'edl': return run('进 EDL', { cmd: 'edl' }, true)
|
||
case 'vbus': return run(on.vb ? '断开 USB' : '接通 USB', { cmd: 'vbus', on: !on.vb }, on.vb)
|
||
case 'power': return run(on.en ? '断电' : '上电', { cmd: 'power', on: !on.en }, on.en)
|
||
case 'flash': return flashMode(!(on.chg > 0))
|
||
case 'flash_add': return run('刷机模式加钟 30 分钟', { cmd: 'flash_mode', add: 30 }) // 板子截断到剩余 120 分钟
|
||
case 'volt': mv.value = on.set || 3900; voltOpen.value = true
|
||
}
|
||
}
|
||
|
||
// 固件 0.1.5 刷机模式:窗口内 USB 通着也允许手机充电(节点被充电器顶到 ~4.4 V 不断 USB),
|
||
// 超过 4.45 V / 反灌 / 欠压照样保护;最多同时 2 路(板子回 state);1 小时到期自动恢复。
|
||
async function flashMode(on) {
|
||
if (!on) return run('退出刷机模式', { cmd: 'flash_mode', on: false })
|
||
try {
|
||
await ElMessageBox.confirm(
|
||
`${who()} 进入刷机模式 1 小时?期间 USB 接通时允许手机充电、不会因为电压被顶高而断 USB,` +
|
||
'电池端会停在约 4.4 V。最多同时 2 路;到期自动恢复。开完再「接通 USB」。',
|
||
'刷机模式', { type: 'warning', confirmButtonText: '进入' })
|
||
} catch { return }
|
||
return run('刷机模式', { cmd: 'flash_mode', on: true, min: 60 })
|
||
}
|
||
|
||
const voltOpen = ref(false)
|
||
const mv = ref(3900)
|
||
async function setVolt() {
|
||
voltOpen.value = false
|
||
await run(`电压 ${(mv.value / 1000).toFixed(3)} V`, { cmd: 'set_voltage', mv: mv.value })
|
||
}
|
||
|
||
async function saveAuto(v) {
|
||
if (v) {
|
||
try {
|
||
await ElMessageBox.confirm(
|
||
`打开 ${who()} 的自动恢复?电流平直或过低持续 5 分钟会自动长按电源,仍不好再断电重启;` +
|
||
'判据阈值还没有实测依据,建议先在一两台上观察。', '自动恢复', { type: 'warning' })
|
||
} catch { auto.value = false; return }
|
||
}
|
||
try { await api.patchPhone(props.board.id, props.rail, { auto_recover: v }) } catch (e) { ElMessage.error(e.message); auto.value = !v }
|
||
}
|
||
|
||
defineExpose({ more })
|
||
</script>
|
||
|
||
<style scoped>
|
||
.ops { display: flex; flex-direction: column; gap: 6px; cursor: default; }
|
||
.btns { display: flex; gap: 6px; flex-wrap: wrap; }
|
||
.btns .el-button + .el-button { margin-left: 0; }
|
||
.ar { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
|
||
</style>
|