diff --git a/apps/admin/LARGE_SCREEN_DEV_GUIDE.md b/apps/admin/LARGE_SCREEN_DEV_GUIDE.md new file mode 100644 index 0000000..60b74b7 --- /dev/null +++ b/apps/admin/LARGE_SCREEN_DEV_GUIDE.md @@ -0,0 +1,110 @@ +# 大屏开发调试指南 + +## 项目配置 + +- **设计基准**: 2048 x 1080 +- **缩放方案**: VScaleScreen +- **目标设备**: 超级大屏(投屏显示) + +## 开发调试方法 + +### 方法 1: Chrome DevTools 自定义设备(推荐) + +1. 打开 Chrome DevTools (F12) +2. 点击设备模拟按钮 (Ctrl/Cmd + Shift + M) +3. 在设备下拉菜单中选择 "Edit..." +4. 添加自定义设备: + - Device Name: `Large Screen 2048x1080` + - Width: `2048` + - Height: `1080` + - Device pixel ratio: `1` + - User agent string: (保持默认) +5. 选择刚创建的设备进行调试 + +### 方法 2: 浏览器窗口缩放 + +1. 将浏览器窗口调整为合适大小 +2. 使用浏览器缩放功能 (Ctrl/Cmd + 鼠标滚轮) +3. 调整到能看到完整布局的比例 + +### 方法 3: 响应式设计模式 + +在 DevTools 中: +1. 选择 "Responsive" 模式 +2. 手动输入宽度: `2048` +3. 手动输入高度: `1080` + +## 字体大小说明 + +当前字体配置已针对 2048x1080 优化: + +| Class | 实际大小 | 适用场景 | +|-------|---------|---------| +| `text-xs` | 12.8px | 辅助文字、标注 | +| `text-sm` | 15.2px | 次要文字 | +| `text-base` | 17.6px | 正文 | +| `text-lg` | 20.8px | 小标题 | +| `text-xl` | 24px | 中标题 | +| `text-2xl` | 28px | 大标题 | +| `text-3xl` | 33.6px | 特大标题 | +| `text-4xl` | 40px | 主标题 | +| `text-5xl` | 51.2px | 超大标题 | +| `text-6xl` | 64px | 巨大标题 | + +## VScaleScreen 工作原理 + +``` +设计基准 (2048x1080) + ↓ +VScaleScreen 自动缩放 + ↓ +实际显示设备 (任意分辨率) +``` + +**示例:** +- 在 1920x1080 屏幕上:内容会略微缩小 +- 在 3840x2160 (4K) 屏幕上:内容会放大约 2 倍 +- 在更大的 LED 屏上:内容会等比例放大 + +## 常见问题 + +### Q: 在我的电脑上字体看起来太大了? +A: 这是正常的!因为你的设计基准 (2048x1080) 比普通电脑屏幕大。请使用上述方法在正确的分辨率下调试。 + +### Q: 如何确认实际大屏的分辨率? +A: +1. 询问现场技术人员 +2. 常见大屏分辨率: + - 1920x1080 (Full HD) + - 2560x1440 (2K) + - 3840x2160 (4K) + - 7680x4320 (8K) + +### Q: 需要修改 VScaleScreen 的尺寸吗? +A: 通常不需要。保持 2048x1080 即可,VScaleScreen 会自动适配。除非: +- 实际大屏比例不是 16:9 +- 需要针对特定分辨率优化 + +### Q: 如何微调字体大小? +A: 修改 `uno.config.ts` 中的 `fontSize` 配置,然后重启开发服务器。 + +## 测试清单 + +部署前请确认: + +- [ ] 在 2048x1080 分辨率下所有文字清晰可读 +- [ ] 所有布局元素位置正确 +- [ ] 动画效果流畅 +- [ ] 图片清晰不模糊(建议使用 2x 或 3x 分辨率的图片) +- [ ] 在实际大屏上测试(如果可能) + +## 性能优化建议 + +1. **图片资源**: 使用 WebP 格式,准备高分辨率版本 +2. **字体**: 使用 Web Font,确保在大屏上渲染清晰 +3. **动画**: 使用 CSS transform 和 opacity,避免触发重排 +4. **测试**: 在接近实际大屏的设备上测试性能 + +## 联系与支持 + +如有问题,请联系开发团队。 diff --git a/apps/admin/font-size-preview.html b/apps/admin/font-size-preview.html new file mode 100644 index 0000000..ced8a93 --- /dev/null +++ b/apps/admin/font-size-preview.html @@ -0,0 +1,239 @@ + + + + + + 字体大小预览 - 2048x1080 + + + +
+ + + +
当前窗口:
+
+ +
+

字体大小预览

