优化 GitHub Pages 部署配置,支持 master 分支,更新 Node.js 版本,调整构建命令,添加详细部署说明

This commit is contained in:
hxuanyu 2025-06-27 16:46:13 +08:00
parent 5064d43bdb
commit b6accfc111
7 changed files with 259 additions and 22 deletions

View File

@ -3,7 +3,7 @@ name: Deploy to GitHub Pages
on:
# 当推送到main分支时触发
push:
branches: [ main ]
branches: [ main, master ]
# 允许手动触发工作流
workflow_dispatch:
@ -30,14 +30,13 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Build for GitHub Pages
run: npm run build:github
- name: Setup Pages
uses: actions/configure-pages@v4

78
DEPLOYMENT.md Normal file
View File

@ -0,0 +1,78 @@
# 部署说明
本项目支持多种部署方式,每种方式都有对应的构建命令。
## 🚀 部署方式
### 1. GitHub Pages自动部署
**特点**:推送代码到 `main``master` 分支后自动部署
**URL格式**`https://username.github.io/obs-overlay-widget/`
**设置步骤**
1. 推送代码到GitHub仓库
2. 在仓库设置中启用GitHub Pages
- 进入 Settings → Pages
- Source 选择 "GitHub Actions"
3. GitHub Actions会自动构建和部署
### 2. 手动部署(任意服务器)
**特点**适用于任何Web服务器包括Apache、Nginx、静态托管服务等
**构建命令**
```bash
npm run build
```
**部署步骤**
1. 运行构建命令
2. 将 `dist` 文件夹中的所有文件上传到服务器
3. 可以部署到根目录或任何子目录
### 3. GitHub Pages手动构建
**特点**如果你需要手动为GitHub Pages构建
**构建命令**
```bash
npm run build:github
```
## 📝 构建命令说明
| 命令 | 用途 | base路径 | 适用场景 |
|------|------|----------|----------|
| `npm run dev` | 开发服务器 | `/` | 本地开发 |
| `npm run build` | 生产构建 | `./` | 手动部署到任意服务器 |
| `npm run build:github` | GitHub Pages构建 | `/obs-overlay-widget/` | 手动GitHub Pages部署 |
| `npm run preview` | 预览构建结果 | - | 本地测试构建结果 |
## 🛠️ 技术说明
### Base路径配置
项目使用智能的base路径配置
- **开发模式**:使用 `/` 作为base路径
- **生产模式**
- 默认使用 `./`(相对路径),适用于任何部署位置
- 如果设置了 `VITE_BASE_PATH` 环境变量,则使用指定路径
### 自动化部署
GitHub Actions工作流`.github/workflows/deploy.yml`)会:
1. 检测代码变更
2. 安装依赖
3. 使用 `npm run build:github` 构建项目
4. 自动部署到GitHub Pages
## 🔧 自定义部署
如果你的仓库名不是 `obs-overlay-widget`,需要修改:
1. **vite.config.ts** 中的仓库名
2. **package.json**`build:github` 脚本的路径
例如,如果仓库名是 `my-widget`
```json
"build:github": "vue-tsc -b && cross-env VITE_BASE_PATH=/my-widget/ vite build"
```

109
package-lock.json generated
View File

@ -15,8 +15,10 @@
"vue-router": "^4.5.1"
},
"devDependencies": {
"@types/node": "^24.0.4",
"@vitejs/plugin-vue": "^6.0.0",
"@vue/tsconfig": "^0.7.0",
"cross-env": "^7.0.3",
"typescript": "~5.8.3",
"vite": "^7.0.0",
"vue-tsc": "^2.2.10"
@ -862,6 +864,16 @@
"@types/lodash": "*"
}
},
"node_modules/@types/node": {
"version": "24.0.4",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-24.0.4.tgz",
"integrity": "sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.8.0"
}
},
"node_modules/@types/web-bluetooth": {
"version": "0.0.16",
"resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
@ -1193,6 +1205,40 @@
"balanced-match": "^1.0.0"
}
},
"node_modules/cross-env": {
"version": "7.0.3",
"resolved": "https://registry.npmmirror.com/cross-env/-/cross-env-7.0.3.tgz",
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
"dev": true,
"license": "MIT",
"dependencies": {
"cross-spawn": "^7.0.1"
},
"bin": {
"cross-env": "src/bin/cross-env.js",
"cross-env-shell": "src/bin/cross-env-shell.js"
},
"engines": {
"node": ">=10.14",
"npm": ">=6",
"yarn": ">=1"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz",
@ -1343,6 +1389,13 @@
"he": "bin/he"
}
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true,
"license": "ISC"
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
@ -1435,6 +1488,16 @@
"dev": true,
"license": "MIT"
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
@ -1522,6 +1585,29 @@
"fsevents": "~2.3.2"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
@ -1562,6 +1648,13 @@
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "7.8.0",
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.8.0.tgz",
"integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
"dev": true,
"license": "MIT"
},
"node_modules/vite": {
"version": "7.0.0",
"resolved": "https://registry.npmmirror.com/vite/-/vite-7.0.0.tgz",
@ -1696,6 +1789,22 @@
"peerDependencies": {
"typescript": ">=5.0.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
}
}
}

View File

