发布 0.1.0:通知渠道、告警规则与多项体验修复
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
import type { AliveStatus } from '@/types/api'
|
||||
|
||||
/** 测活状态 → 展示标签与徽标语义;suspended 来自云端账户能力接口的暂停标记 */
|
||||
export const aliveStatusMeta: Record<
|
||||
AliveStatus,
|
||||
{ label: string; kind: 'run' | 'term' | 'warn' | 'check' }
|
||||
> = {
|
||||
alive: { label: '存活', kind: 'run' },
|
||||
dead: { label: '失联', kind: 'term' },
|
||||
suspended: { label: '暂停', kind: 'warn' },
|
||||
unknown: { label: '未测', kind: 'check' },
|
||||
}
|
||||
|
||||
/** 兜底取值:后端新增状态时前端旧包不至于崩 */
|
||||
export function aliveMeta(status: string) {
|
||||
return aliveStatusMeta[status as AliveStatus] ?? aliveStatusMeta.unknown
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
import { useAppStore } from '@/stores/app'
|
||||
|
||||
/** 扩散揭示时长;与 main.css 中 view-transition 的默认动画禁用配套 */
|
||||
const RIPPLE_DURATION = 560
|
||||
|
||||
/** 进行中标志:扩散未结束时忽略再次触发,防止打断上一个 transition 造成瞬跳闪烁 */
|
||||
let rippling = false
|
||||
|
||||
/** 明暗主题切换,新主题以触发元素为圆心向外扩散揭示(View Transitions);
|
||||
* 浏览器不支持或用户偏好减少动效时退化为直接切换 */
|
||||
export function useThemeRipple() {
|
||||
const app = useAppStore()
|
||||
|
||||
async function toggleDarkFrom(e: MouseEvent) {
|
||||
const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||
if (typeof document.startViewTransition !== 'function' || reduced) {
|
||||
app.toggleDark()
|
||||
return
|
||||
}
|
||||
if (rippling) return
|
||||
rippling = true
|
||||
const { x, y } = originOf(e)
|
||||
// 回调内等 nextTick:html.dark 由 store watcher 刷新,须在快照回调结束前落地。
|
||||
// theme-switching 禁掉页面自身的颜色 transition:::view-transition-new(root)
|
||||
// 是新 DOM 的实时替身,组件残留的渐变会在扩散圆内继续播放,观感即闪烁。
|
||||
const transition = document.startViewTransition(async () => {
|
||||
document.documentElement.classList.add('theme-switching')
|
||||
app.toggleDark()
|
||||
await nextTick()
|
||||
})
|
||||
try {
|
||||
await transition.ready
|
||||
const radius = Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y))
|
||||
document.documentElement.animate(
|
||||
{ clipPath: [`circle(0px at ${x}px ${y}px)`, `circle(${radius}px at ${x}px ${y}px)`] },
|
||||
{ duration: RIPPLE_DURATION, easing: 'ease-in-out', pseudoElement: '::view-transition-new(root)' },
|
||||
)
|
||||
} finally {
|
||||
void transition.finished.finally(() => {
|
||||
document.documentElement.classList.remove('theme-switching')
|
||||
rippling = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return { toggleDarkFrom }
|
||||
}
|
||||
|
||||
/** 扩散圆心:优先触发元素几何中心(键盘触发 clientX/Y 为 0 也稳),兜底点击坐标 */
|
||||
function originOf(e: MouseEvent): { x: number; y: number } {
|
||||
const el = e.currentTarget
|
||||
if (el instanceof Element) {
|
||||
const rect = el.getBoundingClientRect()
|
||||
if (rect.width || rect.height) {
|
||||
return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }
|
||||
}
|
||||
}
|
||||
return { x: e.clientX, y: e.clientY }
|
||||
}
|
||||
@@ -4,6 +4,17 @@ import { h, ref, type Ref, type VNodeChild } from 'vue'
|
||||
type ToastKind = 'error' | 'warning'
|
||||
|
||||
const AUTO_CLOSE_MS = 8000
|
||||
/** 超过此长度的消息自动折叠成「标题 + 展开详情」 */
|
||||
const LONG_TEXT = 64
|
||||
|
||||
/** 长错误提炼标题:优先 hint 分隔符(request.ts 的「hint|error」),次选首个冒号操作前缀,兜底截断 */
|
||||
function summarize(text: string): string {
|
||||
const bar = text.indexOf('|')
|
||||
if (bar > 0 && bar <= 40) return text.slice(0, bar)
|
||||
const colon = text.search(/:|: /)
|
||||
if (colon > 0 && colon <= 40) return text.slice(0, colon)
|
||||
return [...text].slice(0, 40).join('') + '…'
|
||||
}
|
||||
|
||||
function renderDetail(kind: ToastKind, detail: string, copied: Ref<boolean>): VNodeChild {
|
||||
return h('div', { class: 'toast-code' }, [
|
||||
@@ -51,6 +62,11 @@ export function useToast() {
|
||||
const message = useMessage()
|
||||
|
||||
function show(kind: ToastKind, text: string, detail?: string) {
|
||||
// 未显式给 detail 的超长消息(如 OCI 透传错误)自动折叠,避免整段撑爆 toast
|
||||
if (!detail && text.length > LONG_TEXT) {
|
||||
detail = text
|
||||
text = summarize(text)
|
||||
}
|
||||
if (!detail) {
|
||||
message[kind](text)
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user