fix(wish-tree): 修复切换树时导航栏消失问题
Publish Mini Program Dev Version / publish (push) Failing after 10m47s
Build and Deploy Server / build (push) Failing after 2s

- Canvas添加z-index: 1,确保不覆盖导航栏
- top-bar添加pointer-events: none,子元素恢复auto
- WXML添加trees.length保护,防止空数组访问
- switchTree添加空数组保护
This commit is contained in:
gouki
2026-08-07 21:56:38 +00:00
parent a2d4085bf4
commit b5bdf4c88d
8 changed files with 495 additions and 8 deletions
+198
View File
@@ -0,0 +1,198 @@
# 部署通知配置指南
本文档说明如何配置 Gitea Actions 的部署通知功能,支持 Telegram 和 Discord 双渠道通知。
## 功能说明
-**自动触发**:部署成功或失败时自动发送通知
-**双渠道**:同时支持 Telegram 和 Discord
-**详细信息**:包含仓库、分支、提交、作者、工作流等信息
-**失败容错**:通知发送失败不会影响部署流程
## 配置步骤
### 1. Telegram Bot 配置
#### 1.1 创建 Telegram Bot
1. 在 Telegram 中搜索 `@BotFather`
2. 发送 `/newbot` 创建新机器人
3. 按提示设置机器人名称和用户名
4. 获得 Bot Token,格式类似:`123456789:ABCdefGHIjklMNOpqrsTUVwxyz`
#### 1.2 获取 Chat ID
**方法 1:通过 Bot API**
1. 将你的 Bot 添加到目标群组或频道
2. 发送任意消息给 Bot
3. 访问:`https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getUpdates`
4. 在返回的 JSON 中找到 `chat.id`
**方法 2:使用第三方 Bot**
1. 添加 `@userinfobot` 到你的群组
2. 它会自动显示 Chat ID
### 2. Discord Webhook 配置
1. 打开 Discord 服务器设置
2. 进入 **集成****Webhook**
3. 点击 **新建 Webhook**
4. 设置名称和头像(可选)
5. 选择目标频道
6. 复制 Webhook URL,格式类似:
```
https://discord.com/api/webhooks/123456789/abcdefghijklmnopqrstuvwxyz
```
### 3. 配置 Gitea Secrets
由于 Gitea Actions API 在该实例不可用,需要直接写入 MySQL 数据库。
#### 3.1 连接到 Gitea 数据库
```bash
# SSH 到 Gitea 服务器
ssh doc79
# 连接 MySQL(根据实际情况调整)
mysql -u gitea -p gitea
```
#### 3.2 插入 Secrets
```sql
-- Telegram Bot Token
INSERT INTO action_variable (owner_id, repo_id, name, data, created_unix, updated_unix)
VALUES (0, 0, 'TELEGRAM_BOT_TOKEN', '你的Bot Token', UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
-- Telegram Chat ID
INSERT INTO action_variable (owner_id, repo_id, name, data, created_unix, updated_unix)
VALUES (0, 0, 'TELEGRAM_CHAT_ID', '你的Chat ID', UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
-- Discord Webhook URL
INSERT INTO action_variable (owner_id, repo_id, name, data, created_unix, updated_unix)
VALUES (0, 0, 'DISCORD_WEBHOOK_URL', '你的Webhook URL', UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
```
**注意**
- `owner_id=0` 表示全局变量,所有仓库可用
- 如果只想对特定仓库生效,需要查询该仓库的 `repo_id` 并替换
#### 3.3 验证配置
```sql
-- 查看所有 Actions 变量
SELECT id, owner_id, repo_id, name, LEFT(data, 20) as data_preview
FROM action_variable
WHERE name IN ('TELEGRAM_BOT_TOKEN', 'TELEGRAM_CHAT_ID', 'DISCORD_WEBHOOK_URL');
```
### 4. 测试通知
配置完成后,推送代码到 main 分支触发部署:
```bash
# 测试后端部署通知
git add server/
git commit -m "test: 测试部署通知"
git push origin main
# 测试小程序部署通知
git add mini/
git commit -m "test: 测试小程序通知"
git push origin main
```
## 通知示例
### Telegram 通知
```
✅ 后端服务部署成功
状态: 成功
仓库: gouki/lunar
分支: main
提交: abc1234
作者: gouki
工作流: Build and Deploy Server #42
部署包已保存到: /opt/lunar/ci-artifacts/server-deploy-latest.tar.gz
查看详情
```
### Discord 通知
Discord 会显示一个嵌入消息卡片,包含:
- 标题和状态图标
- 仓库、分支、提交、作者信息
- 部署详情
- 时间戳
- 可点击的链接跳转到 Actions 页面
## 故障排查
### 通知未发送
1. **检查 Secrets 配置**
```sql
SELECT * FROM action_variable WHERE name LIKE '%TELEGRAM%' OR name LIKE '%DISCORD%';
```
2. **查看 Actions 日志**
- 在 Gitea 网页界面查看 Actions 运行日志
- 查找 "发送 Telegram 通知" 或 "发送 Discord 通知" 相关输出
3. **检查网络连接**
- 确保运行器可以访问 `api.telegram.org` 和 `discord.com`
- 你的运行器在海外,理论上没有问题
### Telegram 通知失败
- **Bot Token 错误**:检查 Token 是否正确
- **Chat ID 错误**:确保 Bot 已加入群组/频道,且 Chat ID 正确
- **权限问题**:确保 Bot 有发送消息的权限
### Discord 通知失败
- **Webhook URL 错误**:检查 URL 是否完整
- **Webhook 被删除**:重新创建 Webhook
- **频道权限**:确保 Webhook 有权限在目标频道发送消息
## 高级配置
### 自定义通知内容
编辑 `.gitea/workflows/server-deploy.yml` 或 `.gitea/workflows/miniapp-preview.yml`,修改通知步骤的消息内容:
```yaml
.gitea/scripts/notify.sh success \
"自定义标题" \
"自定义消息内容"
```
### 只使用单一渠道
如果只想使用 Telegram 或 Discord 之一,只需配置对应的 Secrets 即可。脚本会自动跳过未配置的渠道。
### 添加更多通知渠道
可以扩展 `.gitea/scripts/notify.sh` 脚本,添加其他通知渠道(如 Slack、钉钉、企业微信等)。
## 安全建议
1. **保护 Secrets**:不要将 Bot Token、Chat ID、Webhook URL 提交到代码仓库
2. **定期轮换**:定期更换 Bot Token 和 Webhook URL
3. **权限最小化**Telegram Bot 只授予发送消息权限,Discord Webhook 只用于特定频道
4. **监控异常**:如果发现异常通知,立即检查 Secrets 是否泄露
## 相关文件
- 通知脚本:`.gitea/scripts/notify.sh`
- 后端部署工作流:`.gitea/workflows/server-deploy.yml`
- 小程序部署工作流:`.gitea/workflows/miniapp-preview.yml`
## 更新日志
- 2026-08-07: 初始版本,支持 Telegram 和 Discord 双渠道通知
+185
View File
@@ -0,0 +1,185 @@
#!/bin/bash
# 部署通知脚本 - 支持 Telegram 和 Discord
# 使用方法: ./notify.sh <status> <title> <message> [color]
# status: success 或 failure
# title: 通知标题
# message: 通知内容
# color: Discord 消息颜色(可选,默认根据 status 自动选择)
set -e
STATUS="$1"
TITLE="$2"
MESSAGE="$3"
COLOR="${4:-}"
# 根据状态设置默认值
if [ "$STATUS" = "success" ]; then
EMOJI="✅"
DEFAULT_COLOR="3066993" # 绿色
STATUS_TEXT="成功"
elif [ "$STATUS" = "failure" ]; then
EMOJI="❌"
DEFAULT_COLOR="15158332" # 红色
STATUS_TEXT="失败"
else
EMOJI="️"
DEFAULT_COLOR="3447003" # 蓝色
STATUS_TEXT="通知"
fi
COLOR="${COLOR:-$DEFAULT_COLOR}"
# 获取 Git 信息
REPO="${GITEA_REPO:-unknown}"
BRANCH="${GITEA_REF_NAME:-unknown}"
COMMIT_SHA="${GITEA_SHA:-unknown}"
COMMIT_SHORT="${COMMIT_SHA:0:7}"
AUTHOR="${GITEA_ACTOR:-unknown}"
WORKFLOW="${GITEA_WORKFLOW:-unknown}"
RUN_NUMBER="${GITEA_RUN_NUMBER:-unknown}"
RUN_URL="${GITEA_SERVER_URL:-}/${GITEA_REPO}/actions/runs/${GITEA_RUN_ID:-}"
# ==================== Telegram 通知 ====================
send_telegram() {
if [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; then
echo "⚠️ Telegram 配置缺失,跳过通知"
return 0
fi
local tg_message="${EMOJI} <b>${TITLE}</b>
<b>状态:</b> ${STATUS_TEXT}
<b>仓库:</b> ${REPO}
<b>分支:</b> ${BRANCH}
<b>提交:</b> <code>${COMMIT_SHORT}</code>
<b>作者:</b> ${AUTHOR}
<b>工作流:</b> ${WORKFLOW} #${RUN_NUMBER}
${MESSAGE}
<a href=\"${RUN_URL}\">查看详情</a>"
echo "📤 发送 Telegram 通知..."
local response
response=$(curl -s -X POST \
"https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
-d "chat_id=${TELEGRAM_CHAT_ID}" \
-d "text=${tg_message}" \
-d "parse_mode=HTML" \
-d "disable_web_page_preview=true")
if echo "$response" | grep -q '"ok":true'; then
echo "✅ Telegram 通知发送成功"
else
echo "❌ Telegram 通知发送失败: $response"
return 1
fi
}
# ==================== Discord 通知 ====================
send_discord() {
if [ -z "$DISCORD_WEBHOOK_URL" ]; then
echo "⚠️ Discord Webhook 配置缺失,跳过通知"
return 0
fi
echo "📤 发送 Discord 通知..."
# 构建 Discord Embed JSON
local discord_payload
discord_payload=$(cat <<EOF
{
"embeds": [{
"title": "${EMOJI} ${TITLE}",
"description": "${MESSAGE}",
"color": ${COLOR},
"fields": [
{
"name": "状态",
"value": "${STATUS_TEXT}",
"inline": true
},
{
"name": "仓库",
"value": "${REPO}",
"inline": true
},
{
"name": "分支",
"value": "${BRANCH}",
"inline": true
},
{
"name": "提交",
"value": "\`${COMMIT_SHORT}\`",
"inline": true
},
{
"name": "作者",
"value": "${AUTHOR}",
"inline": true
},
{
"name": "工作流",
"value": "${WORKFLOW} #${RUN_NUMBER}",
"inline": true
}
],
"footer": {
"text": "Gitea Actions"
},
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"url": "${RUN_URL}"
}]
}
EOF
)
local response
response=$(curl -s -X POST \
"$DISCORD_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "$discord_payload")
if [ -z "$response" ] || echo "$response" | grep -q '"id"'; then
echo "✅ Discord 通知发送成功"
else
echo "❌ Discord 通知发送失败: $response"
return 1
fi
}
# ==================== 主流程 ====================
main() {
echo "🔔 开始发送部署通知..."
echo "状态: $STATUS"
echo "标题: $TITLE"
echo "消息: $MESSAGE"
echo ""
local failed=0
# 发送 Telegram 通知
if ! send_telegram; then
failed=$((failed + 1))
fi
echo ""
# 发送 Discord 通知
if ! send_discord; then
failed=$((failed + 1))
fi
echo ""
if [ $failed -eq 0 ]; then
echo "✅ 所有通知发送完成"
else
echo "⚠️ 部分通知发送失败 ($failed 个)"
# 不退出脚本,避免影响主流程
fi
}
main
+26
View File
@@ -93,6 +93,32 @@ jobs:
cp mini/ci-artifacts/upload-result.json /ci-artifacts/miniapp-upload-latest.json
ls -la /ci-artifacts/miniapp-upload-latest.json
echo "Upload result saved on runner host: /opt/lunar/ci-artifacts/miniapp-upload-latest.json"
- name: Send success notification
if: success()
env:
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
run: |
VERSION="$(tr -d '\n' < mini/ci-artifacts/resolved-version.txt)"
chmod +x .gitea/scripts/notify.sh
.gitea/scripts/notify.sh success \
"小程序开发版上传成功" \
"版本: ${VERSION}\n上传结果已保存到: /opt/lunar/ci-artifacts/miniapp-upload-latest.json"
- name: Send failure notification
if: failure()
env:
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
run: |
chmod +x .gitea/scripts/notify.sh
.gitea/scripts/notify.sh failure \
"小程序开发版上传失败" \
"请检查 Actions 日志了解失败原因"
- name: Remove temporary credentials
if: always()
run: rm -f "$RUNNER_TEMP/wechat-ci.key"
+24
View File
@@ -69,6 +69,30 @@ jobs:
ls -la /ci-artifacts/server-deploy-latest.tar.gz
echo "Deployment package saved on runner host: /opt/lunar/ci-artifacts/server-deploy-latest.tar.gz"
- name: Send success notification
if: success()
env:
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
run: |
chmod +x .gitea/scripts/notify.sh
.gitea/scripts/notify.sh success \
"后端服务部署成功" \
"部署包已保存到: /opt/lunar/ci-artifacts/server-deploy-latest.tar.gz"
- name: Send failure notification
if: failure()
env:
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
run: |
chmod +x .gitea/scripts/notify.sh
.gitea/scripts/notify.sh failure \
"后端服务部署失败" \
"请检查 Actions 日志了解失败原因"
- name: Cleanup
if: always()
run: |
+2
View File
@@ -939,6 +939,8 @@ Page({
const direction = e.currentTarget.dataset.direction;
const { trees, currentTreeIndex } = this.data;
if (!trees.length) return;
let newIndex = currentTreeIndex;
if (direction === 'prev') {
newIndex = (currentTreeIndex - 1 + trees.length) % trees.length;
+1 -1
View File
@@ -13,7 +13,7 @@
<view class="switch-btn" bindtap="switchTree" data-direction="prev">
<text class="switch-icon"></text>
</view>
<view class="tree-name">{{trees[currentTreeIndex].name || '许愿树'}}</view>
<view class="tree-name">{{trees.length > 0 ? trees[currentTreeIndex].name : '许愿树'}}</view>
<view class="switch-btn" bindtap="switchTree" data-direction="next">
<text class="switch-icon"></text>
</view>
+6
View File
@@ -13,6 +13,7 @@
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
/* 顶部导航 */
@@ -26,6 +27,11 @@
justify-content: space-between;
align-items: center;
z-index: 100;
pointer-events: none;
}
.top-bar > * {
pointer-events: auto;
}
.tree-switcher {
+53 -7
View File
@@ -269,14 +269,60 @@ function getConstellation(month, day) {
}
}
/** 获取节气 */
function getSolarTerm(year, month, day) {
const termDates = SOLAR_TERM_DATES[month - 1];
if (day === termDates[0]) {
return SOLAR_TERMS[(month - 1) * 2];
} else if (day === termDates[1]) {
return SOLAR_TERMS[(month - 1) * 2 + 1];
/** 计算某年某节气的公历日期(21世纪 C 值法,误差±1天) */
function getSolarTermDate(year, termIndex) {
// 21世纪 C 值表(小寒=0, 大寒=1, 立春=2 ... 冬至=23
const C21 = [
6.11, 20.84, 4.6295, 19.4599, 6.3826, 21.4155, // 小寒 大寒 立春 雨水 惊蛰 春分
5.59, 20.888, 6.318, 21.86, 6.5, 22.20, // 清明 谷雨 立夏 小满 芒种 夏至
7.928, 23.65, 8.35, 23.95, 8.44, 23.822, // 小暑 大暑 立秋 处暑 白露 秋分
9.098, 24.218, 8.218, 23.08, 7.9, 22.60 // 寒露 霜降 立冬 小雪 大雪 冬至
];
// 20世纪 C 值表
const C20 = [
6.9, 21.37, 5.4055, 20.12, 7.1082, 22.83,
6.38, 21.646, 7.108, 22.36, 7.5, 23.13,
8.318, 24.64, 9.35, 24.77, 9.74, 24.643,
9.878, 24.918, 9.11, 23.65, 8.8, 23.54
];
const Y = year % 100;
const C = year >= 2000 ? C21[termIndex] : C20[termIndex];
// 闰年修正:小寒、大寒、立春、雨水(1-2月)
let leapAdjust = 0;
if (termIndex <= 3 && isLeapYear(year)) leapAdjust = 1;
// 特殊修正
if (year === 2026 && termIndex === 3) leapAdjust = -1; // 2026雨水
if (year === 2084 && termIndex === 3) leapAdjust = 1;
if (year === 1911 && termIndex === 6) leapAdjust = 1; // 清明
if (year === 1984 && termIndex === 7) leapAdjust = -1; // 谷雨
if (year === 1911 && termIndex === 8) leapAdjust = 1; // 立夏
if (year === 1981 && termIndex === 10) leapAdjust = -1; // 芒种
if (year === 1902 && termIndex === 11) leapAdjust = 1; // 夏至
if (year === 1928 && termIndex === 12) leapAdjust = 1; // 小暑
if (year === 1925 && termIndex === 13) leapAdjust = -1; // 大暑
if (year === 2002 && termIndex === 14) leapAdjust = 1; // 立秋
if (year === 1927 && termIndex === 16) leapAdjust = 1; // 白露
if (year === 1942 && termIndex === 17) leapAdjust = 1; // 秋分
if (year === 2089 && termIndex === 18) leapAdjust = 1; // 寒露
if (year === 2089 && termIndex === 19) leapAdjust = 1; // 霜降
if (year === 1978 && termIndex === 20) leapAdjust = 1; // 立冬
if (year === 1954 && termIndex === 21) leapAdjust = -1; // 小雪
if (year === 1918 && termIndex === 22) leapAdjust = 1; // 大雪
if (year === 2021 && termIndex === 22) leapAdjust = -1;
if (year === 1902 && termIndex === 23) leapAdjust = 1; // 冬至
return Math.floor(Y * 0.2422 + C) - Math.floor((Y - 1) / 4) + leapAdjust;
}
/** 获取节气(准确算法) */
function getSolarTerm(year, month, day) {
// 每月两个节气:第1个在 index (month-1)*2,第2个在 (month-1)*2+1
const idx1 = (month - 1) * 2;
const idx2 = idx1 + 1;
const d1 = getSolarTermDate(year, idx1);
const d2 = getSolarTermDate(year, idx2);
if (day === d1) return SOLAR_TERMS[idx1];
if (day === d2) return SOLAR_TERMS[idx2];
return null;
}