+
设计基准: 2048 x 1080 | 使用 VScaleScreen 自动缩放
+ +
+
text-xs
+
0.8rem (12.8px)
+
辅助文字、标注信息 - The quick brown fox jumps over the lazy dog
+
+ +
+
text-sm
+
0.95rem (15.2px)
+
次要文字内容 - The quick brown fox jumps over the lazy dog
+
+ +
+
text-base
+
1.1rem (17.6px)
+
正文内容 - The quick brown fox jumps over the lazy dog
+
+ +
+
text-lg
+
1.3rem (20.8px)
+
小标题 - The quick brown fox jumps over the lazy dog
+
+ +
+
text-xl
+
1.5rem (24px)
+
中标题 - The quick brown fox jumps over the lazy dog
+
+ +
+
text-2xl
+
1.75rem (28px)
+
大标题 - 重庆市教育学会
+
+ +
+
text-3xl
+
2.1rem (33.6px)
+
特大标题 - 阅读之星展示活动
+
+ +
+
text-4xl
+
2.5rem (40px)
+
主标题 - 队伍名单
+
+ +
+
text-5xl
+
3.2rem (51.2px)
+
超大标题 - 星辞海遨游
+
+ +
+
text-6xl
+
4rem (64px)
+
巨大标题 - 开始答题
+
+
+ + + + diff --git a/apps/admin/src/assets/fonts/README.md b/apps/admin/src/assets/fonts/README.md new file mode 100644 index 0000000..02d5ac6 --- /dev/null +++ b/apps/admin/src/assets/fonts/README.md @@ -0,0 +1,33 @@ +# 拼音字体说明 + +## 需要的字体文件 + +为了正确显示拼音声调符号,需要使用支持拼音的字体。 + +### 推荐字体: +1. **GB Pinyinok-B** - 专业拼音字体 +2. **方正楷体拼音** - 楷体风格拼音字体 +3. **华文楷体拼音** - 华文系列拼音字体 + +### 字体文件放置位置: +``` +apps/admin/src/assets/fonts/ +├── GBPinyinok-B.ttf +├── GBPinyinok-B.woff2 +└── GBPinyinok-B.woff +``` + +### 如何获取字体: +1. 从系统字体库复制(macOS: /Library/Fonts/) +2. 从字体网站下载(需要商业授权) +3. 使用开源拼音字体 + +### 字体格式转换: +如果只有 .ttf 文件,可以使用在线工具转换为 woff2 格式: +- https://cloudconvert.com/ttf-to-woff2 +- https://www.fontsquirrel.com/tools/webfont-generator + +### 注意事项: +- 确保字体文件有合法的使用授权 +- woff2 格式文件最小,加载最快 +- 字体文件较大时考虑使用 CDN diff --git a/apps/admin/src/assets/imgs/user/team-title-bg.png b/apps/admin/src/assets/imgs/user/team-title-bg.png new file mode 100644 index 0000000..ebf2208 Binary files /dev/null and b/apps/admin/src/assets/imgs/user/team-title-bg.png differ diff --git a/apps/admin/src/assets/imgs/user/title-bg3.png b/apps/admin/src/assets/imgs/user/title-bg3.png index 4fc7cbe..ef81921 100644 Binary files a/apps/admin/src/assets/imgs/user/title-bg3.png and b/apps/admin/src/assets/imgs/user/title-bg3.png differ diff --git a/apps/admin/src/assets/imgs/user/title-bg4.png b/apps/admin/src/assets/imgs/user/title-bg4.png index f231cf4..ebd8008 100644 Binary files a/apps/admin/src/assets/imgs/user/title-bg4.png and b/apps/admin/src/assets/imgs/user/title-bg4.png differ diff --git a/apps/admin/src/components/custom/user/ActionButton.vue b/apps/admin/src/components/custom/user/ActionButton.vue index cc51bb3..b1a3cbf 100644 --- a/apps/admin/src/components/custom/user/ActionButton.vue +++ b/apps/admin/src/components/custom/user/ActionButton.vue @@ -2,39 +2,42 @@ import { computed } from 'vue' import defaultBtnBg from '@/assets/imgs/user/btn-bg.png' -const props = defineProps({ - text: { - type: String, - default: '下一步', - }, - disabled: { - type: Boolean, - default: false, - }, - theme: { - type: String as () => 'primary' | 'light', - default: 'primary', - }, - type: { - type: String as () => 'button' | 'text' | 'custom', - default: 'button', - }, - btnBg: { - type: String, - default: defaultBtnBg, - }, - fontSize: { - type: Number, - default: 30, - }, +interface Props { + text?: string + disabled?: boolean + theme?: 'primary' | 'light' + type?: 'button' | 'text' | 'custom' + btnBg?: string + fontSize?: number | string +} + +const props = withDefaults(defineProps(), { + text: '下一步', + disabled: false, + theme: 'primary', + type: 'button', + btnBg: defaultBtnBg, + fontSize: '2.4rem', }) -const emits = defineEmits(['click']) +const emits = defineEmits<{ + click: [] +}>() const isCustom = computed(() => props.type === 'custom') const customBg = computed(() => `url(${props.btnBg}) no-repeat center / 100% 100%`) +const fontSizeValue = computed(() => { + // 如果已经是带单位的字符串(如 '2.4rem', '30px'),直接返回 + if (typeof props.fontSize === 'string' && /^[\d.]+(?:px|rem|em)$/.test(props.fontSize)) { + return props.fontSize + } + // 如果是纯数字或数字字符串,添加 px 单位 + const size = typeof props.fontSize === 'number' ? props.fontSize : Number.parseFloat(props.fontSize) + return Number.isNaN(size) ? '2.4rem' : `${size}px` +}) + function handleClick() { if (!props.disabled) { emits('click') @@ -68,11 +71,13 @@ function handleClick() { border: none; outline: none; font-family: inherit; + user-select: none; &.is-disabled { cursor: not-allowed; - filter: grayscale(1); + // filter: grayscale(1); opacity: 0.7; + pointer-events: none; &:hover, &:active { @@ -82,7 +87,7 @@ function handleClick() { } } -// standard: type="button" | type="text" +// Standard button styles: type="button" | type="text" .standard-btn { width: 50px; height: 50px; @@ -91,13 +96,13 @@ function handleClick() { background: #eb5e55; box-shadow: 0 8px 0 #fcc64f; - &:hover { + &:hover:not(.is-disabled) { transform: translateY(-2px); filter: brightness(1.05); box-shadow: 0 10px 0 #fcc64f; } - &:active { + &:active:not(.is-disabled) { transform: translateY(4px); box-shadow: 0 2px 0 #fcc64f; } @@ -107,7 +112,7 @@ function handleClick() { color: #eb5e55; border: 2px solid #eb5e55; - &:hover { + &:hover:not(.is-disabled) { background-color: #fff; } } @@ -126,7 +131,7 @@ function handleClick() { } } -// custom: type="custom",背景完全由 btnBg 决定 +// Custom button: type="custom" - background controlled by btnBg prop .custom-btn { background: v-bind(customBg); background-color: transparent; @@ -136,9 +141,10 @@ function handleClick() { padding: 0 20px; box-shadow: none; color: rgb(0, 94, 174); + font-weight: bold; .btn-text { - font-size: v-bind('`${fontSize}px`'); + font-size: v-bind(fontSizeValue); font-weight: bold; letter-spacing: 2px; margin-bottom: 7px; @@ -146,15 +152,15 @@ function handleClick() { &.is-disabled { color: #eb5e55; - filter: none; - opacity: 1; + filter: grayscale(0.5); + opacity: 0.6; } - &:hover { + &:hover:not(.is-disabled) { transform: scale(1.05); } - &:active { + &:active:not(.is-disabled) { transform: scale(0.95); } } diff --git a/apps/admin/src/components/custom/user/CompetitionLayout.vue b/apps/admin/src/components/custom/user/CompetitionLayout.vue index dd3400d..2c75fdd 100644 --- a/apps/admin/src/components/custom/user/CompetitionLayout.vue +++ b/apps/admin/src/components/custom/user/CompetitionLayout.vue @@ -363,7 +363,7 @@ watch(titleBgUrl, (newUrl) => { } .main-title { - font-size: 2.4rem; + font-size: 2.6rem; color: $text-color; margin: 0; letter-spacing: 3px; diff --git a/apps/admin/src/views/results/results-detail/index.vue b/apps/admin/src/views/results/results-detail/index.vue index 33ed76c..cbe721c 100644 --- a/apps/admin/src/views/results/results-detail/index.vue +++ b/apps/admin/src/views/results/results-detail/index.vue @@ -32,6 +32,8 @@ const currentQuestion = ref() // 当前 const questionInfo = ref() // 题目详情 const currentQuestionDetailID = ref('') // 当前选中的题目详情 ID +const nowTs = ref(Date.now()) + // 判断当前activityId是正在比赛的活动 const isActiveActivityId = computed(() => { if (!currentQuestion.value) @@ -465,6 +467,9 @@ async function handleNexCurrentQuestion() { currentQuestionId.value = currentQuestion.value?.QuestionID || currentQuestionId.value currentGroupId.value = currentQuestion.value?.TeamGroupID || currentGroupId.value } + + // 更新当前时间戳,以便重新计算 canPublish + nowTs.value = Date.now() } function handlePublish() { @@ -580,7 +585,6 @@ function handlePublish() { * 消费并发布当前题目 */ const pollingTimer = ref(null) -const nowTs = ref(Date.now()) const canPublish = computed(() => { const endTime = (currentQuestion.value as any)?.EndTime if (!endTime) @@ -808,27 +812,29 @@ onUnmounted(() => { -
+