Compare commits
208 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80e27a20a7 | ||
|
|
26d1883d98 | ||
|
|
91fd375263 | ||
|
|
3b0ed774df | ||
|
|
5f3f6640a4 | ||
|
|
b1424fb582 | ||
|
|
f64b48c356 | ||
|
|
90242829a9 | ||
|
|
d6ae3765b6 | ||
|
|
92a1775d19 | ||
|
|
fd3822fe70 | ||
|
|
618d3a1632 | ||
|
|
1c3b73bde9 | ||
|
|
c541d67abc | ||
|
|
1d3b1ae78e | ||
|
|
312436b4e2 | ||
|
|
d98663dccb | ||
|
|
653073e684 | ||
|
|
1d09f4817b | ||
|
|
6ca5de28ac | ||
|
|
ff649c2426 | ||
|
|
1c5bebf75e | ||
|
|
b60718aa62 | ||
|
|
c018638e4a | ||
|
|
1906e911c7 | ||
|
|
fbb66b3964 | ||
|
|
561f34cf8b | ||
|
|
5241c08c33 | ||
|
|
630a64323d | ||
|
|
54d2e27054 | ||
|
|
1c69cf07a5 | ||
|
|
715c206945 | ||
|
|
219286f6a1 | ||
|
|
fc3d417961 | ||
|
|
712a09131e | ||
|
|
13e9440c6f | ||
|
|
bed0e528b0 | ||
|
|
43c282efd2 | ||
|
|
b74a3b0bbf | ||
|
|
7c0239391e | ||
|
|
b1060f76c5 | ||
|
|
66137cf5b6 | ||
|
|
69668f5ada | ||
|
|
a5cff46efb | ||
|
|
8037b635ba | ||
|
|
7a060a71f0 | ||
|
|
d583247630 | ||
|
|
c1a7e8448a | ||
|
|
349bab1173 | ||
|
|
db36d18df2 | ||
|
|
f619ccc64b | ||
|
|
8ae1d578ff | ||
|
|
e6eaa2e293 | ||
|
|
a9912cf42b | ||
|
|
4aa9319799 | ||
|
|
38ef0bfcca | ||
|
|
c55f5b9ab8 | ||
|
|
468db1171e | ||
|
|
a474b96280 | ||
|
|
149c7b3f27 | ||
|
|
abca5bd2f9 | ||
|
|
89d3a172b8 | ||
|
|
f318fca8ad | ||
|
|
0b041a3f01 | ||
|
|
2a8b97abe9 | ||
|
|
b390c7851e | ||
|
|
ecb0f1a793 | ||
|
|
b65afba30b | ||
|
|
d4295c7dc9 | ||
|
|
e1e4f805ea | ||
|
|
b6624e8e3b | ||
|
|
79fd18ea3b | ||
|
|
3db8008f3a | ||
|
|
8ce6c547bd | ||
|
|
dc9bfe793a | ||
|
|
9e4270603f | ||
|
|
56c6c4f70f | ||
|
|
803705218b | ||
|
|
8ab7735cad | ||
|
|
9a221f9b64 | ||
|
|
1d408b5d24 | ||
|
|
7349f120e4 | ||
|
|
598a18557f | ||
|
|
69ac285ee2 | ||
|
|
93800a15ce | ||
|
|
a9f92a6042 | ||
|
|
2b9db5b5e0 | ||
|
|
6dd98fbe98 | ||
|
|
ac9851aab0 | ||
|
|
824297142a | ||
|
|
043fda9e33 | ||
|
|
90872a4917 | ||
|
|
f84c4b93ea | ||
|
|
84b98e4ade | ||
|
|
5992567d55 | ||
|
|
9be40181a7 | ||
|
|
0e864fc04f | ||
|
|
0d4fdecd5d | ||
|
|
24aa355f3b | ||
|
|
d422e308fb | ||
|
|
f54ab8daa4 | ||
|
|
d0c0f40638 | ||
|
|
73bf525d42 | ||
|
|
efa2156fb2 | ||
|
|
1dad91dd95 | ||
|
|
3c4e30d0f5 | ||
|
|
5411ae013f | ||
|
|
20113cf81f | ||
|
|
4fc77b60ad | ||
|
|
1bcc5a625b | ||
|
|
6792c0ac0a | ||
|
|
16999f2e58 | ||
|
|
1914261e80 | ||
|
|
35dc972841 | ||
|
|
7ef89c33f1 | ||
|
|
77d3ecbeca | ||
|
|
d18e463f7b | ||
|
|
9b57909dc4 | ||
|
|
c12461ad43 | ||
|
|
287af4031d | ||
|
|
8a3358a355 | ||
|
|
f2db40e3c4 | ||
|
|
2ef2513fcf | ||
|
|
6e18331f2f | ||
|
|
cf801314a1 | ||
|
|
06f4d26184 | ||
|
|
518af605de | ||
|
|
1aa3ba3792 | ||
|
|
fc0b165c8f | ||
|
|
efec9f7917 | ||
|
|
c168a07640 | ||
|
|
839891448c | ||
|
|
cc475a5c50 | ||
|
|
98189974b8 | ||
|
|
f6e531909b | ||
|
|
f7238186f4 | ||
|
|
b71e21a131 | ||
|
|
5c54fc255a | ||
|
|
49b4ad53cd | ||
|
|
32a40b3cad | ||
|
|
b7a555c8b8 | ||
|
|
6ab1f5c842 | ||
|
|
98c3e1a468 | ||
|
|
90775f925b | ||
|
|
751372a4ba | ||
|
|
4ab679dcea | ||
|
|
c41b89acf7 | ||
|
|
2f34b1be45 | ||
|
|
a7ad394c4f | ||
|
|
a8bf9e3c3b | ||
|
|
6af3461168 | ||
|
|
63f72a992c | ||
|
|
f7b60fb218 | ||
|
|
7b76432b1e | ||
|
|
068f8c7647 | ||
|
|
c27d3af5db | ||
|
|
3691397cec | ||
|
|
cbce3eda60 | ||
|
|
54a0cfd03f | ||
|
|
214991fdd9 | ||
|
|
58e3c0e1a6 | ||
|
|
358255d50a | ||
|
|
d2fa3529f8 | ||
|
|
7dedcc4ea9 | ||
|
|
980b420eb1 | ||
|
|
c8c157852f | ||
|
|
48a84918b6 | ||
|
|
f6bea9b2e7 | ||
|
|
3b0bc4b1a5 | ||
|
|
b484e745e0 | ||
|
|
74320f0e9a | ||
|
|
adc96b7649 | ||
|
|
e32988f663 | ||
|
|
1cd1c1f035 | ||
|
|
b04f49ec46 | ||
|
|
5c2bb4e5af | ||
|
|
9a25e387aa | ||
|
|
fa22a45bb1 | ||
|
|
146f3404f0 | ||
|
|
93fe738c97 | ||
|
|
5272108e82 | ||
|
|
faa1832c1e | ||
|
|
5712d4b7fc | ||
|
|
1ad3506f66 | ||
|
|
7841ea4a79 | ||
|
|
66ecd9a91e | ||
|
|
6e79c0a7e0 | ||
|
|
96ab38b932 | ||
|
|
788560f536 | ||
|
|
fdfcc70bcb | ||
|
|
cbb2ddd8a2 | ||
|
|
8aeaf30a89 | ||
|
|
fe1f16584e | ||
|
|
e974f30647 | ||
|
|
ce2ff5b6f5 | ||
|
|
491cdf9af6 | ||
|
|
49c716f009 | ||
|
|
b3bbd4bed7 | ||
|
|
bbc2a3f845 | ||
|
|
3cd2586ed4 | ||
|
|
dd940d5a2b | ||
|
|
b57d34419f | ||
|
|
bd67ee7a25 | ||
|
|
137180028e | ||
|
|
a513b38f14 | ||
|
|
2cbac71b36 | ||
|
|
e95cf683aa | ||
|
|
1fa97d0a6c |
@@ -2,4 +2,9 @@
|
||||
dist
|
||||
src-tauri/target
|
||||
# Submodules
|
||||
TGAssistant
|
||||
TGAssistant
|
||||
# Package files
|
||||
pnpm-lock.yaml
|
||||
# lint files
|
||||
!.prettierrc.yml
|
||||
!.stylelintrc.yml
|
||||
|
||||
149
.eslintrc.yml
@@ -3,37 +3,130 @@ env:
|
||||
browser: true
|
||||
es2021: true
|
||||
extends:
|
||||
- plugin:vue/vue3-recommended
|
||||
- standard-with-typescript
|
||||
- plugin:vue/vue3-essential
|
||||
- prettier
|
||||
- plugin:prettier/recommended
|
||||
parser: vue-eslint-parser
|
||||
- plugin:yml/standard
|
||||
- plugin:jsonc/recommended-with-json
|
||||
parserOptions:
|
||||
extraFileExtensions:
|
||||
- .vue
|
||||
parser: "@typescript-eslint/parser"
|
||||
project:
|
||||
- tsconfig.json
|
||||
- tsconfig.node.json
|
||||
ecmaVersion: latest
|
||||
sourceType: module
|
||||
tsconfigRootDir: .
|
||||
project: ./tsconfig.json
|
||||
globals:
|
||||
TGApp: readonly
|
||||
plugins:
|
||||
- vue
|
||||
- prettier
|
||||
window: readonly
|
||||
rules:
|
||||
# normal
|
||||
array-callback-return: warn
|
||||
# TypeScript
|
||||
"@typescript-eslint/strict-boolean-expressions": off
|
||||
"@typescript-eslint/consistent-type-assertions":
|
||||
- warn
|
||||
- assertionStyle: angle-bracket # 使用尖括号
|
||||
"@typescript-eslint/naming-convention":
|
||||
- warn
|
||||
- selector: variableLike
|
||||
format: ["camelCase", "UPPER_CASE", "PascalCase", "snake_case"]
|
||||
leadingUnderscore: "allow"
|
||||
trailingUnderscore: "allow"
|
||||
"@typescript-eslint/no-non-null-assertion": warn
|
||||
# Vue
|
||||
"vue/multi-word-component-names": off
|
||||
vue/multi-word-component-names: off
|
||||
vue/valid-template-root: off
|
||||
overrides:
|
||||
- files: ["*.ts"]
|
||||
extends: standard-with-typescript
|
||||
# 将此处规则 copy 到下面的 .vue 文件的 rules 中
|
||||
rules: &typescript-rules
|
||||
import/order:
|
||||
- error
|
||||
- groups:
|
||||
- builtin
|
||||
- external
|
||||
- [internal, parent, sibling, index]
|
||||
- unknown
|
||||
newlines-between: always
|
||||
alphabetize:
|
||||
order: asc
|
||||
caseInsensitive: true
|
||||
"@typescript-eslint/indent": off
|
||||
"@typescript-eslint/quotes":
|
||||
- error
|
||||
- double
|
||||
"@typescript-eslint/semi":
|
||||
- error
|
||||
- always
|
||||
"@typescript-eslint/comma-dangle":
|
||||
- error
|
||||
- always-multiline
|
||||
"@typescript-eslint/space-before-function-paren":
|
||||
- error
|
||||
- anonymous: always
|
||||
named: never
|
||||
asyncArrow: always
|
||||
"@typescript-eslint/member-delimiter-style":
|
||||
- error
|
||||
- multiline:
|
||||
delimiter: semi
|
||||
requireLast: true
|
||||
"@typescript-eslint/no-import-type-side-effects": error
|
||||
"@typescript-eslint/strict-boolean-expressions": off
|
||||
"@typescript-eslint/consistent-type-assertions":
|
||||
- warn
|
||||
- assertionStyle: angle-bracket # 使用尖括号
|
||||
"@typescript-eslint/naming-convention":
|
||||
- warn
|
||||
- selector: variableLike
|
||||
format: [camelCase, UPPER_CASE, PascalCase, snake_case]
|
||||
leadingUnderscore: allow
|
||||
trailingUnderscore: allow
|
||||
"@typescript-eslint/no-non-null-assertion": warn
|
||||
"@typescript-eslint/no-misused-promises": off
|
||||
- files: ["*.vue"]
|
||||
parser: vue-eslint-parser
|
||||
parserOptions:
|
||||
parser: "@typescript-eslint/parser"
|
||||
extraFileExtensions: [.vue]
|
||||
rules:
|
||||
<<: *typescript-rules
|
||||
- files: [package.json, tsconfig.json]
|
||||
parser: jsonc-eslint-parser
|
||||
rules:
|
||||
jsonc/sort-array-values:
|
||||
- error
|
||||
- pathPattern: .*
|
||||
order:
|
||||
type: asc
|
||||
jsonc/sort-keys:
|
||||
- error
|
||||
- pathPattern: ^$
|
||||
order:
|
||||
- name
|
||||
- version
|
||||
- description
|
||||
- private
|
||||
- packageManager
|
||||
- scripts
|
||||
- lint-staged
|
||||
- keywords
|
||||
- author
|
||||
- license
|
||||
- repository
|
||||
- homepage
|
||||
- bugs
|
||||
- dependencies
|
||||
- devDependencies
|
||||
- files: ["*.yaml", "*.yml"]
|
||||
parser: yaml-eslint-parser
|
||||
parserOptions:
|
||||
defaultYAMLVersion: "1.2"
|
||||
rules:
|
||||
yml/indent:
|
||||
- error
|
||||
- 2
|
||||
yml/no-multiple-empty-lines: error
|
||||
yml/key-spacing: error
|
||||
yml/quotes:
|
||||
- error
|
||||
- prefer: double
|
||||
avoidEscape: true
|
||||
yml/sort-keys:
|
||||
- error
|
||||
- pathPattern: ^$
|
||||
order:
|
||||
- root
|
||||
- env
|
||||
- extends
|
||||
- parserOptions
|
||||
- globals
|
||||
- rules
|
||||
- overrides
|
||||
- pathPattern: ^rules$
|
||||
order:
|
||||
type: asc
|
||||
|
||||
26
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -4,9 +4,6 @@ title: "[Bug] "
|
||||
labels:
|
||||
- BUG
|
||||
- Question
|
||||
- 待处理
|
||||
assignees:
|
||||
- BTMuli
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
@@ -17,24 +14,13 @@ body:
|
||||
- 复现流程
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Issue重复性检查
|
||||
description: 我确认没有查找过相关的 Issue
|
||||
label: Issue Check
|
||||
options:
|
||||
- label: 我确认没有查找过相关的 Issue
|
||||
- label: 我确认查找过相关的 Issue
|
||||
required: false
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: 问题必要性检查
|
||||
description: 我确认这个问题是无用且不必要的
|
||||
options:
|
||||
- label: 我确认这个问题是无用且不必要的
|
||||
- label: 我确认这个问题是影响使用的
|
||||
required: false
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: 提问的艺术
|
||||
description: 我没有阅读过[提问的艺术](https://github.com/betaseeker/How-To-Ask-Questions)
|
||||
options:
|
||||
- label: 我没有阅读过[提问的艺术](https://github.com/betaseeker/How-To-Ask-Questions)
|
||||
- label: 我阅读过[提问的艺术](https://github.com/betaseeker/How-To-Ask-Questions)
|
||||
required: false
|
||||
- type: textarea
|
||||
id: description
|
||||
@@ -49,7 +35,7 @@ body:
|
||||
attributes:
|
||||
label: 当前使用版本
|
||||
description: 请填写当前使用版本
|
||||
placeholder: 如 Alpha v0.2.0
|
||||
placeholder: 如 Beta v0.3.4
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
@@ -67,4 +53,4 @@ body:
|
||||
description: 请填写其他信息
|
||||
placeholder: 请填写其他信息
|
||||
validations:
|
||||
required: true
|
||||
required: false
|
||||
|
||||
24
.github/ISSUE_TEMPLATE/enhancement.yml
vendored
@@ -3,9 +3,6 @@ description: 提交新功能请求
|
||||
title: "[Feat] "
|
||||
labels:
|
||||
- 新特性
|
||||
- 待处理
|
||||
assignees:
|
||||
- BTMuli
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
@@ -16,24 +13,13 @@ body:
|
||||
- 当前使用版本
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Issue重复性检查
|
||||
description: 我确认没有查找过相关的 Issue
|
||||
label: Issue Check
|
||||
options:
|
||||
- label: 我确认没有查找过相关的 Issue
|
||||
- label: 我确认查找过相关的 Issue
|
||||
required: false
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: 功能必要性检查
|
||||
description: 我确认这个功能是无用且不必要的
|
||||
options:
|
||||
- label: 我确认这个功能是无用且不必要的
|
||||
- label: 我确认这个问题是影响使用的
|
||||
required: false
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: 提问的艺术
|
||||
description: 我没有阅读过[提问的艺术](https://github.com/betaseeker/How-To-Ask-Questions)
|
||||
options:
|
||||
- label: 我没有阅读过[提问的艺术](https://github.com/betaseeker/How-To-Ask-Questions)
|
||||
- label: 我阅读过[提问的艺术](https://github.com/betaseeker/How-To-Ask-Questions)
|
||||
required: false
|
||||
- type: textarea
|
||||
id: description
|
||||
@@ -48,7 +34,7 @@ body:
|
||||
attributes:
|
||||
label: 当前使用版本
|
||||
description: 请填写当前使用版本
|
||||
placeholder: 如 Alpha v0.2.0
|
||||
placeholder: 如 Beta v0.3.4
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
7
.github/ISSUE_TEMPLATE/todo.yml
vendored
@@ -2,10 +2,7 @@ name: 开发目标(开发人员)
|
||||
description: 用于开发人员 Issue 的模板
|
||||
title: "[TODO] "
|
||||
labels:
|
||||
- 计划中
|
||||
- 新特性
|
||||
assignees:
|
||||
- BTMuli
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
@@ -28,7 +25,7 @@ body:
|
||||
attributes:
|
||||
label: 预期版本
|
||||
description: 请填写预期版本
|
||||
placeholder: 如 Alpha v0.2.0
|
||||
placeholder: 如 Beta v0.3.x
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
@@ -36,7 +33,7 @@ body:
|
||||
attributes:
|
||||
label: 当前提交
|
||||
description: 请填写当前提交
|
||||
placeholder: 如 0.2.0
|
||||
placeholder: 如 Beta v0.3.4
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
43
.github/workflows/build.yml
vendored
@@ -11,11 +11,20 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [windows-latest]
|
||||
|
||||
platform: [windows-latest, macos-latest]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Rust setup
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Rust cache
|
||||
uses: swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: "./src-tauri -> target"
|
||||
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
@@ -23,23 +32,12 @@ jobs:
|
||||
- name: setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8.6.7
|
||||
- name: Install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Restore Cargo dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/src-tauri/.cargo/bin/
|
||||
~/src-tauri/.cargo/registry/index/
|
||||
~/src-tauri/.cargo/registry/cache/
|
||||
~/src-tauri/.cargo/git/db/
|
||||
~/src-tauri/target/
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('src-tauri/Cargo.lock') }}
|
||||
restore-keys: ${{ runner.os }}-cargo-
|
||||
version: 8.10.5
|
||||
- name: Install frontend dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- uses: tauri-apps/tauri-action@dev
|
||||
run: pnpm install
|
||||
|
||||
- name: Build app
|
||||
uses: tauri-apps/tauri-action@dev
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
@@ -47,6 +45,11 @@ jobs:
|
||||
with:
|
||||
tagName: v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version
|
||||
releaseName: v__VERSION__-beta
|
||||
releaseBody: https://github.com/BTMuli/Tauri.Genshin/releases/tag/v__VERSION__
|
||||
releaseBody: |
|
||||
> Windows 平台用户建议通过微软应用商店下载,MacOS 平台仅在此发布,Linux 平台暂不支持。
|
||||
|
||||
<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"/>
|
||||
</a>
|
||||
releaseDraft: true
|
||||
prerelease: false
|
||||
|
||||
5
.github/workflows/qodana_code_quality.yml
vendored
@@ -1,7 +1,10 @@
|
||||
name: Qodana
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
qodana:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -5,4 +5,4 @@ useTabs: false
|
||||
tabWidth: 2
|
||||
bracketSpacing: true
|
||||
endOfLine: auto
|
||||
trailingComma: "all"
|
||||
trailingComma: all
|
||||
|
||||
@@ -8,4 +8,4 @@ plugins:
|
||||
- stylelint-prettier
|
||||
- stylelint-order
|
||||
rules:
|
||||
"prettier/prettier": true
|
||||
prettier/prettier: true
|
||||
|
||||
121
CHANGELOG.md
@@ -2,12 +2,129 @@
|
||||
Author: 目棃
|
||||
Description: CHANGELOG
|
||||
Date: 2023-09-08
|
||||
Update: 2023-09-27
|
||||
Update: 2023-11-28
|
||||
---
|
||||
|
||||
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2023-09-08 09:45:17 `
|
||||
>
|
||||
> 更新于 `2023-09-27 08:41:43`
|
||||
> 更新于 `2023-11-28 15:18:27`
|
||||
|
||||
## [0.3.6](https://github.com/BTMuli/TeyvatGuide/releases/v0.3.6) (2023-11-25)
|
||||
|
||||
### Feat
|
||||
|
||||
- 应用:实装 `device_fp`,有效降低 `1034` 错误 [`#58`](https://github.com/BTMuli/TeyvatGuide/issues/58)
|
||||
- 首页:今日素材组件添加留影叙佳期入口,角色生日时颜色变更 [`#61`](https://github.com/BTMuli/TeyvatGuide/issues/61)
|
||||
- 组件:优化 showConfirm 组件 input 模式下的体验
|
||||
- 成就:支持单个成就完成状态修改 [`#60`](https://github.com/BTMuli/TeyvatGuide/issues/60)
|
||||
- 成就:支持隐藏已完成成就 [`#19`](https://github.com/BTMuli/TeyvatGuide/issues/19)
|
||||
- 角色:角色详情页 UI 迭代,支持角色卡片分享 [`#20`](https://github.com/BTMuli/TeyvatGuide/issues/20)
|
||||
|
||||
### Fix
|
||||
|
||||
- JSBridge:修复窗口关闭后无法再次创建的问题
|
||||
- JSBridge:修复保存图片默认路径错误
|
||||
- JSBridge:调整 closePage 逻辑
|
||||
- 应用:在生成分享图时忽略某些元素
|
||||
- 应用:完善数据库检测机制 [`#62`](https://github.com/BTMuli/TeyvatGuide/issues/62)
|
||||
- JSBridge:应用启动时关闭隐藏的子窗口
|
||||
- 应用:完善登录态检测机制
|
||||
|
||||
### Change
|
||||
|
||||
- 应用:米游社 salt 版本更新到 2.63.1
|
||||
- 咨讯:大别野版块不再忽略咨讯区
|
||||
- 分享:提高生成分享图的清晰度
|
||||
- 成就:调整完成 icon 的颜色
|
||||
- 组件:增加素材日历组件 overlay 国家 icon 清晰度
|
||||
- 成就:重构成就页面代码,优化性能
|
||||
|
||||
## [0.3.5](https://github.com/BTMuli/TeyvatGuide/releases/v0.3.5) (2023-11-11)
|
||||
|
||||
### Feat
|
||||
|
||||
- 资源:更新至 4.2 版本 [`#57`](https://github.com/BTMuli/TeyvatGuide/issues/57)
|
||||
- 祈愿:支持 UIGF v2.4 [`#59`](https://github.com/BTMuli/TeyvatGuide/issues/59)
|
||||
|
||||
### Fix
|
||||
|
||||
- JSBridge:修复图片保存失败 [`#56`](https://github.com/BTMuli/TeyvatGuide/issues/56)
|
||||
- JSBridge: 调整 hideSideBar 逻辑 [`a474b962`](https://github.com/BTMuli/TeyvatGuide/commit/a474b962)
|
||||
- 组件:修复 `snackbar` 组件被 `overlay` 遮挡问题 [`db36d18d`](https://github.com/BTMuli/TeyvatGuide/commit/db36d18d)
|
||||
- 数据库:更新数据库时同时更新 `buildTime` [`c1a7e844`](https://github.com/BTMuli/TeyvatGuide/commit/c1a7e844)
|
||||
|
||||
### Change
|
||||
|
||||
- JSBridge:留影叙佳期入口改为工具箱入口 [`149c7b3f`](https://github.com/BTMuli/TeyvatGuide/commit/149c7b3f)
|
||||
- Post:调整基准背景色 [`e6eaa2e2`](https://github.com/BTMuli/TeyvatGuide/commit/e6eaa2e2)
|
||||
- 组件:适应游戏 UI 变更 `confirm` 组件样式 [`7a060a71`](https://github.com/BTMuli/TeyvatGuide/commit/7a060a71)
|
||||
- API:更新祈愿记录获取 `endpoint` [`8037b635`](https://github.com/BTMuli/TeyvatGuide/commit/8037b635)
|
||||
|
||||
## [0.3.4](https://github.com/BTMuli/TeyvatGuide/releases/v0.3.4) (2023-10-28)
|
||||
|
||||
### Feat
|
||||
|
||||
- 应用:Awesome Tauri,[`tauri-apps/awesome-tauri#226`](https://github.com/tauri-apps/awesome-tauri/pull/226)
|
||||
- 应用:支持 MacOS 平台 [`#53`](https://github.com/BTMuli/TeyvatGuide/pull/53)
|
||||
- 应用:实装米游社 JSBridge,支持战绩、签到、酒馆、留影叙佳期等功能 [`#47`](https://github.com/BTMuli/TeyvatGuide/issues/47)
|
||||
- 导出:设置默认导出文件名称
|
||||
- 应用:支持缓存检测&清理 [`#55`](https://github.com/BTMuli/TeyvatGuide/issues/55)
|
||||
- 帖子:展示更多相关信息 [`79fd18ea`](https://github.com/BTMuli/TeyvatGuide/commit/79fd18ea)
|
||||
|
||||
### Fix
|
||||
|
||||
- 应用:窗口创建逻辑重构 [`1914261e`](https://github.com/BTMuli/TeyvatGuide/commit/1914261e)
|
||||
- 分享:修复含视频分享图生成异常 [`#54`](https://github.com/BTMuli/TeyvatGuide/issues/54)
|
||||
- 应用:更新检测上移到应用初始化 [`#45`](https://github.com/BTMuli/TeyvatGuide/issues/45)
|
||||
- 应用:将部分未更正的 `Tauri.Genshin` 改为 `Teyvat Guide`
|
||||
- 应用:`v-select` 样式适应主题变更 [`3db8008f`](https://github.com/BTMuli/TeyvatGuide/commit/3db8008f)
|
||||
- 应用:修复关闭卡顿 [`d4295c7d`](https://github.com/BTMuli/TeyvatGuide/commit/d4295c7d)
|
||||
|
||||
### Change
|
||||
|
||||
- 角色:对获取到的数据进行排序 [`0d4fdecd`](https://github.com/BTMuli/TeyvatGuide/commit/0d4fdecd)
|
||||
- 组件:Confirm 组件渲染调整 `v-if` -> `v-show` [`9be40181`](https://github.com/BTMuli/TeyvatGuide/commit/9be40181)
|
||||
- 设置:删除数据库完整性检测,隐藏数据库重置 [`5992567d`](https://github.com/BTMuli/TeyvatGuide/commit/5992567d)
|
||||
- 极验:移除极验验证相关代码 [`84b98e4a`](https://github.com/BTMuli/TeyvatGuide/commit/84b98e4a)
|
||||
- 战绩:角色数据添加 `title` 属性,展示部分角色信息 [`043fda9e`](https://github.com/BTMuli/TeyvatGuide/commit/043fda9e)
|
||||
- 重构:对基本 `Response` 类型进行重构 [`9a221f9b`](https://github.com/BTMuli/TeyvatGuide/commit/9a221f9b)
|
||||
- 重构:对米游社帖子结构化类型进行重构 [`ecb0f1a7`](https://github.com/BTMuli/TeyvatGuide/commit/ecb0f1a7)
|
||||
|
||||
FullCommits: [`v0.3.3...v0.3.4`](https://BTMuli/TeyvatGuide/compare/v0.3.3...v0.3.4)
|
||||
|
||||
## [0.3.3](https://github.com/BTMuli/TeyvatGuide/releases/v0.3.3) (2023-10-19)
|
||||
|
||||
### Feat
|
||||
|
||||
- 应用:支持含视频帖子分享图生成 [`#44`](https://github.com/BTMuli/TeyvatGuide/issues/44)
|
||||
- 帖子:新增对于大别野卡片 `VillaCard` 的解析渲染
|
||||
- 应用:公告页样式美化
|
||||
- 应用:采取动态路由,提高加载速度
|
||||
- 角色:完善深色模式角色详情页样式
|
||||
|
||||
### Fix
|
||||
|
||||
- 深渊:默认刷新两期 [`#48`](https://github.com/BTMuli/TeyvatGuide/issues/48)
|
||||
- 成就:修复部分成就版本错误 [`DGP-Studio/Snap.Hutao#996`](https://github.com/BTMuli/TeyvatGuide/commit/fdfcc70b)
|
||||
- 应用:完善 DeepLink 处理
|
||||
- 应用:完善 Cookie,BriefInfo 数据获取
|
||||
- 应用:数据库链接保持开启,[`#46`](https://github.com/BTMuli/TeyvatGuide/issues/46)
|
||||
- 组件:修复 `showConfirm` 文字过长时溢出容器问题 [`358255d5`](https://github.com/BTMuli/TeyvatGuide/commit/358255d5)
|
||||
- 应用:更改弹窗弹出机制 [`#45`](https://github.com/BTMuli/TeyvatGuide/issues/45)
|
||||
- 数据:补充 4.1 版本缺漏数据
|
||||
|
||||
### Change
|
||||
|
||||
- 图像:未登录时的默认头像变更 [`2cbac71b`](https://github.com/BTMuli/TeyvatGuide/commit/2cbac71b)
|
||||
- 成就:添加 Finish Icon [`96ab38b9`](https://github.com/BTMuli/TeyvatGuide/commit/96ab38b9)
|
||||
- 应用:浅色主题样式调整
|
||||
- 帖子:未知结构化数据类型样式调整 [`adc96b76`](https://github.com/BTMuli/TeyvatGuide/commit/adc96b76)
|
||||
- 应用:重构创建帖子子窗口代码
|
||||
- 应用:咨讯页路由变更
|
||||
- 组件:`showSnackbar` 样式调整
|
||||
- 应用:重构咨讯页代码
|
||||
|
||||
FullCommits: [`v0.3.2...v0.3.3`](https://BTMuli/TeyvatGuide/compare/v0.3.2...v0.3.3)
|
||||
|
||||
## [0.3.2](https://github.com/BTMuli/TeyvatGuide/releases/v0.3.2) (2023-9-27)
|
||||
|
||||
|
||||
77
README.md
@@ -2,36 +2,26 @@
|
||||
Author: 目棃
|
||||
Description: 说明文档
|
||||
Date: 2023-03-05
|
||||
Update: 2023-09-27
|
||||
Update: 2023-11-15
|
||||
---
|
||||
|
||||
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2023-03-05 14:41:55`
|
||||
>
|
||||
> 更新于 `2023-09-27 08:43:41`
|
||||
> 更新于 `2023-11-15 21:01:51`
|
||||
|
||||
 
|
||||
|
||||
  
|
||||
  
|
||||
|
||||
<div style="width: 100%; text-align: center; margin: 0 auto;">
|
||||
<img alt="icon" src="https://s2.loli.net/2023/10/19/Y5DpBQRy3usLHEb.png" />
|
||||
</div>
|
||||
|
||||
# Teyvat Guide
|
||||
|
||||
基于 Tauri 的原神助手应用。
|
||||
基于 Tauri 的原神工具应用,支持 Windows 和 MacOS 平台。
|
||||
|
||||
A Genshin Impact assistant app based on Tauri.
|
||||
|
||||
## 声明 / Declaration
|
||||
|
||||
本项目仅供个人学习交流使用。请勿用于任何商业或违法违规用途。
|
||||
|
||||
本项目涉及到的隐私数据,如 Cookie、Token 等,仅用于获取相关数据,不会被上传至任何服务器。
|
||||
|
||||
深渊页面的上传功能,采用的是 [Hutao API](https://hut.ao/zh/development/platform.html) 提供的接口,仅上传如下数据:
|
||||
|
||||
- 用户的游戏 UID
|
||||
- 用户的深境螺旋记录
|
||||
- 用户的角色信息及其装备的武器和圣遗物信息
|
||||
|
||||
该功能为用户主动上传,不会在用户不知情的情况下上传数据。
|
||||
Game Tool for Genshin Impact player, supports Windows and MacOS.
|
||||
|
||||
## 下载 / Download
|
||||
|
||||
@@ -41,18 +31,13 @@ A Genshin Impact assistant app based on Tauri.
|
||||
<img src="https://get.microsoft.com/images/zh-cn%20dark.svg" alt="download"/>
|
||||
</a>
|
||||
|
||||
> 不推荐通过 GitHub Release 下载,但仍保留了该功能。
|
||||
> MacOS 用户可以通过 Github Release 下载
|
||||
|
||||
[](https://github.com/BTMuli/TeyvatGuide/releases/latest)
|
||||
|
||||
## 技术栈 / Tech Stack
|
||||
## 仓库概况 / Repo Stats
|
||||
|
||||
- [Tauri](https://github.com/tauri-apps/tauri)
|
||||
- [Vue3](https://github.com/vuejs/core)
|
||||
- [Vite](https://github.com/vitejs/vite)
|
||||
- [TypeScript](https://github.com/microsoft/TypeScript)
|
||||
- [Vuetify](https://github.com/vuetifyjs/vuetify)
|
||||
- [Echarts](https://echarts.apache.org/zh/index.html)
|
||||

|
||||
|
||||
## 功能 / Features
|
||||
|
||||
@@ -74,16 +59,20 @@ A Genshin Impact assistant app based on Tauri.
|
||||
- Wiki 功能:
|
||||
|
||||
- [x] 深渊数据库(Hutao API)
|
||||
- [x] 角色数据库(开发中)
|
||||
- [x] 武器数据库(开发中)
|
||||
- [x] 卡牌数据库(开发中)
|
||||
- [ ] 角色数据库(开发中)
|
||||
- [ ] 武器数据库(开发中)
|
||||
- [ ] 卡牌数据库(开发中)
|
||||
|
||||
- 应用功能:
|
||||
- [x] 浅色/深色主题切换
|
||||
- [x] 米游社 JSBridge
|
||||
|
||||
## 仓库概况 / Repo Stats
|
||||
## 贡献者 / Contributors
|
||||
|
||||

|
||||
- [BTMuli](https://github.com/BTMuli)
|
||||
- [舰队的偶像岛风酱!](https://github.com/frg2089)
|
||||
- [jerry765](https://github.com/jerry765)
|
||||
- [AuroraZiling](https://github.com/AuroraZiling)
|
||||
|
||||
## UI 参考 / UI Reference
|
||||
|
||||
@@ -97,13 +86,21 @@ A Genshin Impact assistant app based on Tauri.
|
||||
- Changelog: [CHANGELOG](CHANGELOG.md)
|
||||
- 资源来源:[项目资源说明](docs/项目资源说明.md)
|
||||
- UIAF:[UIAF v1.1](docs/UIAF.md)
|
||||
- UIGF:[UIGF v2.3](docs/UIGF.md)
|
||||
- UIGF:[UIGF v2.4](docs/UIGF.md)
|
||||
|
||||
## 贡献者 / Contributors
|
||||
## 特定项目 / Special Project
|
||||
|
||||
- [BTMuli](https://github.com/BTMuli)
|
||||
- [舰队的偶像岛风酱!](https://github.com/frg2089)
|
||||
- [jerry765](https://github.com/jerry765)
|
||||
- [MuCli](https://github.com/BTMuli/MuCli):基于 NodeJS 的命令行工具,用于生成项目文档。
|
||||
- [TGAssistant](https://github.com/BTMuli/TGAssistant):Teyvat Guide 的资源获取、解析、处理仓库。
|
||||
- [WhiteTea](https://github.com/BTMuli/WhiteTea):Github Bot,(半)自动化处理 Teyvat Guide 的 Issue 和 Pull Request。
|
||||
|
||||
## 技术栈 / Tech Stack
|
||||
|
||||
- [Tauri](https://github.com/tauri-apps/tauri)
|
||||
- [Vue3](https://github.com/vuejs/core)
|
||||
- [Vite](https://github.com/vitejs/vite)
|
||||
- [Vuetify](https://github.com/vuetifyjs/vuetify)
|
||||
- [Echarts](https://echarts.apache.org/zh/index.html)
|
||||
|
||||
## 协议 / License
|
||||
|
||||
@@ -111,6 +108,8 @@ A Genshin Impact assistant app based on Tauri.
|
||||
|
||||
应用版本号遵循 [Semantic Versioning 2.0.0](https://semver.org/lang/zh-CN/) 规范。
|
||||
|
||||
隐私政策:[Privacy](https://app.btmuli.ink/docs/privacy.html)
|
||||
|
||||
## 鸣谢 / Thanks
|
||||
|
||||
本项目在开发过程中参考了诸多相关开源项目,特此鸣谢。
|
||||
@@ -122,3 +121,7 @@ A Genshin Impact assistant app based on Tauri.
|
||||
- [gs-helper](https://github.com/vikiboss/gs-helper)
|
||||
- [paimon-moe](https://github.com/MadeBaruna/paimon-moe)
|
||||
- [Adachi-BOT](https://github.com/Arondight/Adachi-BOT)
|
||||
|
||||
感谢 JetBrains 提供的开源许可证。
|
||||
|
||||
[](https://www.jetbrains.com/?from=TeyvatGuide)
|
||||
|
||||
364
docs/UIGF.md
@@ -1,50 +1,77 @@
|
||||
---
|
||||
Author: 目棃
|
||||
Date: 2023-04-07
|
||||
Description: UIGF v2.3 Backup
|
||||
Update: 2023-04-07
|
||||
Description: UIGF v2.4 Backup
|
||||
Date: 2023-11-15
|
||||
Update: 2023-11-15
|
||||
---
|
||||
|
||||
> 本文档 [`Front-matter`](https://github.com/BTMuli/Mucli#FrontMatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于`2023-04-07 19:51:40`
|
||||
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2023-11-15 20:58:36`
|
||||
>
|
||||
> 更新于 `2023-04-07 19:51:40`
|
||||
> 更新于 `2023-11-15 20:58:36`
|
||||
>
|
||||
> 本文档为 [UIGF v2.4](https://github.com/UIGF-org/UIGF-org.github.io/blob/main/docs/zh/standards/UIGF.md) 的备份,仅供参考。
|
||||
|
||||
> 本文档为 [`UIGF`](https://github.com/UIGF-org/UIGF-org.github.io/blob/main/docs/zh/standards/UIGF-pre-release.md) 的备份。
|
||||
# 统一可交换抽卡记录标准 v2.4
|
||||
|
||||
# 统一可交换祈愿记录标准 v2.3
|
||||
> Uniformed Interchangeable GachaLog Format standard (UIGF) v2.4 <Badge text="Current" type="message" />
|
||||
>
|
||||
> ::: warning UIGF 标准使用声明
|
||||
> 应用必须在同时支持 UIGF 数据格式**导入**和**导出**功能并在相关功能区域或文档中提供跳转至 [UIGF-Org](https://uigf.org) 的超链接后声明支持 UIGF 格式
|
||||
|
||||
> Uniformed Interchangeable GachaLog Format standard (UIGF) v2.3
|
||||
仅包含导入功能降低了用户数据可流通性,且将数据至于用户不可控的风险中,不符合 UIGF-Org 设计的初衷。
|
||||
:::
|
||||
|
||||
## 更新记录
|
||||
|
||||
| 版本 | 说明 | 兼容 |
|
||||
| ------ | ------------------------------------------------ | -------------- |
|
||||
| `v2.0` | 首个正式版本 | v2.0 |
|
||||
| `v2.1` | 简化了部分语言表述,与 v2.0 在数据格式上完全一致 | v2.1 and lower |
|
||||
| `v2.2` | 新增 `info.export_timestamp` 填充 UNIX 时间戳 | v2.2 and lower |
|
||||
| `v2.3` | 扩充至非中文语境,使用 Json Schema 表述 | v2.3 and lower |
|
||||
| 版本 | 说明 | 兼容 |
|
||||
| ----------------------------- | ---------------------------------------------------------- | -------------- |
|
||||
| `v2.0` | 首个正式版本 | v2.0 |
|
||||
| `v2.1` | 简化了部分语言表述,与 v2.0在数据格式上完全一致 | v2.1 and lower |
|
||||
| [`v2.2`](UIGF-legacy-v2.2.md) | 新增 `info.export_timestamp` 填充 UNIX 时间戳 | v2.2 and lower |
|
||||
| [`v2.3`](UIGF-legacy-v2.3.md) | 扩充至非中文语境,使用 Json Schema 表述。移除了 Excel 格式 | v2.3 and lower |
|
||||
| `v2.4` | 新增 `info.region_time_zone` 支持时区处理 | v2.4 and lower |
|
||||
|
||||
## Id
|
||||
### v2.4 更新内容
|
||||
|
||||
原神的祈愿记录物品内包含了一项较为特殊的字段: `id` ,该值在 1.3 版本后加入
|
||||
所以**先前查询出的物品**若无特殊兼容性修改则不会包含相应的 `id`
|
||||
App 导出 UIGF 时
|
||||
- 国际化兼容性增强
|
||||
- 在 `info` 对象中新增了 `region_time_zone` 字段
|
||||
|
||||
- 需要确保每个物品的 `id` 的有效性。
|
||||
- 从最后一个自带有效 `id` 的物品开始,向前(相对于时间)依次递减 `id` 的值,每次递减的值应保持为 `1`
|
||||
## `info` 字段说明
|
||||
|
||||
导入 UIGF 到 App 时
|
||||
### `region_time_zone`
|
||||
|
||||
- App 不应假设所有的 `gacha_item` 都有有效的 `id` 值
|
||||
- App 应具有处理 `id` 字段为 `null`或 `` 空字符串的能力
|
||||
由于在获取祈愿记录时得到的`time`为服务器时间,为了准确判断时间的时区偏移,引入此字段。
|
||||
|
||||
## GachaType
|
||||
与 SRGF 不同,由于无法直接从服务器获取`region_time_zone`,在导出方未提供此字段时,需要根据 `uid` 进行推断。
|
||||
|
||||
祈愿包含了会共享保底与概率的卡池,所以需要一个额外的字段来界定
|
||||
我们在`UIGF`的所有格式中注入了`uigf_gacha_type`字段
|
||||
在导出到`UIGF`格式时需要注意添加对应的`uigf_gacha_type`字段
|
||||
#### 映射关系
|
||||
|
||||
### 映射关系
|
||||
| `uid`首个字符 | `region_time_zone` | 游戏服务器 |
|
||||
| ------------- | ------------------ | --------------------------------- |
|
||||
| `'6'` | `-5` | os_usa |
|
||||
| `'7'` | `1` | os_euro |
|
||||
| 剩余情况 | `8` | os_cht, os_asia, cn_gf01, cn_qd01 |
|
||||
|
||||
App 不应假定 `region_time_zone` 的值为上表中给出的值,应具有处理非标准 `region_time_zone` 值的能力。
|
||||
若 `region_time_zone` 的值与 `uid` 推断结果不一致,则优先选择 `region_time_zone` 给出的值。
|
||||
|
||||
## `list` 字段说明
|
||||
|
||||
### `id`
|
||||
|
||||
物品内包含了一项较为特殊的字段: `id`,为原神官方 API 中包含的,代表每条抽卡记录唯一性的 `id`。App 导出 UIGF 时
|
||||
|
||||
- 需要确保每个物品都有一个有效的唯一 `id`
|
||||
- 若有记录中不包含`id`,则应从下一个自带有效 `id` 的物品开始,为每条缺失`id`字段的数据补全`id`。
|
||||
赋值数据向前(时间排序)依次递减,每次递减的值应保持为 `1`
|
||||
|
||||
### `gacha_type`
|
||||
|
||||
由于存在会共享保底与概率的卡池,所以需要一个额外的字段来界定
|
||||
我们在 `UIGF` 的所有格式中注入了 `uigf_gacha_type` 字段
|
||||
在导出到 `UIGF` 格式时需要注意添加对应的 `uigf_gacha_type` 字段
|
||||
|
||||
#### 映射关系
|
||||
|
||||
| `uigf_gacha_type` | `gacha_type` |
|
||||
| ----------------- | -------------- |
|
||||
@@ -53,225 +80,112 @@ App 导出 UIGF 时
|
||||
| `301` | `301` or `400` |
|
||||
| `302` | `302` |
|
||||
|
||||
## Json 格式
|
||||
### `item_id`
|
||||
|
||||
> Uniformed Interchangeable GachaLog Format standard of Json (UIGF.J)
|
||||
> Json 格式 由于 与从官方接口获取到的格式一致
|
||||
> 更便于各 App 的导入与导出,我们也在此做出规范
|
||||
> 该格式应仅用于各 App 间的数据互通
|
||||
物品游戏内ID,你可以通过 [UIGF API](../API.md) 获取这一数据
|
||||
|
||||
### 导出的格式
|
||||
## Json Schema
|
||||
|
||||
> UIGF-Org 提供[Json Schema](/schema/uigf.json) 用于验证
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"title": "UIGF object",
|
||||
"properties": {
|
||||
"info": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uid": {
|
||||
"type": "string",
|
||||
"title": "Uid",
|
||||
"description": "Uid"
|
||||
},
|
||||
"lang": {
|
||||
"type": "string",
|
||||
"title": "Language",
|
||||
"description": "语言 ISO 3166"
|
||||
},
|
||||
"uigf_version": {
|
||||
"type": "string",
|
||||
"title": "UIGF Version",
|
||||
"description": "UIGF 版本号"
|
||||
},
|
||||
"export_timestamp": {
|
||||
"type": "number",
|
||||
"title": "Export Timestamp",
|
||||
"description": "导出时间戳(秒)"
|
||||
},
|
||||
"export_time": {
|
||||
"type": "string",
|
||||
"description": "导出时间",
|
||||
"format": "date-time",
|
||||
"pattern": "yyyy-MM-dd HH:mm:ss",
|
||||
"title": "Export Time"
|
||||
},
|
||||
"export_app": {
|
||||
"type": "string",
|
||||
"title": "Export App",
|
||||
"description": "导出应用"
|
||||
},
|
||||
"export_app_version": {
|
||||
"type": "string",
|
||||
"title": "Export App Version",
|
||||
"description": "导出应用版本"
|
||||
}
|
||||
},
|
||||
"title": "Infomation",
|
||||
"required": ["uid", "lang", "uigf_version"],
|
||||
"description": "包含导出方定义的基本信息"
|
||||
},
|
||||
"list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"root": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"info": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"gacha_type": {
|
||||
"uid": {
|
||||
"type": "string",
|
||||
"description": "祈愿类型"
|
||||
"title": "导出记录的 UID"
|
||||
},
|
||||
"item_id": {
|
||||
"lang": {
|
||||
"type": "string",
|
||||
"title": "Item Id",
|
||||
"description": "空字符串"
|
||||
"title": "语言 languagecode2-country/regioncode2"
|
||||
},
|
||||
"count": {
|
||||
"type": "string",
|
||||
"title": "Count",
|
||||
"description": "数量"
|
||||
"export_timestamp": {
|
||||
"type": "number",
|
||||
"title": "导出 UNIX 时间戳(秒)"
|
||||
},
|
||||
"time": {
|
||||
"export_time": {
|
||||
"type": "string",
|
||||
"title": "Time",
|
||||
"description": "物品获取时间",
|
||||
"pattern": "yyyy-MM-dd HH:mm:ss",
|
||||
"format": "date-time"
|
||||
"title": "导出时间",
|
||||
"description": "yyyy-MM-dd HH:mm:ss"
|
||||
},
|
||||
"name": {
|
||||
"export_app": {
|
||||
"type": "string",
|
||||
"title": "Name",
|
||||
"description": "名称"
|
||||
"title": "导出 App 名称"
|
||||
},
|
||||
"item_type": {
|
||||
"export_app_version": {
|
||||
"type": "string",
|
||||
"title": "Item Type",
|
||||
"description": "物品类型"
|
||||
"title": "导出 App 版本"
|
||||
},
|
||||
"rank_type": {
|
||||
"uigf_version": {
|
||||
"type": "string",
|
||||
"title": "Item Quality",
|
||||
"description": "物品星级"
|
||||
"title": "UIGF 版本号",
|
||||
"pattern": "v\\d+\\.\\d+"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"title": "Id",
|
||||
"description": "内部数据库Id"
|
||||
},
|
||||
"uigf_gacha_type": {
|
||||
"type": "string",
|
||||
"title": "Query Type",
|
||||
"description": "向接口查询时需要的 gacha_type"
|
||||
"region_time_zone": {
|
||||
"type": "number",
|
||||
"title": "区域时区偏移"
|
||||
}
|
||||
},
|
||||
"required": ["gacha_type", "name", "id", "uigf_gacha_type", "time"],
|
||||
"title": "Gacha Item",
|
||||
"description": "祈愿物品"
|
||||
"required": ["uid", "uigf_version"],
|
||||
"title": "UIGF 导出信息"
|
||||
},
|
||||
"title": "List",
|
||||
"description": "物品列表"
|
||||
}
|
||||
},
|
||||
"required": ["info", "list"],
|
||||
"description": "UIGF 根对象"
|
||||
"list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uigf_gacha_type": {
|
||||
"type": "string",
|
||||
"title": "UIGF 卡池类型",
|
||||
"description": "用于区分卡池类型不同,但卡池保底计算相同的物品"
|
||||
},
|
||||
"gacha_type": {
|
||||
"type": "string",
|
||||
"title": "卡池类型"
|
||||
},
|
||||
"item_id": {
|
||||
"type": "string",
|
||||
"title": "物品的内部 ID"
|
||||
},
|
||||
"count": {
|
||||
"type": "string",
|
||||
"title": "个数",
|
||||
"description": "一般为1"
|
||||
},
|
||||
"time": {
|
||||
"type": "string",
|
||||
"title": "获取物品的时间"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"title": "物品名称"
|
||||
},
|
||||
"item_type": {
|
||||
"type": "string",
|
||||
"title": "物品类型"
|
||||
},
|
||||
"rank_type": {
|
||||
"type": "string",
|
||||
"title": "物品等级"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"title": "记录内部 ID"
|
||||
}
|
||||
},
|
||||
"required": ["uigf_gacha_type", "gacha_type", "id", "item_id", "time"],
|
||||
"title": "UIGF 物品"
|
||||
},
|
||||
"title": "物品列表"
|
||||
}
|
||||
},
|
||||
"required": ["info", "list"],
|
||||
"title": "UIGF 根对象"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Excel 工作簿 (Workbook Format)
|
||||
|
||||
> Uniformed Interchangeable GachaLog Format standard of Workbook (UIGF.W)
|
||||
|
||||
### 单元格的格式
|
||||
|
||||
- 在填充单元格内的数据时,应统一转换到 `String` 字符串类型后填入
|
||||
|
||||
### 表名及内容
|
||||
|
||||
| 表名 | 内容 | 类型 | 是否必要 |
|
||||
| ------------ | -------------------------------------- | ------ | ------------------------------ |
|
||||
| 统计分析 | 统计分析内容等 | 任意 | 否 |
|
||||
| 角色活动祈愿 | `gacha_type` : `301 or 400` 的祈愿数据 | 祈愿表 | 否,但是应该导出 |
|
||||
| 武器活动祈愿 | `gacha_type` : `302` 的祈愿数据 | 祈愿表 | 否,但是应该导出 |
|
||||
| 常驻祈愿 | `gacha_type` : `200` 的祈愿数据 | 祈愿表 | 否,但是应该导出 |
|
||||
| 新手祈愿 | `gacha_type` : `100` 的祈愿数据 | 祈愿表 | 否,但是应该导出 |
|
||||
| 原始数据 | 全部祈愿数据 | 数据表 | **详见下方原始数据表结构说明** |
|
||||
|
||||
- 表的顺序可以是任意的
|
||||
- 可以隐藏部分表,防止用户随意篡改数据
|
||||
- Sheet 的名称应与游戏内祈愿记录页面显示的名称保持一致
|
||||
|
||||
> App 间应依据 `原始数据表` 的内容,来进行数据互通
|
||||
|
||||
### 祈愿表结构
|
||||
|
||||
本节内容是为了规范兼容分析类 App
|
||||
|
||||
- 表头对应的内容填充**顺序需要严格按照下方说明**排布
|
||||
- **共享保底的卡池**按祈愿类型 (`gacha_type`) 区分
|
||||
- 此类 `Sheet` 存在的目的,是为了便于用户观看与祈愿分析工具的分析
|
||||
|
||||
| 表头 | 内容 | 是否必要 |
|
||||
| -------- | ---------------------------------------- | ---------------------------- |
|
||||
| 时间 | `yyyy-MM-dd HH:mm:ss` 格式的 `time` 时间 | 是 |
|
||||
| 名称 | `name`物品名称 | 是 |
|
||||
| 物品类型 | `item_type` | 是 |
|
||||
| 星级 | `rank_type` | 是 |
|
||||
| 祈愿类型 | `gacha_type` 的转义名称 | 是,尽管部分工具不会分析此项 |
|
||||
| ... | ... | 否 |
|
||||
|
||||
> 如果你认为有必要的话,可以额外增加其他表头,但请确保表头的前几列为上表规范的内容
|
||||
> 表内的数据通常按祈愿 Id 升序或降序排列,分析 App 不应假设表内的顺序为特定的升序与降序
|
||||
|
||||
#### `gacha_type` 转义名称
|
||||
|
||||
| gacha_type | 名称 |
|
||||
| ---------- | -------------- |
|
||||
| 100 | 新手祈愿 |
|
||||
| 200 | 常驻祈愿 |
|
||||
| 301 | 角色活动祈愿 |
|
||||
| 400 | 角色活动祈愿-2 |
|
||||
| 302 | 武器活动祈愿 |
|
||||
|
||||
#### 示例
|
||||
|
||||
| 时间 | 名称 | 类别 | 星级 | 祈愿类型 | ... |
|
||||
| ------------------- | -------- | ---- | ---- | -------------- | --- |
|
||||
| 2021-02-17 18:45:09 | 以理服人 | 武器 | 3 | 角色活动祈愿-2 | ... |
|
||||
| ... | ... | ... | ... | ... | ... |
|
||||
|
||||
### 原始数据表结构
|
||||
|
||||
导出时
|
||||
|
||||
- App 在导出时应尽可能询问用户是否应包含原始数据表
|
||||
- 一旦在工作簿内包含了名为 `原始数据` 的表,即表示支持本格式
|
||||
- 该表内的内容应严格按照本格式所述填充
|
||||
- **表头的顺序需严格按照下表设置**。
|
||||
- 现有的字段采用**字典顺序**递增排序,后续新增的字段依添加的顺序排在后侧。
|
||||
- 若无特殊需求,我们建议导出所有 json 数据内包含的字段
|
||||
|
||||
导入时
|
||||
|
||||
- 强烈建议您编写不依赖于列的顺序位置便可实现导入的程序,以达到最大化的兼容。
|
||||
- 如果省略了其中某些非必要字段的值,请保持表头存在,对应的列则空置。
|
||||
|
||||
| 表头 | 是否必要 |
|
||||
| ----------------- | ---------------------------------------------------- |
|
||||
| `count` | 否,但是建议保留,不排除后续会有`count`不为 1 的情况 |
|
||||
| `gacha_type` | 是 |
|
||||
| `id` | 是,且大部分 App 按此字段排序数据 |
|
||||
| `item_id` | 否,目前官方已经弃用了此字段 |
|
||||
| `item_type` | 是 |
|
||||
| `lang` | 否,但建议保留,以便国际化 |
|
||||
| `name` | 是 |
|
||||
| `rank_type` | 否,但建议保留,以便分析 |
|
||||
| `time` | 否,但建议保留,以便分析 |
|
||||
| `uid` | 否,但建议将选择权交予用户,保留以便分析 |
|
||||
| `uigf_gacha_type` | 是 |
|
||||
|
||||
#### 示例
|
||||
|
||||
| count | gacha_type | id | item_id | item_type | lang | name | rank_type | time | uid | uigf_gacha_type |
|
||||
| ----- | ---------- | ------------------- | ------- | --------- | ----- | -------- | --------- | ------------------- | --------- | --------------- |
|
||||
| 1 | 301 | 1613556360008291100 | | 武器 | zh-cn | 以理服人 | 3 | 2021-02-17 18:45:09 | 123456789 | 301 |
|
||||
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Tauri.Genshin</title>
|
||||
<title>TeyvatGuide</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
120
package.json
@@ -1,10 +1,9 @@
|
||||
{
|
||||
"name": "TeyvatGuide",
|
||||
"description": "A Genshin Tool build with Tauri",
|
||||
"version": "0.3.6",
|
||||
"description": "Game Tool for Genshin Impact player",
|
||||
"private": true,
|
||||
"version": "0.3.2",
|
||||
"author": "BTMuli <bt-muli@outlook.com>",
|
||||
"packageManager": "pnpm@8.7.6",
|
||||
"packageManager": "pnpm@8.10.5",
|
||||
"scripts": {
|
||||
"build": "tauri build",
|
||||
"debug": "tauri build --debug",
|
||||
@@ -15,79 +14,103 @@
|
||||
"lint:code:fix": "eslint . --fix",
|
||||
"lint:style": "stylelint \"src/**/*.{vue,css}\"",
|
||||
"lint:style:fix": "pnpm lint:style --fix",
|
||||
"lint:rust:fix": "cd src-tauri && cargo fmt",
|
||||
"prettier": "prettier . --write",
|
||||
"tauri": "tauri",
|
||||
"tauri:icon": "tauri icon ./public/icon.png",
|
||||
"vite:dev": "vite dev",
|
||||
"vite:build": "vite build",
|
||||
"prepare": "husky install"
|
||||
"prepare": "husky install",
|
||||
"fix:pnpm": "pnpm add https://github.com/tauri-apps/tauri-plugin-sql#v1"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,vue}": "eslint --fix",
|
||||
"*.{vue,css}": "stylelint --fix",
|
||||
"*.{ts,vue,css,yml,json,md}": "prettier --write"
|
||||
"*.ts": [
|
||||
"eslint --fix",
|
||||
"prettier --write"
|
||||
],
|
||||
"*.vue": [
|
||||
"eslint --fix",
|
||||
"prettier --write",
|
||||
"stylelint --fix"
|
||||
],
|
||||
"*.css": [
|
||||
"prettier --write",
|
||||
"stylelint --fix"
|
||||
],
|
||||
"*.{yml,json,md}": [
|
||||
"prettier --write"
|
||||
],
|
||||
"*.rs": [
|
||||
"rustfmt"
|
||||
]
|
||||
},
|
||||
"keywords": [
|
||||
"Genshin Impact",
|
||||
"Node",
|
||||
"Tauri",
|
||||
"Typescript",
|
||||
"Vite",
|
||||
"Vuetify"
|
||||
],
|
||||
"author": "BTMuli <bt-muli@outlook.com>",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/BTMuli/TeyvatGuide.git"
|
||||
},
|
||||
"homepage": "https://github.com/BTMuli/TeyvatGuide#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/BTMuli/TeyvatGuide/issues"
|
||||
},
|
||||
"directories": {
|
||||
"doc": "docs"
|
||||
},
|
||||
"homepage": "https://github.com/BTMuli/TeyvatGuide#readme",
|
||||
"keywords": [
|
||||
"Tauri",
|
||||
"Node",
|
||||
"Typescript",
|
||||
"Vite",
|
||||
"Genshin Impact",
|
||||
"Vuetify"
|
||||
],
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/BTMuli/TeyvatGuide.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdi/font": "7.2.96",
|
||||
"@tauri-apps/api": "^1.4.0",
|
||||
"@mdi/font": "7.3.67",
|
||||
"@tauri-apps/api": "^1.5.1",
|
||||
"clipboard": "^2.0.11",
|
||||
"color-convert": "^2.0.1",
|
||||
"echarts": "^5.4.3",
|
||||
"html2canvas": "^1.4.1",
|
||||
"js-md5": "^0.7.3",
|
||||
"pinia": "^2.1.6",
|
||||
"js-md5": "^0.8.3",
|
||||
"pinia": "^2.1.7",
|
||||
"pinia-plugin-persistedstate": "^3.2.0",
|
||||
"qrcode.vue": "^3.4.1",
|
||||
"tauri-plugin-sql-api": "github:tauri-apps/tauri-plugin-sql#v1",
|
||||
"vue": "^3.3.4",
|
||||
"uuid": "^9.0.1",
|
||||
"vue": "^3.3.8",
|
||||
"vue-echarts": "^6.6.1",
|
||||
"vue-json-viewer": "^3.0.4",
|
||||
"vue-router": "^4.2.5",
|
||||
"vuetify": "^3.3.17",
|
||||
"vuetify": "^3.4.2",
|
||||
"wcag-color": "^1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^1.4.0",
|
||||
"@types/color-convert": "^2.0.1",
|
||||
"@types/js-md5": "^0.7.0",
|
||||
"@types/node": "^20.6.3",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.2",
|
||||
"@typescript-eslint/parser": "^6.7.2",
|
||||
"@vitejs/plugin-vue": "^4.3.4",
|
||||
"@vue/devtools": "^6.5.0",
|
||||
"concurrently": "^8.2.1",
|
||||
"eslint": "^8.50.0",
|
||||
"@tauri-apps/cli": "^1.5.6",
|
||||
"@types/color-convert": "^2.0.3",
|
||||
"@types/js-md5": "^0.7.2",
|
||||
"@types/node": "^20.9.1",
|
||||
"@types/uuid": "^9.0.7",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"@vitejs/plugin-vue": "^4.5.0",
|
||||
"@vue/devtools": "^6.5.1",
|
||||
"concurrently": "^8.2.2",
|
||||
"eslint": "^8.53.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-config-standard-with-typescript": "^39.1.0",
|
||||
"eslint-plugin-import": "^2.28.1",
|
||||
"eslint-plugin-n": "^16.1.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-config-standard-with-typescript": "^39.1.1",
|
||||
"eslint-plugin-import": "^2.29.0",
|
||||
"eslint-plugin-jsonc": "^2.10.0",
|
||||
"eslint-plugin-n": "^16.3.1",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-vue": "^9.17.0",
|
||||
"eslint-plugin-vue": "^9.18.1",
|
||||
"eslint-plugin-yml": "^1.10.0",
|
||||
"husky": "^8.0.3",
|
||||
"lint-staged": "^14.0.1",
|
||||
"prettier": "3.0.3",
|
||||
"stylelint": "^15.10.3",
|
||||
"jsonc-eslint-parser": "^2.4.0",
|
||||
"lint-staged": "^15.1.0",
|
||||
"prettier": "3.1.0",
|
||||
"stylelint": "^15.11.0",
|
||||
"stylelint-config-idiomatic-order": "^9.0.0",
|
||||
"stylelint-config-standard-vue": "^1.0.0",
|
||||
"stylelint-declaration-block-no-ignored-properties": "^2.7.0",
|
||||
@@ -95,7 +118,8 @@
|
||||
"stylelint-order": "^6.0.3",
|
||||
"stylelint-prettier": "^4.0.2",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^4.4.9",
|
||||
"vite-plugin-vuetify": "^1.0.2"
|
||||
"vite": "^5.0.0",
|
||||
"vite-plugin-vuetify": "^1.0.2",
|
||||
"yaml-eslint-parser": "^1.2.2"
|
||||
}
|
||||
}
|
||||
|
||||
1233
pnpm-lock.yaml
generated
BIN
public/WIKI/GCG/normal/万家灶火.webp
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
public/WIKI/GCG/normal/在地为化.webp
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
public/WIKI/GCG/normal/多莉.webp
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
public/WIKI/GCG/normal/妮露.webp
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
public/WIKI/GCG/normal/星天的花雨.webp
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
public/WIKI/GCG/normal/沙王的投影.webp
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
public/WIKI/GCG/normal/海染砗磲.webp
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
public/WIKI/GCG/normal/琴音之诗.webp
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
public/WIKI/GCG/normal/白术.webp
Normal file
|
After Width: | Height: | Size: 69 KiB |
BIN
public/WIKI/GCG/normal/酌盈剂虚.webp
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
public/WIKI/GCG/normal/风龙废墟.webp
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
public/WIKI/character/icon/10000088.webp
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
public/WIKI/character/icon/10000089.webp
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
public/WIKI/weapon/icon/11428.webp
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
public/WIKI/weapon/icon/11513.webp
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
public/icon/achievement/UI_AchievementIcon_A016_Part3.webp
Normal file
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 41 KiB |
BIN
public/source/UI/finish.webp
Normal file
|
After Width: | Height: | Size: 896 B |
BIN
public/source/UI/toolbox.webp
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
19
public/source/UI/video_play.svg
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
public/source/nameCard/bg/夏洛蒂·独家.webp
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
public/source/nameCard/bg/枫丹·浑然.webp
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
public/source/nameCard/bg/纪行·旋舞.webp
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
public/source/nameCard/bg/芙宁娜·欢宴.webp
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
public/source/nameCard/icon/夏洛蒂·独家.webp
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
public/source/nameCard/icon/枫丹·浑然.webp
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
public/source/nameCard/icon/纪行·旋舞.webp
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
public/source/nameCard/icon/芙宁娜·欢宴.webp
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
public/source/nameCard/profile/夏洛蒂·独家.webp
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
public/source/nameCard/profile/枫丹·浑然.webp
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
public/source/nameCard/profile/纪行·旋舞.webp
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
public/source/nameCard/profile/芙宁娜·欢宴.webp
Normal file
|
After Width: | Height: | Size: 50 KiB |
28
qodana.yaml
@@ -1,32 +1,32 @@
|
||||
#-------------------------------------------------------------------------------#
|
||||
# -------------------------------------------------------------------------------#
|
||||
# Qodana analysis is configured by qodana.yaml file #
|
||||
# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
|
||||
#-------------------------------------------------------------------------------#
|
||||
# -------------------------------------------------------------------------------#
|
||||
version: "1.0"
|
||||
|
||||
#Specify inspection profile for code analysis
|
||||
# Specify inspection profile for code analysis
|
||||
profile:
|
||||
name: qodana.starter
|
||||
|
||||
#Enable inspections
|
||||
#include:
|
||||
# Enable inspections
|
||||
# include:
|
||||
# - name: <SomeEnabledInspectionId>
|
||||
|
||||
#Disable inspections
|
||||
#exclude:
|
||||
# Disable inspections
|
||||
# exclude:
|
||||
# - name: <SomeDisabledInspectionId>
|
||||
# paths:
|
||||
# - <path/where/not/run/inspection>
|
||||
|
||||
#The following options are only applied in CI/CD environment
|
||||
#These options are ignored during local run
|
||||
# The following options are only applied in CI/CD environment
|
||||
# These options are ignored during local run
|
||||
|
||||
#Execute shell command before Qodana execution
|
||||
#bootstrap: sh ./prepare-qodana.sh
|
||||
# Execute shell command before Qodana execution
|
||||
# bootstrap: sh ./prepare-qodana.sh
|
||||
|
||||
#Install IDE plugins before Qodana execution
|
||||
#plugins:
|
||||
# Install IDE plugins before Qodana execution
|
||||
# plugins:
|
||||
# - id: <plugin.id> #(plugin id can be found at https://plugins.jetbrains.com)
|
||||
|
||||
#Specify Qodana linter for analysis
|
||||
# Specify Qodana linter for analysis
|
||||
linter: jetbrains/qodana-js:latest
|
||||
|
||||
1356
src-tauri/Cargo.lock
generated
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "TeyvatGuide"
|
||||
version = "0.3.2"
|
||||
description = "A Genshin Tool build with Tauri"
|
||||
authors = ["BTMuli<bt-muli@outlook.com>"]
|
||||
version = "0.3.6"
|
||||
description = "Game Tool for Genshin Impact player"
|
||||
authors = ["BTMuli <bt-muli@outlook.com>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/BTMuli/TeyvatGuide"
|
||||
edition = "2021"
|
||||
@@ -13,16 +13,30 @@ edition = "2021"
|
||||
tauri-build = { version = "1.4", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "1.4", features = ["api-all"] }
|
||||
tauri = { version = "1.4", features = [ "dialog-message", "process-exit", "fs-read-dir", "window-hide", "os-all", "clipboard-all", "dialog-open", "dialog-save", "fs-create-dir", "fs-remove-dir", "fs-write-file", "fs-remove-file", "fs-read-file", "path-all", "fs-exists", "window-close", "window-set-title", "window-unminimize", "window-show", "window-set-focus", "http-request", "shell-open"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tauri-plugin-deep-link="0.1.2"
|
||||
url = "2.4.1"
|
||||
tauri-utils = "1.5.0"
|
||||
walkdir = "2"
|
||||
|
||||
# sqlite 插件
|
||||
[dependencies.tauri-plugin-sql]
|
||||
git = "https://github.com/tauri-apps/plugins-workspace"
|
||||
branch = "v1"
|
||||
features = ["sqlite"]
|
||||
|
||||
# deep link 插件
|
||||
[dependencies.tauri-plugin-deep-link]
|
||||
version = "0.1.2"
|
||||
|
||||
# 用于打包
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
opt-level = "s"
|
||||
panic = "abort"
|
||||
|
||||
[features]
|
||||
# this feature is used for production builds or when `devPath` points to the filesystem
|
||||
# DO NOT REMOVE!!
|
||||
|
||||
9
src-tauri/rustfmt.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
# File rustfmt.toml
|
||||
# Desc Rust 格式化配置文件
|
||||
# Since Beta v0.3.3
|
||||
|
||||
max_width = 100
|
||||
tab_spaces = 2
|
||||
edition = "2018"
|
||||
use_small_heuristics = "Max"
|
||||
newline_style = "Auto"
|
||||
55
src-tauri/src/client.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
//! @file src/client.rs
|
||||
//! @desc 客户端模块,负责操作米游社客户端
|
||||
//! @since Beta v0.3.6
|
||||
|
||||
use tauri::{AppHandle, Manager, WindowBuilder, WindowUrl};
|
||||
use url::Url;
|
||||
|
||||
// 获取米游社客户端入口地址
|
||||
fn get_mhy_client_url(func: String) -> WindowUrl {
|
||||
let mut url_res: Url = "https://bbs.mihoyo.com/ys/".parse().unwrap();
|
||||
if func == "sign_in" {
|
||||
url_res = "https://webstatic.mihoyo.com/bbs/event/signin-ys/index.html?act_id=e202009291139501"
|
||||
.parse()
|
||||
.unwrap();
|
||||
} else if func == "game_record" {
|
||||
url_res =
|
||||
"https://webstatic.mihoyo.com/app/community-game-records/index.html?bbs_presentation_style=fullscreen".parse().unwrap();
|
||||
} else if func == "birthday" {
|
||||
url_res = "https://webstatic.mihoyo.com/ys/event/e20220303-birthday/index.html?activity_id=20220301153521"
|
||||
.parse()
|
||||
.unwrap();
|
||||
}
|
||||
return WindowUrl::External(url_res);
|
||||
}
|
||||
|
||||
// 操作米游社客户端
|
||||
#[tauri::command]
|
||||
pub async fn create_mhy_client(handle: AppHandle, func: String, url: String) {
|
||||
let mut mhy_window_config = handle.config().tauri.windows.get(1).unwrap().clone();
|
||||
// 如果没有传入 url 参数,则使用默认的米游社客户端入口地址
|
||||
if url != "" {
|
||||
mhy_window_config.url = WindowUrl::External(url.parse().unwrap());
|
||||
} else {
|
||||
mhy_window_config.url = get_mhy_client_url(func.clone());
|
||||
}
|
||||
if func == "birthday"
|
||||
|| url.starts_with("https://webstatic.mihoyo.com/ys/event/e20220303-birthday/index.html")
|
||||
{
|
||||
mhy_window_config.width = 1280.0;
|
||||
mhy_window_config.height = 720.0;
|
||||
}
|
||||
let has_mhy_client = handle.get_window("mhy_client").is_some();
|
||||
if has_mhy_client {
|
||||
dbg!("mhy_client exists");
|
||||
return;
|
||||
}
|
||||
let mhy_client = WindowBuilder::from_config(&handle, mhy_window_config).build().unwrap();
|
||||
let js_bridge = r#"
|
||||
window.MiHoYoJSInterface = {
|
||||
postMessage: function(arg) { window.__TAURI__.event.emit('post_mhy_client', arg) },
|
||||
closePage: function() { this.postMessage('{"method":"closePage"}') },
|
||||
};
|
||||
"#;
|
||||
mhy_client.eval(&js_bridge).ok().unwrap();
|
||||
}
|
||||
@@ -1,60 +1,131 @@
|
||||
//! @file src/main.rs
|
||||
//! @desc 主模块,用于启动应用
|
||||
//! @since Beta v0.3.4
|
||||
|
||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
use tauri::Manager;
|
||||
use tauri::{Manager, WindowBuilder};
|
||||
use tauri_utils::config::WindowConfig;
|
||||
mod client;
|
||||
|
||||
// 放一个常数,用来判断是否注册deep link
|
||||
// 放一个常数,用来判断应用是否初始化
|
||||
static mut APP_INITIALIZED: bool = false;
|
||||
static mut DEEP_LINK_REGISTERED: bool = false;
|
||||
|
||||
#[tauri::command]
|
||||
async fn init_app(app_handle: tauri::AppHandle) {
|
||||
dbg!("init_app");
|
||||
unsafe {
|
||||
if APP_INITIALIZED == true && DEEP_LINK_REGISTERED == true {
|
||||
return;
|
||||
}
|
||||
}
|
||||
app_handle.emit_all("initApp", ()).unwrap();
|
||||
unsafe {
|
||||
APP_INITIALIZED = true;
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn register_deep_link(app_handle: tauri::AppHandle) {
|
||||
unsafe {
|
||||
if DEEP_LINK_REGISTERED {
|
||||
return;
|
||||
}
|
||||
dbg!("register_deep_link");
|
||||
unsafe {
|
||||
if DEEP_LINK_REGISTERED == true {
|
||||
return;
|
||||
}
|
||||
tauri_plugin_deep_link::register(
|
||||
"teyvatguide",
|
||||
move |request| {
|
||||
dbg!(&request);
|
||||
app_handle.emit_all("active_deep_link", request).unwrap();
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
unsafe {
|
||||
DEEP_LINK_REGISTERED = true;
|
||||
}
|
||||
tauri_plugin_deep_link::register("teyvatguide", move |request| {
|
||||
dbg!(&request);
|
||||
app_handle.emit_all("active_deep_link", request).unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
unsafe {
|
||||
DEEP_LINK_REGISTERED = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 执行 js
|
||||
#[tauri::command]
|
||||
async fn execute_js(app_handle: tauri::AppHandle, label: String, js: String) {
|
||||
let window = app_handle.get_window(&label).unwrap();
|
||||
dbg!(&js);
|
||||
window.eval(&js).ok().unwrap();
|
||||
}
|
||||
|
||||
// 创建窗口
|
||||
#[tauri::command]
|
||||
async fn create_window(app_handle: tauri::AppHandle, label: String, mut option: WindowConfig) {
|
||||
let window_old = app_handle.get_window(&label);
|
||||
option.label = label.clone();
|
||||
if window_old.is_some() {
|
||||
dbg!("window exists");
|
||||
window_old.unwrap().close().unwrap();
|
||||
return;
|
||||
}
|
||||
let window_new =
|
||||
Some(WindowBuilder::from_config(&app_handle, option).build().expect("failed to create window"));
|
||||
window_new.unwrap();
|
||||
}
|
||||
|
||||
// 读取目录大小
|
||||
#[tauri::command]
|
||||
async fn get_dir_size(path: String) -> u64 {
|
||||
dbg!(&path);
|
||||
let walk_dir = walkdir::WalkDir::new(path);
|
||||
let mut size = 0;
|
||||
for entry in walk_dir {
|
||||
let entry = entry.unwrap();
|
||||
let file_type = entry.file_type();
|
||||
if file_type.is_file() {
|
||||
size += entry.metadata().unwrap().len();
|
||||
}
|
||||
}
|
||||
size
|
||||
}
|
||||
|
||||
fn main() {
|
||||
tauri_plugin_deep_link::prepare("teyvatguide");
|
||||
tauri::Builder::default()
|
||||
.on_window_event(|event| {
|
||||
match event.event() {
|
||||
tauri::WindowEvent::CloseRequested { api, .. } => {
|
||||
api.prevent_close();
|
||||
let window = event.window().clone();
|
||||
if window.label() == "TeyvatGuide" {
|
||||
// 子窗口 label 的数组
|
||||
const SUB_WINDOW_LABELS: [&str; 2] = ["Sub_window", "Dev_JSON"];
|
||||
for label in SUB_WINDOW_LABELS.iter() {
|
||||
let sub = window.get_window(label).unwrap();
|
||||
sub.close().unwrap();
|
||||
}
|
||||
}
|
||||
window.close().unwrap();
|
||||
},
|
||||
_ => {}
|
||||
tauri_plugin_deep_link::prepare("teyvatguide");
|
||||
tauri::Builder::default()
|
||||
.on_window_event(|event| {
|
||||
match event.event() {
|
||||
tauri::WindowEvent::CloseRequested { api, .. } => {
|
||||
api.prevent_close();
|
||||
let window = event.window().clone();
|
||||
if window.label() == "TeyvatGuide" {
|
||||
// 子窗口 label 的数组
|
||||
const SUB_WINDOW_LABELS: [&str; 3] = ["Sub_window", "Dev_JSON", "mhy_client"];
|
||||
for label in SUB_WINDOW_LABELS.iter() {
|
||||
let sub = window.get_window(label);
|
||||
if sub.is_some() {
|
||||
sub.unwrap().close().unwrap();
|
||||
}
|
||||
}
|
||||
})
|
||||
.plugin(tauri_plugin_sql::Builder::default().build())
|
||||
.invoke_handler(tauri::generate_handler![register_deep_link])
|
||||
.setup(|_app| {
|
||||
let _window = _app.get_window("TeyvatGuide").unwrap();
|
||||
#[cfg(debug_assertions)] // only include this code on debug builds
|
||||
_window.open_devtools(); // open the devtools on startup
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
window.close().unwrap();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
.plugin(tauri_plugin_sql::Builder::default().build())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
register_deep_link,
|
||||
init_app,
|
||||
execute_js,
|
||||
create_window,
|
||||
get_dir_size,
|
||||
client::create_mhy_client,
|
||||
])
|
||||
.setup(|_app| {
|
||||
let _window = _app.get_window("TeyvatGuide").unwrap();
|
||||
let _mhy = _app.get_window("mhy_client");
|
||||
if _mhy.is_some() {
|
||||
_mhy.unwrap().close().unwrap();
|
||||
}
|
||||
#[cfg(debug_assertions)] // only include this code on debug builds
|
||||
_window.open_devtools(); // open the devtools on startup
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
@@ -2,77 +2,117 @@
|
||||
"build": {
|
||||
"beforeDevCommand": "pnpm vite:dev",
|
||||
"beforeBuildCommand": "pnpm vite:build",
|
||||
"devPath": "http://localhost:3000",
|
||||
"devPath": "http://localhost:4000",
|
||||
"distDir": "../dist",
|
||||
"withGlobalTauri": true
|
||||
},
|
||||
"package": {
|
||||
"productName": "TeyvatGuide",
|
||||
"version": "0.3.2"
|
||||
"version": "0.3.6"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
"all": true,
|
||||
"all": false,
|
||||
"fs": {
|
||||
"all": true,
|
||||
"all": false,
|
||||
"exists": true,
|
||||
"readFile": true,
|
||||
"readDir": true,
|
||||
"writeFile": true,
|
||||
"createDir": true,
|
||||
"removeDir": true,
|
||||
"removeFile": true,
|
||||
"scope": ["**", "**/*"]
|
||||
},
|
||||
"http": {
|
||||
"all": true,
|
||||
"all": false,
|
||||
"request": true,
|
||||
"scope": [
|
||||
"https://api-takumi.mihoyo.com/*",
|
||||
"https://api-takumi-record.mihoyo.com/*",
|
||||
"https://api-static.mihoyo.com/*",
|
||||
"https://bbs-api.mihoyo.com/*",
|
||||
"https://bbs-api.miyoushe.com/*",
|
||||
"https://bbs-api-static.miyoushe.com/*",
|
||||
"https://bbs.mihoyo.com/*",
|
||||
"https://hk4e-api.mihoyo.com/*",
|
||||
"https://hk4e-sdk.mihoyo.com/*",
|
||||
"https://passport-api.mihoyo.com/*",
|
||||
"https://passport-api.miyoushe.com/*",
|
||||
"https://passport-api-v4.mihoyo.com/*",
|
||||
"https://act-webstatic.mihoyo.com/*",
|
||||
"https://sdk-webstatic.mihoyo.com/*",
|
||||
"https://homa.snapgenshin.com/*",
|
||||
"https://enka-api.hut.ao/*"
|
||||
]
|
||||
"scope": ["http://**", "https://**"]
|
||||
},
|
||||
"shell": {
|
||||
"all": false,
|
||||
"open": true
|
||||
},
|
||||
"dialog": {
|
||||
"all": false,
|
||||
"open": true,
|
||||
"save": true,
|
||||
"message": true
|
||||
},
|
||||
"clipboard": {
|
||||
"all": true
|
||||
},
|
||||
"path": {
|
||||
"all": true
|
||||
},
|
||||
"window": {
|
||||
"all": false,
|
||||
"setTitle": true,
|
||||
"unminimize": true,
|
||||
"show": true,
|
||||
"close": true,
|
||||
"setFocus": true,
|
||||
"hide": true
|
||||
},
|
||||
"os": {
|
||||
"all": true
|
||||
},
|
||||
"process": {
|
||||
"all": false,
|
||||
"exit": true
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico",
|
||||
"icons/icon.png",
|
||||
"icons/Square30x30Logo.png",
|
||||
"icons/Square44x44Logo.png",
|
||||
"icons/Square71x71Logo.png",
|
||||
"icons/Square89x89Logo.png",
|
||||
"icons/32x32.png",
|
||||
"icons/Square107x107Logo.png",
|
||||
"icons/Square142x142Logo.png",
|
||||
"icons/Square150x150Logo.png",
|
||||
"icons/Square284x284Logo.png",
|
||||
"icons/Square30x30Logo.png",
|
||||
"icons/Square310x310Logo.png",
|
||||
"icons/StoreLogo.png"
|
||||
"icons/Square44x44Logo.png",
|
||||
"icons/Square71x71Logo.png",
|
||||
"icons/Square89x89Logo.png",
|
||||
"icons/StoreLogo.png",
|
||||
"icons/icon.ico",
|
||||
"icons/icon.png"
|
||||
],
|
||||
"identifier": "TeyvatGuide",
|
||||
"targets": ["msi"],
|
||||
"targets": ["msi", "app", "dmg"],
|
||||
"windows": {
|
||||
"wix": {
|
||||
"language": "zh-CN"
|
||||
}
|
||||
}
|
||||
},
|
||||
"macOS": {}
|
||||
},
|
||||
"security": {
|
||||
"dangerousRemoteDomainIpcAccess": [
|
||||
{
|
||||
"domain": "act.mihoyo.com",
|
||||
"windows": ["mhy_client"],
|
||||
"enableTauriAPI": true
|
||||
},
|
||||
{
|
||||
"domain": "m.miyoushe.com",
|
||||
"windows": ["mhy_client"],
|
||||
"enableTauriAPI": true
|
||||
},
|
||||
{
|
||||
"domain": "webstatic.mihoyo.com",
|
||||
"windows": ["mhy_client"],
|
||||
"enableTauriAPI": true
|
||||
},
|
||||
{
|
||||
"domain": "api-takumi-record.mihoyo.com",
|
||||
"windows": ["mhy_client"],
|
||||
"enableTauriAPI": true
|
||||
}
|
||||
],
|
||||
"csp": null
|
||||
},
|
||||
"updater": {
|
||||
@@ -94,6 +134,20 @@
|
||||
"height": 900,
|
||||
"center": true,
|
||||
"transparent": true
|
||||
},
|
||||
{
|
||||
"fullscreen": false,
|
||||
"resizable": false,
|
||||
"title": "米游社",
|
||||
"label": "mhy_client",
|
||||
"url": "https://api-static.mihoyo.com/",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 12) Mobile miHoYoBBS/2.63.1",
|
||||
"visible": false,
|
||||
"width": 400,
|
||||
"height": 800,
|
||||
"center": true,
|
||||
"decorations": true,
|
||||
"closable": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
204
src/App.vue
@@ -11,19 +11,19 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { app, event, fs, tauri, window as TauriWindow } from "@tauri-apps/api";
|
||||
import { onBeforeMount, onMounted, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import TSidebar from "./components/app/t-sidebar.vue";
|
||||
|
||||
import TBackTop from "./components/app/t-backTop.vue";
|
||||
// tauri
|
||||
import { app, event, fs, tauri, window as TauriWindow } from "@tauri-apps/api";
|
||||
// store
|
||||
import { useAppStore } from "./store/modules/app";
|
||||
// utils
|
||||
import { getEmojis } from "./plugins/Mys/request/getEmojis";
|
||||
import TSidebar from "./components/app/t-sidebar.vue";
|
||||
import showConfirm from "./components/func/confirm";
|
||||
import showSnackbar from "./components/func/snackbar";
|
||||
import { getEmojis } from "./plugins/Mys/request/getEmojis";
|
||||
import TGSqlite from "./plugins/Sqlite";
|
||||
import { useAppStore } from "./store/modules/app";
|
||||
import { useUserStore } from "./store/modules/user";
|
||||
import { getBuildTime } from "./utils/TGBuild";
|
||||
|
||||
const appStore = useAppStore();
|
||||
const isMain = ref<boolean>(false);
|
||||
@@ -36,11 +36,9 @@ onBeforeMount(async () => {
|
||||
isMain.value = win.label === "TeyvatGuide";
|
||||
if (isMain.value) {
|
||||
const title = "Teyvat Guide v" + (await app.getVersion()) + " Beta";
|
||||
await tauri.invoke("register_deep_link");
|
||||
await getDeepLink();
|
||||
await win.setTitle(title);
|
||||
await emojiLoad();
|
||||
await checkLoad();
|
||||
await listenOnInit();
|
||||
await tauri.invoke("init_app");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -61,6 +59,19 @@ async function listenOnTheme(): Promise<void> {
|
||||
});
|
||||
}
|
||||
|
||||
// 启动后只执行一次的监听
|
||||
async function listenOnInit(): Promise<void> {
|
||||
await event.listen("initApp", async () => {
|
||||
await tauri.invoke("register_deep_link");
|
||||
await getDeepLink();
|
||||
await emojiLoad();
|
||||
await checkAppLoad();
|
||||
await checkUserLoad();
|
||||
await checkUpdate();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
async function emojiLoad(): Promise<void> {
|
||||
const res = await getEmojis();
|
||||
if ("retcode" in res) {
|
||||
@@ -75,15 +86,79 @@ async function emojiLoad(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async function checkLoad(): Promise<void> {
|
||||
async function checkAppLoad(): Promise<void> {
|
||||
if (appStore.loading) {
|
||||
console.info("数据已加载!");
|
||||
return;
|
||||
}
|
||||
await createDataDir();
|
||||
await initData();
|
||||
appStore.loading = true;
|
||||
console.info("数据加载完成!");
|
||||
const checkDB = await TGSqlite.check();
|
||||
if (!checkDB) {
|
||||
await TGSqlite.reset();
|
||||
showSnackbar({
|
||||
text: "检测到数据库不完整!已重置数据库!",
|
||||
color: "error",
|
||||
timeout: 3000,
|
||||
});
|
||||
await createDataDir();
|
||||
} else {
|
||||
appStore.loading = true;
|
||||
console.info("数据库已加载!");
|
||||
}
|
||||
}
|
||||
|
||||
// 检测 ck,info 数据
|
||||
async function checkUserLoad(): Promise<void> {
|
||||
if (!appStore.isLogin) {
|
||||
console.info("未登录!");
|
||||
return;
|
||||
}
|
||||
const userStore = useUserStore();
|
||||
const ckLocal = userStore.cookie;
|
||||
const ckDB = await TGSqlite.getCookie();
|
||||
if (JSON.stringify(ckLocal) !== JSON.stringify(ckDB)) {
|
||||
userStore.cookie = ckDB;
|
||||
console.info("cookie 数据已更新!");
|
||||
} else if (JSON.stringify(ckLocal) === "{}") {
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
showSnackbar({
|
||||
text: "获取 Cookie 失败!请重新登录!",
|
||||
color: "error",
|
||||
timeout: 3000,
|
||||
});
|
||||
resolve(true);
|
||||
}, 3000);
|
||||
});
|
||||
} else {
|
||||
console.info("cookie 数据已加载!");
|
||||
}
|
||||
const infoLocal = userStore.briefInfo;
|
||||
const appData = await TGSqlite.getAppData();
|
||||
const infoDB = appData.find((item) => item.key === "userInfo")?.value;
|
||||
if (infoDB === undefined && JSON.stringify(infoLocal) !== "{}") {
|
||||
await TGSqlite.saveAppData("userInfo", JSON.stringify(infoLocal));
|
||||
} else if (infoDB !== undefined && infoLocal !== JSON.parse(infoDB)) {
|
||||
userStore.setBriefInfo(JSON.parse(infoDB));
|
||||
console.info("briefInfo 数据已更新!");
|
||||
} else {
|
||||
console.info("briefInfo 数据已加载!");
|
||||
}
|
||||
const accountLocal = userStore.getCurAccount();
|
||||
const accountDB = await TGSqlite.getCurAccount();
|
||||
if (accountDB === false) {
|
||||
showSnackbar({
|
||||
text: "获取 GameAccount 失败!请尝试更新数据库!",
|
||||
color: "error",
|
||||
timeout: 3000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (accountDB !== accountLocal) {
|
||||
userStore.setCurAccount(accountDB);
|
||||
console.info("curAccount 数据已更新!");
|
||||
} else {
|
||||
console.info("curAccount 数据已加载!");
|
||||
}
|
||||
}
|
||||
|
||||
// 创建数据文件夹
|
||||
@@ -94,49 +169,66 @@ async function createDataDir(): Promise<void> {
|
||||
console.info("数据文件夹创建完成!");
|
||||
}
|
||||
|
||||
// 初始化数据库
|
||||
async function initData(): Promise<void> {
|
||||
if (appStore.devEnv) {
|
||||
console.info("开发环境,跳过数据库初始化!");
|
||||
return;
|
||||
}
|
||||
await TGSqlite.reset();
|
||||
showSnackbar({
|
||||
text: "已成功初始化数据库!",
|
||||
async function getDeepLink(): Promise<void> {
|
||||
await event.listen("active_deep_link", async (e) => {
|
||||
const windowGet = new TauriWindow.WebviewWindow("TeyvatGuide");
|
||||
if (await windowGet.isMinimized()) {
|
||||
await windowGet.unminimize();
|
||||
}
|
||||
await windowGet.setFocus();
|
||||
if (typeof e.payload !== "string") {
|
||||
showSnackbar({
|
||||
text: "无效的 deep link!",
|
||||
color: "error",
|
||||
timeout: 3000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (e.payload === "") return;
|
||||
// 导入格式: teyvatguide://import_uigf?app=appName
|
||||
// 跳转格式: localhost:4000/achievements/?app=appName
|
||||
if (e.payload.startsWith("teyvatguide://import_uigf")) {
|
||||
const param = (<string>e.payload).split("teyvatguide://import_uigf/?")[1];
|
||||
let appName = "";
|
||||
if (param) {
|
||||
appName = param.split("app=")[1];
|
||||
}
|
||||
if (appName === "") {
|
||||
await router.push("/achievements");
|
||||
} else {
|
||||
await router.push("/achievements/?app=" + appName);
|
||||
}
|
||||
} else {
|
||||
showSnackbar({
|
||||
text: "无效的 deep link!",
|
||||
color: "error",
|
||||
timeout: 3000,
|
||||
});
|
||||
}
|
||||
});
|
||||
console.info("已成功初始化数据库!");
|
||||
}
|
||||
|
||||
async function getDeepLink(): Promise<void> {
|
||||
await event.listen("active_deep_link", (e) => {
|
||||
new TauriWindow.WebviewWindow("TeyvatGuide")
|
||||
.setFocus()
|
||||
.then(async () => {
|
||||
// 导入格式: teyvatguide://import_uigf?app=appName
|
||||
// 跳转格式: localhost:4000/achievements/?app=appName
|
||||
if ((<string>e.payload).startsWith("teyvatguide://import_uigf")) {
|
||||
const param = (<string>e.payload).split("teyvatguide://import_uigf/?")[1];
|
||||
let appName = "";
|
||||
if (param) {
|
||||
appName = param.split("app=")[1];
|
||||
}
|
||||
if (appName === "") {
|
||||
await router.push("/achievements");
|
||||
} else {
|
||||
await router.push("/achievements/?app=" + appName);
|
||||
}
|
||||
} else {
|
||||
showSnackbar({
|
||||
text: "无效的 deep link!",
|
||||
color: "error",
|
||||
timeout: 3000,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
// 检测更新
|
||||
async function checkUpdate(): Promise<void> {
|
||||
if (!appStore.loading) return;
|
||||
const isProdEnv = import.meta.env.MODE === "production";
|
||||
const needUpdate = await TGSqlite.checkUpdate();
|
||||
if (needUpdate && isProdEnv) {
|
||||
const confirm = await showConfirm({
|
||||
title: "检测到版本更新",
|
||||
text: "请到设置页手动更新版本,即将弹出更新说明子页面",
|
||||
});
|
||||
if (confirm) {
|
||||
appStore.buildTime = getBuildTime();
|
||||
window.open("https://app.btmuli.ink/docs/Changelogs.html");
|
||||
} else {
|
||||
showSnackbar({
|
||||
text: "请到设置页手动更新版本!",
|
||||
color: "error",
|
||||
timeout: 3000,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="css">
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
/*
|
||||
* @file assets css post-parser.css
|
||||
* @description 游戏公告解析 css
|
||||
* @author BTMuli <bt-muli@outlook.com
|
||||
* @since Alpha v0.2.0
|
||||
* @since Beta v0.3.3
|
||||
*/
|
||||
|
||||
.anno-body {
|
||||
@@ -11,15 +10,19 @@
|
||||
font-family: var(--font-text);
|
||||
}
|
||||
|
||||
.anno-title {
|
||||
.anno-title,
|
||||
.anno-subtitle {
|
||||
color: var(--common-text-title);
|
||||
font-family: var(--font-title);
|
||||
}
|
||||
|
||||
.anno-title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.anno-subtitle {
|
||||
color: var(--common-text-quote);
|
||||
font-size: 16px;
|
||||
font-size: 18px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.anno-img {
|
||||
@@ -27,7 +30,7 @@
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 10px;
|
||||
margin: 10px auto;
|
||||
}
|
||||
|
||||
.anno-content {
|
||||
|
||||
@@ -1,33 +1,12 @@
|
||||
/*
|
||||
* @file assets css post-parser.css
|
||||
* @file assets/css/post-parser.css
|
||||
* @description 米游社解析 css
|
||||
* @todo 需要完善
|
||||
* @author BTMuli <bt-muli@outlook.com>
|
||||
* @since Beta v0.3.0
|
||||
* @since Beta v0.3.4
|
||||
*/
|
||||
|
||||
.mys-post-body {
|
||||
width: 800px;
|
||||
margin: 0 auto;
|
||||
font-family: var(--font-text);
|
||||
}
|
||||
|
||||
.mys-post-title {
|
||||
color: var(--common-text-title);
|
||||
font-family: var(--font-title);
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.mys-post-subtitle {
|
||||
font-size: 16px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.mys-post-content {
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
:deep(.mys-post-div) {
|
||||
position: relative;
|
||||
margin: 10px auto;
|
||||
}
|
||||
|
||||
@@ -73,6 +52,50 @@
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
:deep(.mys-post-vod-cover-div) {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
width: 800px;
|
||||
height: 450px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid var(--common-shadow-2);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
:deep(.mys-post-vod-cover) {
|
||||
max-width: 800px;
|
||||
max-height: 450px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
:deep(.mys-post-vod-icon) {
|
||||
position: absolute;
|
||||
top: calc(50% - 40px);
|
||||
left: calc(50% - 40px);
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
font-size: 50px;
|
||||
line-height: 80px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:deep(.mys-post-vod-time) {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
padding: 0 5px;
|
||||
border-radius: 5px;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
color: #ffffff;
|
||||
font-family: var(--font-title);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
:deep(.mys-post-iframe) {
|
||||
overflow: hidden;
|
||||
width: 800px;
|
||||
@@ -92,17 +115,6 @@
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
:deep(.mys-post-unknown) {
|
||||
width: 800px;
|
||||
padding: 10px;
|
||||
border: 2px solid #485466;
|
||||
border-radius: 10px;
|
||||
margin: 10px auto;
|
||||
background: #5b738f;
|
||||
color: #faf7e8;
|
||||
font-family: Consolas, monospace;
|
||||
}
|
||||
|
||||
:deep(.mys-post-link-card-cover) {
|
||||
width: auto;
|
||||
height: 180px;
|
||||
@@ -114,12 +126,6 @@
|
||||
max-width: 400px;
|
||||
height: 180px;
|
||||
border-radius: 10px;
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
:deep(.mys-post-link-card-cover):hover img {
|
||||
transform: scale(1.05);
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
:deep(.mys-post-link-card-content) {
|
||||
@@ -132,9 +138,11 @@
|
||||
}
|
||||
|
||||
:deep(.mys-post-link-card-title) {
|
||||
width: 100%;
|
||||
color: var(--common-text-title);
|
||||
font-family: var(--font-title);
|
||||
font-size: 20px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
:deep(.mys-post-link-card-price) {
|
||||
@@ -152,8 +160,145 @@
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* 表情包 */
|
||||
:deep(.mys-post-emoji) {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
/* 大别野卡片 */
|
||||
:deep(.mys-post-villa-card) {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 800px;
|
||||
border: 1px solid var(--common-shadow-2);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
:deep(.mys-post-villa-card-bg) {
|
||||
position: absolute;
|
||||
top: -40px;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
border-radius: 10px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
:deep(.mys-post-villa-card-bg-before) {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(rgba(255, 255, 255, 0) 40px, var(--box-bg-1) 140px);
|
||||
}
|
||||
|
||||
:deep(.mys-post-villa-card-flex) {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
:deep(.mys-post-villa-card-top) {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
:deep(.mys-post-villa-card-icon) {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
:deep(.mys-post-villa-card-content) {
|
||||
display: flex;
|
||||
height: 80px;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
:deep(.mys-post-villa-card-title) {
|
||||
padding: 0 5px;
|
||||
border-radius: 5px;
|
||||
backdrop-filter: blur(5px);
|
||||
background: var(--common-shadow-t-4);
|
||||
color: var(--common-text-title);
|
||||
font-family: var(--font-title);
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
:deep(.mys-post-villa-card-desc) {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding: 0 5px;
|
||||
border-radius: 5px;
|
||||
backdrop-filter: blur(5px);
|
||||
background: var(--common-shadow-t-2);
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
:deep(.mys-post-villa-card-desc-icon) {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
:deep(.mys-post-villa-card-desc-text) {
|
||||
color: var(--common-text-content);
|
||||
font-family: var(--font-text);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
:deep(.mys-post-villa-card-mid) {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
:deep(.mys-post-villa-card-tag) {
|
||||
padding: 2px 5px;
|
||||
border-radius: 5px;
|
||||
background: var(--box-bg-2);
|
||||
color: var(--box-text-2);
|
||||
font-family: var(--font-text);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
:deep(.mys-post-villa-card-bottom) {
|
||||
width: 100%;
|
||||
font-family: var(--font-title);
|
||||
font-size: 20px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* 未知类型 */
|
||||
:deep(.mys-post-unknown) {
|
||||
width: 800px;
|
||||
padding: 10px;
|
||||
border: 1px solid var(--common-shadow-2);
|
||||
border-radius: 10px;
|
||||
background: var(--box-bg-1);
|
||||
color: var(--common-text-content);
|
||||
}
|
||||
|
||||
:deep(.mys-post-unknown-code) {
|
||||
font-family: var(--font-text);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
/*
|
||||
* @file assets index.css
|
||||
* @file assets/index.css
|
||||
* @description 全局样式文件
|
||||
* @author BTMuli <bt-muli@outlook.com>
|
||||
* @since Beta v0.3.0
|
||||
* @since Beta v0.3.5
|
||||
*/
|
||||
|
||||
@import "fonts/index.css";
|
||||
@@ -36,6 +35,7 @@
|
||||
--tgc-red-1: #e06c63; /* Mys bbs color */
|
||||
--tgc-yellow-1: #ffcd0c; /* Genshin btn bg */
|
||||
--tgc-yellow-2: #f4d8a8; /* Genshin confirm text */
|
||||
--tgc-yellow-3: #e0c06b; /* Genshin btn border */
|
||||
|
||||
/* some css same in dark and default theme */
|
||||
--tgc-btn-1: var(--tgc-dark-7); /* button bg */
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
/**
|
||||
* @file assets themes default.css
|
||||
* @description 主题样式文件-默认(浅色)主题
|
||||
* @author BTMuli <bt-muli@outlook.com>
|
||||
* @since Beta v0.3.2
|
||||
* @since Beta v0.3.3
|
||||
*/
|
||||
|
||||
/* default(light) theme */
|
||||
@@ -14,7 +13,7 @@ html.default {
|
||||
--app-side-content: #222222;
|
||||
|
||||
/* box container */
|
||||
--box-bg-1: #fffdfa;
|
||||
--box-bg-1: #ebe7df;
|
||||
--box-bg-2: #f2f9f6;
|
||||
--box-bg-3: #dee4e9;
|
||||
--box-bg-4: #f5f5f5;
|
||||
|
||||
@@ -57,12 +57,12 @@
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-divider />
|
||||
<v-list-item v-show="appStore.devEnv" title="测试" value="test" :link="true" href="/test">
|
||||
<v-list-item v-show="isDevEnv" title="测试" value="test" :link="true" href="/test">
|
||||
<template #prepend>
|
||||
<v-icon>mdi-test-tube</v-icon>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-divider v-show="appStore.devEnv" />
|
||||
<v-divider v-show="isDevEnv" />
|
||||
<v-list-group value="wiki" :fluid="true">
|
||||
<template #activator="{ props }">
|
||||
<v-list-item title="图鉴" v-bind="props">
|
||||
@@ -93,16 +93,47 @@
|
||||
</v-list-item>
|
||||
</v-list-group>
|
||||
<div class="bottom-menu">
|
||||
<v-list-item>
|
||||
<template #prepend>
|
||||
<img :src="userInfo.avatar" alt="userIcon" class="side-icon" />
|
||||
<v-menu open-on-click location="end">
|
||||
<template #activator="{ props }">
|
||||
<v-list-item :title="userInfo.nickname" v-bind="props">
|
||||
<template #prepend>
|
||||
<img :src="userInfo.avatar" alt="userIcon" class="side-icon" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
<template #default>
|
||||
<v-list-item-title>
|
||||
{{ userInfo.nickname }}
|
||||
</v-list-item-title>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list class="side-list-user" density="compact" :nav="true">
|
||||
<v-list-item class="side-item-user" title="签到" @click="openClient('sign_in')">
|
||||
<template #prepend>
|
||||
<img src="/source/UI/userGacha.webp" class="side-icon-user" alt="sing_in" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item class="side-item-user" title="战绩" @click="openClient('game_record')">
|
||||
<template #prepend>
|
||||
<img src="/source/UI/userRecord.webp" class="side-icon-user" alt="game_record" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item class="side-item-user" title="酒馆" @click="openClient('tavern')">
|
||||
<template #prepend>
|
||||
<img src="/platforms/mhy/mys.webp" alt="酒馆" class="side-icon-user" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item class="side-item-user" title="工具箱" @click="openClient('toolbox')">
|
||||
<template #prepend>
|
||||
<img src="/source/UI/toolbox.webp" alt="工具箱" class="side-icon-user" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item
|
||||
class="side-item-user"
|
||||
title="登录"
|
||||
@click="login"
|
||||
v-show="userStore.cookie?.game_token === ''"
|
||||
>
|
||||
<template #prepend>
|
||||
<img src="/source/UI/defaultUser.webp" class="side-icon-user" alt="login" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
<v-list-item :title="themeTitle" @click="switchTheme()">
|
||||
<template #prepend>
|
||||
<v-icon>
|
||||
@@ -121,23 +152,32 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
// tauri
|
||||
import { event } from "@tauri-apps/api";
|
||||
// store
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
|
||||
import { useAppStore } from "../../store/modules/app";
|
||||
import { useUserStore } from "../../store/modules/user";
|
||||
import mhyClient from "../../utils/TGClient";
|
||||
import showSnackbar from "../func/snackbar";
|
||||
|
||||
const appStore = useAppStore();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const isDevEnv = ref<boolean>(import.meta.env.MODE === "development");
|
||||
|
||||
const userInfo = computed(() => {
|
||||
const info = userStore.getBriefInfo();
|
||||
return {
|
||||
nickname: info.nickname || "未登录",
|
||||
avatar: info.avatar || "/source/UI/defaultUser.webp",
|
||||
};
|
||||
if (appStore.isLogin) {
|
||||
const info = userStore.getBriefInfo();
|
||||
return {
|
||||
nickname: info.nickname,
|
||||
avatar: info.avatar,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
nickname: "未登录",
|
||||
avatar: "/source/UI/defaultUser.webp",
|
||||
};
|
||||
}
|
||||
});
|
||||
const rail = ref(appStore.sidebar.collapse);
|
||||
// theme
|
||||
@@ -169,6 +209,7 @@ function collapse(): void {
|
||||
|
||||
onMounted(async () => {
|
||||
await listenOnTheme();
|
||||
await mhyClient.run();
|
||||
});
|
||||
|
||||
async function listenOnTheme(): Promise<void> {
|
||||
@@ -181,6 +222,20 @@ async function listenOnTheme(): Promise<void> {
|
||||
async function switchTheme(): Promise<void> {
|
||||
await event.emit("readTheme", themeGet.value === "default" ? "dark" : "default");
|
||||
}
|
||||
|
||||
async function openClient(func: string): Promise<void> {
|
||||
if (appStore.isLogin) {
|
||||
await mhyClient.open(func);
|
||||
} else {
|
||||
login();
|
||||
}
|
||||
}
|
||||
|
||||
function login(): void {
|
||||
showSnackbar({
|
||||
text: "请前往设置页面扫码登录",
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
@@ -207,10 +262,21 @@ async function switchTheme(): Promise<void> {
|
||||
margin-right: 32px;
|
||||
}
|
||||
|
||||
.side-icon-mini {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
margin-right: 20px;
|
||||
transform: translateX(-6px);
|
||||
.side-list-user {
|
||||
background: var(--app-side-bg) !important;
|
||||
color: var(--app-side-content) !important;
|
||||
font-family: var(--font-title);
|
||||
}
|
||||
|
||||
.side-item-user {
|
||||
border: 1px solid var(--common-shadow-2);
|
||||
background: var(--box-bg-1);
|
||||
}
|
||||
|
||||
.side-icon-user {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 5px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -8,11 +8,9 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { computed, onMounted } from "vue";
|
||||
// tauri
|
||||
import { event } from "@tauri-apps/api";
|
||||
// store
|
||||
import { computed, onMounted } from "vue";
|
||||
|
||||
import { useAppStore } from "../../store/modules/app";
|
||||
|
||||
// store
|
||||
|
||||
84
src/components/devCharacter/duc-detail-olb.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<div class="duc-dolb-box">
|
||||
<div
|
||||
v-for="constellation in constellations"
|
||||
:key="constellation.pos"
|
||||
:title="constellation.name"
|
||||
class="duc-dolb-item"
|
||||
>
|
||||
<div v-if="!constellation.active" class="duc-dolb-lock">
|
||||
<v-icon color="white">mdi-lock</v-icon>
|
||||
</div>
|
||||
<img class="duc-dolb-icon" :src="constellation.icon" alt="constellation" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUpdated, ref } from "vue";
|
||||
|
||||
import { saveImgLocal } from "../../utils/TGShare";
|
||||
|
||||
interface DucDetailOlbProps {
|
||||
modelValue: TGApp.Sqlite.Character.RoleConstellation[];
|
||||
}
|
||||
|
||||
const props = defineProps<DucDetailOlbProps>();
|
||||
const constellations = ref<TGApp.Sqlite.Character.RoleConstellation[]>([]);
|
||||
|
||||
async function loadData() {
|
||||
const tempConstellations = props.modelValue;
|
||||
for (const constellation of tempConstellations) {
|
||||
if (constellation.icon.startsWith("blob:")) return;
|
||||
constellation.icon = await saveImgLocal(constellation.icon);
|
||||
}
|
||||
constellations.value = tempConstellations;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadData();
|
||||
});
|
||||
|
||||
onUpdated(async () => {
|
||||
await loadData();
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
.duc-dolb-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
column-gap: 10px;
|
||||
}
|
||||
|
||||
.duc-dolb-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
backdrop-filter: blur(5px);
|
||||
background: rgb(0 0 0/40%);
|
||||
}
|
||||
|
||||
.duc-dolb-lock {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3px;
|
||||
border-radius: 50%;
|
||||
backdrop-filter: blur(5px);
|
||||
background-color: rgb(0 0 0 / 40%);
|
||||
}
|
||||
|
||||
.duc-dolb-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
padding: 5px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
</style>
|
||||
109
src/components/devCharacter/duc-detail-olt.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div class="ddo-lt-box">
|
||||
<div class="ddo-ltb-icon" :title="getTitle">
|
||||
<TItemBox :model-value="boxData" />
|
||||
</div>
|
||||
<div class="ddo-ltb-info">
|
||||
<span>{{ props.data.name }}</span>
|
||||
<span>Lv.{{ props.data.level }}</span>
|
||||
<span>{{ info }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
import TItemBox, { TItemBoxData } from "../main/t-itembox.vue";
|
||||
|
||||
type DucDetailOltProps =
|
||||
| {
|
||||
data: TGApp.Sqlite.Character.UserRole;
|
||||
mode: "avatar";
|
||||
}
|
||||
| {
|
||||
data: TGApp.Sqlite.Character.RoleWeapon;
|
||||
mode: "weapon";
|
||||
};
|
||||
|
||||
const props = defineProps<DucDetailOltProps>();
|
||||
const getTitle = computed(() => {
|
||||
if (props.mode === "avatar") {
|
||||
return `${props.data.name}`;
|
||||
} else {
|
||||
const descriptionList = props.data.description.split("");
|
||||
return descriptionList.reduce((prev: string, cur: string, index: number) => {
|
||||
if (index % 10 === 0) {
|
||||
return `${prev}\n${cur}`;
|
||||
} else {
|
||||
return `${prev}${cur}`;
|
||||
}
|
||||
}, "");
|
||||
}
|
||||
});
|
||||
const boxData = computed<TItemBoxData>(() => {
|
||||
if (props.mode === "avatar") {
|
||||
return {
|
||||
bg: `/icon/bg/${props.data.star}-Star.webp`,
|
||||
icon: `/WIKI/character/icon/${props.data.cid}.webp`,
|
||||
size: "100px",
|
||||
height: "100px",
|
||||
display: "inner",
|
||||
innerHeight: 0,
|
||||
innerText: "",
|
||||
clickable: false,
|
||||
lt: `/icon/element/${props.data.element}.webp`,
|
||||
ltSize: "30px",
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
bg: `/icon/bg/${props.data.star}-Star.webp`,
|
||||
icon: `/WIKI/weapon/icon/${props.data.id}.webp`,
|
||||
size: "100px",
|
||||
height: "100px",
|
||||
display: "inner",
|
||||
innerHeight: 0,
|
||||
innerText: "",
|
||||
clickable: false,
|
||||
lt: `/icon/weapon/${props.data.type}.webp`,
|
||||
ltSize: "30px",
|
||||
};
|
||||
}
|
||||
});
|
||||
const info = computed(() => {
|
||||
if (props.mode === "avatar") {
|
||||
return `好感 ${props.data.fetter}`;
|
||||
} else {
|
||||
return `精炼 ${props.data.affix}`;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.ddo-lt-box {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
column-gap: 10px;
|
||||
}
|
||||
|
||||
.ddo-ltb-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
color: var(--tgc-white-1);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.ddo-ltb-info :nth-child(1) {
|
||||
margin-bottom: 10px;
|
||||
font-family: var(--font-title);
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.ddo-ltb-info :not(:nth-child(1)) {
|
||||
font-family: var(--font-text);
|
||||
font-size: 16px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
</style>
|
||||
75
src/components/devCharacter/duc-detail-ort.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<div class="duc-dort-box">
|
||||
<div :title="talent.name" v-for="talent in talents" :key="talent.pos" class="duc-dort-item">
|
||||
<span>{{ talent.name }}</span>
|
||||
<img :src="talent.icon" alt="talent" />
|
||||
<span>Lv.{{ talent.level }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUpdated, ref } from "vue";
|
||||
|
||||
import { saveImgLocal } from "../../utils/TGShare";
|
||||
|
||||
interface DucDetailOrtProps {
|
||||
modelValue: TGApp.Sqlite.Character.RoleTalent[];
|
||||
}
|
||||
|
||||
const props = defineProps<DucDetailOrtProps>();
|
||||
const talents = ref<TGApp.Sqlite.Character.RoleTalent[]>([]);
|
||||
|
||||
async function loadData(): Promise<void> {
|
||||
const tempTalent = props.modelValue;
|
||||
for (const talent of tempTalent) {
|
||||
if (talent.icon.startsWith("blob:")) return;
|
||||
talent.icon = await saveImgLocal(talent.icon);
|
||||
}
|
||||
talents.value = tempTalent;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadData();
|
||||
});
|
||||
|
||||
onUpdated(async () => {
|
||||
await loadData();
|
||||
});
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.duc-dort-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 10px;
|
||||
}
|
||||
|
||||
.duc-dort-item {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
column-gap: 10px;
|
||||
}
|
||||
|
||||
.duc-dort-item img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
padding: 5px;
|
||||
border-radius: 50%;
|
||||
backdrop-filter: blur(5px);
|
||||
background: rgba(0 0 0 /40%);
|
||||
}
|
||||
|
||||
.duc-dort-item span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--tgc-white-1);
|
||||
font-family: var(--font-title);
|
||||
font-size: 16px;
|
||||
text-shadow: 0 0 5px rgba(0 0 0/40%);
|
||||
}
|
||||
|
||||
.duc-dort-item :nth-last-child(1) {
|
||||
width: 48px;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
</style>
|
||||
199
src/components/devCharacter/duc-detail-overlay.vue
Normal file
@@ -0,0 +1,199 @@
|
||||
<template>
|
||||
<TOverlay v-model="visible" hide :to-click="onOverlayCancel" blur-val="20px">
|
||||
<div class="duc-do-box">
|
||||
<!-- 左侧箭头 -->
|
||||
<div class="duc-arrow-left" @click="handleClick('left')">
|
||||
<img src="../../assets/icons/arrow-right.svg" alt="left" />
|
||||
</div>
|
||||
<!-- 中间内容 -->
|
||||
<div class="duc-do-container">
|
||||
<img :src="nameCard" class="duc-doc-bg" v-if="nameCard !== false" alt="bg" />
|
||||
<div class="duc-doc-bgc" />
|
||||
<!-- 左上角色跟武器 -->
|
||||
<div class="duc-doc-lt">
|
||||
<DucDetailOlt :data="props.dataVal" mode="avatar" />
|
||||
<DucDetailOlt :data="JSON.parse(props.dataVal.weapon)" mode="weapon" />
|
||||
<v-btn
|
||||
class="duc-doc-btn"
|
||||
@click="share"
|
||||
variant="outlined"
|
||||
:loading="loading"
|
||||
data-html2canvas-ignore
|
||||
>
|
||||
<v-icon>mdi-share-variant</v-icon>
|
||||
<span>分享</span>
|
||||
</v-btn>
|
||||
</div>
|
||||
<!-- 右侧天赋 -->
|
||||
<div class="duc-doc-rt">
|
||||
<DucDetailOrt :model-value="JSON.parse(props.dataVal.talent)" />
|
||||
</div>
|
||||
<!-- 左下命座 -->
|
||||
<div class="duc-doc-lb">
|
||||
<DucDetailOlb :model-value="JSON.parse(props.dataVal.constellation)" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右侧箭头 -->
|
||||
<div class="duc-arrow-right" @click="handleClick('right')">
|
||||
<img src="../../assets/icons/arrow-right.svg" alt="right" />
|
||||
</div>
|
||||
</div>
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, onUpdated, ref } from "vue";
|
||||
|
||||
import DucDetailOlb from "./duc-detail-olb.vue";
|
||||
import DucDetailOlt from "./duc-detail-olt.vue";
|
||||
import DucDetailOrt from "./duc-detail-ort.vue";
|
||||
import TGSqlite from "../../plugins/Sqlite";
|
||||
import { generateShareImg } from "../../utils/TGShare";
|
||||
import TOverlay from "../main/t-overlay.vue";
|
||||
|
||||
interface DucDetailOverlayProps {
|
||||
modelValue: boolean;
|
||||
dataVal: TGApp.Sqlite.Character.UserRole;
|
||||
}
|
||||
|
||||
type DucDetailOverlayEmits = {
|
||||
(e: "update:modelValue", value: boolean): void;
|
||||
(e: "clickL"): void;
|
||||
(e: "clickR"): void;
|
||||
};
|
||||
|
||||
const props = defineProps<DucDetailOverlayProps>();
|
||||
const emits = defineEmits<DucDetailOverlayEmits>();
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => {
|
||||
emits("update:modelValue", value);
|
||||
},
|
||||
});
|
||||
|
||||
// share
|
||||
const loading = ref<boolean>(false);
|
||||
// 渲染数据
|
||||
const nameCard = ref<string | false>(false);
|
||||
|
||||
function onOverlayCancel() {
|
||||
visible.value = false;
|
||||
emits("update:modelValue", false);
|
||||
}
|
||||
|
||||
function handleClick(pos: "left" | "right") {
|
||||
pos === "left" ? emits("clickL") : emits("clickR");
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadData();
|
||||
});
|
||||
onUpdated(async () => {
|
||||
await loadData();
|
||||
});
|
||||
|
||||
async function loadData(): Promise<void> {
|
||||
if (!props.modelValue) return;
|
||||
if (props.dataVal.cid !== 10000005 && props.dataVal.cid !== 10000007) {
|
||||
const role = await TGSqlite.getAppCharacter(props.dataVal.cid);
|
||||
nameCard.value = `/source/nameCard/profile/${role.nameCard}.webp`;
|
||||
}
|
||||
}
|
||||
|
||||
async function share(): Promise<void> {
|
||||
const detailBox = <HTMLElement>document.querySelector(".duc-do-container");
|
||||
const fileName = `【角色详情】-${props.dataVal.name}`;
|
||||
loading.value = true;
|
||||
await generateShareImg(fileName, detailBox);
|
||||
loading.value = false;
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.duc-do-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
column-gap: 10px;
|
||||
}
|
||||
|
||||
.duc-arrow-left,
|
||||
.duc-arrow-right {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dark .duc-arrow-left,
|
||||
.dark .duc-arrow-right {
|
||||
filter: invert(11%) sepia(73%) saturate(11%) hue-rotate(139deg) brightness(97%) contrast(81%);
|
||||
}
|
||||
|
||||
.duc-arrow-left img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.duc-arrow-right img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.duc-do-container {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 800px;
|
||||
border-radius: 5px;
|
||||
aspect-ratio: 21 / 10;
|
||||
background: var(--box-bg-1);
|
||||
}
|
||||
|
||||
.duc-doc-bg {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.duc-doc-bgc {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgb(0 0 0 / 20%);
|
||||
}
|
||||
|
||||
.duc-doc-lt {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 5px;
|
||||
row-gap: 10px;
|
||||
}
|
||||
|
||||
.duc-doc-btn {
|
||||
color: var(--tgc-white-1);
|
||||
}
|
||||
|
||||
.duc-doc-rt {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.duc-doc-lb {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
padding: 5px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,17 +1,29 @@
|
||||
/**
|
||||
* @file component func confirm.ts
|
||||
* @description 封装自定义 confirm 组件,通过函数调用的方式,简化 confirm 的使用
|
||||
* @author BTMuli <bt-muli@outlook.com>
|
||||
* @since Alpha v0.2.3
|
||||
* @since Beta v0.3.4
|
||||
*/
|
||||
|
||||
// vue
|
||||
import { h, render, type VNode } from "vue";
|
||||
// confirm
|
||||
import { h, render } from "vue";
|
||||
import type { ComponentInternalInstance, VNode } from "vue";
|
||||
|
||||
import confirm from "./confirm.vue";
|
||||
|
||||
const confirmId = "tg-func-confirm";
|
||||
|
||||
/**
|
||||
* @description 自定义 confirm 组件
|
||||
* @since Beta v0.3.4
|
||||
* @extends ComponentInternalInstance
|
||||
* @property {Function} exposeProxy.displayBox 显示 confirm
|
||||
* @return ConfirmInstance
|
||||
*/
|
||||
interface ConfirmInstance extends ComponentInternalInstance {
|
||||
exposeProxy: {
|
||||
displayBox: (props: TGApp.Component.Confirm.Params) => Promise<string | boolean>;
|
||||
};
|
||||
}
|
||||
|
||||
const renderBox = (props: TGApp.Component.Confirm.Params): VNode => {
|
||||
const container = document.createElement("div");
|
||||
container.id = confirmId;
|
||||
@@ -23,14 +35,17 @@ const renderBox = (props: TGApp.Component.Confirm.Params): VNode => {
|
||||
|
||||
let confirmInstance: VNode;
|
||||
|
||||
const showConfirm = async (props: TGApp.Component.Confirm.Params): Promise<string | boolean> => {
|
||||
if (confirmInstance) {
|
||||
const boxVue = confirmInstance.component;
|
||||
return boxVue?.exposeProxy?.displayBox(props);
|
||||
async function showConfirm(props: TGApp.Component.Confirm.ParamsConfirm): Promise<boolean>;
|
||||
async function showConfirm(props: TGApp.Component.Confirm.ParamsInput): Promise<string | false>;
|
||||
async function showConfirm(props: TGApp.Component.Confirm.Params): Promise<string | boolean>;
|
||||
async function showConfirm(props: TGApp.Component.Confirm.Params): Promise<string | boolean> {
|
||||
if (confirmInstance !== undefined) {
|
||||
const boxVue = <ConfirmInstance>confirmInstance.component;
|
||||
return await boxVue.exposeProxy.displayBox(props);
|
||||
} else {
|
||||
confirmInstance = renderBox(props);
|
||||
return await showConfirm(props);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default showConfirm;
|
||||
|
||||
@@ -3,29 +3,26 @@
|
||||
<div v-show="show || showOuter" class="confirm-overlay" @click.self.prevent="handleOuter">
|
||||
<transition name="func-confirm-inner">
|
||||
<div v-show="showInner" class="confirm-box">
|
||||
<div class="confirm-title">
|
||||
{{ data.title }}
|
||||
</div>
|
||||
<div v-if="data?.text !== '' && data.mode === 'confirm'" class="confirm-subtitle">
|
||||
<div class="confirm-title">{{ data.title }}</div>
|
||||
<div
|
||||
v-show="data?.text !== '' && data.mode === 'confirm'"
|
||||
class="confirm-subtitle"
|
||||
:title="data.text"
|
||||
>
|
||||
{{ data.text }}
|
||||
</div>
|
||||
<div v-if="data?.text !== '' && data.mode === 'input'" class="confirm-input">
|
||||
<div class="confirm-input-label">
|
||||
{{ data.text }}
|
||||
</div>
|
||||
<input v-model="inputVal" class="confirm-input-box" />
|
||||
<div v-show="data?.text !== '' && data.mode === 'input'" class="confirm-input">
|
||||
<div class="confirm-input-label">{{ data.text }}</div>
|
||||
<input
|
||||
v-model="inputVal"
|
||||
class="confirm-input-box"
|
||||
ref="inputRef"
|
||||
@keydown.enter="handleClick(true)"
|
||||
/>
|
||||
</div>
|
||||
<div class="confirm-btn-box">
|
||||
<button class="confirm-btn" @click="handleClick(false)">取消</button>
|
||||
<button
|
||||
class="confirm-btn"
|
||||
:style="{
|
||||
backgroundColor: 'var(--tgc-dark-2)',
|
||||
}"
|
||||
@click="handleClick(true)"
|
||||
>
|
||||
确定
|
||||
</button>
|
||||
<button class="confirm-btn no-btn" @click="handleClick(false)">取消</button>
|
||||
<button class="confirm-btn ok-btn" @click="handleClick(true)">确定</button>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
@@ -34,8 +31,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { onMounted, reactive, ref, watch } from "vue";
|
||||
import { nextTick, onMounted, reactive, ref, watch } from "vue";
|
||||
|
||||
interface ConfirmProps {
|
||||
title: string;
|
||||
@@ -60,6 +56,7 @@ const showOuter = ref<boolean>(false);
|
||||
const showInner = ref<boolean>(false);
|
||||
const confirmVal = ref<boolean | string>(false);
|
||||
const inputVal = ref<string>("");
|
||||
const inputRef = ref<HTMLInputElement>();
|
||||
|
||||
watch(show, () => {
|
||||
if (show.value) {
|
||||
@@ -89,6 +86,14 @@ async function displayBox(params: TGApp.Component.Confirm.Params): Promise<strin
|
||||
show.value = true;
|
||||
// 等待确认框关闭,返回关闭后的confirmVal
|
||||
return await new Promise<string | boolean>((resolve) => {
|
||||
nextTick(() => {
|
||||
if (data.mode === "input") {
|
||||
// 等待确认框打开,聚焦输入框
|
||||
setTimeout(() => {
|
||||
inputRef.value?.focus();
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
watch(show, () => {
|
||||
// 等 0.5s 动画
|
||||
setTimeout(() => {
|
||||
@@ -175,7 +180,16 @@ defineExpose({
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
backdrop-filter: blur(10px);
|
||||
background: var(--common-shadow-t-1);
|
||||
|
||||
/* 颜色变量 */
|
||||
--confirm-title: var(--tgc-dark-7);
|
||||
--confirm-bg: var(--tgc-white-1);
|
||||
}
|
||||
|
||||
/* 深色模式 */
|
||||
.dark .confirm-overlay {
|
||||
--confirm-title: var(--tgc-white-1);
|
||||
--confirm-bg: var(--tgc-dark-7);
|
||||
}
|
||||
|
||||
.confirm-box {
|
||||
@@ -187,25 +201,30 @@ defineExpose({
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
border: 1px solid var(--common-shadow-1);
|
||||
border-radius: 5px;
|
||||
background: var(--box-bg-4);
|
||||
color: var(--box-text-1);
|
||||
border-radius: 15px;
|
||||
background: var(--confirm-bg);
|
||||
box-shadow: 0 0 10px var(--common-shadow-t-1);
|
||||
color: var(--tgc-yellow-3);
|
||||
}
|
||||
|
||||
.confirm-title {
|
||||
width: 100%;
|
||||
border-bottom: 1px solid var(--common-shadow-4);
|
||||
border-bottom: 1px solid var(--confirm-title);
|
||||
color: var(--confirm-title);
|
||||
font-family: var(--font-title);
|
||||
font-size: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.confirm-subtitle {
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
color: var(--box-text-4);
|
||||
font-family: var(--font-text);
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.confirm-input {
|
||||
@@ -222,10 +241,10 @@ defineExpose({
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
padding: 5px;
|
||||
border: 1px solid var(--common-shadow-4);
|
||||
border: 1px solid var(--confirm-title);
|
||||
border-radius: 5px;
|
||||
background: inherit;
|
||||
color: var(--box-text-1);
|
||||
color: var(--confirm-title);
|
||||
}
|
||||
|
||||
.confirm-btn-box {
|
||||
@@ -242,11 +261,17 @@ defineExpose({
|
||||
height: 60px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid var(--common-shadow-4);
|
||||
border-radius: 5px;
|
||||
color: var(--btn-text);
|
||||
border-radius: 15px;
|
||||
cursor: pointer;
|
||||
font-family: var(--font-title);
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.no-btn {
|
||||
border: 1px solid var(--tgc-yellow-1);
|
||||
}
|
||||
|
||||
.ok-btn {
|
||||
background: var(--confirm-title);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,17 +1,29 @@
|
||||
/**
|
||||
* @file component func snackbar.ts
|
||||
* @description 封装 vuetify 的 snackbar 组件,通过函数调用的方式,简化 snackbar 的使用
|
||||
* @author BTMuli <bt-muli@outlook.com>
|
||||
* @since Alpha v0.2.3
|
||||
* @since Beta v0.3.4
|
||||
*/
|
||||
|
||||
// vue
|
||||
import { h, render, type VNode } from "vue";
|
||||
// snackbar
|
||||
import type { ComponentInternalInstance, VNode } from "vue";
|
||||
import { h, render } from "vue";
|
||||
|
||||
import snackbar from "./snackbar.vue";
|
||||
|
||||
const snackbarId = "tg-func-snackbar";
|
||||
|
||||
/**
|
||||
* @description 自定义 snackbar 组件
|
||||
* @since Beta v0.3.3
|
||||
* @extends ComponentInternalInstance
|
||||
* @property {Function} exposeProxy.displayBox 显示 snackbar
|
||||
* @return SnackbarInstance
|
||||
*/
|
||||
interface SnackbarInstance extends ComponentInternalInstance {
|
||||
exposeProxy: {
|
||||
displayBox: (props: TGApp.Component.Snackbar.Params) => void;
|
||||
};
|
||||
}
|
||||
|
||||
const renderBox = (props: TGApp.Component.Snackbar.Params): VNode => {
|
||||
const container = document.createElement("div");
|
||||
container.id = snackbarId;
|
||||
@@ -23,14 +35,14 @@ const renderBox = (props: TGApp.Component.Snackbar.Params): VNode => {
|
||||
|
||||
let snackbarInstance: VNode;
|
||||
|
||||
const showSnackbar = (props: TGApp.Component.Snackbar.Params): void => {
|
||||
if (snackbarInstance) {
|
||||
const boxVue = snackbarInstance.component;
|
||||
boxVue?.exposeProxy?.displayBox(props);
|
||||
function showSnackbar(props: TGApp.Component.Snackbar.Params): void {
|
||||
if (snackbarInstance !== undefined) {
|
||||
const boxVue = <SnackbarInstance>snackbarInstance.component;
|
||||
boxVue.exposeProxy.displayBox(props);
|
||||
} else {
|
||||
snackbarInstance = renderBox(props);
|
||||
showSnackbar(props);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default showSnackbar;
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
<template>
|
||||
<transition name="func-snackbar">
|
||||
<div v-show="show" class="func-snackbar" :style="{ backgroundColor: data.color }">
|
||||
<slot name="text">
|
||||
<span class="func-snackbar-text">{{ data.text }}</span>
|
||||
</slot>
|
||||
<div class="func-snackbar-container" v-show="show">
|
||||
<div class="func-snackbar" :style="{ backgroundColor: data.color }">
|
||||
<slot name="text">
|
||||
<span class="func-snackbar-text">{{ data.text }}</span>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { ref, reactive, onMounted } from "vue";
|
||||
import { onMounted, reactive, ref } from "vue";
|
||||
|
||||
interface SnackbarProps {
|
||||
text: string;
|
||||
@@ -41,13 +42,13 @@ function transColor(color: string): string {
|
||||
}
|
||||
switch (color) {
|
||||
case "success":
|
||||
return "#41b883";
|
||||
return "#7ebd21";
|
||||
case "error":
|
||||
return "#ff4d4f";
|
||||
return "#e63f5a";
|
||||
case "warn":
|
||||
return "#faad14";
|
||||
case "info":
|
||||
return "#1890ff";
|
||||
return "#ecb349";
|
||||
case "cancel":
|
||||
return "#cdcbc4";
|
||||
default:
|
||||
return color;
|
||||
}
|
||||
@@ -77,20 +78,28 @@ defineExpose({
|
||||
.func-snackbar-enter-from,
|
||||
.func-snackbar-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(-50%) translateY(20px);
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
.func-snackbar-enter-to,
|
||||
.func-snackbar-leave-from {
|
||||
opacity: 1;
|
||||
transform: translateX(-50%) translateY(0);
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.func-snackbar-container {
|
||||
position: fixed;
|
||||
z-index: 999;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.func-snackbar {
|
||||
position: fixed;
|
||||
z-index: 999;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
display: flex;
|
||||
min-width: 200px;
|
||||
height: 40px;
|
||||
@@ -99,10 +108,9 @@ defineExpose({
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 10px rgb(0 0 0 / 20%);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.func-snackbar .func-snackbar-text {
|
||||
.func-snackbar-text {
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
|
||||
@@ -2,22 +2,22 @@
|
||||
<v-chart :option="getPoolData()" autoresize />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { onMounted, provide } from "vue";
|
||||
import VChart, { THEME_KEY } from "vue-echarts";
|
||||
import showSnackbar from "../func/snackbar";
|
||||
// echarts
|
||||
import { use } from "echarts/core";
|
||||
import type { EChartsOption } from "echarts";
|
||||
import { PieChart } from "echarts/charts";
|
||||
import {
|
||||
LegendComponent,
|
||||
TitleComponent,
|
||||
TooltipComponent,
|
||||
ToolboxComponent,
|
||||
} from "echarts/components";
|
||||
import { PieChart } from "echarts/charts";
|
||||
import { use } from "echarts/core";
|
||||
import { LabelLayout } from "echarts/features";
|
||||
import { CanvasRenderer } from "echarts/renderers";
|
||||
import { type EChartsOption } from "echarts";
|
||||
import { onMounted, provide } from "vue";
|
||||
import VChart, { THEME_KEY } from "vue-echarts";
|
||||
|
||||
import showSnackbar from "../func/snackbar";
|
||||
// echarts
|
||||
|
||||
use([
|
||||
TitleComponent,
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { watch } from "vue";
|
||||
|
||||
import GroDataview from "./gro-dataview.vue";
|
||||
|
||||
interface GachaOverviewProps {
|
||||
|
||||
@@ -2,9 +2,21 @@
|
||||
<div class="calendar-box">
|
||||
<div class="calendar-title">
|
||||
<div class="calendar-title-left">
|
||||
<v-icon size="small"> mdi-calendar-clock</v-icon>
|
||||
<v-icon size="small" style="opacity: 0.8">mdi-calendar-clock</v-icon>
|
||||
<span>今日素材</span>
|
||||
<span>{{ dateNow }}</span>
|
||||
<!-- 如果是某人生日,礼物图标颜色为红色 -->
|
||||
<span
|
||||
v-if="birthInfo.isLogin"
|
||||
@click="toBirthday"
|
||||
class="calendar-title-gift"
|
||||
:style="{
|
||||
color: birthInfo.active ? 'var(--tgc-red-1)' : 'inherit',
|
||||
}"
|
||||
:title="birthInfo.text"
|
||||
>
|
||||
<v-icon size="small">mdi-gift</v-icon>
|
||||
</span>
|
||||
</div>
|
||||
<div class="calendar-title-mid">
|
||||
<v-btn
|
||||
@@ -25,14 +37,14 @@
|
||||
<div class="calendar-title-right">
|
||||
<v-switch
|
||||
class="calendar-title-switch"
|
||||
color="grey"
|
||||
color="var(--common-shadow-4)"
|
||||
variant="outline"
|
||||
:label="switchType === 'avatar' ? '角色' : '武器'"
|
||||
@change="switchType = switchType === 'avatar' ? 'weapon' : 'avatar'"
|
||||
/>
|
||||
<v-btn class="calendar-title-btn" @click="share">
|
||||
<v-btn class="calendar-title-btn" @click="share" data-html2canvas-ignore>
|
||||
<template #prepend>
|
||||
<v-icon> mdi-share-variant</v-icon>
|
||||
<v-icon>mdi-share-variant</v-icon>
|
||||
</template>
|
||||
<span>分享</span>
|
||||
</v-btn>
|
||||
@@ -61,14 +73,15 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import ToCalendar from "../overlay/to-calendar.vue";
|
||||
import TibCalendarItem from "../itembox/tib-calendar-item.vue";
|
||||
// data
|
||||
|
||||
import { AppCalendarData } from "../../data";
|
||||
// utils
|
||||
import TGSqlite from "../../plugins/Sqlite";
|
||||
import { useAppStore } from "../../store/modules/app";
|
||||
import TGClient from "../../utils/TGClient";
|
||||
import { generateShareImg } from "../../utils/TGShare";
|
||||
import TibCalendarItem from "../itembox/tib-calendar-item.vue";
|
||||
import ToCalendar from "../overlay/to-calendar.vue";
|
||||
|
||||
// loading
|
||||
const loading = ref<boolean>(true);
|
||||
@@ -90,6 +103,13 @@ const switchType = ref<string>("avatar");
|
||||
const selectedItem = ref<TGApp.App.Calendar.Item>(<TGApp.App.Calendar.Item>{});
|
||||
const selectedType = ref<"avatar" | "weapon">("avatar");
|
||||
|
||||
// birthday
|
||||
const birthInfo = ref({
|
||||
isLogin: true,
|
||||
active: false,
|
||||
text: "点击前往留影叙佳期",
|
||||
});
|
||||
|
||||
const btnText = [
|
||||
{
|
||||
week: 7,
|
||||
@@ -127,7 +147,17 @@ defineExpose({
|
||||
loading,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
const appStore = useAppStore();
|
||||
if (appStore.isLogin) {
|
||||
const birthRes = await TGSqlite.isBirthday();
|
||||
if (birthRes !== false) {
|
||||
birthInfo.value.active = true;
|
||||
birthInfo.value.text = `今天是 ${birthRes} 的生日!`;
|
||||
}
|
||||
} else {
|
||||
birthInfo.value.isLogin = false;
|
||||
}
|
||||
const dayNow = new Date().getDay() === 0 ? 7 : new Date().getDay();
|
||||
const week = <{ week: number; text: string }>btnText.find((item) => item.week === dayNow);
|
||||
dateNow.value =
|
||||
@@ -171,12 +201,16 @@ function getContents(day: number): void {
|
||||
}
|
||||
|
||||
async function share(): Promise<void> {
|
||||
// todo 唤起外部 loading
|
||||
const div = <HTMLElement>document.querySelector(".calendar-box");
|
||||
const showType = switchType.value === "avatar" ? "角色" : "武器";
|
||||
const title = `【今日素材】${showType}${btnNow.value}`;
|
||||
await generateShareImg(title, div);
|
||||
}
|
||||
|
||||
// 前往留影叙佳期
|
||||
async function toBirthday(): Promise<void> {
|
||||
await TGClient.open("birthday");
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.calendar-box {
|
||||
@@ -204,6 +238,13 @@ async function share(): Promise<void> {
|
||||
column-gap: 10px;
|
||||
}
|
||||
|
||||
.calendar-title-gift {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.calendar-title-mid {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
<template>
|
||||
<div class="pool-box">
|
||||
<div class="pool-title">
|
||||
<img src="../../assets/icons/icon-wish.svg" alt="wish" />
|
||||
限时祈愿
|
||||
<div class="pool-title-left">
|
||||
<img src="../../assets/icons/icon-wish.svg" alt="wish" />
|
||||
<span>限时祈愿</span>
|
||||
</div>
|
||||
<div class="pool-title-right">
|
||||
<v-switch
|
||||
class="pool-switch"
|
||||
color="var(--common-shadow-4)"
|
||||
variant="outline"
|
||||
:label="showNew ? '查看当前祈愿' : '查看后续祈愿'"
|
||||
v-show="hasNew"
|
||||
@change="switchPool"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!loading" class="pool-grid">
|
||||
<div v-for="pool in poolCards" :key="pool.postId" class="pool-card">
|
||||
<div class="pool-cover" @click="toPost(pool)">
|
||||
<div v-for="pool in poolSelect" :key="pool.postId" class="pool-card">
|
||||
<div class="pool-cover" @click="createPost(pool.postId, pool.title)">
|
||||
<img :src="pool.cover" alt="cover" />
|
||||
</div>
|
||||
<div class="pool-bottom">
|
||||
@@ -25,7 +37,7 @@
|
||||
<div class="pool-time">
|
||||
<div>
|
||||
<v-icon>mdi-calendar-clock</v-icon>
|
||||
{{ pool.time.start }}~{{ pool.time.end }}
|
||||
{{ pool.time.str }}
|
||||
</div>
|
||||
<v-progress-linear :model-value="poolTimePass[pool.postId]" :rounded="true">
|
||||
</v-progress-linear>
|
||||
@@ -48,19 +60,12 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { ref, onMounted, onUnmounted } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
// store
|
||||
import { useHomeStore } from "../../store/modules/home";
|
||||
// utils
|
||||
import { createTGWindow } from "../../utils/TGWindow";
|
||||
import { stamp2LastTime } from "../../utils/toolFunc";
|
||||
// plugins
|
||||
import Mys from "../../plugins/Mys";
|
||||
|
||||
// vue
|
||||
const router = useRouter();
|
||||
import Mys from "../../plugins/Mys";
|
||||
import { useHomeStore } from "../../store/modules/home";
|
||||
import { createPost, createTGWindow } from "../../utils/TGWindow";
|
||||
import { stamp2LastTime } from "../../utils/toolFunc";
|
||||
|
||||
// store
|
||||
const homeStore = useHomeStore();
|
||||
@@ -72,8 +77,12 @@ const showBar = ref<boolean>(false);
|
||||
const barText = ref<string>("");
|
||||
const barColor = ref<string>("error");
|
||||
|
||||
const hasNew = ref<boolean>(false);
|
||||
const showNew = ref<boolean>(false);
|
||||
|
||||
// data
|
||||
const poolCards = ref<TGApp.Plugins.Mys.Gacha.RenderCard[]>([]);
|
||||
const poolSelect = ref<TGApp.Plugins.Mys.Gacha.RenderCard[]>([]);
|
||||
const poolTimeGet = ref<Record<number, string>>({});
|
||||
const poolTimePass = ref<Record<number, number>>({});
|
||||
const timer = ref<Record<number, any>>({});
|
||||
@@ -131,6 +140,7 @@ onMounted(async () => {
|
||||
if (poolTimePass.value[pool.postId] <= 0) {
|
||||
poolTimeGet.value[pool.postId] = "已结束";
|
||||
poolTimePass.value[pool.postId] = 100;
|
||||
showNew.value = false;
|
||||
} else if (pool.time.startStamp - Date.now() > 0) {
|
||||
poolTimeGet.value[pool.postId] = "未开始";
|
||||
poolTimePass.value[pool.postId] = 100;
|
||||
@@ -140,6 +150,17 @@ onMounted(async () => {
|
||||
}, 1000);
|
||||
return pool;
|
||||
});
|
||||
if (poolCards.value.length > 2) {
|
||||
poolSelect.value = poolCards.value.filter(
|
||||
(pool) =>
|
||||
poolTimeGet.value[pool.postId] !== "未开始" && poolTimeGet.value[pool.postId] !== "已结束",
|
||||
);
|
||||
hasNew.value =
|
||||
poolCards.value.filter((pool) => poolTimeGet.value[pool.postId] === "未开始").length > 0;
|
||||
} else {
|
||||
poolSelect.value = poolCards.value;
|
||||
hasNew.value = false;
|
||||
}
|
||||
loading.value = false;
|
||||
});
|
||||
|
||||
@@ -178,14 +199,19 @@ async function toOuter(url: string, title: string): Promise<void> {
|
||||
createTGWindow(url, "Sub_window", `Pool_${title}`, 1200, 800, true, true);
|
||||
}
|
||||
|
||||
function toPost(pool: TGApp.Plugins.Mys.Gacha.RenderCard): void {
|
||||
const path = router.resolve({
|
||||
name: "帖子详情",
|
||||
params: {
|
||||
post_id: pool.postId.toString(),
|
||||
},
|
||||
}).href;
|
||||
createTGWindow(path, "Sub_window", `Post_${pool.postId} ${pool.title}`, 960, 720, false, false);
|
||||
// 更换显示的卡池
|
||||
async function switchPool(): Promise<void> {
|
||||
showNew.value = !showNew.value;
|
||||
if (showNew.value) {
|
||||
poolSelect.value = poolCards.value.filter(
|
||||
(pool) => poolTimeGet.value[pool.postId] === "未开始",
|
||||
);
|
||||
} else {
|
||||
poolSelect.value = poolCards.value.filter(
|
||||
(pool) =>
|
||||
poolTimeGet.value[pool.postId] !== "未开始" && poolTimeGet.value[pool.postId] !== "已结束",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
@@ -207,32 +233,55 @@ onUnmounted(() => {
|
||||
|
||||
.pool-title {
|
||||
display: flex;
|
||||
color: var(--common-text-title);
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-family: var(--font-title);
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.pool-title img {
|
||||
.pool-title-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
color: var(--common-text-title);
|
||||
column-gap: 10px;
|
||||
}
|
||||
|
||||
.pool-title-left img {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border-radius: 50%;
|
||||
margin-right: 10px;
|
||||
background: var(--common-shadow-4);
|
||||
transform: translate(0, 2px);
|
||||
filter: brightness(0.8);
|
||||
}
|
||||
|
||||
.pool-title-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.pool-switch {
|
||||
display: flex;
|
||||
height: 36px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--box-text-1);
|
||||
}
|
||||
|
||||
.pool-grid {
|
||||
display: flex;
|
||||
display: grid;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.pool-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 50%;
|
||||
width: 100%;
|
||||
border-radius: 5px;
|
||||
aspect-ratio: 69 / 32;
|
||||
box-shadow: 0 5px 5px var(--common-shadow-4);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,12 +15,14 @@
|
||||
<v-list class="position-list">
|
||||
<v-list-item :title="card.title" :subtitle="card.abstract">
|
||||
<template #prepend>
|
||||
<v-avatar rounded="0" @click="toPost(card)">
|
||||
<v-avatar rounded="0" @click="createPost(card.postId, card.title)">
|
||||
<v-img :src="card.icon" class="position-icon" />
|
||||
</v-avatar>
|
||||
</template>
|
||||
<template #append>
|
||||
<v-btn class="position-card-btn" @click="toPost(card)"> 查看 </v-btn>
|
||||
<v-btn class="position-card-btn" @click="createPost(card.postId, card.title)">
|
||||
查看
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
@@ -32,7 +34,6 @@
|
||||
</div>
|
||||
<div class="position-card-text">
|
||||
<v-icon>mdi-clock-outline</v-icon>
|
||||
<span>剩余时间:</span>
|
||||
<span v-if="positionTimeGet[card.postId] !== '已结束'">{{
|
||||
positionTimeGet[card.postId]
|
||||
}}</span>
|
||||
@@ -44,17 +45,11 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { ref, onMounted, onUnmounted } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
// utils
|
||||
import { createTGWindow } from "../../utils/TGWindow";
|
||||
import { stamp2LastTime } from "../../utils/toolFunc";
|
||||
// plugins
|
||||
import Mys from "../../plugins/Mys";
|
||||
|
||||
// vue
|
||||
const router = useRouter();
|
||||
import Mys from "../../plugins/Mys";
|
||||
import { createPost } from "../../utils/TGWindow";
|
||||
import { stamp2LastTime } from "../../utils/toolFunc";
|
||||
|
||||
// loading
|
||||
const loading = ref<boolean>(true);
|
||||
@@ -107,18 +102,6 @@ onMounted(async () => {
|
||||
loading.value = false;
|
||||
});
|
||||
|
||||
async function toPost(card: TGApp.Plugins.Mys.Position.RenderCard): Promise<void> {
|
||||
// 获取路由路径
|
||||
const path = router.resolve({
|
||||
name: "帖子详情",
|
||||
params: {
|
||||
post_id: card.postId,
|
||||
},
|
||||
}).href;
|
||||
// 打开新窗口
|
||||
createTGWindow(path, "Sub_window", `Post_${card.postId} ${card.title}`, 960, 720, false, false);
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
Object.keys(positionTimer.value).forEach((key) => {
|
||||
clearInterval(positionTimer.value[Number(key)]);
|
||||
@@ -146,6 +129,7 @@ onUnmounted(() => {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: 0 10px;
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
|
||||
.position-grid {
|
||||
@@ -198,5 +182,11 @@ onUnmounted(() => {
|
||||
display: inline-block;
|
||||
min-width: 200px;
|
||||
align-items: flex-start;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.position-card-text :nth-child(1) {
|
||||
color: var(--btn-text);
|
||||
filter: brightness(0.8);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { computed } from "vue";
|
||||
|
||||
import TOverlay from "../main/t-overlay.vue";
|
||||
|
||||
interface HtaOverlayOverviewProps {
|
||||
|
||||
@@ -45,8 +45,8 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { onMounted, ref } from "vue";
|
||||
|
||||
import TibWikiAbyss2 from "../itembox/tib-wiki-abyss-2.vue";
|
||||
|
||||
interface HtaTabTeamProps {
|
||||
@@ -104,14 +104,13 @@ onMounted(async () => {
|
||||
|
||||
.hta-tuf-box {
|
||||
display: flex;
|
||||
overflow: hidden auto;
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 5px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
row-gap: 10px;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { onMounted, ref } from "vue";
|
||||
|
||||
import TibWikiAbyss from "../itembox/tib-wiki-abyss.vue";
|
||||
|
||||
interface HtaTabUseProps {
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { onMounted, ref } from "vue";
|
||||
|
||||
import TibWikiAbyss from "../itembox/tib-wiki-abyss.vue";
|
||||
|
||||
interface HtaTabUseProps {
|
||||
|
||||
@@ -2,12 +2,10 @@
|
||||
<TItemBox :model-value="box" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { onMounted, ref } from "vue";
|
||||
import TItemBox from "../main/t-itembox.vue";
|
||||
// utils
|
||||
|
||||
import TGSqlite from "../../plugins/Sqlite";
|
||||
// types
|
||||
import TItemBox from "../main/t-itembox.vue";
|
||||
import type { TItemBoxData } from "../main/t-itembox.vue";
|
||||
|
||||
interface TibAbyssDetailProps {
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
<template>
|
||||
<TItemBox :model-value="box" />
|
||||
<TItemBox v-model="box" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { onMounted, ref } from "vue";
|
||||
import TItemBox from "../main/t-itembox.vue";
|
||||
// utils
|
||||
|
||||
import TGSqlite from "../../plugins/Sqlite";
|
||||
// types
|
||||
import TItemBox from "../main/t-itembox.vue";
|
||||
import type { TItemBoxData } from "../main/t-itembox.vue";
|
||||
|
||||
interface TibAbyssOverviewProps {
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
<TItemBox :model-value="box" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { computed } from "vue";
|
||||
import { onMounted, ref } from "vue";
|
||||
|
||||
import TItemBox from "../main/t-itembox.vue";
|
||||
// types
|
||||
import type { TItemBoxData } from "../main/t-itembox.vue";
|
||||
|
||||
interface TibCalendarItemProps {
|
||||
@@ -15,33 +14,48 @@ interface TibCalendarItemProps {
|
||||
}
|
||||
|
||||
const props = defineProps<TibCalendarItemProps>();
|
||||
const box = computed<TItemBoxData>(() => {
|
||||
const box = ref<TItemBoxData>({
|
||||
bg: "",
|
||||
icon: "",
|
||||
size: "100px",
|
||||
height: "100px",
|
||||
display: "inner",
|
||||
clickable: false,
|
||||
lt: "",
|
||||
ltSize: "30px",
|
||||
innerHeight: 25,
|
||||
innerIcon: "",
|
||||
innerText: "",
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (props.model === "avatar") {
|
||||
return {
|
||||
box.value = {
|
||||
bg: props.data.bg,
|
||||
icon: props.data.icon,
|
||||
size: "100px",
|
||||
height: "100px",
|
||||
display: "inner",
|
||||
clickable: props.clickable,
|
||||
lt: props.data.elementIcon,
|
||||
lt: props.data.elementIcon ?? "",
|
||||
ltSize: "30px",
|
||||
innerHeight: 25,
|
||||
innerIcon: props.data.weaponIcon,
|
||||
innerText: props.data.name,
|
||||
};
|
||||
} else {
|
||||
box.value = {
|
||||
bg: props.data.bg,
|
||||
icon: props.data.icon,
|
||||
size: "100px",
|
||||
height: "100px",
|
||||
display: "inner",
|
||||
clickable: props.clickable,
|
||||
lt: props.data.weaponIcon,
|
||||
ltSize: "30px",
|
||||
innerHeight: 25,
|
||||
innerText: props.data.name,
|
||||
};
|
||||
}
|
||||
return {
|
||||
bg: props.data.bg,
|
||||
icon: props.data.icon,
|
||||
size: "100px",
|
||||
height: "100px",
|
||||
display: "inner",
|
||||
clickable: props.clickable,
|
||||
lt: props.data.weaponIcon,
|
||||
ltSize: "30px",
|
||||
innerHeight: 25,
|
||||
innerText: props.data.name,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
<template>
|
||||
<TItemBox v-model="box" @click="showData" />
|
||||
<TItemBox v-model="box" :title="hoverTitle" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { onMounted, ref } from "vue";
|
||||
|
||||
import TItemBox from "../main/t-itembox.vue";
|
||||
// types
|
||||
import type { TItemBoxData } from "../main/t-itembox.vue";
|
||||
|
||||
interface TibUrAvatarProps {
|
||||
@@ -14,12 +13,13 @@ interface TibUrAvatarProps {
|
||||
|
||||
const props = defineProps<TibUrAvatarProps>();
|
||||
const box = ref<TItemBoxData>(<TItemBoxData>{});
|
||||
const hoverTitle = ref<string>("点击查看详情");
|
||||
const getName = (): string => {
|
||||
return props.modelValue.id === 10000005
|
||||
? "旅行者-空"
|
||||
: props.modelValue.id === 10000007
|
||||
? "旅行者-荧"
|
||||
: props.modelValue.name;
|
||||
? "旅行者-荧"
|
||||
: props.modelValue.name;
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
@@ -37,10 +37,6 @@ onMounted(async () => {
|
||||
innerHeight: 20,
|
||||
display: "inner",
|
||||
};
|
||||
hoverTitle.value = `等级:${props.modelValue.level}\n好感:${props.modelValue.fetter}\n角色ID:${props.modelValue.id}`;
|
||||
});
|
||||
|
||||
function showData(): void {
|
||||
// todo @click 应该出来的是一个弹窗,而不是 console
|
||||
console.log(props.modelValue);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
<TItemBox :model-value="box" />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
// vue
|
||||
import { ref, onMounted, computed } from "vue";
|
||||
import TItemBox, { type TItemBoxData } from "../main/t-itembox.vue";
|
||||
// plugins
|
||||
|
||||
import TGSqlite from "../../plugins/Sqlite";
|
||||
import TItemBox, { type TItemBoxData } from "../main/t-itembox.vue";
|
||||
|
||||
interface TibWikiAbyssProps {
|
||||
modelValue: string;
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
<TItemBox :model-value="box" />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
// vue
|
||||
import { ref, onMounted, computed } from "vue";
|
||||
import TItemBox, { type TItemBoxData } from "../main/t-itembox.vue";
|
||||
// plugins
|
||||
|
||||
import TGSqlite from "../../plugins/Sqlite";
|
||||
import TItemBox, { type TItemBoxData } from "../main/t-itembox.vue";
|
||||
|
||||
interface TibWikiAbyssProps {
|
||||
modelValue: {
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<TItemBox :model-value="box" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { computed } from "vue";
|
||||
|
||||
import TItemBox, { type TItemBoxData } from "../main/t-itembox.vue";
|
||||
|
||||
interface TibCalendarAvatarProps {
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
<TItemBox :model-value="box" style="cursor: pointer" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { computed } from "vue";
|
||||
|
||||
import TItemBox from "../main/t-itembox.vue";
|
||||
// types
|
||||
import type { TItemBoxData } from "../main/t-itembox.vue";
|
||||
|
||||
interface TibCalendarWeaponProps {
|
||||
|
||||
@@ -211,7 +211,10 @@ const props = defineProps<TItemBoxProps>();
|
||||
.tib-outer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--common-text-title);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -35,16 +35,14 @@
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
// vue
|
||||
import { computed } from "vue";
|
||||
|
||||
import Mys from "../../plugins/Mys";
|
||||
import { createTGWindow } from "../../utils/TGWindow";
|
||||
import showSnackbar from "../func/snackbar";
|
||||
import TOverlay from "../main/t-overlay.vue";
|
||||
import TibCalendarItem from "../itembox/tib-calendar-item.vue";
|
||||
import TibCalendarMaterial from "../itembox/tib-calendar-material.vue";
|
||||
// utils
|
||||
import { createTGWindow } from "../../utils/TGWindow";
|
||||
// plugins
|
||||
import Mys from "../../plugins/Mys";
|
||||
import TOverlay from "../main/t-overlay.vue";
|
||||
|
||||
interface ToCalendarProps {
|
||||
modelValue: boolean;
|
||||
@@ -76,7 +74,7 @@ const onCancel = (): void => {
|
||||
|
||||
function toDetail(item: TGApp.App.Calendar.Item): void {
|
||||
if (item.contentId === 0) {
|
||||
const itemType = item.itemType === "avatar" ? "角色" : "武器";
|
||||
const itemType = item.itemType === "character" ? "角色" : "武器";
|
||||
showSnackbar({
|
||||
text: `[${itemType}] ${item.name} 暂无详情`,
|
||||
color: "warn",
|
||||
@@ -145,10 +143,13 @@ function toDetail(item: TGApp.App.Calendar.Item): void {
|
||||
|
||||
.toc-src-box :nth-child(2) {
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
margin: 5px;
|
||||
aspect-ratio: 1;
|
||||
background: var(--common-shadow-4);
|
||||
filter: invert(87%) sepia(14%) saturate(216%) hue-rotate(180deg) brightness(81%) contrast(87%);
|
||||
}
|
||||
|
||||
.dark .toc-src-box :nth-child(2) {
|
||||
filter: none;
|
||||
}
|
||||
|
||||
.toc-src-text {
|
||||
|
||||
@@ -24,9 +24,9 @@
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { computed } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
import TOverlay from "../main/t-overlay.vue";
|
||||
|
||||
interface ToChannelProps {
|
||||
|
||||
@@ -17,17 +17,15 @@
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
// vue
|
||||
import QrcodeVue from "qrcode.vue";
|
||||
import { computed, reactive, ref, watch } from "vue";
|
||||
|
||||
import Mys from "../../plugins/Mys";
|
||||
import TGSqlite from "../../plugins/Sqlite";
|
||||
import { useUserStore } from "../../store/modules/user";
|
||||
import TGRequest from "../../web/request/TGRequest";
|
||||
import showSnackbar from "../func/snackbar";
|
||||
import TOverlay from "../main/t-overlay.vue";
|
||||
import QrcodeVue from "qrcode.vue";
|
||||
// store
|
||||
import { useUserStore } from "../../store/modules/user";
|
||||
// utils
|
||||
import Mys from "../../plugins/Mys";
|
||||
import TGRequest from "../../web/request/TGRequest";
|
||||
import TGSqlite from "../../plugins/Sqlite";
|
||||
|
||||
interface ToWebLoginProps {
|
||||
modelValue: boolean;
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { ref, watch } from "vue";
|
||||
|
||||
import TOverlay from "../main/t-overlay.vue";
|
||||
|
||||
interface LoadingProps {
|
||||
|
||||
@@ -10,9 +10,8 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import TuaDetailTitle from "./tua-detail-title.vue";
|
||||
import TuaDetailBattle from "./tua-detail-battle.vue";
|
||||
import TuaDetailTitle from "./tua-detail-title.vue";
|
||||
|
||||
interface TuaDetailLevelProps {
|
||||
modelValue: TGApp.Sqlite.Abyss.Level;
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TuaDetailTitle from "./tua-detail-title.vue";
|
||||
import TuaDetailLevel from "./tua-detail-level.vue";
|
||||
import TuaDetailTitle from "./tua-detail-title.vue";
|
||||
|
||||
interface TuaDetailProps {
|
||||
modelValue: TGApp.Sqlite.Abyss.Floor;
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { computed } from "vue";
|
||||
|
||||
import TibAbyssOverview from "../itembox/tib-abyss-overview.vue";
|
||||
|
||||
interface TAOProps {
|
||||
|
||||
@@ -24,9 +24,8 @@
|
||||
</TucDetailDesc>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import TucDetailDesc from "./tuc-detail-desc.vue";
|
||||
import TucDetailConstellation from "./tuc-detail-constellation.vue";
|
||||
import TucDetailDesc from "./tuc-detail-desc.vue";
|
||||
|
||||
interface TucDetailDescConstellationProps {
|
||||
modelValue: TGApp.Sqlite.Character.RoleConstellation;
|
||||
@@ -38,10 +37,10 @@ const props = defineProps<TucDetailDescConstellationProps>();
|
||||
function parseDesc(desc: string): string {
|
||||
const reg = /<color=(.*?)>(.*?)<\/color>/g;
|
||||
let match = reg.exec(desc);
|
||||
while (match) {
|
||||
while (match !== null) {
|
||||
const color = match[1];
|
||||
const text = match[2];
|
||||
desc = desc.replace(match[0], `<span style="color: ${color}">${text}</span>`);
|
||||
desc = desc.replace(match[0], `<span title="${text}" style="color: ${color};">${text}</span>`);
|
||||
match = reg.exec(desc);
|
||||
}
|
||||
desc = desc.replace(/\\n/g, "<br />");
|
||||
@@ -61,11 +60,12 @@ function parseDesc(desc: string): string {
|
||||
|
||||
.tuc-ddc-top {
|
||||
height: 20px;
|
||||
color: var(--tgc-blue-1);
|
||||
color: var(--box-text-3);
|
||||
}
|
||||
|
||||
.tuc-ddc-bottom {
|
||||
height: 20px;
|
||||
color: var(--box-text-1);
|
||||
}
|
||||
|
||||
.tuc-ddc-bottom :nth-child(1) {
|
||||
|
||||
@@ -48,7 +48,7 @@ const props = defineProps<TucDetailDescRelicProps>();
|
||||
|
||||
.tuc-ddrc-top {
|
||||
height: 20px;
|
||||
color: var(--tgc-dark-1);
|
||||
color: var(--box-text-1);
|
||||
}
|
||||
|
||||
.tuc-ddrc-top :nth-child(1) {
|
||||
@@ -70,7 +70,7 @@ const props = defineProps<TucDetailDescRelicProps>();
|
||||
}
|
||||
|
||||
.tuc-ddrd-title {
|
||||
color: var(--tgc-dark-1);
|
||||
color: var(--box-text-3);
|
||||
font-family: var(--font-title);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
</TucDetailDesc>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { computed } from "vue";
|
||||
|
||||
import TucDetailDesc from "./tuc-detail-desc.vue";
|
||||
import TucDetailItemBox from "./tuc-detail-itembox.vue";
|
||||
|
||||
@@ -49,7 +49,7 @@ const box = computed(() => {
|
||||
align-items: start;
|
||||
justify-content: space-around;
|
||||
margin-left: 5px;
|
||||
color: var(--tgc-dark-1);
|
||||
color: var(--box-text-1);
|
||||
}
|
||||
|
||||
.tuc-ddwc-top {
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
<style lang="css" scoped>
|
||||
.tuc-dd-box {
|
||||
padding: 10px;
|
||||
border: 1px solid rgb(0 0 0 /40%);
|
||||
border: 1px solid var(--common-shadow-2);
|
||||
border-radius: 5px;
|
||||
background: var(--tgc-white-1);
|
||||
background: var(--box-bg-2);
|
||||
}
|
||||
|
||||
.tuc-dd-title {
|
||||
width: 100%;
|
||||
color: var(--tgc-dark-1);
|
||||
color: var(--common-text-title);
|
||||
font-family: var(--font-title);
|
||||
font-size: 20px;
|
||||
text-align: left;
|
||||
@@ -32,8 +32,7 @@
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
margin: 5px 0;
|
||||
background: var(--tgc-dark-1);
|
||||
opacity: 0.5;
|
||||
background: var(--common-shadow-2);
|
||||
}
|
||||
|
||||
.tuc-dd-content {
|
||||
@@ -48,12 +47,16 @@
|
||||
width: 100%;
|
||||
max-height: 50px;
|
||||
margin-top: 5px;
|
||||
color: var(--tgc-dark-1);
|
||||
color: var(--box-text-4);
|
||||
font-family: var(--font-text);
|
||||
font-size: 14px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
text-align: left;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* 隐藏 desc 侧面滚动条 */
|
||||
.tuc-dd-desc::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,90 +1,101 @@
|
||||
<template>
|
||||
<TOverlay v-model="visible" hide :to-click="onCancel" blur-val="20px">
|
||||
<div class="tuc-do-box">
|
||||
<div class="tuc-do-bg">
|
||||
<img
|
||||
:src="data.bg"
|
||||
alt="role"
|
||||
:style="{
|
||||
objectFit: data.bgFit,
|
||||
}"
|
||||
/>
|
||||
<div class="tuc-do-div">
|
||||
<!-- 左侧箭头 -->
|
||||
<div class="tuc-arrow-left" @click="handleClick('left')">
|
||||
<img src="../../assets/icons/arrow-right.svg" alt="left" />
|
||||
</div>
|
||||
<div class="tuc-do-quote">* 所有数据以游戏内为准,此处仅供参考</div>
|
||||
<!-- 衣装 -->
|
||||
<div v-if="data.costume.length > 0" class="tuc-do-costume">
|
||||
<v-switch v-model="showCostumeSwitch" color="#fb7299" @click="switchBg">
|
||||
<template #label>
|
||||
<v-icon>mdi-tshirt-crew-outline</v-icon>
|
||||
</template>
|
||||
</v-switch>
|
||||
</div>
|
||||
<div v-if="showCostumeSwitch" class="tuc-do-costume-name">
|
||||
{{ data.costume[0].name }}
|
||||
</div>
|
||||
<div v-if="data" class="tuc-do-show">
|
||||
<!-- 左侧武器跟圣遗物 -->
|
||||
<div class="tuc-do-left">
|
||||
<div
|
||||
class="tuc-dol-item"
|
||||
<!-- 中间内容 -->
|
||||
<div class="tuc-do-box">
|
||||
<div class="tuc-do-bg">
|
||||
<img
|
||||
:src="data.bg"
|
||||
alt="role"
|
||||
:style="{
|
||||
opacity: selected.pos === 0 ? '1' : '0.5',
|
||||
objectFit: data.bgFit,
|
||||
}"
|
||||
@click="showDetail(data.weapon, '武器', 0)"
|
||||
>
|
||||
<TucDetailItemBox v-model="weaponBox" />
|
||||
</div>
|
||||
<div
|
||||
v-for="(item, index) in data.reliquary"
|
||||
:key="index"
|
||||
class="tuc-dol-item"
|
||||
:style="{
|
||||
cursor: item ? 'pointer' : 'default',
|
||||
opacity: selected.pos === index + 1 ? '1' : item ? '0.5' : '1',
|
||||
}"
|
||||
@click="showDetail(item, '圣遗物', index + 1)"
|
||||
>
|
||||
<TucDetailRelic :model-value="item" :pos="index + 1" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右侧环状排列6个命座 -->
|
||||
<div class="tuc-do-right">
|
||||
<div class="tuc-dor-box">
|
||||
<TucDetailConstellation
|
||||
v-for="item in data.constellation"
|
||||
:key="item.pos"
|
||||
class="tuc-dor-item"
|
||||
:model-value="item"
|
||||
:style="{
|
||||
border: selected.pos === item.pos + 5 ? '2px solid var(--tgc-yellow-1)' : '',
|
||||
}"
|
||||
@click="showDetail(item, '命座', item.pos + 5)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 底部说明 -->
|
||||
<div class="tuc-do-bottom">
|
||||
<TucDetailDescWeapon v-if="selected.type === '武器'" v-model="selectWeapon" />
|
||||
<TucDetailDescConstellation
|
||||
v-if="selected.type === '命座'"
|
||||
v-model="selectConstellation"
|
||||
/>
|
||||
<TucDetailDescRelic v-if="selected.type === '圣遗物'" v-model="selectRelic" />
|
||||
</div>
|
||||
<div class="tuc-do-quote">* 所有数据以游戏内为准,此处仅供参考</div>
|
||||
<!-- 衣装 -->
|
||||
<div v-if="data.costume.length > 0" class="tuc-do-costume">
|
||||
<v-switch v-model="showCostumeSwitch" color="#fb7299" @click="switchBg">
|
||||
<template #label>
|
||||
<v-icon>mdi-tshirt-crew-outline</v-icon>
|
||||
</template>
|
||||
</v-switch>
|
||||
</div>
|
||||
<div v-if="showCostumeSwitch" class="tuc-do-costume-name">
|
||||
{{ data.costume[0].name }}
|
||||
</div>
|
||||
<div v-if="data" class="tuc-do-show">
|
||||
<!-- 左侧武器跟圣遗物 -->
|
||||
<div class="tuc-do-left">
|
||||
<div
|
||||
class="tuc-dol-item"
|
||||
:style="{
|
||||
opacity: selected.pos === 0 ? '1' : '0.5',
|
||||
}"
|
||||
@click="showDetail(data.weapon, '武器', 0)"
|
||||
>
|
||||
<TucDetailItemBox v-model="weaponBox" />
|
||||
</div>
|
||||
<div
|
||||
v-for="(item, index) in data.reliquary"
|
||||
:key="index"
|
||||
class="tuc-dol-item"
|
||||
:style="{
|
||||
cursor: item ? 'pointer' : 'default',
|
||||
opacity: selected.pos === index + 1 ? '1' : item ? '0.5' : '1',
|
||||
}"
|
||||
@click="showDetail(item, '圣遗物', index + 1)"
|
||||
>
|
||||
<TucDetailRelic :model-value="item" :pos="index + 1" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右侧环状排列6个命座 -->
|
||||
<div class="tuc-do-right">
|
||||
<div class="tuc-dor-box">
|
||||
<TucDetailConstellation
|
||||
v-for="item in data.constellation"
|
||||
:key="item.pos"
|
||||
class="tuc-dor-item"
|
||||
:model-value="item"
|
||||
:style="{
|
||||
border: selected.pos === item.pos + 5 ? '2px solid var(--tgc-yellow-1)' : '',
|
||||
}"
|
||||
@click="showDetail(item, '命座', item.pos + 5)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 底部说明 -->
|
||||
<div class="tuc-do-bottom">
|
||||
<TucDetailDescWeapon v-if="selected.type === '武器'" v-model="selectWeapon" />
|
||||
<TucDetailDescConstellation
|
||||
v-if="selected.type === '命座'"
|
||||
v-model="selectConstellation"
|
||||
/>
|
||||
<TucDetailDescRelic v-if="selected.type === '圣遗物'" v-model="selectRelic" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右侧箭头 -->
|
||||
<div class="tuc-arrow-right" @click="handleClick('right')">
|
||||
<img src="../../assets/icons/arrow-right.svg" alt="right" />
|
||||
</div>
|
||||
</div>
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { computed, onMounted, onUpdated, ref } from "vue";
|
||||
import TOverlay from "../main/t-overlay.vue";
|
||||
import TucDetailDescWeapon from "./tuc-detail-desc-weapon.vue";
|
||||
|
||||
import TucDetailConstellation from "./tuc-detail-constellation.vue";
|
||||
import TucDetailDescConstellation from "./tuc-detail-desc-constellation.vue";
|
||||
import TucDetailDescRelic from "./tuc-detail-desc-relic.vue";
|
||||
import TucDetailDescWeapon from "./tuc-detail-desc-weapon.vue";
|
||||
import TucDetailItemBox from "./tuc-detail-itembox.vue";
|
||||
import TucDetailConstellation from "./tuc-detail-constellation.vue";
|
||||
import TucDetailRelic from "./tuc-detail-relic.vue";
|
||||
import TOverlay from "../main/t-overlay.vue";
|
||||
|
||||
interface ToUcDetailProps {
|
||||
modelValue: boolean;
|
||||
@@ -94,7 +105,9 @@ interface ToUcDetailProps {
|
||||
interface ToUcDetailEmits {
|
||||
(e: "update:modelValue", value: boolean): void;
|
||||
|
||||
(e: "cancel"): void;
|
||||
(e: "clickL"): void;
|
||||
|
||||
(e: "clickR"): void;
|
||||
}
|
||||
|
||||
type fixedLenArray<T, N extends number> = [T, ...T[]] & { length: N };
|
||||
@@ -214,9 +227,13 @@ const weaponBox = computed(() => {
|
||||
|
||||
const onCancel = (): void => {
|
||||
visible.value = false;
|
||||
emits("cancel");
|
||||
emits("update:modelValue", false);
|
||||
};
|
||||
|
||||
function handleClick(pos: "left" | "right") {
|
||||
pos === "left" ? emits("clickL") : emits("clickR");
|
||||
}
|
||||
|
||||
function showDetail(
|
||||
item:
|
||||
| TGApp.Sqlite.Character.RoleConstellation
|
||||
@@ -259,13 +276,47 @@ function switchBg(): void {
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.tuc-do-div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
column-gap: 10px;
|
||||
}
|
||||
|
||||
.tuc-arrow-left,
|
||||
.tuc-arrow-right {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dark .tuc-arrow-left,
|
||||
.dark .tuc-arrow-right {
|
||||
filter: invert(11%) sepia(73%) saturate(11%) hue-rotate(139deg) brightness(97%) contrast(81%);
|
||||
}
|
||||
|
||||
.tuc-arrow-left img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.tuc-arrow-right img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tuc-do-box {
|
||||
position: relative;
|
||||
width: 500px;
|
||||
height: 620px;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
background: var(--tgc-white-1);
|
||||
background: var(--box-bg-1);
|
||||
}
|
||||
|
||||
.tuc-do-bg {
|
||||
@@ -290,7 +341,7 @@ function switchBg(): void {
|
||||
bottom: 0;
|
||||
padding: 2px 5px;
|
||||
backdrop-filter: blur(10px);
|
||||
background: rgb(0 0 0 /50%);
|
||||
background: var(--common-shadow-2);
|
||||
border-bottom-right-radius: 5px;
|
||||
border-top-left-radius: 5px;
|
||||
color: var(--tgc-white-1);
|
||||
|
||||