Compare commits
144 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc03edd30b | ||
|
|
d18b6bb898 | ||
|
|
0f107abde6 | ||
|
|
46cf40734f | ||
|
|
676ef8e8ee | ||
|
|
5d8ea639b8 | ||
|
|
bc64e20ebd | ||
|
|
98efd557d6 | ||
|
|
b0b3120b7b | ||
|
|
c2ea3cf026 | ||
|
|
90d71be17e | ||
|
|
9435622a6d | ||
|
|
5ac9c24379 | ||
|
|
9e359b9621 | ||
|
|
cde9149bbd | ||
|
|
b38c3f9fbe | ||
|
|
92ad548061 | ||
|
|
37cea99bbd | ||
|
|
b267599039 | ||
|
|
1d204c8284 | ||
|
|
51ce0217f0 | ||
|
|
fac394be8b | ||
|
|
a12a12e786 | ||
|
|
2d1890645d | ||
|
|
38f3301664 | ||
|
|
14c47369e7 | ||
|
|
93be279cbb | ||
|
|
aca47f822b | ||
|
|
ccb4730c82 | ||
|
|
670e9deba3 | ||
|
|
93cca5f715 | ||
|
|
d787b8dc8b | ||
|
|
6b90dde0ab | ||
|
|
323b951c10 | ||
|
|
4c3648481e | ||
|
|
afcba5ec1a | ||
|
|
725d62b755 | ||
|
|
e72d6b1b9f | ||
|
|
482c7fb1c9 | ||
|
|
d84d68607b | ||
|
|
758f0d519f | ||
|
|
72b7dc5405 | ||
|
|
50a528d25b | ||
|
|
4d937b365b | ||
|
|
1124927c0e | ||
|
|
b3c42428e9 | ||
|
|
db03f211d4 | ||
|
|
5df5868549 | ||
|
|
b5e4b013c9 | ||
|
|
6af43bf957 | ||
|
|
256b529b16 | ||
|
|
c521f3cc26 | ||
|
|
dcc0d7d052 | ||
|
|
3a542ead17 | ||
|
|
dce90b64a6 | ||
|
|
2fdb2e7b51 | ||
|
|
586b506fca | ||
|
|
45ff02b998 | ||
|
|
955a1a3c54 | ||
|
|
5b5589d213 | ||
|
|
759804a99a | ||
|
|
310c1c91cf | ||
|
|
954fb1a1e8 | ||
|
|
17f5a87e31 | ||
|
|
7a9ef78376 | ||
|
|
aa5ef06ffd | ||
|
|
260e9ce4dd | ||
|
|
e45ebff0fc | ||
|
|
04c1bd0446 | ||
|
|
239196149e | ||
|
|
77da679a70 | ||
|
|
50383c2365 | ||
|
|
a099e4e413 | ||
|
|
df8af9eecd | ||
|
|
0ba4690085 | ||
|
|
98c911469a | ||
|
|
999ddc708c | ||
|
|
5298ecdd0a | ||
|
|
dc18cd75a7 | ||
|
|
1f4248bde8 | ||
|
|
ae7b4acb88 | ||
|
|
2ab31d8f5c | ||
|
|
1af990512d | ||
|
|
d96d451156 | ||
|
|
f029306ebb | ||
|
|
d3c5baa0c2 | ||
|
|
ba0802752c | ||
|
|
ff94e12ff5 | ||
|
|
0fbf1f7c2a | ||
|
|
68809a93c6 | ||
|
|
0edcadef63 | ||
|
|
9f9c30914f | ||
|
|
04cf372798 | ||
|
|
6617a26c90 | ||
|
|
d244423800 | ||
|
|
3366efaadd | ||
|
|
d74e7a7a31 | ||
|
|
2d0b409813 | ||
|
|
942068faea | ||
|
|
0f0f7684d2 | ||
|
|
531cb32f72 | ||
|
|
a368223805 | ||
|
|
6eab6c81f1 | ||
|
|
68594a2a76 | ||
|
|
5d5f22d76e | ||
|
|
65e948c34c | ||
|
|
68dead3d84 | ||
|
|
babc6a9a75 | ||
|
|
6db4ff5ac9 | ||
|
|
ce1b6f365e | ||
|
|
b6ed9668ac | ||
|
|
2a2a190f5f | ||
|
|
5d03a32362 | ||
|
|
33d9ba5c4d | ||
|
|
9020214d23 | ||
|
|
78c3f79bfd | ||
|
|
ee0fc6dbae | ||
|
|
8c51b79558 | ||
|
|
8c1899637f | ||
|
|
56df920a7d | ||
|
|
64c6f4ab8f | ||
|
|
d3902d6e31 | ||
|
|
01e355b0d6 | ||
|
|
c40b3c6ff0 | ||
|
|
4305967ba9 | ||
|
|
78f454bee5 | ||
|
|
e9a38e1474 | ||
|
|
9fb2aa6112 | ||
|
|
a0554e4355 | ||
|
|
f890165894 | ||
|
|
bc22612da7 | ||
|
|
a9ec93b18d | ||
|
|
651cbef0a0 | ||
|
|
41a144fec2 | ||
|
|
3f219ebb82 | ||
|
|
43c85afd1e | ||
|
|
48771f57a0 | ||
|
|
6e3884df58 | ||
|
|
7a6a06bb25 | ||
|
|
eac3691d0b | ||
|
|
3ece987c80 | ||
|
|
145438373b | ||
|
|
b62b0b4902 | ||
|
|
ed878dea9e |
42
.github/ISSUE_TEMPLATE/source_update.yml
vendored
@@ -1,42 +0,0 @@
|
||||
name: 原神游戏资源更新(仅供开发者使用)
|
||||
description: 版本前瞻后的例行资源更新
|
||||
title: "[Update] "
|
||||
labels:
|
||||
- 资源
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Issue Check
|
||||
options:
|
||||
- label: 个人明确了解该模板仅供开发者使用
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: 游戏版本
|
||||
description: 请填写游戏版本
|
||||
placeholder: 如 4.6
|
||||
- type: checkboxes
|
||||
id: resources
|
||||
attributes:
|
||||
label: 包括的资源
|
||||
options:
|
||||
- label: 角色&名片,有新角色时选择
|
||||
required: false
|
||||
- label: 武器,有新武器时选择
|
||||
required: false
|
||||
- label: 成就,有新成就时选择
|
||||
required: false
|
||||
- label: 材料,有新材料时选择
|
||||
required: false
|
||||
- type: textarea
|
||||
id: detail
|
||||
attributes:
|
||||
label: 详情
|
||||
description: 对上述内容进行详细说明
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: 其他信息
|
||||
description: 请填写其他信息
|
||||
placeholder: 请填写其他信息
|
||||
10
.github/workflows/build.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
- platform: macos-latest
|
||||
args: "--target x86_64-apple-darwin"
|
||||
target: "macos-intel"
|
||||
- platform: macos-15-intel
|
||||
- platform: macos-latest
|
||||
args: "--target aarch64-apple-darwin"
|
||||
target: "macos-arm"
|
||||
runs-on: ${{ matrix.settings.platform }}
|
||||
@@ -58,7 +58,8 @@ jobs:
|
||||
run: rustup target add aarch64-apple-darwin
|
||||
- name: Output toolchain
|
||||
run: rustup show
|
||||
|
||||
- name: Add Offset Conf
|
||||
run: echo '${{ secrets.YAE_CONF }}' | jq -c . > ./src-tauri/lib/conf.json
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
@@ -66,7 +67,7 @@ jobs:
|
||||
- name: setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 10.16.1
|
||||
version: 10.23.0
|
||||
- name: Install frontend dependencies
|
||||
run: pnpm install
|
||||
|
||||
@@ -81,7 +82,8 @@ jobs:
|
||||
releaseBody: |
|
||||
> [!TIP]
|
||||
> Windows 平台用户建议通过微软应用商店下载,macOS 平台仅在此发布,Linux 平台暂不支持。
|
||||
> 如有使用问题可加入 [反馈QQ群](https://h5.qun.qq.com/s/3cgX0hJ4GA)
|
||||
> 如有使用问题可加入 [反馈QQ群](https://qm.qq.com/q/hUxIfSWluo)
|
||||
> MacOS 用户参考 [安装指南](https://github.com/BTMuli/TeyvatGuide/blob/master/docs/macos-gatekeeper/README.md)
|
||||
|
||||
<a href="https://apps.microsoft.com/store/detail/9NLBNNNBNSJN?launch=true&cid=BTMuli&mode=mini">
|
||||
<img src="https://get.microsoft.com/images/zh-cn%20dark.svg" alt="download"/>
|
||||
|
||||
132
.github/workflows/debug.yml
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
name: Build Debug for Mac
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
build-debug:
|
||||
description: "Build debug version"
|
||||
required: true
|
||||
default: true
|
||||
type: boolean
|
||||
build-release:
|
||||
description: "Build release version"
|
||||
required: true
|
||||
default: false
|
||||
type: boolean
|
||||
jobs:
|
||||
build-debug-mac:
|
||||
permissions:
|
||||
contents: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
settings:
|
||||
- platform: macos-latest
|
||||
args: "--target x86_64-apple-darwin"
|
||||
target: "macos-intel"
|
||||
artifact: "debug-build-macos-intel"
|
||||
- platform: macos-latest
|
||||
args: "--target aarch64-apple-darwin"
|
||||
target: "macos-arm"
|
||||
artifact: "debug-build-macos-arm"
|
||||
runs-on: ${{ matrix.settings.platform }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup SSH
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.SSH_PRIVATE_KEY }}" | tr -d '\r' > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
- name: Add Github RSA
|
||||
run: |
|
||||
echo "${{ secrets.KNOWN_GITHUB_RSA }}" >> ~/.ssh/known_hosts
|
||||
chmod 644 ~/.ssh/known_hosts
|
||||
- name: Test SSH connection
|
||||
run: ssh -T git@github.com || true
|
||||
|
||||
- name: Rust setup
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Rust cache
|
||||
uses: swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: "./src-tauri -> target"
|
||||
|
||||
- name: Add Rust targets(macOS Intel)
|
||||
if: matrix.settings.target == 'macos-intel'
|
||||
run: rustup target add x86_64-apple-darwin
|
||||
- name: Add Rust targets(macOS ARM)
|
||||
if: matrix.settings.target == 'macos-arm'
|
||||
run: rustup target add aarch64-apple-darwin
|
||||
- name: Output toolchain
|
||||
run: rustup show
|
||||
- name: Add Offset Conf
|
||||
run: echo '${{ secrets.YAE_CONF }}' | jq -c . > ./src-tauri/lib/conf.json
|
||||
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 24.8.0
|
||||
- name: setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 10.23.0
|
||||
- name: Install frontend dependencies
|
||||
run: pnpm install
|
||||
# 获取commit hash,后续用这个做文件命名
|
||||
- name: Get Commit Hash
|
||||
id: get_commit_hash
|
||||
run: echo "COMMIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
|
||||
# Build Debug
|
||||
- name: Build debug app
|
||||
if: github.event.inputs.build-debug == 'true'
|
||||
uses: tauri-apps/tauri-action@dev
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
args: ${{ matrix.settings.args }} --debug
|
||||
- name: Move Debug Intel
|
||||
if: matrix.settings.target == 'macos-intel' && github.event.inputs.build-debug == 'true'
|
||||
run: mv src-tauri/target/x86_64-apple-darwin/debug/bundle/dmg/*.dmg TeyvatGuide_${{ env.COMMIT_HASH }}_intel-debug.dmg
|
||||
- name: Move Debug ARM
|
||||
if: matrix.settings.target == 'macos-arm' && github.event.inputs.build-debug == 'true'
|
||||
run: mv src-tauri/target/aarch64-apple-darwin/debug/bundle/dmg/*.dmg TeyvatGuide_${{ env.COMMIT_HASH }}_arm-debug.dmg
|
||||
- name: Upload Debug Intel
|
||||
if: matrix.settings.target == 'macos-intel' && github.event.inputs.build-debug == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: debug-macos-intel
|
||||
path: TeyvatGuide_${{ env.COMMIT_HASH }}_intel-debug.dmg
|
||||
- name: Upload Debug ARM
|
||||
if: matrix.settings.target == 'macos-arm' && github.event.inputs.build-debug == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: debug-macos-arm
|
||||
path: TeyvatGuide_${{ env.COMMIT_HASH }}_arm-debug.dmg
|
||||
# Build Release
|
||||
- name: Build app
|
||||
if: github.event.inputs.build-release == 'true'
|
||||
uses: tauri-apps/tauri-action@dev
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
args: ${{ matrix.settings.args }}
|
||||
- name: Move Release Intel
|
||||
if: matrix.settings.target == 'macos-intel' && github.event.inputs.build-release == 'true'
|
||||
run: mv src-tauri/target/x86_64-apple-darwin/release/bundle/dmg/*.dmg TeyvatGuide_${{ env.COMMIT_HASH }}_intel-release.dmg
|
||||
- name: Move Release ARM
|
||||
if: matrix.settings.target == 'macos-arm' && github.event.inputs.build-release == 'true'
|
||||
run: mv src-tauri/target/aarch64-apple-darwin/release/bundle/dmg/*.dmg TeyvatGuide_${{ env.COMMIT_HASH }}_arm-release.dmg
|
||||
- name: Upload Release Intel
|
||||
if: matrix.settings.target == 'macos-intel' && github.event.inputs.build-release
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-macos-intel
|
||||
path: TeyvatGuide_${{ env.COMMIT_HASH }}_intel-release.dmg
|
||||
- name: Upload Release ARM
|
||||
if: matrix.settings.target == 'macos-arm' && github.event.inputs.build-release == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-macos-arm
|
||||
path: TeyvatGuide_${{ env.COMMIT_HASH }}_arm-release.dmg
|
||||
7
.github/workflows/qodana_code_quality.yml
vendored
@@ -1,8 +1,9 @@
|
||||
name: Qodana
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
# push:
|
||||
# branches:
|
||||
# - master
|
||||
|
||||
jobs:
|
||||
qodana:
|
||||
|
||||
77
CHANGELOG.md
@@ -2,12 +2,85 @@
|
||||
Author: 目棃
|
||||
Description: CHANGELOG
|
||||
Date: 2025-09-09
|
||||
Update: 2025-09-27
|
||||
Update: 2025-12-03
|
||||
---
|
||||
|
||||
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2025-09-09 14:30:56`
|
||||
>
|
||||
> 更新于 `2025-09-27 10:18:07`
|
||||
> 更新于 `2025-12-03 20:02:17`
|
||||
|
||||
## [0.8.8](https://github.com/BTMuli/TeyvatGuide/releases/v0.8.8) (2025-12-03)
|
||||
|
||||
- 🐛 修复成就数据读取异常
|
||||
- 🐛 重构管理员权限重启逻辑
|
||||
|
||||
## [0.8.7](https://github.com/BTMuli/TeyvatGuide/releases/v0.8.7) (2025-12-03)
|
||||
|
||||
- 🍱 更新 6.2 版本资源
|
||||
- ✨ 帖子搜索支持“最新”“最热”排序
|
||||
- ✨ 登录支持 Gt4 验证 [`#162`](https://github.com/BTMuli/TeyvatGuide/issues/162)
|
||||
- ✨ 帖子视图支持窄视图模式,**未完全适配所有组件,可能存在显示异常**
|
||||
- ✨ 支持通过内置 Yae 自动获取成就数据 [`#142`](https://github.com/BTMuli/TeyvatGuide/issues/142)
|
||||
- 🐛 修复无法手动关闭极验验证弹窗
|
||||
- 🐛 修复数据刷新后渲染异常 [`#163`](https://github.com/BTMuli/TeyvatGuide/issues/163)
|
||||
- 🐛 重构祈愿图表,修复祈愿日历没有下拉条 [`#165`](https://github.com/BTMuli/TeyvatGuide/issues/165)
|
||||
- 🐛 修复 MacOS 下极验验证浮窗加载异常 [`#164`](https://github.com/BTMuli/TeyvatGuide/issues/164)
|
||||
- 🐛 重构回复浮窗处理,调整 UI ,修复滚动异常 [`#168`](https://github.com/BTMuli/TeyvatGuide/issues/168)
|
||||
- 🐛 修复自定义表情格式解析异常,增加文本清晰度
|
||||
- 🐛 调整回复按钮展示判断,修复特定条件下的数据对应异常
|
||||
- 🐛 修复角色 Wiki 左侧列表顺序概率异常
|
||||
- ✏️ 修正通过 Yae 导入成就的文本错误
|
||||
- ✏️ 修正清除缓存后的提示文本
|
||||
- 🚸 执行脚本时不允许切换账号
|
||||
- 🚸 调整外部导入祈愿记录时进度显示逻辑,导入后刷新页面
|
||||
- 🚸 增加部分 UI 在浅色模式下的可见度
|
||||
- 🚸 账号相关操作(添加,切换)移至侧栏 [`#170`](https://github.com/BTMuli/TeyvatGuide/issues/170)
|
||||
- 🚸 侧栏添加启动入口,满足条件时显示
|
||||
- 🚸 完善角色 Wiki 侧边栏奇偶点击处理
|
||||
- 👽️ 完善前瞻识别规则,增加空列表处理
|
||||
- 📝 更新Q群链接
|
||||
|
||||
## [0.8.6](https://github.com/BTMuli/TeyvatGuide/releases/v0.8.6) (2025-11-19)
|
||||
|
||||
> 关于胡桃数据库导入功能的说明请参考 [导入胡桃数据库](https://app.btmuli.ink/docs/TeyvatGuide/import-hutao-db.html)
|
||||
|
||||
- 👽️ 移除剧诗概览,支持导入胡桃剧诗数据
|
||||
- 👽️ 移除深渊上传,支持导入胡桃深渊数据
|
||||
- 🔥 移除胡桃深渊统计页面
|
||||
- 🚸 调整导入祈愿记录浮窗ui,显示导入进度
|
||||
- 🐛 修复图片渲染异常
|
||||
- 🥅 处理清除缓存异常,清除缓存后重启
|
||||
- 🚸 帖子详情添加AIGC相关注释
|
||||
- 🚸 添加跳转视频链接
|
||||
- 📝 更新相关文档
|
||||
|
||||
## [0.8.5](https://github.com/BTMuli/TeyvatGuide/releases/v0.8.5) (2025-11-10)
|
||||
|
||||
- 🍱 更新下半数据
|
||||
|
||||
## [0.8.4](https://github.com/BTMuli/TeyvatGuide/releases/v0.8.4) (2025-10-27)
|
||||
|
||||
- 👽️ 公告添加千星奇域分类
|
||||
- 🚸 兑换码浮窗显示游戏名称
|
||||
- ✨ 嵌入官方公告页面(已登录)
|
||||
- ✨ 嵌入官方祈愿详情(已登录)
|
||||
- ✨ 完善投稿活动类型声明,渲染投稿活动&交互
|
||||
- 🐛 修复部分帖子解析异常
|
||||
- ✨ 重构帖子解析逻辑,增加新类型解析
|
||||
- 💄 调整名片样式
|
||||
- ✨ 添加getRegionRoleInfo事件处理
|
||||
- 🐛 公告解析剔除多余换行
|
||||
- ✨ 千星奇域祈愿页面草创
|
||||
|
||||
## [0.8.3](https://github.com/BTMuli/TeyvatGuide/releases/v0.8.3) (2025-10-22)
|
||||
|
||||
- 🍱 更新6.1版本数据
|
||||
- 👽️ 适配月谕圣牌模式
|
||||
- 🐛 重构帖子数据解析,修复HEIC格式图片渲染异常
|
||||
- 🐛 修复切换角色导致ck对应异常
|
||||
- 🚸 优化图片调整浮窗样式
|
||||
- ♻️ 重构gt返回逻辑
|
||||
- 💄 调整布局
|
||||
|
||||
## [0.8.2](https://github.com/BTMuli/TeyvatGuide/releases/v0.8.2) (2025-09-27)
|
||||
|
||||
|
||||
26
README.md
@@ -2,16 +2,20 @@
|
||||
Author: 目棃
|
||||
Description: 说明文档
|
||||
Date: 2023-03-05
|
||||
Update: 2025-09-09
|
||||
Update: 2025-12-03
|
||||
---
|
||||
|
||||
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2023-03-05 14:41:55`
|
||||
>
|
||||
> 更新于 `2025-09-09 14:37:02`
|
||||
> 更新于 `2025-12-03 10:22:51`
|
||||
|
||||
[](https://deepwiki.com/BTMuli/TeyvatGuide)  
|
||||
[](https://deepwiki.com/BTMuli/TeyvatGuide)
|
||||
|
||||
   
|
||||
[](https://github.com/BTMuli/TeyvatGuide/commits) [](https://github.com/BTMuli/TeyvatGuide/commits)
|
||||
|
||||
[](./docs/standards/UIAF.md) [](./docs/standards/UIGF3.md) [](./docs/standards/UIGF.md)
|
||||
|
||||
[](./LICENSE)
|
||||
|
||||
<div style="width: 100%; text-align: center; margin: 0 auto;">
|
||||
<img alt="icon" src="https://s2.loli.net/2023/10/19/Y5DpBQRy3usLHEb.png" />
|
||||
@@ -49,7 +53,7 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
|
||||
- [x] 米游社官方帖获取(支持通过 ID 获取)
|
||||
- [x] 米游社各分区帖子获取(支持通过 ID 获取)
|
||||
- [x] 米游社话题帖子获取(通过话题点击跳转)
|
||||
- [x] 成就管理(UIAF v1.1),支持 [`Yae`](https://github.com/HolographicHat/Yae) 导入
|
||||
- [x] 成就管理(UIAF v1.1),支持 [`Yae`](https://github.com/HolographicHat/Yae) 导入 & 自动导入(内置Yae)
|
||||
- [x] 祈愿管理(UIGF v3.0,UIGF v4.1)
|
||||
- [x] 留影叙佳期画片查看
|
||||
- [x] 帖子收藏
|
||||
@@ -65,13 +69,13 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
|
||||
- [x] 真境剧诗
|
||||
- [x] 幽境危战
|
||||
- [x] 祈愿数据获取(近一年)
|
||||
- [x] 千星奇域祈愿数据获取(近一年)
|
||||
- [x] 用户收藏帖子获取
|
||||
- [x] 用户关注帖子获取
|
||||
- [x] 一键完成米游币每日任务
|
||||
- [x] 一键完成游戏签到
|
||||
|
||||
- Wiki 功能:
|
||||
- [x] 深渊数据库(Hutao API)
|
||||
- [x] 角色图鉴
|
||||
- [x] 武器图鉴
|
||||
- [x] 名片图鉴
|
||||
@@ -90,7 +94,7 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
|
||||
|
||||
## UI 参考 / UI Reference
|
||||
|
||||
- [Snap.Hutao](https://github.com/DGP-Studio/Snap.Hutao)
|
||||
- ~~[Snap.Hutao](https://github.com/DGP-Studio/Snap.Hutao)~~
|
||||
- [Starward](https://github.com/Scighost/Starward)
|
||||
- [米游社](https://www.miyoushe.com/ys/)
|
||||
- [原神](https://yuanshen.com/)
|
||||
@@ -102,6 +106,7 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
|
||||
- UIAF:[UIAF v1.1](docs/standards/UIAF.md)
|
||||
- UIGF:[UIGF v3.0](docs/standards/UIGF3.md),[UIGF v4.0](docs/standards/UIGF.md)
|
||||
- [macOS 平台门禁属性导致应用无法打开应用的修复指引](docs/macos-gatekeeper/README.md)
|
||||
- [如何导入胡桃数据库](https://app.btmuli.ink/docs/TeyvatGuide/import-hutao-db.html)
|
||||
|
||||
## 特定项目 / Special Project
|
||||
|
||||
@@ -136,7 +141,7 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
|
||||
本项目在开发过程中参考了诸多相关开源项目,特此鸣谢。
|
||||
|
||||
- [UIGF Organization](https://github.com/UIGF-org)
|
||||
- [Snap.Hutao](https://github.com/DGP-Studio/Snap.Hutao)
|
||||
- ~~[Snap.Hutao](https://github.com/DGP-Studio/Snap.Hutao)~~
|
||||
- [StarWard](https://github.com/Scighost/Starward)
|
||||
- [xunkong](https://github.com/xunkong/xunkong)
|
||||
- [gs-helper](https://github.com/vikiboss/gs-helper)
|
||||
@@ -145,9 +150,6 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
|
||||
- [amos-data](https://github.com/yuehaiteam/amos-data)
|
||||
- [MihoyoBBSTools](https://github.com/Womsxd/MihoyoBBSTools)
|
||||
- [nonebot-plugin-mystool](https://github.com/Ljzd-PRO/nonebot-plugin-mystool)
|
||||
|
||||
感谢 JetBrains 提供的开源许可证。
|
||||
|
||||

|
||||
- [Yae](https://github.com/HolographicHat/Yae)
|
||||
|
||||
[](https://star-history.com/#BTMuli/TeyvatGuide&Timeline)
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
Author: 目棃
|
||||
Description: 项目资源说明
|
||||
Date: 2023-03-10
|
||||
Update: 2025-02-28
|
||||
Update: 2025-11-19
|
||||
---
|
||||
|
||||
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2023-03-10 22:05:44`
|
||||
>
|
||||
> 更新于 `2025-02-28 09:40:33`
|
||||
> 更新于 `2025-11-19 12:31:22`
|
||||
|
||||
## 说明
|
||||
|
||||
@@ -40,8 +40,8 @@ Update: 2025-02-28
|
||||
相关仓库:
|
||||
|
||||
- [TGAssistant](https://github.com/BTMuli/TGAssistant):项目下游仓库,用于处理项目数据。
|
||||
- [Snap.Metadata](https://github.com/DGP-Studio/Snap.Metadata):胡桃元数据仓库,项目大部分数据来源于此。
|
||||
- [Snap.Static](https://github.com/DGP-Studio/Snap.Static):胡桃静态资源仓库,项目部分图像资源来源于此。
|
||||
- ~~[Snap.Metadata](https://github.com/DGP-Studio/Snap.Metadata)~~:胡桃元数据仓库,项目大部分数据来源于此。
|
||||
- ~~[Snap.Static](https://github.com/DGP-Studio/Snap.Static)~~:胡桃静态资源仓库,项目部分图像资源来源于此。
|
||||
- [amos-data](https://github.com/yuehaiteam/amos-data):成就数据仓库,成就数据的详细信息来源于此。
|
||||
|
||||
## 字体
|
||||
|
||||
98
package.json
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "teyvatguide",
|
||||
"version": "0.8.2",
|
||||
"version": "0.8.8",
|
||||
"description": "Game Tool for GenshinImpact player",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.16.1",
|
||||
"packageManager": "pnpm@10.24.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tauri build",
|
||||
@@ -71,68 +71,66 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdi/font": "7.4.47",
|
||||
"@tauri-apps/api": "^2.8.0",
|
||||
"@tauri-apps/plugin-deep-link": "^2.4.3",
|
||||
"@tauri-apps/plugin-dialog": "^2.4.0",
|
||||
"@tauri-apps/plugin-fs": "^2.4.2",
|
||||
"@tauri-apps/plugin-http": "^2.5.2",
|
||||
"@tauri-apps/plugin-log": "^2.7.0",
|
||||
"@tauri-apps/plugin-opener": "^2.5.0",
|
||||
"@tauri-apps/plugin-os": "^2.3.1",
|
||||
"@tauri-apps/plugin-process": "^2.3.0",
|
||||
"@tauri-apps/plugin-shell": "^2.3.1",
|
||||
"@tauri-apps/plugin-sql": "^2.3.0",
|
||||
"@tauri-apps/api": "^2.9.1",
|
||||
"@tauri-apps/plugin-deep-link": "^2.4.5",
|
||||
"@tauri-apps/plugin-dialog": "^2.4.2",
|
||||
"@tauri-apps/plugin-fs": "^2.4.4",
|
||||
"@tauri-apps/plugin-http": "github:tauri-apps/tauri-plugin-http",
|
||||
"@tauri-apps/plugin-log": "^2.7.1",
|
||||
"@tauri-apps/plugin-opener": "^2.5.2",
|
||||
"@tauri-apps/plugin-os": "^2.3.2",
|
||||
"@tauri-apps/plugin-process": "^2.3.1",
|
||||
"@tauri-apps/plugin-shell": "^2.3.3",
|
||||
"@tauri-apps/plugin-sql": "^2.3.1",
|
||||
"ajv": "^8.17.1",
|
||||
"artplayer": "^5.3.0",
|
||||
"clipboard": "^2.0.11",
|
||||
"color-convert": "^3.1.2",
|
||||
"color-convert": "^3.1.3",
|
||||
"echarts": "^6.0.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"js-md5": "^0.8.3",
|
||||
"jsencrypt": "^3.5.4",
|
||||
"pinia": "^3.0.3",
|
||||
"pinia-plugin-persistedstate": "^4.5.0",
|
||||
"pinia": "^3.0.4",
|
||||
"pinia-plugin-persistedstate": "^4.7.1",
|
||||
"qrcode.vue": "^3.6.0",
|
||||
"sass-embedded": "^1.92.1",
|
||||
"swiper": "^12.0.1",
|
||||
"sass-embedded": "^1.93.3",
|
||||
"swiper": "^12.0.3",
|
||||
"uuid": "^13.0.0",
|
||||
"vue": "^3.5.21",
|
||||
"vue-echarts": "^7.0.3",
|
||||
"vue-json-pretty": "^2.5.0",
|
||||
"vue-router": "^4.5.1",
|
||||
"vuetify": "^3.10.0",
|
||||
"vue": "^3.5.25",
|
||||
"vue-echarts": "^8.0.1",
|
||||
"vue-json-pretty": "^2.6.0",
|
||||
"vue-router": "^4.6.3",
|
||||
"vuetify": "^3.11.0",
|
||||
"wcag-color": "^1.1.1",
|
||||
"xml-js": "^1.6.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@btmuli/stylelint-plugin-color": "^0.1.0",
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@eslint/js": "^9.35.0",
|
||||
"@tauri-apps/cli": "2.8.4",
|
||||
"@eslint/eslintrc": "^3.3.3",
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@tauri-apps/cli": "2.9.5",
|
||||
"@types/color-convert": "^2.0.4",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/js-md5": "^0.8.0",
|
||||
"@types/node": "^24.4.0",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@typescript-eslint/parser": "^8.43.0",
|
||||
"@typescript/native-preview": "7.0.0-dev.20250914.1",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"@types/node": "^24.10.1",
|
||||
"@typescript-eslint/parser": "^8.48.1",
|
||||
"@typescript/native-preview": "7.0.0-dev.20251202.1",
|
||||
"@vitejs/plugin-vue": "^6.0.2",
|
||||
"app-root-path": "^3.1.0",
|
||||
"concurrently": "^9.2.1",
|
||||
"eslint": "^9.35.0",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-jsonc": "^2.20.1",
|
||||
"eslint-plugin-jsonc": "^2.21.0",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"eslint-plugin-vue": "^10.4.0",
|
||||
"eslint-plugin-yml": "^1.18.0",
|
||||
"fs-extra": "^11.3.1",
|
||||
"globals": "^16.4.0",
|
||||
"eslint-plugin-vue": "^10.6.2",
|
||||
"eslint-plugin-yml": "^1.19.0",
|
||||
"fs-extra": "^11.3.2",
|
||||
"globals": "^16.5.0",
|
||||
"husky": "^9.1.7",
|
||||
"jsonc-eslint-parser": "^2.4.0",
|
||||
"lint-staged": "^16.1.6",
|
||||
"oxlint": "^1.15.0",
|
||||
"prettier": "3.6.2",
|
||||
"stylelint": "^16.24.0",
|
||||
"jsonc-eslint-parser": "^2.4.1",
|
||||
"lint-staged": "^16.2.7",
|
||||
"oxlint": "^1.31.0",
|
||||
"prettier": "3.7.3",
|
||||
"stylelint": "^16.26.1",
|
||||
"stylelint-config-idiomatic-order": "^10.0.0",
|
||||
"stylelint-config-standard-scss": "^16.0.0",
|
||||
"stylelint-config-standard-vue": "^1.0.0",
|
||||
@@ -141,14 +139,14 @@
|
||||
"stylelint-order": "^7.0.0",
|
||||
"stylelint-prettier": "^5.0.3",
|
||||
"stylelint-scss": "^6.12.1",
|
||||
"tsx": "^4.20.5",
|
||||
"typescript": "^5.9.2",
|
||||
"typescript-eslint": "^8.43.0",
|
||||
"vite": "^7.1.5",
|
||||
"vite-plugin-vue-devtools": "^8.0.2",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.48.1",
|
||||
"vite": "npm:rolldown-vite@^7.2.9",
|
||||
"vite-plugin-vue-devtools": "^8.0.5",
|
||||
"vite-plugin-vuetify": "^2.1.2",
|
||||
"vue-eslint-parser": "^10.2.0",
|
||||
"vue-tsc": "^3.0.7",
|
||||
"yaml-eslint-parser": "^1.3.0"
|
||||
"vue-tsc": "^3.1.5",
|
||||
"yaml-eslint-parser": "^1.3.1"
|
||||
}
|
||||
}
|
||||
|
||||
3701
pnpm-lock.yaml
generated
BIN
public/WIKI/character/10000117.webp
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
public/WIKI/character/10000118.webp
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
public/WIKI/character/10000122.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
public/WIKI/character/10000123.webp
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
public/WIKI/character/10000124.webp
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
public/WIKI/nameCard/bg/原神·印象.webp
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
public/WIKI/nameCard/bg/奈芙尔·秘闻.webp
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
public/WIKI/nameCard/bg/庆典·梦读.webp
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
public/WIKI/nameCard/bg/杜林·曜心.webp
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
public/WIKI/nameCard/bg/纪行·故墟.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/WIKI/nameCard/bg/纪行·炽雪.webp
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
public/WIKI/nameCard/bg/雅珂达·帮手.webp
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
public/WIKI/nameCard/icon/奈芙尔·秘闻.webp
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
public/WIKI/nameCard/icon/庆典·梦读.webp
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
public/WIKI/nameCard/icon/杜林·曜心.webp
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
public/WIKI/nameCard/icon/纪行·故墟.webp
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
public/WIKI/nameCard/icon/纪行·炽雪.webp
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
public/WIKI/nameCard/icon/雅珂达·帮手.webp
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
public/WIKI/nameCard/profile/奈芙尔·秘闻.webp
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
public/WIKI/nameCard/profile/庆典·梦读.webp
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
public/WIKI/nameCard/profile/杜林·曜心.webp
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
public/WIKI/nameCard/profile/纪行·故墟.webp
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
public/WIKI/nameCard/profile/纪行·炽雪.webp
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
public/WIKI/nameCard/profile/雅珂达·帮手.webp
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
public/WIKI/weapon/11518.webp
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
public/WIKI/weapon/13434.webp
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
public/WIKI/weapon/14434.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/WIKI/weapon/14521.webp
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
public/WIKI/weapon/15434.webp
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
public/WIKI/weapon/15515.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
BIN
public/icon/combat/tarot.webp
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
public/icon/combat/tarot_0.webp
Normal file
|
After Width: | Height: | Size: 836 B |
BIN
public/icon/combat/tarot_1.webp
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
public/icon/constellations/UI_Talent_S_Durin_03.webp
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
public/icon/constellations/UI_Talent_S_Durin_04.webp
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
public/icon/constellations/UI_Talent_S_Durin_05.webp
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
public/icon/constellations/UI_Talent_S_Durin_06.webp
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
public/icon/constellations/UI_Talent_S_Jahoda_01.webp
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
public/icon/constellations/UI_Talent_S_Jahoda_02.webp
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
public/icon/constellations/UI_Talent_S_Jahoda_03.webp
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
public/icon/constellations/UI_Talent_S_Jahoda_04.webp
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
public/icon/constellations/UI_Talent_S_Nefer_01.webp
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
public/icon/constellations/UI_Talent_S_Nefer_02.webp
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
public/icon/constellations/UI_Talent_S_Nefer_03.webp
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
public/icon/constellations/UI_Talent_S_Nefer_04.webp
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
public/icon/constellations/UI_Talent_U_Durin_01.webp
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
public/icon/constellations/UI_Talent_U_Durin_02.webp
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
public/icon/constellations/UI_Talent_U_Jahoda_01.webp
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
public/icon/constellations/UI_Talent_U_Jahoda_02.webp
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
public/icon/constellations/UI_Talent_U_Nefer_01.webp
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
public/icon/constellations/UI_Talent_U_Nefer_02.webp
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
public/icon/material/113079.webp
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
public/icon/material/113080.webp
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
public/icon/material/220126.webp
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
public/icon/nation/千星奇域.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/icon/talents/Skill_E_Durin_01.webp
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
public/icon/talents/Skill_E_Jahoda_01.webp
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
public/icon/talents/Skill_E_Nefer_01.webp
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
public/icon/talents/Skill_S_Durin_01.webp
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
public/icon/talents/Skill_S_Jahoda_01.webp
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
public/icon/talents/Skill_S_Nefer_01.webp
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
public/icon/talents/UI_Talent_S_Durin_01.webp
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
public/icon/talents/UI_Talent_S_Durin_02.webp
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
public/icon/talents/UI_Talent_S_Durin_07.webp
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
public/icon/talents/UI_Talent_S_Jahoda_05.webp
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
public/icon/talents/UI_Talent_S_Jahoda_06.webp
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
public/icon/talents/UI_Talent_S_Jahoda_07.webp
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
public/icon/talents/UI_Talent_S_Jahoda_08.webp
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
public/icon/talents/UI_Talent_S_Nefer_05.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
public/icon/talents/UI_Talent_S_Nefer_06.webp
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
public/icon/talents/UI_Talent_S_Nefer_07.webp
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
public/icon/talents/UI_Talent_S_Nefer_08.webp
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
3
src-tauri/.gitignore
vendored
@@ -5,3 +5,6 @@
|
||||
# Generated by Tauri
|
||||
# will have schema files for capabilities auto-completion
|
||||
/gen/schemas
|
||||
|
||||
# secret
|
||||
/lib/*.json
|
||||
|
||||
1664
src-tauri/Cargo.lock
generated
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "TeyvatGuide"
|
||||
version = "0.8.2"
|
||||
version = "0.8.8"
|
||||
description = "Game Tool for Genshin Impact player"
|
||||
authors = ["BTMuli <bt-muli@outlook.com>"]
|
||||
license = "MIT"
|
||||
@@ -17,18 +17,33 @@ name = "teyvat_guide_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.4.1", features = [] }
|
||||
tauri-build = { version = "2.5.3", features = [] }
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4.42"
|
||||
log = "0.4.28"
|
||||
serde = { version = "1.0.221", features = ["derive"] }
|
||||
serde_json = "1.0.144"
|
||||
tauri = { version = "2.8.5", features = [] }
|
||||
tauri-utils = "2.7.0"
|
||||
log = "0.4.29"
|
||||
prost = "0.14.1"
|
||||
prost-types = "0.14.1"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.145"
|
||||
tauri = { version = "2.9.4", features = [] }
|
||||
tauri-utils = "2.8.1"
|
||||
url = "2.5.7"
|
||||
walkdir = "2.5.0"
|
||||
|
||||
[target.'cfg(windows)'.dependencies.windows-sys]
|
||||
version = "0.61.2"
|
||||
features = [
|
||||
"Win32_System_Diagnostics",
|
||||
"Win32_System_Diagnostics_Debug",
|
||||
"Win32_System_Diagnostics_ToolHelp",
|
||||
"Win32_System_LibraryLoader",
|
||||
"Win32_System_Memory",
|
||||
"Win32_System_Pipes",
|
||||
"Win32_System_Threading",
|
||||
"Win32_System_WindowsProgramming",
|
||||
]
|
||||
|
||||
# deep link 插件
|
||||
[dependencies.tauri-plugin-deep-link]
|
||||
git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
|
||||
|
||||
BIN
src-tauri/lib/YaeAchievementLib.dll
Normal file
@@ -8,7 +8,7 @@ mod utils;
|
||||
use tauri::{AppHandle, Manager, WebviewWindowBuilder};
|
||||
use tauri_utils::config::WebviewUrl;
|
||||
|
||||
static BBS_VERSION: &'static str = "2.93.0";
|
||||
static BBS_VERSION: &'static str = "2.95.1";
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn create_mhy_client(handle: AppHandle, func: String, url: String) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
//! @file src/commands.rs
|
||||
//! @desc 命令模块,负责处理命令
|
||||
//! @since Beta v0.7.4
|
||||
//! 命令模块,负责处理命令
|
||||
//! @since Beta v0.8.8
|
||||
|
||||
use tauri::{AppHandle, Emitter, Manager, WebviewWindowBuilder};
|
||||
use tauri_utils::config::{WebviewUrl, WindowConfig};
|
||||
@@ -70,3 +69,61 @@ pub async fn get_dir_size(path: String) -> u64 {
|
||||
}
|
||||
size
|
||||
}
|
||||
|
||||
// 判断是否是管理员权限
|
||||
#[tauri::command]
|
||||
pub fn is_in_admin() -> bool {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use windows_sys::Win32::Foundation::{CloseHandle, HANDLE};
|
||||
use windows_sys::Win32::Security::{
|
||||
AllocateAndInitializeSid, CheckTokenMembership, FreeSid, SID_IDENTIFIER_AUTHORITY,
|
||||
TOKEN_QUERY,
|
||||
};
|
||||
use windows_sys::Win32::System::SystemServices::{
|
||||
DOMAIN_ALIAS_RID_ADMINS, SECURITY_BUILTIN_DOMAIN_RID,
|
||||
};
|
||||
use windows_sys::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
|
||||
|
||||
unsafe {
|
||||
let mut token_handle: HANDLE = std::ptr::null_mut();
|
||||
if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token_handle) == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let nt_authority = SID_IDENTIFIER_AUTHORITY { Value: [0, 0, 0, 0, 0, 5] };
|
||||
let mut admin_group = std::ptr::null_mut();
|
||||
|
||||
let success = AllocateAndInitializeSid(
|
||||
&nt_authority,
|
||||
2,
|
||||
SECURITY_BUILTIN_DOMAIN_RID.try_into().unwrap(),
|
||||
DOMAIN_ALIAS_RID_ADMINS.try_into().unwrap(),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
&mut admin_group,
|
||||
);
|
||||
|
||||
if success == 0 {
|
||||
CloseHandle(token_handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut is_admin = 0i32;
|
||||
let result = CheckTokenMembership(std::ptr::null_mut(), admin_group, &mut is_admin);
|
||||
|
||||
FreeSid(admin_group);
|
||||
CloseHandle(token_handle);
|
||||
|
||||
result != 0 && is_admin != 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
//! @file src/lib.rs
|
||||
//! @desc 主模块,用于启动应用
|
||||
//! @since Beta v0.7.2
|
||||
//! 主模块,用于启动应用
|
||||
//! @since Beta v0.8.8
|
||||
|
||||
mod client;
|
||||
mod commands;
|
||||
mod plugins;
|
||||
mod utils;
|
||||
#[cfg(target_os = "windows")]
|
||||
mod watchdog;
|
||||
#[cfg(target_os = "windows")]
|
||||
mod yae;
|
||||
|
||||
use crate::client::create_mhy_client;
|
||||
use crate::commands::{create_window, execute_js, get_dir_size, init_app};
|
||||
use crate::commands::{create_window, execute_js, get_dir_size, init_app, is_in_admin};
|
||||
use crate::plugins::{build_log_plugin, build_si_plugin};
|
||||
use tauri::{generate_context, generate_handler, Builder, Manager, Window, WindowEvent};
|
||||
use tauri::{generate_context, generate_handler, Manager, Window, WindowEvent};
|
||||
|
||||
// 窗口事件处理
|
||||
fn window_event_handler(app: &Window, event: &WindowEvent) {
|
||||
@@ -35,9 +38,35 @@ fn window_event_handler(app: &Window, event: &WindowEvent) {
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
Builder::default()
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let is_watchdog = args.iter().any(|a| a == "--watchdog");
|
||||
// 看门狗模式:不初始化 Tauri,不加载单例,纯等待 + 提权启动
|
||||
if is_watchdog {
|
||||
// 解析父进程 PID
|
||||
let mut ppid: u32 = 0;
|
||||
for a in &args {
|
||||
if let Some(rest) = a.strip_prefix("--ppid=") {
|
||||
if let Ok(v) = rest.parse::<u32>() {
|
||||
ppid = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 等父进程退出后再 runas 启动管理员实例,传入 --elevated 标志
|
||||
let _ = watchdog::run_watchdog(ppid, "--elevated");
|
||||
// 看门狗退出
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 正常应用实例:加载单例插件,防止多实例
|
||||
let mut builder = tauri::Builder::default();
|
||||
|
||||
// 只有在正常/管理员实例下才加载单例插件;看门狗不加载
|
||||
builder = builder.plugin(build_si_plugin());
|
||||
builder
|
||||
.on_window_event(move |app, event| window_event_handler(app, event))
|
||||
.plugin(build_si_plugin())
|
||||
.plugin(tauri_plugin_deep_link::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
@@ -61,7 +90,12 @@ pub fn run() {
|
||||
create_window,
|
||||
execute_js,
|
||||
get_dir_size,
|
||||
create_mhy_client
|
||||
create_mhy_client,
|
||||
is_in_admin,
|
||||
#[cfg(target_os = "windows")]
|
||||
yae::call_yae_dll,
|
||||
#[cfg(target_os = "windows")]
|
||||
watchdog::run_with_admin
|
||||
])
|
||||
.run(generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
//! @file src/plugins.rs
|
||||
//! @desc 插件模块,用于注册插件
|
||||
//! @since Beta v0.6.2
|
||||
//! 插件模块,用于注册插件
|
||||
//! @since Beta v0.7.8
|
||||
|
||||
use crate::utils::get_current_date;
|
||||
use log::LevelFilter;
|
||||
@@ -11,7 +10,23 @@ use tauri_plugin_single_instance::init;
|
||||
|
||||
// 单例插件
|
||||
pub fn build_si_plugin<R: Runtime>() -> TauriPlugin<R> {
|
||||
init(move |app, argv, _cwd| app.emit("active_deep_link", argv).unwrap())
|
||||
init(move |app, argv, _cwd| {
|
||||
// 把 argv 转成 Vec<String>
|
||||
// let args: Vec<String> = argv.iter().map(|s| s.to_string()).collect();
|
||||
|
||||
// 如果包含提升约定参数,发出专门事件并短路退出
|
||||
// if args.iter().any(|a| a == "--elevated") {
|
||||
// 提升实例通常只负责传参或执行一次性任务,退出避免与主实例冲突
|
||||
// std::process::exit(0);
|
||||
// }
|
||||
|
||||
// 非提升启动:按原逻辑广播 deep link
|
||||
if let Err(e) = app.emit("active_deep_link", argv) {
|
||||
eprintln!("emit active_deep_link failed: {}", e);
|
||||
}
|
||||
|
||||
// 回调必须返回 unit,直接结束即可
|
||||
})
|
||||
}
|
||||
|
||||
// 日志插件
|
||||
|
||||
79
src-tauri/src/watchdog.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
//! 重启提权相关处理
|
||||
//! @since Beta v0.8.7
|
||||
#![cfg(target_os = "windows")]
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::iter::once;
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
use std::ptr::null_mut;
|
||||
use std::time::Duration;
|
||||
use tauri::AppHandle;
|
||||
use windows_sys::Win32::Foundation::{HANDLE, HWND};
|
||||
use windows_sys::Win32::Storage::FileSystem::SYNCHRONIZE;
|
||||
use windows_sys::Win32::System::Threading::{OpenProcess, WaitForSingleObject, INFINITE};
|
||||
use windows_sys::Win32::UI::Shell::ShellExecuteW;
|
||||
use windows_sys::Win32::UI::WindowsAndMessaging::SW_SHOWNORMAL;
|
||||
|
||||
// 带参数启动
|
||||
fn shell_runas_with_args(args: &str) -> Result<(), String> {
|
||||
fn to_wide(s: &OsStr) -> Vec<u16> {
|
||||
s.encode_wide().chain(once(0)).collect()
|
||||
}
|
||||
|
||||
let exe_path = std::env::current_exe().map_err(|e| e.to_string())?;
|
||||
let exe_wide = to_wide(exe_path.as_os_str());
|
||||
let args_wide = to_wide(OsStr::new(args));
|
||||
let cwd_wide =
|
||||
exe_path.parent().map(|p| to_wide(p.as_os_str())).unwrap_or_else(|| to_wide(OsStr::new("")));
|
||||
|
||||
unsafe {
|
||||
let result = ShellExecuteW(
|
||||
0 as HWND,
|
||||
to_wide(OsStr::new("runas")).as_ptr(),
|
||||
exe_wide.as_ptr(),
|
||||
args_wide.as_ptr(),
|
||||
if cwd_wide.len() > 1 { cwd_wide.as_ptr() } else { null_mut() },
|
||||
SW_SHOWNORMAL,
|
||||
);
|
||||
if (result as usize) > 32 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Failed to ShellExecuteW runas".into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 等待父进程退出(释放单例锁)后,再以管理员身份启动新实例
|
||||
pub fn run_watchdog(parent_pid: u32, args_to_pass: &str) -> Result<(), String> {
|
||||
// 打开父进程句柄用于等待
|
||||
let handle: HANDLE = unsafe { OpenProcess(SYNCHRONIZE, 0, parent_pid) };
|
||||
if handle == std::ptr::null_mut() {
|
||||
// 如果拿不到句柄,可能父进程已退出,稍作等待后继续
|
||||
std::thread::sleep(Duration::from_millis(300));
|
||||
} else {
|
||||
unsafe {
|
||||
WaitForSingleObject(handle, INFINITE);
|
||||
}
|
||||
}
|
||||
|
||||
// 父进程已退出 → 触发 UAC 提权启动新实例
|
||||
shell_runas_with_args(args_to_pass)
|
||||
}
|
||||
|
||||
// 以管理员权限重启应用
|
||||
#[tauri::command]
|
||||
pub fn run_with_admin(app_handle: AppHandle) -> Result<(), String> {
|
||||
let parent_pid = std::process::id();
|
||||
let exe = std::env::current_exe().map_err(|e| e.to_string())?;
|
||||
let mut cmd = std::process::Command::new(exe);
|
||||
cmd
|
||||
.arg("--watchdog")
|
||||
.arg(format!("--ppid={}", parent_pid))
|
||||
// 看门狗不加载单例插件(通过参数决定 main 的初始化)
|
||||
.spawn()
|
||||
.map_err(|e| format!("spawn watchdog failed: {e}"))?;
|
||||
|
||||
// 立即退出:单例锁释放
|
||||
app_handle.exit(0);
|
||||
Ok(())
|
||||
}
|
||||
191
src-tauri/src/yae/inject.rs
Normal file
@@ -0,0 +1,191 @@
|
||||
//! DLL 注入相关功能
|
||||
//! @since Beta v0.7.8
|
||||
#![cfg(target_os = "windows")]
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::iter::once;
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
use std::ptr;
|
||||
use windows_sys::Win32::Foundation::{CloseHandle, FreeLibrary, HANDLE, INVALID_HANDLE_VALUE};
|
||||
use windows_sys::Win32::Storage::FileSystem::PIPE_ACCESS_DUPLEX;
|
||||
use windows_sys::Win32::System::Diagnostics::Debug::WriteProcessMemory;
|
||||
use windows_sys::Win32::System::Diagnostics::ToolHelp::{
|
||||
CreateToolhelp32Snapshot, Module32FirstW, Module32NextW, MODULEENTRY32W, TH32CS_SNAPMODULE,
|
||||
};
|
||||
use windows_sys::Win32::System::LibraryLoader::{
|
||||
GetModuleHandleA, GetProcAddress, LoadLibraryExW, DONT_RESOLVE_DLL_REFERENCES,
|
||||
};
|
||||
use windows_sys::Win32::System::Memory::{VirtualAllocEx, MEM_COMMIT, PAGE_READWRITE};
|
||||
use windows_sys::Win32::System::Pipes::{
|
||||
CreateNamedPipeW, PIPE_READMODE_MESSAGE, PIPE_TYPE_MESSAGE, PIPE_UNLIMITED_INSTANCES, PIPE_WAIT,
|
||||
};
|
||||
use windows_sys::Win32::System::Threading::{
|
||||
CreateProcessW, CreateRemoteThread, WaitForSingleObject, INFINITE, PROCESS_INFORMATION,
|
||||
STARTUPINFOW,
|
||||
};
|
||||
|
||||
/// 转为宽字符串
|
||||
pub fn to_wide_string(s: &str) -> Vec<u16> {
|
||||
OsStr::new(s).encode_wide().chain(once(0)).collect()
|
||||
}
|
||||
|
||||
/// 创建命名管道
|
||||
pub fn create_named_pipe(pipe_name: &str) -> HANDLE {
|
||||
let full_pipe_name = format!(r"\\.\pipe\{}", pipe_name);
|
||||
let wide: Vec<u16> = to_wide_string(&full_pipe_name);
|
||||
|
||||
unsafe {
|
||||
let handle = CreateNamedPipeW(
|
||||
wide.as_ptr(),
|
||||
PIPE_ACCESS_DUPLEX,
|
||||
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
|
||||
PIPE_UNLIMITED_INSTANCES,
|
||||
512,
|
||||
512,
|
||||
0,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
if handle == INVALID_HANDLE_VALUE {
|
||||
panic!("CreateNamedPipeW failed");
|
||||
}
|
||||
handle
|
||||
}
|
||||
}
|
||||
|
||||
/// 启动目标进程,附加cwd
|
||||
pub fn spawn_process(path: &str) -> PROCESS_INFORMATION {
|
||||
let wide_path: Vec<u16> = to_wide_string(path);
|
||||
let cwd = std::path::Path::new(path).parent().unwrap().to_str().unwrap();
|
||||
let wide_cwd: Vec<u16> = to_wide_string(cwd);
|
||||
|
||||
unsafe {
|
||||
let mut si: STARTUPINFOW = std::mem::zeroed();
|
||||
si.cb = std::mem::size_of::<STARTUPINFOW>() as u32;
|
||||
let mut pi: PROCESS_INFORMATION = std::mem::zeroed();
|
||||
|
||||
let success = CreateProcessW(
|
||||
wide_path.as_ptr(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
0,
|
||||
ptr::null_mut(),
|
||||
wide_cwd.as_ptr(),
|
||||
&mut si,
|
||||
&mut pi,
|
||||
);
|
||||
|
||||
if success == 0 {
|
||||
panic!("CreateProcessW failed");
|
||||
}
|
||||
|
||||
pi
|
||||
}
|
||||
}
|
||||
|
||||
/// 注入 DLL
|
||||
pub fn inject_dll(pi: &PROCESS_INFORMATION, dll_path: &str) {
|
||||
let dll_utf16: Vec<u16> = to_wide_string(dll_path);
|
||||
let size = dll_utf16.len() * 2;
|
||||
|
||||
unsafe {
|
||||
let addr = VirtualAllocEx(pi.hProcess, ptr::null_mut(), size, MEM_COMMIT, PAGE_READWRITE);
|
||||
if addr.is_null() {
|
||||
panic!("VirtualAllocEx failed");
|
||||
}
|
||||
|
||||
let success =
|
||||
WriteProcessMemory(pi.hProcess, addr, dll_utf16.as_ptr() as *const _, size, ptr::null_mut());
|
||||
if success == 0 {
|
||||
panic!("WriteProcessMemory failed");
|
||||
}
|
||||
|
||||
let k32 = GetModuleHandleA(b"kernel32.dll\0".as_ptr());
|
||||
if k32 == std::ptr::null_mut() {
|
||||
panic!("GetModuleHandleA failed");
|
||||
}
|
||||
|
||||
let loadlib = GetProcAddress(k32, b"LoadLibraryW\0".as_ptr());
|
||||
if loadlib.is_none() {
|
||||
panic!("GetProcAddress failed");
|
||||
}
|
||||
|
||||
let thread = CreateRemoteThread(
|
||||
pi.hProcess,
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
Some(std::mem::transmute(loadlib)),
|
||||
addr,
|
||||
0,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
if thread.is_null() {
|
||||
panic!("CreateRemoteThread failed");
|
||||
}
|
||||
|
||||
WaitForSingleObject(thread, INFINITE);
|
||||
}
|
||||
}
|
||||
|
||||
/// 枚举模块,找到 DLL 基址
|
||||
pub fn find_module_base(pid: u32, dll_name: &str) -> Option<usize> {
|
||||
unsafe {
|
||||
let snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);
|
||||
if snapshot == INVALID_HANDLE_VALUE {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut me32 =
|
||||
MODULEENTRY32W { dwSize: std::mem::size_of::<MODULEENTRY32W>() as u32, ..Default::default() };
|
||||
|
||||
if Module32FirstW(snapshot, &mut me32) != 0 {
|
||||
loop {
|
||||
let name = String::from_utf16_lossy(&me32.szModule);
|
||||
if name.contains(dll_name) {
|
||||
CloseHandle(snapshot);
|
||||
return Some(me32.modBaseAddr as usize);
|
||||
}
|
||||
if Module32NextW(snapshot, &mut me32) == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CloseHandle(snapshot);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// 执行 YaeMain
|
||||
pub fn call_yaemain(pi: &PROCESS_INFORMATION, base: usize, dll_path: &str) {
|
||||
let dll_path_wide: Vec<u16> = to_wide_string(dll_path);
|
||||
unsafe {
|
||||
let local =
|
||||
LoadLibraryExW(dll_path_wide.as_ptr(), std::ptr::null_mut(), DONT_RESOLVE_DLL_REFERENCES);
|
||||
if local == std::ptr::null_mut() {
|
||||
panic!("LoadLibraryExW failed");
|
||||
}
|
||||
|
||||
let proc = GetProcAddress(local, b"YaeMain\0".as_ptr()).expect("无法找到 YaeMain");
|
||||
|
||||
// Option<unsafe extern "system" fn() -> isize>
|
||||
let proc_addr = proc as *const () as usize;
|
||||
let rva = proc_addr - local as usize;
|
||||
println!("YaeMain RVA: {:#x}", rva);
|
||||
|
||||
FreeLibrary(local);
|
||||
|
||||
let remote_yaemain = (base + rva) as *mut std::ffi::c_void;
|
||||
// 在远程进程里调用 YaeMain(hModule)
|
||||
CreateRemoteThread(
|
||||
pi.hProcess,
|
||||
std::ptr::null_mut(),
|
||||
0,
|
||||
Some(std::mem::transmute(remote_yaemain)),
|
||||
base as *mut _,
|
||||
0,
|
||||
std::ptr::null_mut(),
|
||||
);
|
||||
}
|
||||
}
|
||||
204
src-tauri/src/yae/mod.rs
Normal file
@@ -0,0 +1,204 @@
|
||||
//! Yae 相关处理
|
||||
//! @since Beta v0.8.7
|
||||
#![cfg(target_os = "windows")]
|
||||
|
||||
pub mod inject;
|
||||
pub mod proto;
|
||||
|
||||
use inject::{call_yaemain, create_named_pipe, find_module_base, inject_dll, spawn_process};
|
||||
use proto::parse_achi_list;
|
||||
use serde_json::Value;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::os::windows::io::{FromRawHandle, RawHandle};
|
||||
use std::sync::Arc;
|
||||
use tauri::{AppHandle, Emitter, Manager};
|
||||
use windows_sys::Win32::Foundation::CloseHandle;
|
||||
use windows_sys::Win32::System::Pipes::ConnectNamedPipe;
|
||||
|
||||
// 读取配置值
|
||||
fn read_rva(key: &str) -> i32 {
|
||||
let path = format!("nativeConfig.methodRva.chinese.{}", key);
|
||||
read_conf(&path)
|
||||
}
|
||||
|
||||
// 读取配置文件
|
||||
pub fn read_conf(path: &str) -> i32 {
|
||||
// 编译时嵌入 JSON 文件,值都是32位整数
|
||||
let data = include_str!("../../lib/conf.json");
|
||||
let json: Value = serde_json::from_str(data).expect("Invalid JSON");
|
||||
|
||||
// 按 '.' 分割 key
|
||||
let mut current = &json;
|
||||
for key in path.split('.') {
|
||||
match current.get(key) {
|
||||
Some(value) => current = value,
|
||||
None => return 0, // 如果找不到 key,返回默认值 0
|
||||
}
|
||||
}
|
||||
current.as_i64().unwrap_or(0) as i32
|
||||
}
|
||||
|
||||
fn read_u32_le<R: Read>(r: &mut R) -> io::Result<u32> {
|
||||
let mut buf = [0u8; 4];
|
||||
match r.read_exact(&mut buf) {
|
||||
Ok(_) => Ok(u32::from_le_bytes(buf)),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_f64_le<R: Read>(r: &mut R) -> io::Result<f64> {
|
||||
let mut buf = [0u8; 8];
|
||||
match r.read_exact(&mut buf) {
|
||||
Ok(_) => Ok(f64::from_le_bytes(buf)),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_exact_vec<R: Read>(r: &mut R, len: usize) -> io::Result<Vec<u8>> {
|
||||
let mut v = vec![0u8; len];
|
||||
match r.read_exact(&mut v) {
|
||||
Ok(_) => Ok(v),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// 调用 dll
|
||||
#[tauri::command]
|
||||
pub fn call_yae_dll(app_handle: AppHandle, game_path: String) -> Result<(), String> {
|
||||
let dll_path = app_handle.path().resource_dir().unwrap().join("resources/YaeAchievementLib.dll");
|
||||
dbg!(&dll_path);
|
||||
// 0. 创建 YaeAchievementPipe 的 命名管道,获取句柄
|
||||
dbg!("开始启动 YaeAchievementPipe 命名管道");
|
||||
let _pipe_handle = create_named_pipe("YaeAchievementPipe");
|
||||
|
||||
// 1. 启动游戏进程
|
||||
let pi = spawn_process(&game_path);
|
||||
dbg!("游戏进程启动完成");
|
||||
|
||||
// 2. 注入 DLL
|
||||
inject_dll(&pi, dll_path.to_str().unwrap());
|
||||
dbg!("DLL 注入完成");
|
||||
|
||||
// 3. 找到 DLL 基址
|
||||
let base = find_module_base(pi.dwProcessId, "YaeAchievementLib.dll").expect("找不到 DLL 基址");
|
||||
dbg!("找到 DLL 基址: {:X}", base);
|
||||
|
||||
// 4. 调用 YaeMain
|
||||
call_yaemain(&pi, base, dll_path.to_str().unwrap());
|
||||
dbg!("YaeMain 调用完成");
|
||||
|
||||
// 根据句柄等待命名管道连接
|
||||
let retry_count = 50;
|
||||
for _ in 0..retry_count {
|
||||
let result = unsafe { ConnectNamedPipe(_pipe_handle, std::ptr::null_mut()) };
|
||||
if result != 0 {
|
||||
dbg!("命名管道连接成功");
|
||||
break;
|
||||
} else {
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
}
|
||||
}
|
||||
// 5. 读取命名管道数据,循环读取,根据接受字节进行通信
|
||||
let file = unsafe { File::from_raw_handle(_pipe_handle as RawHandle) };
|
||||
let file = Arc::new(file);
|
||||
|
||||
// No raw HANDLE captured in the closure. Only Arc<File>.
|
||||
std::thread::spawn({
|
||||
let file = file.clone();
|
||||
move || {
|
||||
let mut file = file.try_clone().expect("Failed to clone pipe file");
|
||||
let mut cmd = [0u8; 1];
|
||||
|
||||
loop {
|
||||
match file.read_exact(&mut cmd) {
|
||||
// 输出命令字节
|
||||
Ok(_) => {
|
||||
println!("收到命令: {}", cmd[0]);
|
||||
match cmd[0] {
|
||||
0x01 => {
|
||||
match read_u32_le(&mut file) {
|
||||
Ok(len) => {
|
||||
// 再读数据
|
||||
match read_exact_vec(&mut file, len as usize) {
|
||||
Ok(data) => {
|
||||
println!("长度: {}", len);
|
||||
// 解码成 AchievementInfo
|
||||
match parse_achi_list(&data) {
|
||||
Ok(list) => {
|
||||
println!("解码成功,成就列表长度: {}", list.len());
|
||||
let json = serde_json::to_string_pretty(&list).unwrap();
|
||||
let _ = app_handle.emit("yae_achi_list", json);
|
||||
}
|
||||
Err(e) => println!("解析失败: {:?}", e),
|
||||
}
|
||||
}
|
||||
Err(e) => println!("读取数据失败: {:?}", e),
|
||||
}
|
||||
}
|
||||
Err(e) => println!("读取长度失败: {:?}", e),
|
||||
}
|
||||
}
|
||||
0x02 => {
|
||||
println!("PlayerStoreNotify");
|
||||
// 读取剩余数据
|
||||
match read_u32_le(&mut file) {
|
||||
Ok(len) => match read_exact_vec(&mut file, len as usize) {
|
||||
Ok(_data) => println!("长度: {}", len),
|
||||
Err(e) => println!("读取数据失败: {:?}", e),
|
||||
},
|
||||
Err(e) => println!("读取长度失败: {:?}", e),
|
||||
}
|
||||
}
|
||||
0x03 => {
|
||||
println!("PlayerPropNotify");
|
||||
// 读取剩余数据
|
||||
match read_u32_le(&mut file) {
|
||||
Ok(prop_type) => match read_f64_le(&mut file) {
|
||||
Ok(value) => println!("Prop 类型: {}, 值: {}", prop_type, value),
|
||||
Err(e) => println!("读取值失败: {:?}", e),
|
||||
},
|
||||
Err(e) => println!("读取类型失败: {:?}", e),
|
||||
}
|
||||
}
|
||||
0xFC => {
|
||||
let _ = file.write_all(&read_conf("nativeConfig.achievementCmdId").to_le_bytes());
|
||||
let _ = file.write_all(&read_conf("nativeConfig.storeCmdId").to_le_bytes());
|
||||
}
|
||||
0xFD => {
|
||||
for key in [
|
||||
"doCmd",
|
||||
"updateNormalProp",
|
||||
"newString",
|
||||
"findGameObject",
|
||||
"eventSystemUpdate",
|
||||
"simulatePointerClick",
|
||||
"toInt32",
|
||||
"tcpStatePtr",
|
||||
"sharedInfoPtr",
|
||||
"decompress",
|
||||
] {
|
||||
let _ = file.write_all(&read_rva(key).to_le_bytes());
|
||||
}
|
||||
}
|
||||
0xFF => {
|
||||
let _ = file.write_all(&[1]);
|
||||
break;
|
||||
}
|
||||
_ => println!("收到未知命令: {}", cmd[0]),
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("读取失败: {:?}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
unsafe {
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
170
src-tauri/src/yae/proto.rs
Normal file
@@ -0,0 +1,170 @@
|
||||
//! Yae 成就信息的 Protobuf 定义
|
||||
//! @since Beta v0.7.9
|
||||
#![cfg(target_os = "windows")]
|
||||
|
||||
use crate::yae::read_conf;
|
||||
use prost::encoding::{decode_key, WireType};
|
||||
use prost::DecodeError;
|
||||
use prost::Message;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::io::{Cursor, Read};
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, PartialEq, Message, Serialize)]
|
||||
pub struct AchievementProtoFieldInfo {
|
||||
#[prost(uint32, tag = "1")]
|
||||
pub id: u32,
|
||||
|
||||
#[prost(uint32, tag = "2")]
|
||||
pub status: u32,
|
||||
|
||||
#[prost(uint32, tag = "3")]
|
||||
pub total_progress: u32,
|
||||
|
||||
#[prost(uint32, tag = "4")]
|
||||
pub current_progress: u32,
|
||||
|
||||
#[prost(uint32, tag = "5")]
|
||||
pub finish_timestamp: u32,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, PartialEq, Message, Serialize)]
|
||||
pub struct AchievementItem {
|
||||
#[prost(uint32, tag = "1")]
|
||||
pub pre: u32,
|
||||
|
||||
#[prost(uint32, tag = "2")]
|
||||
pub group: u32,
|
||||
|
||||
#[prost(string, tag = "3")]
|
||||
pub name: String,
|
||||
|
||||
#[prost(string, tag = "4")]
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, PartialEq, Message, Serialize)]
|
||||
pub struct MethodRvaConfig {
|
||||
#[prost(uint32, tag = "1")]
|
||||
pub do_cmd: u32,
|
||||
|
||||
#[prost(uint32, tag = "3")]
|
||||
pub update_normal_prop: u32,
|
||||
|
||||
#[prost(uint32, tag = "4")]
|
||||
pub new_string: u32,
|
||||
|
||||
#[prost(uint32, tag = "5")]
|
||||
pub find_game_object: u32,
|
||||
|
||||
#[prost(uint32, tag = "6")]
|
||||
pub event_system_update: u32,
|
||||
|
||||
#[prost(uint32, tag = "7")]
|
||||
pub simulate_pointer_click: u32,
|
||||
|
||||
#[prost(uint32, tag = "8")]
|
||||
pub to_int32: u32,
|
||||
|
||||
#[prost(uint32, tag = "9")]
|
||||
pub tcp_state_ptr: u32,
|
||||
|
||||
#[prost(uint32, tag = "10")]
|
||||
pub shared_info_ptr: u32,
|
||||
|
||||
#[prost(uint32, tag = "11")]
|
||||
pub decompress: u32,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, PartialEq, Message, Serialize)]
|
||||
pub struct NativeLibConfig {
|
||||
#[prost(uint32, tag = "1")]
|
||||
pub store_cmd_id: u32,
|
||||
|
||||
#[prost(uint32, tag = "2")]
|
||||
pub achievement_cmd_id: u32,
|
||||
|
||||
#[prost(map = "uint32, message", tag = "10")]
|
||||
pub method_rva: HashMap<u32, MethodRvaConfig>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, PartialEq, Message, Serialize)]
|
||||
pub struct AchievementInfo {
|
||||
#[prost(string, tag = "1")]
|
||||
pub version: String,
|
||||
|
||||
#[prost(map = "uint32, string", tag = "2")]
|
||||
pub group: HashMap<u32, String>,
|
||||
|
||||
#[prost(map = "uint32, message", tag = "3")]
|
||||
pub items: HashMap<u32, AchievementItem>,
|
||||
|
||||
#[prost(message, tag = "4")]
|
||||
pub pb_info: Option<AchievementProtoFieldInfo>,
|
||||
|
||||
#[prost(message, tag = "5")]
|
||||
pub native_config: Option<NativeLibConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize)]
|
||||
pub struct UiafAchiItem {
|
||||
pub id: u32,
|
||||
pub current: u32,
|
||||
pub timestamp: u32,
|
||||
pub status: u32,
|
||||
}
|
||||
|
||||
pub fn parse_achi_list(bytes: &[u8]) -> Result<Vec<UiafAchiItem>, DecodeError> {
|
||||
let mut cursor = Cursor::new(bytes);
|
||||
let mut dicts: Vec<HashMap<u32, u32>> = Vec::new();
|
||||
|
||||
while let Ok((_, wire_type)) = decode_key(&mut cursor) {
|
||||
if wire_type == WireType::LengthDelimited {
|
||||
let len = prost::encoding::decode_varint(&mut cursor)? as usize;
|
||||
let mut buf = vec![0u8; len];
|
||||
if cursor.read_exact(&mut buf).is_err() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut inner = Cursor::new(&buf);
|
||||
let mut dict = HashMap::new();
|
||||
|
||||
while let Ok((tag, wire_type)) = decode_key(&mut inner) {
|
||||
if wire_type != WireType::Varint {
|
||||
break;
|
||||
}
|
||||
let value = prost::encoding::decode_varint(&mut inner)? as u32;
|
||||
dict.insert(tag, value);
|
||||
}
|
||||
// 输出 dict
|
||||
println!("{:?}", dict);
|
||||
// dict 至少需要两个 key
|
||||
if dict.len() > 2 {
|
||||
dicts.push(dict)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _id = read_conf("id") as u32;
|
||||
let _status = read_conf("status") as u32;
|
||||
let _cur = read_conf("currentProgress") as u32;
|
||||
let _ts = read_conf("finishTimestamp") as u32;
|
||||
|
||||
let achievements = dicts
|
||||
.into_iter()
|
||||
.map(|d| UiafAchiItem {
|
||||
id: d.get(&_id).copied().unwrap_or(0),
|
||||
status: d.get(&_status).copied().unwrap_or(0),
|
||||
current: d.get(&_cur).copied().unwrap_or(0),
|
||||
timestamp: d.get(&_ts).copied().unwrap_or(0),
|
||||
})
|
||||
.filter(|a| a.status != 0)
|
||||
.collect();
|
||||
|
||||
Ok(achievements)
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "TeyvatGuide",
|
||||
"identifier": "TeyvatGuide",
|
||||
"version": "0.8.2",
|
||||
"version": "0.8.8",
|
||||
"build": {
|
||||
"beforeDevCommand": "pnpm vite:dev",
|
||||
"beforeBuildCommand": "pnpm vite:build",
|
||||
@@ -30,7 +30,8 @@
|
||||
],
|
||||
"targets": ["msi", "app", "dmg"],
|
||||
"windows": { "wix": { "language": "zh-CN" } },
|
||||
"macOS": {}
|
||||
"macOS": {},
|
||||
"resources": { "lib/YaeAchievementLib.dll": "resources/YaeAchievementLib.dll" }
|
||||
},
|
||||
"app": {
|
||||
"withGlobalTauri": true,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!--主界面 -->
|
||||
<template>
|
||||
<v-app v-model:theme="vuetifyTheme">
|
||||
<TSidebar v-if="isMain" />
|
||||
@@ -164,7 +165,7 @@ async function getDeepLink(): Promise<UnlistenFn> {
|
||||
const windowGet = new webviewWindow.WebviewWindow("TeyvatGuide");
|
||||
if (await windowGet.isMinimized()) await windowGet.unminimize();
|
||||
await windowGet.setFocus();
|
||||
const payload = parseDeepLink(e.payload);
|
||||
const payload = await parseDeepLink(e.payload);
|
||||
if (payload === false) {
|
||||
showSnackbar.error("无效的 deep link!", 3000);
|
||||
await TGLogger.Error(`[App][getDeepLink] 无效的 deep link! ${JSON.stringify(e.payload)}`);
|
||||
@@ -175,14 +176,14 @@ async function getDeepLink(): Promise<UnlistenFn> {
|
||||
});
|
||||
}
|
||||
|
||||
function parseDeepLink(payload: string | string[]): string | false {
|
||||
async function parseDeepLink(payload: string | string[]): Promise<string | false> {
|
||||
try {
|
||||
if (typeof payload === "string") return payload;
|
||||
if (payload.length < 2) return "teyvatguide://";
|
||||
return payload[1];
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
TGLogger.Error(`[App][parseDeepLink] ${e.name}: ${e.message}`);
|
||||
await TGLogger.Error(`[App][parseDeepLink] ${e.name}: ${e.message}`);
|
||||
} else console.error(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- 版块小组件菜单 -->
|
||||
<template>
|
||||
<div class="tgn-container">
|
||||
<div v-for="navItem in nav" :key="navItem.id" class="tgn-nav" @click="toNav(navItem)">
|
||||
@@ -5,13 +6,14 @@
|
||||
<span>{{ navItem.name }}</span>
|
||||
</div>
|
||||
<div v-if="hasNav" class="tgn-nav">
|
||||
<v-icon size="25" @click="tryGetCode" title="查看兑换码">mdi-code-tags-check</v-icon>
|
||||
<v-icon size="25" @click="tryGetCode" title="查看兑换码" color="var(--tgc-od-orange)">
|
||||
mdi-code-tags-check
|
||||
</v-icon>
|
||||
</div>
|
||||
<ToLivecode v-model="showOverlay" :data="codeData" v-model:actId="actId" />
|
||||
<ToLivecode v-model="showOverlay" :gid="model" :data="codeData" :actId="actId" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TMiImg from "@comp/app/t-mi-img.vue";
|
||||
import showDialog from "@comp/func/dialog.js";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import ApiHubReq from "@req/apiHubReq.js";
|
||||
@@ -25,18 +27,19 @@ import { createPost } from "@utils/TGWindow.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed, onMounted, ref, shallowRef, watch } from "vue";
|
||||
|
||||
import TMiImg from "./t-mi-img.vue";
|
||||
import ToLivecode from "./to-livecode.vue";
|
||||
|
||||
const { isLogin } = storeToRefs(useAppStore());
|
||||
|
||||
const model = defineModel<number>({ default: 2 });
|
||||
|
||||
const { isLogin } = storeToRefs(useAppStore());
|
||||
const actId = ref<string>();
|
||||
const showOverlay = ref<boolean>(false);
|
||||
const nav = shallowRef<TGApp.BBS.Navigator.Navigator[]>([]);
|
||||
const codeData = shallowRef<TGApp.BBS.Navigator.CodeData[]>([]);
|
||||
const showOverlay = ref<boolean>(false);
|
||||
const actId = ref<string>();
|
||||
|
||||
const hasNav = computed<TGApp.BBS.Navigator.Navigator | undefined>(() => {
|
||||
const liveNames = ["前瞻直播", "前瞻节目", "直播兑换码"];
|
||||
const liveNames = ["前瞻直播", "前瞻节目", "直播兑换码", "特别节目"];
|
||||
const find = nav.value.find((item) => liveNames.includes(item.name));
|
||||
if (find) return find;
|
||||
return nav.value.find((item) => item.name.includes("前瞻"));
|
||||
@@ -49,34 +52,55 @@ watch(
|
||||
async () => await loadNav(),
|
||||
);
|
||||
|
||||
/**
|
||||
* 加载组件数据
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function loadNav(): Promise<void> {
|
||||
nav.value = await ApiHubReq.home(model.value);
|
||||
try {
|
||||
nav.value = await ApiHubReq.home(model.value);
|
||||
console.debug(`[TGameNav][loadNav] 组件数据:`, nav.value);
|
||||
} catch (e) {
|
||||
await TGLogger.Error(`[TGameNav][loadNav] 加载组件数据失败:${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取兑换码
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function tryGetCode(): Promise<void> {
|
||||
if (!hasNav.value) return;
|
||||
const actIdFind = new URL(hasNav.value.app_path).searchParams.get("act_id");
|
||||
if (!actIdFind) {
|
||||
showSnackbar.warn("未找到活动ID");
|
||||
await TGLogger.Warn(`[TGameNav][tryGetCode] 未找到活动ID,链接:${hasNav.value.app_path}`);
|
||||
return;
|
||||
}
|
||||
actId.value = actIdFind;
|
||||
const res = await OtherApi.code(actIdFind);
|
||||
if (!Array.isArray(res)) {
|
||||
showSnackbar.warn(`[${res.retcode}] ${res.message}`);
|
||||
await TGLogger.Warn(`[TGameNav][tryGetCode] 获取兑换码失败:${JSON.stringify(res)}`);
|
||||
return;
|
||||
}
|
||||
codeData.value = res;
|
||||
console.debug(`[TGameNave][tryGetCode] 兑换码数据:`, codeData.value);
|
||||
showSnackbar.success("获取兑换码成功");
|
||||
await TGLogger.Info(JSON.stringify(res));
|
||||
showOverlay.value = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转到活动页面
|
||||
* @param {TGApp.BBS.Navigator.Navigator} item 导航项
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function toNav(item: TGApp.BBS.Navigator.Navigator): Promise<void> {
|
||||
if (!isLogin.value) {
|
||||
showSnackbar.warn("请先登录");
|
||||
return;
|
||||
}
|
||||
console.debug(`[TGameNav][toNav] 跳转到活动页面:`, item);
|
||||
await TGLogger.Info(`[TGameNav][toNav] 打开网页活动 ${item.name}`);
|
||||
await TGLogger.Info(`[TGameNav][toNav] ${item.app_path}`);
|
||||
const link = new URL(item.app_path);
|
||||
@@ -110,7 +134,11 @@ async function toNav(item: TGApp.BBS.Navigator.Navigator): Promise<void> {
|
||||
else await TGClient.open("web_act", item.app_path);
|
||||
}
|
||||
|
||||
// 处理 protocol
|
||||
/**
|
||||
* 处理米游社论坛链接
|
||||
* @param {URL} link 链接
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function toBBS(link: URL): Promise<void> {
|
||||
if (link.protocol == "mihoyobbs:") {
|
||||
if (link.hostname === "article") {
|
||||
@@ -148,25 +176,26 @@ async function toBBS(link: URL): Promise<void> {
|
||||
border-radius: 4px;
|
||||
color: var(--tgc-white-1);
|
||||
cursor: pointer;
|
||||
|
||||
img {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
span {
|
||||
display: none;
|
||||
margin-left: 4px;
|
||||
color: var(--common-text-title);
|
||||
font-family: var(--font-title);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
&:hover span {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.dark .tgn-nav {
|
||||
@include github-styles.github-card("dark");
|
||||
}
|
||||
|
||||
.tgn-nav img {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.tgn-nav span {
|
||||
display: none;
|
||||
color: var(--common-text-title);
|
||||
font-family: var(--font-title);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.tgn-nav:hover span {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
||||
95
src/components/app/t-postWidth.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<!-- 改变帖子视图 -->
|
||||
<template>
|
||||
<div
|
||||
class="tpw2-box"
|
||||
:class="postViewWide ? '' : 'active'"
|
||||
:title="postViewWide ? '切换为窄屏视图' : '切换为宽屏视图'"
|
||||
data-html2canvas-ignore
|
||||
>
|
||||
<div class="tpw2-btn" @click="switchPostWidth()">
|
||||
<v-icon size="20">
|
||||
{{ postViewWide ? "mdi-arrow-collapse-horizontal" : "mdi-arrow-expand-horizontal" }}
|
||||
</v-icon>
|
||||
</div>
|
||||
<div class="tpw2-beta-hint" title="测试功能,可能存在适配问题">β</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import useAppStore from "@store/app.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
|
||||
const { postViewWide } = storeToRefs(useAppStore());
|
||||
|
||||
function switchPostWidth(): void {
|
||||
postViewWide.value = !postViewWide.value;
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@use "@styles/github.styles.scss" as github-styles;
|
||||
|
||||
.tpw2-box {
|
||||
@include github-styles.github-card;
|
||||
|
||||
position: fixed;
|
||||
top: 112px;
|
||||
left: 16px;
|
||||
display: flex;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
|
||||
&.active {
|
||||
background: var(--tgc-btn-1);
|
||||
box-shadow: 1px 3px 6px var(--common-shadow-2);
|
||||
color: var(--btn-text);
|
||||
}
|
||||
|
||||
&:hover:not(.active) {
|
||||
background: var(--common-shadow-1);
|
||||
}
|
||||
}
|
||||
|
||||
.dark .tpw2-box {
|
||||
border: 1px solid var(--common-shadow-1);
|
||||
box-shadow: 1px 3px 6px var(--common-shadow-t-2);
|
||||
|
||||
&:not(.active) {
|
||||
@include github-styles.github-card("dark");
|
||||
|
||||
&:hover {
|
||||
background: var(--common-shadow-6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tpw2-btn {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tpw2-beta-hint {
|
||||
position: absolute;
|
||||
right: -5px;
|
||||
bottom: -5px;
|
||||
display: flex;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background: var(--tgc-od-green);
|
||||
box-shadow: 1px 3px 6px var(--common-shadow-2);
|
||||
color: var(--tgc-blue-1);
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
@@ -112,7 +112,10 @@
|
||||
}"
|
||||
v-else
|
||||
>
|
||||
{{ props.modelValue.post.post_id }}
|
||||
<span>{{ props.modelValue.post.post_id }}</span>
|
||||
<template v-if="isDevEnv">
|
||||
<span data-html2canvas-ignore>[{{ props.modelValue.post.view_type }}]</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -154,7 +157,8 @@ export type RenderCard = {
|
||||
topics: Array<TGApp.BBS.Post.Topic>;
|
||||
reasons: Array<TGApp.BBS.Post.RecommendTags>;
|
||||
};
|
||||
|
||||
// @ts-expect-error The import.meta meta-property is not allowed in files which will build into CommonJS output.
|
||||
const isDevEnv = import.meta.env.MODE === "development";
|
||||
const stats: Readonly<Array<RenderStatus>> = [
|
||||
{ stat: 0, label: "未知", color: "var(--tgc-od-red)" },
|
||||
{ stat: 1, label: "进行中", color: "var(--tgc-od-green)" },
|
||||
|
||||