@ -6,6 +6,7 @@
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
"build:github": "vue-tsc -b && cross-env VITE_BASE_PATH=/obs-overlay-widget/ vite build",
"preview": "vite preview"
},
"dependencies": {
@ -16,8 +17,10 @@
"vue-router": "^4.5.1"
},
"devDependencies": {
"@types/node": "^24.0.4",
"@vitejs/plugin-vue": "^6.0.0",
"@vue/tsconfig": "^0.7.0",
"cross-env": "^7.0.3",
"typescript": "~5.8.3",
"vite": "^7.0.0",
"vue-tsc": "^2.2.10"

View File

@ -182,7 +182,7 @@
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue';
import type { TimerConfig } from './types';
import type { TimerConfig, TimerMode } from './types';
// Define props with default values
const props = withDefaults(defineProps<{
@ -271,12 +271,13 @@ onMounted(() => {
// Preset styles
const presets = {
gaming: {
mode: 'countdown',
mode: 'countdown' as TimerMode,
duration: 1800, // 30
format: 'mm:ss',
fontSize: 64,
fontFamily: 'Impact',
fontWeight: 'bold',
color: '#ff6b35',
useGradient: true,
gradientColors: ['#ff6b35', '#f7931e'],
textShadow: true,
@ -288,10 +289,13 @@ const presets = {
warningThreshold: 60,
warningColor: '#ff0000',
playSound: true,
soundVolume: 80
soundVolume: 80,
autoStart: false,
showMilliseconds: false,
finishedColor: '#ff0000'
},
meeting: {
mode: 'countdown',
mode: 'countdown' as TimerMode,
duration: 3600, // 60
format: 'hh:mm:ss',
fontSize: 52,
@ -299,6 +303,7 @@ const presets = {
fontWeight: 'normal',
color: '#2ecc71',
useGradient: false,
gradientColors: ['#2ecc71', '#27ae60'],
textShadow: true,
shadowColor: 'rgba(0, 0, 0, 0.3)',
shadowBlur: 4,
@ -309,24 +314,36 @@ const presets = {
warningColor: '#f39c12',
finishedColor: '#e74c3c',
playSound: true,
soundVolume: 60
soundVolume: 60,
autoStart: false,
showMilliseconds: false
},
workout: {
mode: 'stopwatch',
mode: 'stopwatch' as TimerMode,
duration: 0,
format: 'mm:ss',
fontSize: 72,
fontFamily: 'JetBrains Mono',
fontWeight: 'bold',
color: '#e74c3c',
useGradient: true,
gradientColors: ['#e74c3c', '#c0392b'],
textShadow: true,
shadowColor: 'rgba(0, 0, 0, 0.5)',
shadowBlur: 6,
showMilliseconds: true,
autoStart: false
autoStart: false,
showProgress: false,
progressColor: '#e74c3c',
progressHeight: 4,
warningThreshold: 0,
warningColor: '#f39c12',
finishedColor: '#e74c3c',
playSound: false,
soundVolume: 50
},
minimal: {
mode: 'countdown',
mode: 'countdown' as TimerMode,
duration: 300,
format: 'mm:ss',
fontSize: 48,
@ -334,10 +351,20 @@ const presets = {
fontWeight: 'lighter',
color: '#ffffff',
useGradient: false,
gradientColors: ['#ffffff', '#f8f9fa'],
textShadow: false,
shadowColor: 'rgba(0, 0, 0, 0.5)',
shadowBlur: 4,
showProgress: false,
progressColor: '#ffffff',
progressHeight: 4,
warningThreshold: 30,
warningColor: '#f39c12',
finishedColor: '#e74c3c',
playSound: false,
autoStart: false
soundVolume: 50,
autoStart: false,
showMilliseconds: false
}
};

View File

@ -23,7 +23,7 @@
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
import type { TimerConfig, TimerStatus, TimerMode } from './types';
import type { TimerConfig, TimerStatus } from './types';
// Define props with default values
const props = withDefaults(defineProps<{
@ -125,8 +125,8 @@ const progressFillStyle = computed(() => {
//
const containerStyle = computed(() => ({
textAlign: 'center',
userSelect: 'none'
textAlign: 'center' as const,
userSelect: 'none' as const
}));
//
@ -292,7 +292,7 @@ watch(() => props.config.duration, (newDuration) => {
}
});
watch(() => props.config.mode, (newMode) => {
watch(() => props.config.mode, () => {
resetTimer();
});

View File

@ -1,8 +1,29 @@
import { defineConfig } from 'vite'
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vite.dev/config/
export default defineConfig({
plugins: [vue()],
base: process.env.NODE_ENV === 'production' ? '/obs-overlay-widget/' : '/',
export default defineConfig(({ command, mode }) => {
// 加载环境变量
const env = loadEnv(mode, process.cwd(), '')
// 根据构建环境动态设置base路径
const base = (() => {
// 开发模式使用根路径
if (command === 'serve') {
return '/'
}
// 生产模式检查是否有指定的base路径用于GitHub Pages
if (env.VITE_BASE_PATH) {
return env.VITE_BASE_PATH
}
// 默认使用相对路径,适用于任何部署场景
return './'
})()
return {
plugins: [vue()],
base,
}
})