feat(server): 用户管理与变更历史接口;修复 result_revisions.parent_id 从未写入
This commit is contained in:
1 parent
f3813171d3
commit
72462792ec
39 files changed
+10001
-28
No files matched your search
+41
-17
@@ -5,9 +5,11 @@ import { mix, parseHexColor, readableForeground, toCss } from './color'
|
||||
import type { DisplayPageKey } from './types'
|
||||
import { useScoreboard } from './useScoreboard'
|
||||
import { useStageScale } from './useStageScale'
|
||||
import { WINDOWED_TITLEBAR_HEIGHT, isWindowed } from './windowed'
|
||||
import DetailOverlay from './components/DetailOverlay.vue'
|
||||
import TickerBar from './components/TickerBar.vue'
|
||||
import TopBar from './components/TopBar.vue'
|
||||
import WindowedTitleBar from './components/WindowedTitleBar.vue'
|
||||
import AnnouncementPage from './components/pages/AnnouncementPage.vue'
|
||||
import AthleteStandingsPage from './components/pages/AthleteStandingsPage.vue'
|
||||
import EventResultsPage from './components/pages/EventResultsPage.vue'
|
||||
@@ -23,10 +25,19 @@ const PAGES: Record<DisplayPageKey, Component> = {
|
||||
}
|
||||
|
||||
const { bootstrap, dispose, page, settings } = useScoreboard()
|
||||
const { scale, offsetX, offsetY } = useStageScale()
|
||||
|
||||
// 单屏窗口化才有标题栏;kiosk 下 windowed 为 false,下面所有相关分支都不生效
|
||||
const windowed = isWindowed()
|
||||
const { scale, offsetX, offsetY } = useStageScale({
|
||||
topInset: () => (windowed ? WINDOWED_TITLEBAR_HEIGHT : 0),
|
||||
})
|
||||
|
||||
const currentPage = computed<Component>(() => PAGES[page.value])
|
||||
|
||||
const rootStyle = { '--display-titlebar-h': `${WINDOWED_TITLEBAR_HEIGHT}px` }
|
||||
const barTitle = computed(() => settings.value?.display.title.trim() || '赛事标题待配置')
|
||||
const barLogo = computed(() => settings.value?.display.logo?.trim() ?? '')
|
||||
|
||||
const stageStyle = computed(() => ({
|
||||
transform: `scale(${scale.value})`,
|
||||
left: `${offsetX.value}px`,
|
||||
@@ -72,30 +83,43 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="screen">
|
||||
<div class="stage" :style="[stageStyle, themeVars]">
|
||||
<img v-if="background" class="bg" :src="background" alt="" />
|
||||
<div class="bg-fallback" />
|
||||
<div class="root" :style="rootStyle">
|
||||
<WindowedTitleBar v-if="windowed" :title="barTitle" :logo="barLogo" />
|
||||
|
||||
<div class="content">
|
||||
<TopBar />
|
||||
<main class="main">
|
||||
<Transition name="page" mode="out-in">
|
||||
<component :is="currentPage" :key="page" />
|
||||
</Transition>
|
||||
</main>
|
||||
<TickerBar />
|
||||
<div class="screen">
|
||||
<div class="stage" :style="[stageStyle, themeVars]">
|
||||
<img v-if="background" class="bg" :src="background" alt="" />
|
||||
<div class="bg-fallback" />
|
||||
|
||||
<div class="content">
|
||||
<TopBar />
|
||||
<main class="main">
|
||||
<Transition name="page" mode="out-in">
|
||||
<component :is="currentPage" :key="page" />
|
||||
</Transition>
|
||||
</main>
|
||||
<TickerBar />
|
||||
</div>
|
||||
|
||||
<DetailOverlay />
|
||||
</div>
|
||||
|
||||
<DetailOverlay />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background: #000;
|
||||
}
|
||||
/* kiosk 下 root 里只有 .screen,flex:1 等价于原来的铺满视口 */
|
||||
.screen {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
background: #000;
|
||||
cursor: none;
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 窗口化模式标题栏(单屏调试用)。
|
||||
*
|
||||
* 样式对齐 Console 自绘标题栏(console/renderer/styles.css 的 .topbar):
|
||||
* 同样的 50px 高、8×22 圆角标记、--panel 底色与 --border 分隔线、整条可拖拽。
|
||||
* 这里不引用 MIUIx 令牌——大屏端不加载管理端样式(07 第 9 节),所以直接写死同一组色值。
|
||||
* 不放窗口控制按钮:大屏窗口由 Console 统一管理,本条只负责拖拽与提示。
|
||||
*/
|
||||
defineProps<{
|
||||
title: string
|
||||
logo: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header class="titlebar">
|
||||
<div class="brand">
|
||||
<img v-if="logo" class="brand-logo" :src="logo" alt="会标" />
|
||||
<span v-else class="brand-mark" aria-hidden="true" />
|
||||
<span class="brand-title">{{ title }}</span>
|
||||
</div>
|
||||
|
||||
<span class="hint">按 <kbd>F11</kbd> 切换全屏</span>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.titlebar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
height: var(--display-titlebar-h, 50px);
|
||||
flex: 0 0 auto;
|
||||
padding: 0 16px;
|
||||
border-bottom: 1px solid #27314a;
|
||||
background: #161d2d;
|
||||
color: #e6ecf7;
|
||||
user-select: none;
|
||||
/* 无边框窗口靠这条拖动;条内没有可点控件,整条都是拖拽区 */
|
||||
-webkit-app-region: drag;
|
||||
cursor: default;
|
||||
}
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
}
|
||||
.brand-mark {
|
||||
width: 8px;
|
||||
height: 22px;
|
||||
flex: 0 0 auto;
|
||||
border-radius: 4px;
|
||||
background: linear-gradient(180deg, #4fd8ff, #1b6bb8);
|
||||
}
|
||||
.brand-logo {
|
||||
height: 26px;
|
||||
max-width: 96px;
|
||||
flex: 0 0 auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
.brand-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
line-height: 1.25;
|
||||
letter-spacing: 0.02em;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.hint {
|
||||
flex: 0 0 auto;
|
||||
font-size: 11px;
|
||||
line-height: 1.3;
|
||||
color: #8b98b4;
|
||||
white-space: nowrap;
|
||||
}
|
||||
kbd {
|
||||
padding: 1px 6px;
|
||||
border: 1px solid #27314a;
|
||||
border-radius: 5px;
|
||||
background: #1d2637;
|
||||
font-family: inherit;
|
||||
font-size: 11px;
|
||||
color: #e6ecf7;
|
||||
}
|
||||
</style>
|
||||
@@ -3,20 +3,30 @@ import { onUnmounted, ref } from 'vue'
|
||||
export const DESIGN_WIDTH = 1920
|
||||
export const DESIGN_HEIGHT = 1080
|
||||
|
||||
export interface StageScaleOptions {
|
||||
/**
|
||||
* 窗口顶部被标题栏占掉的高度(kiosk 为 0)。
|
||||
* 传 getter 而不是数值:缩放回调在 resize 时重新取值,避免外部状态变化后拿到旧值。
|
||||
*/
|
||||
topInset?: () => number
|
||||
}
|
||||
|
||||
/**
|
||||
* 1920×1080 设计基准 + `transform: scale()` 等比适配(06 第 3 节)。
|
||||
*
|
||||
* 只按「较小的一边」缩放并居中,非 16:9 窗口留黑边而不拉伸;
|
||||
* 不写死设备像素,1080p / 4K / 系统缩放都能正确铺满。
|
||||
*/
|
||||
export function useStageScale() {
|
||||
export function useStageScale(options: StageScaleOptions = {}) {
|
||||
const topInset = options.topInset ?? ((): number => 0)
|
||||
const scale = ref(1)
|
||||
const offsetX = ref(0)
|
||||
const offsetY = ref(0)
|
||||
|
||||
function fit(): void {
|
||||
const width = window.innerWidth
|
||||
const height = window.innerHeight
|
||||
// 可用高度要扣掉窗口化标题栏,否则 1920×1080 舞台会被标题栏遮住顶部
|
||||
const height = Math.max(0, window.innerHeight - topInset())
|
||||
const next = Math.min(width / DESIGN_WIDTH, height / DESIGN_HEIGHT)
|
||||
scale.value = next
|
||||
offsetX.value = (width - DESIGN_WIDTH * next) / 2
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 窗口化模式标记。
|
||||
*
|
||||
* 单屏调试时 Console 会给 URL 追加 `windowed=1`(06 第 6.3 节),页面据此画标题栏;
|
||||
* 多屏 kiosk 不追加,页面必须保持零占用的全屏布局。
|
||||
*/
|
||||
export const WINDOWED_PARAM = 'windowed'
|
||||
|
||||
/** 标题栏高度:与 Console 自绘标题栏一致,缩放计算要减掉这一段。 */
|
||||
export const WINDOWED_TITLEBAR_HEIGHT = 50
|
||||
|
||||
export function isWindowed(): boolean {
|
||||
return new URLSearchParams(window.location.search).get(WINDOWED_PARAM) === '1'
|
||||
}
|
||||
Reference in new issue
Block a user