Compare commits
29 Commits
065b354b2a
...
custom
| Author | SHA1 | Date | |
|---|---|---|---|
| 1655f11514 | |||
| 0fb2dfdc63 | |||
| d629dfe18c | |||
| 76a274e1cc | |||
| 291bd86c94 | |||
| 8658e849e7 | |||
| 6d0329210f | |||
| c57c1e8490 | |||
| 2425c358e3 | |||
| 373ccba9d6 | |||
| ce345f79ab | |||
| 6baf668696 | |||
| 9ef0e62b05 | |||
| 7df2d49c56 | |||
| 65d52571de | |||
| 4bdd2c7195 | |||
| 9da91ad54f | |||
| 55c175acca | |||
| 3ed37e7719 | |||
| 32a49be913 | |||
| ebcc03d3d0 | |||
| 0be04287c8 | |||
| dfbc2b1440 | |||
| 47b5641597 | |||
| 11da502f75 | |||
| 29c164439c | |||
| 989ffef156 | |||
| c929e950e1 | |||
| 6b8d9fcb13 |
@@ -0,0 +1,167 @@
|
||||
name: Build Docker Image
|
||||
|
||||
on:
|
||||
# 自动触发:push 到 custom 分支时跑(force-push 后的 rebase 也会触发,可接受)
|
||||
# paths-ignore:纯文档/配置改动跳过,避免浪费 ~10 分钟构建
|
||||
# ⚠️ 已知 quirk(2026-05-02 验证):empty commit(git commit --allow-empty)
|
||||
# 不会触发 paths-ignore 过滤的 workflow,Gitea 把 zero-paths-changed 当作
|
||||
# "vacuously matches ignore list" 跳过。要强制触发必须至少改一个非 ignore 路径
|
||||
# 的真实文件(改这个 yml 自己最稳)。
|
||||
push:
|
||||
branches: [custom]
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '.gitignore'
|
||||
- 'LICENSE'
|
||||
- 'screenshot/**'
|
||||
# sync-upstream.yml 改的是 main reset 逻辑,跟 build 无关
|
||||
# build-image.yml 自己留着会触发,作为 workflow 改动的 self-test
|
||||
- '.gitea/workflows/sync-upstream.yml'
|
||||
# 手动触发:保留作为应急通道(重新打包旧 commit、用自定义 tag 等等)
|
||||
# 注意:手动触发也会跑 deploy job —— 如果只想 build 不部署,临时把 deploy
|
||||
# job 注释掉或在 deploy 里加 if 条件
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
branch:
|
||||
description: '要打包的分支(仅手动触发生效)'
|
||||
required: true
|
||||
default: 'custom'
|
||||
tag:
|
||||
description: '镜像 tag(留空则用 commit short hash)'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
# 并发控制:同一分支的连续 push 只跑最新的,旧 in-progress run 会被取消
|
||||
# 例:连续 3 次 push,第 1 次 build 跑了 30s,第 2 次开始 → 取消第 1,第 2 跑;
|
||||
# 期间第 3 次又来 → 取消第 2,第 3 跑。最后只构建+部署最新代码,省 CI 时间。
|
||||
# group 包含 ref 是为了不同分支的 build 互不干扰(虽然当前只有 custom 用)
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout target branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# workflow_dispatch 时用用户填的 branch;push 触发时 inputs.branch 为空,
|
||||
# fallback 到 github.ref_name(即触发的分支名,push 到 custom 时就是 custom)
|
||||
ref: ${{ inputs.branch || github.ref_name }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
# 钉到 v0.13.2(自带 runc 1.1.x),避免 runc 1.2+ 的 procfs 安全检查
|
||||
# 在 DSM 老内核(4.4.x)上撞 openat2/fsmount 不存在导致 build 失败
|
||||
driver-opts: |
|
||||
image=moby/buildkit:v0.13.2
|
||||
|
||||
- name: Login to Gitea Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: git.zhengchentao.win
|
||||
username: ${{ gitea.actor }}
|
||||
password: ${{ secrets.PACKAGES_TOKEN }}
|
||||
|
||||
- name: Determine image tag and revision
|
||||
id: meta
|
||||
run: |
|
||||
if [ -n "${{ inputs.tag }}" ]; then
|
||||
IMAGE_TAG="${{ inputs.tag }}"
|
||||
else
|
||||
IMAGE_TAG="$(git rev-parse --short HEAD)"
|
||||
fi
|
||||
echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT
|
||||
echo "full_sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
||||
echo "==> Image tag: $IMAGE_TAG"
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
# 上游 Dockerfile 用 BUILD_PIPELINE 作为 CI 跳过开关:
|
||||
# 设为 "1" 时 pkg/exchangerates 跳过依赖第三方 API 的活测试
|
||||
# (加拿大银行/乌兹别克央行 API 国内不稳,跑就超时)
|
||||
# CHECK_3RD_API 留空 → 三方 API 测试不跑;想跑设 "1"
|
||||
build-args: |
|
||||
BUILD_PIPELINE=1
|
||||
# OCI 标签:
|
||||
# - source 让 Gitea 收包时自动把镜像关联到对应 repo(不再需要手动去
|
||||
# "包设置 → 链接到仓库")
|
||||
# - revision 把构建时的 commit full SHA 烙进镜像 manifest,
|
||||
# docker inspect 能反推回源码版本
|
||||
labels: |
|
||||
org.opencontainers.image.source=https://git.zhengchentao.win/zhengchen.tao/ezbookkeeping
|
||||
org.opencontainers.image.revision=${{ steps.meta.outputs.full_sha }}
|
||||
tags: |
|
||||
git.zhengchentao.win/zhengchen.tao/ezbookkeeping:${{ steps.meta.outputs.image_tag }}
|
||||
git.zhengchentao.win/zhengchen.tao/ezbookkeeping:latest
|
||||
|
||||
- name: Build summary
|
||||
# 把构建出的镜像 tag 与源 commit 显式列在 Action run summary 区,
|
||||
# 方便从 UI 一眼看到本次 build 产出。always() 保证 build 失败也输出。
|
||||
if: always()
|
||||
run: |
|
||||
{
|
||||
echo "## Build Summary"
|
||||
echo ""
|
||||
echo "| 项 | 值 |"
|
||||
echo "|---|---|"
|
||||
echo "| 触发方式 | \`${{ github.event_name }}\` |"
|
||||
echo "| 源分支 | \`${{ inputs.branch || github.ref_name }}\` |"
|
||||
echo "| 源 commit (full) | \`${{ steps.meta.outputs.full_sha }}\` |"
|
||||
echo "| 源 commit (short) | \`${{ steps.meta.outputs.image_tag }}\` |"
|
||||
echo "| 镜像 tag | \`git.zhengchentao.win/zhengchen.tao/ezbookkeeping:${{ steps.meta.outputs.image_tag }}\` + \`:latest\` |"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
deploy:
|
||||
# needs: build 串起来 —— build 失败 deploy 自动跳过,无需 if 条件
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# 登录 Gitea Container Registry,否则 docker compose pull 私有镜像 401。
|
||||
# 跟 build job 那步是同一个 PACKAGES_TOKEN,但每个 job 跑在独立 runner 上,
|
||||
# 凭据不会从 build job 继承,必须在这里再登一次。
|
||||
- name: Login to Gitea Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: git.zhengchentao.win
|
||||
username: ${{ gitea.actor }}
|
||||
password: ${{ secrets.PACKAGES_TOKEN }}
|
||||
|
||||
- name: Pull and restart ezbookkeeping
|
||||
# 部署逻辑直接内联在这。runner 容器挂了 host docker.sock,
|
||||
# 所以这里 docker 命令直接操作的是宿主机 docker daemon,
|
||||
# 容器层面相当于 "ssh 到 NAS 跑 docker compose"。
|
||||
#
|
||||
# NAS_INFRA_TOKEN secret 仅在 nas-infra 是私有仓库时需要;
|
||||
# 公开仓库不设这个 secret 也能拉。
|
||||
env:
|
||||
NAS_INFRA_TOKEN: ${{ secrets.NAS_INFRA_TOKEN }}
|
||||
run: |
|
||||
set -e
|
||||
|
||||
TMPDIR=$(mktemp -d)
|
||||
trap 'rm -rf "$TMPDIR"' EXIT
|
||||
|
||||
# 决定 clone URL:有 token 用 token(私有),没有用裸 URL(公开)
|
||||
if [ -n "$NAS_INFRA_TOKEN" ]; then
|
||||
CLONE_URL="https://x-access-token:${NAS_INFRA_TOKEN}@git.zhengchentao.win/dev/nas-infra.git"
|
||||
else
|
||||
CLONE_URL="https://git.zhengchentao.win/dev/nas-infra.git"
|
||||
fi
|
||||
|
||||
git clone --depth 1 "$CLONE_URL" "$TMPDIR/nas-infra"
|
||||
cd "$TMPDIR/nas-infra/ezbookkeeping"
|
||||
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
|
||||
# 简单 health:列容器状态 + 输出最近日志
|
||||
sleep 3
|
||||
docker compose ps
|
||||
docker compose logs --tail=30 ezbookkeeping
|
||||
@@ -1,17 +0,0 @@
|
||||
name: Deploy Docker Image
|
||||
|
||||
on:
|
||||
workflow_dispatch
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Execute custom script
|
||||
run: |
|
||||
cat >> deploy.sh <<EOF
|
||||
#!/bin/sh
|
||||
${{ vars.CUSTOM_DEPLOY_SCRIPTS }}
|
||||
EOF
|
||||
chmod +x deploy.sh
|
||||
./deploy.sh
|
||||
@@ -1,64 +0,0 @@
|
||||
name: Docker Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ secrets.DOCKER_REPO }}/mayswind/ezbookkeeping
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=raw,value=latest
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Set up the environment
|
||||
id: setup
|
||||
run: |
|
||||
echo "build_unix_time=$(date '+%s')" >> "$GITHUB_OUTPUT"
|
||||
echo "build_date=$(date '+%Y%m%d')" >> "$GITHUB_OUTPUT"
|
||||
sed -r -i 's#FROM( --.*)? (.*:.*)?#FROM\1 ${{ secrets.DOCKER_REPO }}/mirrors/\2#g' Dockerfile
|
||||
cat >> docker/custom-backend-pre-setup.sh <<EOF
|
||||
#!/bin/sh
|
||||
${{ vars.CUSTOM_BACKEND_PRE_SETUP }}
|
||||
EOF
|
||||
cat >> docker/custom-frontend-pre-setup.sh <<EOF
|
||||
#!/bin/sh
|
||||
${{ vars.CUSTOM_FRONTEND_PRE_SETUP }}
|
||||
EOF
|
||||
chmod +x docker/custom-backend-pre-setup.sh
|
||||
chmod +x docker/custom-frontend-pre-setup.sh
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
file: Dockerfile
|
||||
context: .
|
||||
platforms: ${{ vars.BUILD_RELEASE_PLATFORMS }}
|
||||
push: true
|
||||
build-args: |
|
||||
RELEASE_BUILD=1
|
||||
BUILD_PIPELINE=1
|
||||
BUILD_UNIXTIME=${{ steps.setup.outputs.build_unix_time }}
|
||||
BUILD_DATE=${{ steps.setup.outputs.build_date }}
|
||||
CHECK_3RD_API=${{ vars.CHECK_3RD_API }}
|
||||
SKIP_TESTS=${{ vars.SKIP_TESTS }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
@@ -1,63 +0,0 @@
|
||||
name: Docker Snapshot
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ secrets.DOCKER_REPO }}/mayswind/ezbookkeeping
|
||||
tags: |
|
||||
type=raw,value=SNAPSHOT-{{date 'YYYYMMDD'}}
|
||||
type=raw,value=latest-snapshot
|
||||
type=sha,format=short,prefix=SNAPSHOT-
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Set up the environment
|
||||
id: setup
|
||||
run: |
|
||||
echo "build_unix_time=$(date '+%s')" >> "$GITHUB_OUTPUT"
|
||||
echo "build_date=$(date '+%Y%m%d')" >> "$GITHUB_OUTPUT"
|
||||
sed -r -i 's#FROM( --.*)? (.*:.*)?#FROM\1 ${{ secrets.DOCKER_REPO }}/mirrors/\2#g' Dockerfile
|
||||
cat >> docker/custom-backend-pre-setup.sh <<EOF
|
||||
#!/bin/sh
|
||||
${{ vars.CUSTOM_BACKEND_PRE_SETUP }}
|
||||
EOF
|
||||
cat >> docker/custom-frontend-pre-setup.sh <<EOF
|
||||
#!/bin/sh
|
||||
${{ vars.CUSTOM_FRONTEND_PRE_SETUP }}
|
||||
EOF
|
||||
chmod +x docker/custom-backend-pre-setup.sh
|
||||
chmod +x docker/custom-frontend-pre-setup.sh
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
file: Dockerfile
|
||||
context: .
|
||||
platforms: ${{ vars.BUILD_SNAPSHOT_PLATFORMS }}
|
||||
push: true
|
||||
build-args: |
|
||||
BUILD_PIPELINE=1
|
||||
BUILD_UNIXTIME=${{ steps.setup.outputs.build_unix_time }}
|
||||
BUILD_DATE=${{ steps.setup.outputs.build_date }}
|
||||
CHECK_3RD_API=${{ vars.CHECK_3RD_API }}
|
||||
SKIP_TESTS=${{ vars.SKIP_TESTS }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
@@ -0,0 +1,39 @@
|
||||
name: Sync from upstream
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: '要同步的 release tag(留空则同步到 upstream/main 的最新 tag)'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.SYNC_TOKEN }}
|
||||
|
||||
- name: Sync main to release tag
|
||||
run: |
|
||||
git config user.name "gitea-actions"
|
||||
git config user.email "actions@gitea.local"
|
||||
git remote add upstream https://git.zhengchentao.win/mirror/ezbookkeeping.git
|
||||
git fetch upstream --tags
|
||||
|
||||
if [ -n "${{ inputs.tag }}" ]; then
|
||||
TARGET="${{ inputs.tag }}"
|
||||
else
|
||||
TARGET=$(git tag -l --sort=-v:refname | head -n 1)
|
||||
fi
|
||||
|
||||
echo "==> Syncing main to $TARGET"
|
||||
git rev-parse "$TARGET" || { echo "❌ Tag $TARGET not found"; exit 1; }
|
||||
|
||||
git checkout -B main origin/main
|
||||
git reset --hard "$TARGET"
|
||||
git push origin main --force-with-lease
|
||||
git push origin --tags
|
||||
@@ -0,0 +1,127 @@
|
||||
# CLAUDE.md
|
||||
|
||||
本项目是 [mayswind/ezbookkeeping](https://github.com/mayswind/ezbookkeeping) 的个人 fork。
|
||||
|
||||
> **本文件**:仓库分支模型、上游同步流程、CI 故障排查 —— meta 层
|
||||
> **[`FORK.md`](FORK.md)**:fork 相对上游的具体改动清单(feature 维度 + 进度状态)
|
||||
> **个人笔记**:通用 fork 工作流决策框架在 `fork-工作流决策框架.md`(不入库)
|
||||
|
||||
本文件只记录**这个仓库的具体事实**,避免 Claude 会话误判。
|
||||
|
||||
---
|
||||
|
||||
## 仓库拓扑
|
||||
|
||||
```
|
||||
github.com/mayswind/ezbookkeeping (上游)
|
||||
│ Gitea pull mirror(后台异步)
|
||||
▼
|
||||
git.zhengchentao.win/mirror/ezbookkeeping (只读镜像)
|
||||
│ CI workflow 拉过来
|
||||
▼
|
||||
git.zhengchentao.win/dev/ezbookkeeping (origin,本地唯一 remote)
|
||||
```
|
||||
|
||||
本地 `git remote -v` 只有 origin 一项,**没有手工配 upstream**。上游同步通过 custom 分支上的 workflow 在服务端完成,不是本地操作。
|
||||
|
||||
---
|
||||
|
||||
## 两个分支的职责(必须先理解,否则会改错地方)
|
||||
|
||||
| 分支 | 职责 | force push? |
|
||||
|---|---|---|
|
||||
| `main` | **锚定上游 release tag**(当前 v1.4.0)。被 `.gitea/workflows/sync-upstream.yml` `git reset --hard <tag>` 覆写。**别在 main 上做任何改动** | 是(由 CI 做) |
|
||||
| `custom` | **所有个人改动 + workflow 文件都在这**:信用额度功能、UI 调整、个人需求清单、`.gitea/workflows/*.yml` 等。具体改动清单见 [`FORK.md`](FORK.md)。日常开发分支,**default branch** | 是(rebase 后人工做) |
|
||||
|
||||
⚠️ **default branch 是 `custom`**。`git clone` 默认 checkout custom,直接是开发分支。
|
||||
|
||||
### 历史:曾经存在过的 ci 分支(已退役)
|
||||
|
||||
2026-05-02 之前曾经有第三个分支 `ci`,最初设计是把 `.gitea/workflows/*.yml` 单独放它上面以"meta/code 分离"。两周后发现 Gitea Actions runs 列表显示的 commit 是 workflow 文件所在 commit(即 ci 的 HEAD),不是被构建的代码 commit,UX 误导性强。
|
||||
|
||||
把 workflow 挪回 custom 之后:
|
||||
- runs 列表 commit = 真实代码 commit ✅
|
||||
- `git clone` 默认落 custom 直接是开发分支 ✅
|
||||
- rebase 上游时 workflow 跟 custom 一起平移 ✅
|
||||
- 代价:失去"workflow 与代码完全独立"的设计美感 —— 这个分离原本就是过度设计
|
||||
|
||||
**ci 分支于 2026-05-02 删除**,仅保留这段说明给后续 Claude 会话理解 git log 里"workflow 文件迁到 custom"这条提交(commit `555ecc1a`)的来龙去脉。**workflow 改动直接在 custom 上做**。
|
||||
|
||||
---
|
||||
|
||||
## custom 分支 workflow 清单
|
||||
|
||||
`.gitea/workflows/` 当前有 2 个 workflow(2026-05-04 起 build+deploy 合并为单 workflow 双 job):
|
||||
|
||||
| 文件 | 触发 | 干什么 | 状态 |
|
||||
|---|---|---|---|
|
||||
| `sync-upstream.yml` | 手动(`workflow_dispatch`,可填 tag) | 服务端把 `dev/main` 强制 reset 到 mirror 上的指定 release tag(默认最新),然后 `push --force-with-lease` + 推 tags | ✅ 在用 |
|
||||
| `build-image.yml` | **自动**(push 到 custom 触发,`paths-ignore` 屏蔽 `**.md` / `.gitignore` / `LICENSE` / `screenshot/**` / `sync-upstream.yml`)+ 手动备选 | **两个 job 串联在同一 run 里**:①`build` job 装 buildkit v0.13.2 → 登录 Gitea registry → 构建镜像(带 OCI 标签 source/revision,Gitea 自动关联包到 repo)→ push 到 `git.zhengchentao.win/dev/ezbookkeeping:<hash>` 与 `:latest`,`build-args: BUILD_PIPELINE=1` 跳过活 API 测试。②`deploy` job (`needs: build`) 登录 registry → clone nas-infra → `docker compose pull && up -d` 重启 ezbookkeeping。私有 nas-infra 需要 `secrets.NAS_INFRA_TOKEN`,公开仓库不需要。UI 上 Actions 列表显示一条 run,run 详情里 dependency graph 显示 build → deploy | ✅ 日常发布通道 + 自动 CD |
|
||||
|
||||
**已删**:
|
||||
- `docker-snapshot.yml` / `docker-release.yml`(2026-05-02,依赖未配的 `secrets.DOCKER_REPO`,永远失败)
|
||||
- `deploy.yml`(2026-05-04,合并进 `build-image.yml` 作为第二个 job,理由:原先 `workflow_run` 链触发会在 Actions 列表产生两条独立 run,UX 割裂;合并后单 run + dependency graph 看 build/deploy 状态一目了然)
|
||||
|
||||
需要时再从 git 历史 cherry-pick 回来。
|
||||
|
||||
---
|
||||
|
||||
## 同步发布流程(rebase 模型)
|
||||
|
||||
1. 上游出新 release(如 v1.4.0)→ Gitea pull mirror 自动把 tag 同步到 mirror
|
||||
2. 人工触发 `Sync from upstream` workflow → 服务端把 dev/main reset 到该 tag
|
||||
3. 本地 `git fetch && git checkout custom && git rebase origin/main`
|
||||
4. 解冲突(如有)→ 验证 → `git push --force-with-lease origin custom`
|
||||
5. **build-image workflow 自动触发**(force-push 也算 push 事件),构建新镜像;不需要手动点
|
||||
|
||||
日常 feature commit 流程(全自动 CD):
|
||||
|
||||
1. 在 custom 上改代码 → commit → push
|
||||
2. **自动触发 build job**(除非只改了 `**.md` / `.gitignore` / `LICENSE` / `screenshot/**` / `sync-upstream.yml`)
|
||||
3. build 成功 → **同 run 内 deploy job 接力跑**(`needs: build` 串联):clone nas-infra → docker compose pull → up -d
|
||||
4. 整条 push → build → deploy 链路无人工介入,UI 上是单条 run
|
||||
|
||||
**并发取消策略**:`build-image.yml` 设了 `concurrency.cancel-in-progress: true`,连续多次 push 时**只构建+部署最新那一次**(同 run 里 build/deploy 是原子单元,一起取消)。例:连续 3 次 push 间隔 30 秒,第 1 次 build 跑到 30%、第 2 次到来取消它、第 3 次又取消第 2,最终只 build + deploy 第 3 次的代码。省 CI 时间又保证最终一致性。
|
||||
|
||||
如果想跳过 build/deploy(例如手动多次 push 调试),commit 时只改文档相关文件即可(落在 paths-ignore 范围内)。如果想强制重打某个旧 commit,去 Actions UI 手动触发 `Build Docker Image`,填要打包的 branch / tag —— 注意手动触发也会跑 deploy job,**没有"只重新部署不重新 build"的单点入口了**(合并的代价,原 `deploy.yml` 那条路径已废)。临时只想重启容器:直接到 NAS 上 `docker compose up -d` 或在 Actions UI 临时禁用 deploy job。
|
||||
|
||||
**为什么 rebase 不 merge**:个人项目,无团队协作语义要保留,线性历史更清爽。
|
||||
|
||||
---
|
||||
|
||||
## 给后续 Claude 会话的明确提示
|
||||
|
||||
- 用户说"我的分支" / "切换到我的分支" → 指 `custom`
|
||||
- 用户说"rebase main" → 指 `git rebase origin/main`,目标是把 custom 的改动叠到最新上游 tag 之上
|
||||
- **不要在 `main` 分支上提交任何东西**(会被 CI 覆写)
|
||||
- **workflow 文件改动直接在 custom 上做**(2026-05-02 起,不再是 ci 分支)
|
||||
- force-push custom 是常规操作,但每次用 `--force-with-lease`,不直接 `--force`
|
||||
- 如果发现本地配了 upstream remote,那是历史遗留,不要依赖;以 origin/main 为准
|
||||
- `.claude/` 在 `.gitignore` 里(个人本地配置不入库),但 `CLAUDE.md` 本身入库
|
||||
|
||||
---
|
||||
|
||||
## 同步历史
|
||||
|
||||
- **2026-05-01**:rebase custom → origin/main (v1.4.0)。22 个 custom-only 提交(含一个旧的 `Merge branch 'main' into myrequirement` commit)压平为 21 个线性提交。已 force-push origin/custom(`08c69042` → `fe265259`)。
|
||||
- **2026-05-02**:修 Gitea Actions `Build Docker Image` 工作流。三层故障,全部不在本仓库代码里:
|
||||
- **TLS 雷**:`docker login` 走 host 进程不命中 PREROUTING REDIRECT,且 v6 撞 DSM nginx 的 CF Origin Cert。NAS 侧修:iptables 补 OUTPUT 对称规则 + `/etc/hosts` 显式 v4 兜底。详见 obsidian vault [[NAS/notes/内网证书路径]] §三.5/§三.6
|
||||
- **buildkit 内核兼容**:runc 1.2+ 撞 DSM 4.4 内核。`.gitea/workflows/build-image.yml` 钉 `moby/buildkit:v0.13.2`(commit `acdbb5bf`)
|
||||
- **backend 单元测试撞活 API**:`pkg/exchangerates/` 的 `TestExchangeRatesApiLatestExchangeRateHandler_*` 跑活 API(加拿大银行 / 乌兹别克央行),国内访问超时。upstream Dockerfile 已设 `ARG BUILD_PIPELINE`,测试代码看到 `BUILD_PIPELINE=1 && CHECK_3RD_API!=1` 时早退。修:workflow 加 `build-args: BUILD_PIPELINE=1`(commit `2dd8f099`),对齐上游 GH Actions
|
||||
- **2026-05-02 (后续)**:workflow 文件从 ci 分支迁到 custom,default branch 切到 custom(commit `555ecc1a`),随后**删掉 ci 分支**。原因:Gitea Actions runs 列表的 commit 字段一直显示 ci 的 workflow commit,不是被构建的代码 commit,UX 误导性强。挪到 custom 后列表直接显示真实代码 commit。同时清理上游残留的 `docker-release.yml` / `docker-snapshot.yml`(依赖未配的 `secrets.DOCKER_REPO`,永远失败)。仓库回到朴素的 main + custom 双分支模型
|
||||
- **2026-05-02 (numpad fix)**:FORK.md #11 定位 + 修复。小键盘点击卡顿真因是 `.numpad-button` 的 `touch-action: none`(上游 e178a079 引入)与 F7 tap 处理叠加,改为 `touch-action: manipulation`(commit `75b4d78d`)
|
||||
- **2026-05-04**:把 `deploy.yml` 合并进 `build-image.yml` 作为第二个 job(`needs: build`),删除 `deploy.yml`。原先 `workflow_run` 链路会在 Actions 列表产生两条独立 run(build 完一条、deploy 又一条),用户视角割裂;合并后 UI 列表单条 run,run 详情里 dependency graph 显示 build → deploy 串联。代价:失去"不 rebuild 只 redeploy"的 UI 单点触发,临时只想重启容器需直接 ssh NAS 跑 compose。`paths-ignore` 移除已不存在的 `deploy.yml` 项
|
||||
|
||||
## 给后续 Claude 会话:CI 故障排查路径
|
||||
|
||||
如果 Gitea Actions build 又炸,按 NAS 域问题 vs 仓库代码问题分别排查:
|
||||
|
||||
| 现象 | 大概率位置 | 文档 |
|
||||
|---|---|---|
|
||||
| `Login to Gitea Container Registry` 步骤报 `x509: certificate signed by unknown authority` | NAS 网络层(iptables / dnsmasq / DSM nginx 占 443) | obsidian vault `NAS/notes/内网证书路径.md` + `NAS/notes/IPv6 设计.md` |
|
||||
| `Build and push` 步骤里 `RUN ...` 在第二条之内就炸 `unsafe procfs detected` 之类 | buildkit/runc 与 DSM 内核版本 | `.gitea/workflows/build-image.yml` 的 `driver-opts` |
|
||||
| `Failed to pass unit testing` / `Failed to pass lint checking`(build.sh 报) | **先看 Dockerfile 顶部 `ARG`**,多半是 CI 跳过开关没传(如 `BUILD_PIPELINE` / `CHECK_3RD_API` / `SKIP_TESTS`)。**不要先去改测试代码** | `Dockerfile` 顶部 ARG + `.gitea/workflows/build-image.yml` 的 `build-args` |
|
||||
| `actions/checkout` 报 fetch 失败 | Gitea SSH/HTTPS 路径或 token 权限 | gitea-runner 的 `GITEA_RUNNER_REGISTRATION_TOKEN` + NPM `git.zhengchentao.win` 的 Advanced 配置 |
|
||||
| Dockerfile 里某条指令业务逻辑报错 | 真正的代码问题 | 本仓库 `Dockerfile` |
|
||||
|
||||
**通用排查原则**:build.sh 报的"测试失败 / lint 失败"先看是不是上游已经设计了 CI 跳过路径。Dockerfile 的 `ARG` + `build.sh` 内的 `os.Getenv()` 检查通常成对出现(如 `BUILD_PIPELINE=1` → 跳过 3rd API 测试,`SKIP_TESTS=...` → 跳过指定测试名)。对齐上游 `.github/actions/` 下的传参,绝大多数情况能直接对齐。
|
||||
@@ -1,115 +0,0 @@
|
||||
# 部署说明
|
||||
|
||||
## 镜像地址
|
||||
|
||||
```
|
||||
ghcr.io/zhengchentao/ezbookkeeping:latest
|
||||
```
|
||||
|
||||
每次向 `myrequirement` 分支推送代码,GitHub Actions 自动构建并推送新镜像。
|
||||
|
||||
---
|
||||
|
||||
## 首次迁移(从官方镜像换成自定义镜像)
|
||||
|
||||
### 1. 备份容器内配置文件到宿主机
|
||||
|
||||
```bash
|
||||
sudo docker cp ezbookkeeping:/ezbookkeeping/conf/ezbookkeeping.ini /opt/ezbookkeeping/ezbookkeeping.ini
|
||||
```
|
||||
|
||||
> 这样之后删容器也不会丢配置。
|
||||
|
||||
### 2. 停止并删除旧容器
|
||||
|
||||
```bash
|
||||
docker stop ezbookkeeping && docker rm ezbookkeeping
|
||||
```
|
||||
|
||||
> 只删容器本身,数据目录不受影响。
|
||||
|
||||
### 3. 登录 GitHub Container Registry(只需一次)
|
||||
|
||||
在 GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic) 生成 token,勾选 `read:packages`,然后:
|
||||
|
||||
```bash
|
||||
echo 你的TOKEN | docker login ghcr.io -u zhengchentao --password-stdin
|
||||
```
|
||||
|
||||
### 4. 拉取新镜像
|
||||
|
||||
```bash
|
||||
docker pull ghcr.io/zhengchentao/ezbookkeeping:latest
|
||||
```
|
||||
|
||||
### 5. 启动容器
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name ezbookkeeping \
|
||||
--restart unless-stopped \
|
||||
-p 8080:8080 \
|
||||
-v /opt/ezbookkeeping/data:/ezbookkeeping/data \
|
||||
-v /opt/ezbookkeeping/ezbookkeeping.ini:/ezbookkeeping/conf/ezbookkeeping.ini \
|
||||
-e EBK_MCP_ENABLE_MCP=true \
|
||||
-e EBK_SECURITY_ENABLE_API_TOKEN=true \
|
||||
ghcr.io/zhengchentao/ezbookkeeping:latest
|
||||
```
|
||||
|
||||
**参数说明:**
|
||||
| 参数 | 含义 |
|
||||
|------|------|
|
||||
| `-d` | 后台运行 |
|
||||
| `--restart unless-stopped` | 服务器重启后自动启动 |
|
||||
| `-p 8080:8080` | 端口映射 |
|
||||
| `-v .../data:...` | 挂载数据目录(数据库、图片等) |
|
||||
| `-v .../ezbookkeeping.ini:...` | 挂载配置文件 |
|
||||
| `-e EBK_*` | 环境变量覆盖配置 |
|
||||
|
||||
### 6. 确认运行正常
|
||||
|
||||
```bash
|
||||
docker ps # 确认容器在运行
|
||||
docker logs ezbookkeeping # 查看启动日志,确认无报错
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 后续更新(代码有改动时)
|
||||
|
||||
```bash
|
||||
# 拉取最新镜像
|
||||
docker pull ghcr.io/zhengchentao/ezbookkeeping:latest
|
||||
|
||||
# 停止并删除旧容器
|
||||
docker stop ezbookkeeping && docker rm ezbookkeeping
|
||||
|
||||
# 重新启动(与首次启动命令相同)
|
||||
docker run -d \
|
||||
--name ezbookkeeping \
|
||||
--restart unless-stopped \
|
||||
-p 8080:8080 \
|
||||
-v /opt/ezbookkeeping/data:/ezbookkeeping/data \
|
||||
-v /opt/ezbookkeeping/ezbookkeeping.ini:/ezbookkeeping/conf/ezbookkeeping.ini \
|
||||
-e EBK_MCP_ENABLE_MCP=true \
|
||||
-e EBK_SECURITY_ENABLE_API_TOKEN=true \
|
||||
ghcr.io/zhengchentao/ezbookkeeping:latest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常用运维命令
|
||||
|
||||
```bash
|
||||
# 查看运行中的容器
|
||||
docker ps
|
||||
|
||||
# 查看容器实时日志(Ctrl+C 退出)
|
||||
docker logs -f ezbookkeeping
|
||||
|
||||
# 进入容器内部排查问题
|
||||
docker exec -it ezbookkeeping sh
|
||||
|
||||
# 查看磁盘占用
|
||||
docker system df
|
||||
```
|
||||
@@ -1,6 +1,11 @@
|
||||
# ezBookkeeping 个人需求清单
|
||||
# ezBookkeeping 个人 fork 改动清单
|
||||
|
||||
> 基于 fork 版本的定制开发需求,持续更新。
|
||||
> 本文件记录这个 fork 相对上游 [mayswind/ezbookkeeping](https://github.com/mayswind/ezbookkeeping) 的所有定制改动 + 进度状态。
|
||||
|
||||
> 关联文档:
|
||||
> - [`CLAUDE.md`](CLAUDE.md) —— 仓库分支模型 / 上游同步流程 / CI 排查路径(meta 层)
|
||||
> - 部署:见自家 NAS infra repo `git.zhengchentao.win/dev/nas-infra` 的 README(compose-level)
|
||||
>
|
||||
> 标注:❌ 难/暂缓 | ❓ 待定 | 🔍 调查中 | 🟢 已完成
|
||||
|
||||
---
|
||||
@@ -79,7 +84,8 @@
|
||||
1 2 3 +
|
||||
C 0 . OK
|
||||
```
|
||||
- ⌫ 单击退格,长按清除;C 清除全部
|
||||
- ⌫ 单击退格;按住不放先删一位、约 500ms 后清空全部(长按响应细节见 #11 第二阶段)
|
||||
- C 一键清除全部
|
||||
- 涉及文件:`src/components/mobile/NumberPadSheet.vue`
|
||||
|
||||
---
|
||||
@@ -98,14 +104,12 @@
|
||||
|
||||
## 五、交易时间选择
|
||||
|
||||
### 8. 🟢 点击交易时间标题默认打开日期选择(仅移动端)
|
||||
**描述:** 在移动端记账/编辑页面点击「Transaction Time」标题行时,默认弹出日期选择器而非时间选择器。
|
||||
### 8. ❌ 点击交易时间标题默认打开日期选择(已回滚)
|
||||
**描述:** 原想让点击「Transaction Time」标题行时默认弹日期选择器。
|
||||
|
||||
**已完成:**
|
||||
- 点击标题行(`transaction-edit-datetime-header`)改为以 `'date'` 模式打开
|
||||
- 点击日期部分 → 日期选择器;点击时间部分 → 时间选择器(保持不变)
|
||||
- PC 端使用统一的 `date-time-select` 组件,无此分离交互,无需修改
|
||||
- 涉及文件:`src/views/mobile/transactions/EditPage.vue`
|
||||
**为何回滚:** 改动改的是 `template #header` 那行 label 的点击 handler(`'time'` → `'date'`),实际操作中用户点的是 `template #title` 里的日期/时间文本。上游早在 commit `368322f9` 已实现"点哪走哪"的智能路由——点日期开日期选择器、点时间开时间选择器。所以这条改动**用户视角无可见差异**,纯空改,回滚到上游行为。
|
||||
|
||||
**留档教训:** 改 UI 行为前先把"用户实际点哪个元素"摸清楚,别只看着 DOM 结构想当然。`#header` slot 只是上方的 label 行,正常用户极少触发。
|
||||
|
||||
---
|
||||
|
||||
@@ -133,10 +137,24 @@
|
||||
- Tab 切换动画保持原样(设置中已有开关可控制)
|
||||
- 涉及文件:`src/styles/mobile/global.scss`
|
||||
|
||||
### 11. 🔍 点击响应卡顿(暂调查)
|
||||
**描述:** 移动端点击按钮有延迟感,点不上的问题。
|
||||
### 11. 🟢 小键盘点击卡顿(三次修正)
|
||||
**描述:** 移动端小键盘点击有延迟感。
|
||||
|
||||
**初步判断:** 可能是接口响应慢导致,非前端交互延迟。等 #12 离线缓存方向明确后再评估。
|
||||
**第一阶段(2026-05-02)`touch-action: none` 引发的 300ms 双击延迟:** 上游在 `.numpad-button` 上设了 `touch-action: none`(commit `e178a079` "code refactor" by MaysWind),与浏览器双击缩放检测叠加后保留了老式 300ms 点击延迟。
|
||||
- 修复:`.numpad-button` 的 `touch-action: none` 改为 `touch-action: manipulation`(W3C 标准"快速点击"值,禁双击缩放)
|
||||
|
||||
**第二阶段(2026-05-08)退格键 `@taphold` 等待 750ms:** backspace 单点仍可感知延迟。根因是 `@click` + `@taphold` 让 F7 必须等 ~750ms 判别 tap vs hold,期间 click 被抑制。
|
||||
- 修复:弃用 `@click="backspace" @taphold="clear()"`,改为原生 `pointerdown`/`pointerup`/`pointercancel`/`pointerleave` + 自管定时器
|
||||
- 行为:单击立即删一位;按住不放先删一位、约 500ms 后清空全部
|
||||
|
||||
**第三阶段(2026-05-08)所有数字/运算键也延迟:** 第一阶段修完后用户反馈数字键仍有"等一拍"感。怀疑 F7 整套 tap 处理(含 active-state 检测、`fastClicks` 兼容代码、tap-hold 全局监听)即便不显式声明 `@taphold` 也会给 `@click` 加上判别期。
|
||||
- 修复:把所有按键(数字 0-9、运算 ×−+、C 清空、小数点/双零、OK 确认)的 `@click` 全部换成 `@pointerdown.left`
|
||||
- 原理:`pointerdown` 在按下瞬间触发,绕开 F7 的 tap 合成路径。`.left` 修饰符限制只响应主键(触屏 button=0 始终满足,桌面右键不会误触发)
|
||||
- F7 的 `.active-state` 视觉反馈基于独立的 touchstart/touchend 监听,不依赖 `@click`,按下视觉效果保留
|
||||
|
||||
涉及文件:`src/components/mobile/NumberPadSheet.vue`
|
||||
|
||||
**附带认知:** 原 #11 假设是"全局点击响应慢"或"接口慢",与 #12 离线缓存挂钩调研。实际诊断后跟那两条都无关——纯 F7 框架 tap 合成 + 双击缩放 + taphold 检测三者叠加。最终通过完全弃用 `@click` 改 pointer 事件解决。该认知值得记录避免后续误诊路径。
|
||||
|
||||
---
|
||||
|
||||
@@ -165,8 +183,8 @@
|
||||
| 5 | 记住上次账户 | ❓ 待定 |
|
||||
| 6 | 小键盘布局 | 🟢 已完成 |
|
||||
| 7 | 详情编辑/删除 | 🟢 已完成 |
|
||||
| 8 | 点击时间默认日期 | 🟢 已完成 |
|
||||
| 8 | 点击时间默认日期 | ❌ 已回滚(无效改动) |
|
||||
| 9 | 分类默认展开 | 🟢 已完成 |
|
||||
| 10 | 全局动画加速 | 🟢 已完成 |
|
||||
| 11 | 点击卡顿优化 | 🔍 暂调查 |
|
||||
| 11 | 小键盘点击卡顿(touch-action 修复) | 🟢 已完成 |
|
||||
| 12 | 离线缓存 | ❌ 暂缓 |
|
||||
@@ -1,6 +1,7 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2026 MaysWind (i@mayswind.net)
|
||||
Copyright (c) 2026 Zhengchen Tao (fork modifications)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,4 +1,29 @@
|
||||
# ezBookkeeping
|
||||
|
||||
> ## Personal fork notice
|
||||
>
|
||||
> This repository is a personal fork of [mayswind/ezbookkeeping](https://github.com/mayswind/ezbookkeeping) (MIT) with the following custom additions on top of upstream releases:
|
||||
>
|
||||
> - **Credit card accounts**: credit-limit field; account list shows available credit
|
||||
> - **Account-filtered transactions**: when filtering by a single account, show an account info card on top (icon / name / balance / available credit)
|
||||
> - **Account editing**: edit the balance field directly; a "balance adjustment" transaction is generated automatically
|
||||
> - **Add-transaction page**: live display of balance or available credit after selecting an account
|
||||
> - **Numpad**: custom layout (4-column calculator style) + `touch-action` fix for tap latency
|
||||
> - **Mobile animations**: generic transitions 300ms → 150ms
|
||||
> - **Transaction detail**: edit / delete entries added to the mobile three-dot menu
|
||||
> - **Category picker**: optional "expand all by default" on mobile (cloud-sync allowlisted)
|
||||
>
|
||||
> Full list with implementation details: [`FORK.md`](FORK.md)
|
||||
> Branch model / upstream sync / CI troubleshooting: [`CLAUDE.md`](CLAUDE.md)
|
||||
>
|
||||
> All modifications are released under the same MIT License as upstream — see [`LICENSE`](LICENSE).
|
||||
>
|
||||
> ---
|
||||
>
|
||||
> Upstream README content follows below.
|
||||
|
||||
---
|
||||
|
||||
[](https://github.com/mayswind/ezbookkeeping/blob/master/LICENSE)
|
||||
[](https://goreportcard.com/report/github.com/mayswind/ezbookkeeping)
|
||||
[](https://github.com/mayswind/ezbookkeeping/releases)
|
||||
|
||||
@@ -8,7 +8,11 @@
|
||||
</div>
|
||||
<div class="numpad-values">
|
||||
<span id="numpad-value" class="numpad-value" :class="currentDisplayNumClass" @click="onDisplayValueClick">{{ currentDisplay }}</span>
|
||||
<f7-button class="numpad-backspace-button" @click="backspace" @taphold="clear()">
|
||||
<f7-button class="numpad-backspace-button"
|
||||
@pointerdown="onBackspacePointerDown"
|
||||
@pointerup="onBackspacePointerEnd"
|
||||
@pointercancel="onBackspacePointerEnd"
|
||||
@pointerleave="onBackspacePointerEnd">
|
||||
<f7-icon class="icon-with-direction" f7="delete_left"></f7-icon>
|
||||
</f7-button>
|
||||
</div>
|
||||
@@ -21,55 +25,55 @@
|
||||
</f7-popover>
|
||||
|
||||
<div class="numpad-buttons">
|
||||
<f7-button class="numpad-button numpad-button-num" @click="inputNum(7)">
|
||||
<f7-button class="numpad-button numpad-button-num" @pointerdown.left="inputNum(7)">
|
||||
<span class="numpad-button-text numpad-button-text-normal">{{ digits[7] }}</span>
|
||||
</f7-button>
|
||||
<f7-button class="numpad-button numpad-button-num" @click="inputNum(8)">
|
||||
<f7-button class="numpad-button numpad-button-num" @pointerdown.left="inputNum(8)">
|
||||
<span class="numpad-button-text numpad-button-text-normal">{{ digits[8] }}</span>
|
||||
</f7-button>
|
||||
<f7-button class="numpad-button numpad-button-num" @click="inputNum(9)">
|
||||
<f7-button class="numpad-button numpad-button-num" @pointerdown.left="inputNum(9)">
|
||||
<span class="numpad-button-text numpad-button-text-normal">{{ digits[9] }}</span>
|
||||
</f7-button>
|
||||
<f7-button class="numpad-button numpad-button-function no-right-border" @click="setSymbol('×')">
|
||||
<f7-button class="numpad-button numpad-button-function no-right-border" @pointerdown.left="setSymbol('×')">
|
||||
<span class="numpad-button-text numpad-button-text-normal">×</span>
|
||||
</f7-button>
|
||||
<f7-button class="numpad-button numpad-button-num" @click="inputNum(4)">
|
||||
<f7-button class="numpad-button numpad-button-num" @pointerdown.left="inputNum(4)">
|
||||
<span class="numpad-button-text numpad-button-text-normal">{{ digits[4] }}</span>
|
||||
</f7-button>
|
||||
<f7-button class="numpad-button numpad-button-num" @click="inputNum(5)">
|
||||
<f7-button class="numpad-button numpad-button-num" @pointerdown.left="inputNum(5)">
|
||||
<span class="numpad-button-text numpad-button-text-normal">{{ digits[5] }}</span>
|
||||
</f7-button>
|
||||
<f7-button class="numpad-button numpad-button-num" @click="inputNum(6)">
|
||||
<f7-button class="numpad-button numpad-button-num" @pointerdown.left="inputNum(6)">
|
||||
<span class="numpad-button-text numpad-button-text-normal">{{ digits[6] }}</span>
|
||||
</f7-button>
|
||||
<f7-button class="numpad-button numpad-button-function no-right-border" @click="setSymbol('−')">
|
||||
<f7-button class="numpad-button numpad-button-function no-right-border" @pointerdown.left="setSymbol('−')">
|
||||
<span class="numpad-button-text numpad-button-text-normal">−</span>
|
||||
</f7-button>
|
||||
<f7-button class="numpad-button numpad-button-num" @click="inputNum(1)">
|
||||
<f7-button class="numpad-button numpad-button-num" @pointerdown.left="inputNum(1)">
|
||||
<span class="numpad-button-text numpad-button-text-normal">{{ digits[1] }}</span>
|
||||
</f7-button>
|
||||
<f7-button class="numpad-button numpad-button-num" @click="inputNum(2)">
|
||||
<f7-button class="numpad-button numpad-button-num" @pointerdown.left="inputNum(2)">
|
||||
<span class="numpad-button-text numpad-button-text-normal">{{ digits[2] }}</span>
|
||||
</f7-button>
|
||||
<f7-button class="numpad-button numpad-button-num" @click="inputNum(3)">
|
||||
<f7-button class="numpad-button numpad-button-num" @pointerdown.left="inputNum(3)">
|
||||
<span class="numpad-button-text numpad-button-text-normal">{{ digits[3] }}</span>
|
||||
</f7-button>
|
||||
<f7-button class="numpad-button numpad-button-function no-right-border" @click="setSymbol('+')">
|
||||
<f7-button class="numpad-button numpad-button-function no-right-border" @pointerdown.left="setSymbol('+')">
|
||||
<span class="numpad-button-text numpad-button-text-normal">+</span>
|
||||
</f7-button>
|
||||
<f7-button class="numpad-button numpad-button-num" @click="clear()">
|
||||
<f7-button class="numpad-button numpad-button-num" @pointerdown.left="clear()">
|
||||
<span class="numpad-button-text numpad-button-text-normal">C</span>
|
||||
</f7-button>
|
||||
<f7-button class="numpad-button numpad-button-num" @click="inputNum(0)">
|
||||
<f7-button class="numpad-button numpad-button-num" @pointerdown.left="inputNum(0)">
|
||||
<span class="numpad-button-text numpad-button-text-normal">{{ digits[0] }}</span>
|
||||
</f7-button>
|
||||
<f7-button class="numpad-button numpad-button-num" v-if="supportDecimalSeparator" @click="inputDecimalSeparator()">
|
||||
<f7-button class="numpad-button numpad-button-num" v-if="supportDecimalSeparator" @pointerdown.left="inputDecimalSeparator()">
|
||||
<span class="numpad-button-text numpad-button-text-normal">{{ decimalSeparator }}</span>
|
||||
</f7-button>
|
||||
<f7-button class="numpad-button numpad-button-num" v-if="!supportDecimalSeparator" @click="inputDoubleNum(0)">
|
||||
<f7-button class="numpad-button numpad-button-num" v-if="!supportDecimalSeparator" @pointerdown.left="inputDoubleNum(0)">
|
||||
<span class="numpad-button-text numpad-button-text-normal">{{ `${digits[0]}${digits[0]}` }}</span>
|
||||
</f7-button>
|
||||
<f7-button class="numpad-button numpad-button-confirm no-right-border no-bottom-border" fill @click="confirm()">
|
||||
<f7-button class="numpad-button numpad-button-confirm no-right-border no-bottom-border" fill @pointerdown.left="confirm()">
|
||||
<span :class="{ 'numpad-button-text': true, 'numpad-button-text-confirm': !currentSymbol }">{{ confirmText }}</span>
|
||||
</f7-button>
|
||||
</div>
|
||||
@@ -326,6 +330,31 @@ function clear(): void {
|
||||
currentSymbol.value = '';
|
||||
}
|
||||
|
||||
const BACKSPACE_HOLD_TO_CLEAR_MS = 500;
|
||||
let backspaceClearTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
function onBackspacePointerDown(event: PointerEvent): void {
|
||||
// 按下立刻删一位(消除 F7 taphold 判别期带来的点击延迟)
|
||||
if (event.button !== undefined && event.button !== 0) {
|
||||
return;
|
||||
}
|
||||
backspace();
|
||||
if (backspaceClearTimer !== null) {
|
||||
clearTimeout(backspaceClearTimer);
|
||||
}
|
||||
backspaceClearTimer = setTimeout(() => {
|
||||
clear();
|
||||
backspaceClearTimer = null;
|
||||
}, BACKSPACE_HOLD_TO_CLEAR_MS);
|
||||
}
|
||||
|
||||
function onBackspacePointerEnd(): void {
|
||||
if (backspaceClearTimer !== null) {
|
||||
clearTimeout(backspaceClearTimer);
|
||||
backspaceClearTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function paste(): void {
|
||||
showPastePopover.value = false;
|
||||
|
||||
@@ -442,6 +471,7 @@ function onSheetOpen(): void {
|
||||
}
|
||||
|
||||
function onSheetClosed(): void {
|
||||
onBackspacePointerEnd();
|
||||
close();
|
||||
}
|
||||
|
||||
@@ -531,7 +561,10 @@ watch(() => props.flipNegative, (newValue) => {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
user-select: none;
|
||||
touch-action: none;
|
||||
/* 上游设的 touch-action: none 在 F7 tap 处理下让 click 慢一拍(小键盘卡顿
|
||||
的实际根因,不是网络/渲染)。改 manipulation:禁双击缩放 + 消除 300ms
|
||||
老延迟,但保留 click 正常合成。详见 FORK.md #11 */
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
.numpad-button-num {
|
||||
|
||||
@@ -248,7 +248,7 @@
|
||||
v-if="pageTypeAndMode?.type === TransactionEditPageType.Transaction"
|
||||
>
|
||||
<template #header>
|
||||
<div class="transaction-edit-datetime-header" @click="showDateTimeDialog('date')">{{ tt('Transaction Time') }}</div>
|
||||
<div class="transaction-edit-datetime-header" @click="showDateTimeDialog('time')">{{ tt('Transaction Time') }}</div>
|
||||
</template>
|
||||
<template #title>
|
||||
<div class="transaction-edit-datetime-title">
|
||||
|
||||
Reference in New Issue
Block a user