init
This commit is contained in:
@@ -1,55 +0,0 @@
|
|||||||
# dcalc Agent Guide
|
|
||||||
|
|
||||||
本仓库前端迁移主线是将 frontend 迁移到 frontendNew。
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
- 主线目录:frontendNew
|
|
||||||
- 对照目录:frontend(仅用于行为对照,不作为新实现落点)
|
|
||||||
- 本仓库优先维护 Dcalc Character 模块:equipment、skill、forge、result
|
|
||||||
|
|
||||||
## Runbook
|
|
||||||
|
|
||||||
- frontendNew 开发:`cd frontendNew && pnpm install && pnpm dev`
|
|
||||||
- frontendNew 质量检查:`pnpm type-check && pnpm lint && pnpm build`
|
|
||||||
- backend 开发:`cd backend && uv sync && python main.py`
|
|
||||||
|
|
||||||
## Non-Negotiables
|
|
||||||
|
|
||||||
- 不要改动 /calc 的 API 契约与 IResult 数据结构。
|
|
||||||
- 不要打破配置链路:config 变化 -> result.execute -> saveConfig。
|
|
||||||
- 不要为了 UI 重构而改写 Skill/Forge 的核心业务逻辑。
|
|
||||||
- 优先复用 frontendNew 现有 shadcn-vue 组件,不引入新的 UI 框架。
|
|
||||||
|
|
||||||
## Frontend Migration Priorities
|
|
||||||
|
|
||||||
1. P0:Skill 配置回写完整(VP/UP 约束与持久化一致)。
|
|
||||||
2. P0:Forge 配置编辑完整(装备/誓约/时装/徽章联动计算)。
|
|
||||||
3. P0:Result 完整展示(loading/error/empty/success 四态与伤害明细)。
|
|
||||||
4. P1:响应式体验优化(桌面双栏,移动端结果抽屉)。
|
|
||||||
5. P1:Tab 汇总(装备详情/结果、技能详情/结果切换)。
|
|
||||||
|
|
||||||
## Design/Docs Entry
|
|
||||||
|
|
||||||
- [README](README.md)
|
|
||||||
- [frontendNew README](frontendNew/README.md)
|
|
||||||
- [frontendNew Migration Plan](frontendNew/MIGRATION_PLAN.md)
|
|
||||||
- [frontendNew Design](frontendNew/DESIGN.md)
|
|
||||||
- [角色信息与结果链路专项分析](frontend-角色信息与计算结果展示链路-专项分析.md)
|
|
||||||
- [装备技能打造交互与布局设计](frontend-装备技能打造-完整交互与布局设计文档.md)
|
|
||||||
|
|
||||||
## Key Code Entry
|
|
||||||
|
|
||||||
- [Character Container](frontendNew/src/pages/Dcalc/Character/Index.vue)
|
|
||||||
- [Result Panel](frontendNew/src/pages/Dcalc/Character/Result/Index.vue)
|
|
||||||
- [Skill Page](frontendNew/src/pages/Dcalc/Character/Skill/index.vue)
|
|
||||||
- [Forge Page](frontendNew/src/pages/Dcalc/Character/Forge/index.vue)
|
|
||||||
- [Config Store](frontendNew/src/stores/config.ts)
|
|
||||||
- [Router](frontendNew/src/router/index.ts)
|
|
||||||
|
|
||||||
## Implementation Bias
|
|
||||||
|
|
||||||
- 同页配置 + 结果展示优先。
|
|
||||||
- Skill/Forge 在交互上尽量小改,优先行为兼容。
|
|
||||||
- 新增交互优先使用低侵入方式:Tab、Sheet、折叠区。
|
|
||||||
- 文档遵循 link-not-embed:链接现有文档,不重复长文说明。
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
from datetime import datetime
|
|
||||||
import logging
|
import logging
|
||||||
import traceback
|
import traceback
|
||||||
from fastapi import Request, status
|
from fastapi import Request, status
|
||||||
@@ -7,15 +6,7 @@ from fastapi.responses import JSONResponse
|
|||||||
|
|
||||||
from api.core.exception import ResponseException
|
from api.core.exception import ResponseException
|
||||||
|
|
||||||
logFormatter = logging.Formatter(
|
logger = logging.getLogger('dcalc.exception')
|
||||||
"""
|
|
||||||
[start]
|
|
||||||
%(asctime)s
|
|
||||||
<%(name)s>
|
|
||||||
%(message)s
|
|
||||||
[end]
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def format_exception() -> str:
|
def format_exception() -> str:
|
||||||
@@ -30,32 +21,10 @@ async def all_exception_handler(request: Request, ex: Exception):
|
|||||||
info = ''
|
info = ''
|
||||||
if not isinstance(ex, ResponseException):
|
if not isinstance(ex, ResponseException):
|
||||||
info = format_exception()
|
info = format_exception()
|
||||||
if request.client.host != '127.0.0.1':
|
client_host = request.client.host if request.client else ''
|
||||||
from fastapi.logger import logger
|
if client_host != '127.0.0.1':
|
||||||
logger.setLevel(logging.ERROR)
|
logger.error('Unhandled exception for %s', request.url.path, exc_info=(type(ex), ex, ex.__traceback__))
|
||||||
logger.name = 'dcalc'
|
info = '服务器内部错误'
|
||||||
log_directory = 'logs'
|
|
||||||
import os
|
|
||||||
|
|
||||||
if not os.path.exists('./logs'):
|
|
||||||
os.makedirs('./logs')
|
|
||||||
time_str = datetime.now().strftime('%Y_%m_%d')
|
|
||||||
fileHandler = logging.FileHandler(f'{log_directory}/{logger.name}_{time_str}.log', encoding='utf-8')
|
|
||||||
fileHandler.setFormatter(logFormatter)
|
|
||||||
fileHandler.setLevel(logging.ERROR)
|
|
||||||
logger.addHandler(fileHandler)
|
|
||||||
try:
|
|
||||||
import pathlib
|
|
||||||
pathlib.Path(log_directory).mkdir(parents=True, exist_ok=True)
|
|
||||||
except PermissionError:
|
|
||||||
print('创建日志目录logs失败,请确认是否限制了基础的运行权限')
|
|
||||||
logFunc = logger.error
|
|
||||||
try:
|
|
||||||
logFunc(info)
|
|
||||||
info = '服务器内部错误'
|
|
||||||
finally:
|
|
||||||
logger.removeHandler(fileHandler)
|
|
||||||
fileHandler.close()
|
|
||||||
else:
|
else:
|
||||||
info = ex.args[0]
|
info = ex.args[0]
|
||||||
# traceback.print_last(sys.exc_info())
|
# traceback.print_last(sys.exc_info())
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from config.main import BACKEND_DIR
|
||||||
|
|
||||||
|
|
||||||
|
LOG_DIR = BACKEND_DIR / 'logs'
|
||||||
|
|
||||||
|
|
||||||
|
def configure_logging() -> None:
|
||||||
|
LOG_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
log_path = LOG_DIR / f'dcalc_{datetime.now().strftime("%Y_%m_%d")}.log'
|
||||||
|
|
||||||
|
logger = logging.getLogger('dcalc')
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
logger.propagate = True
|
||||||
|
|
||||||
|
if any(isinstance(handler, logging.FileHandler) and Path(handler.baseFilename) == log_path for handler in logger.handlers):
|
||||||
|
return
|
||||||
|
|
||||||
|
handler = logging.FileHandler(log_path, encoding='utf-8')
|
||||||
|
handler.setLevel(logging.ERROR)
|
||||||
|
handler.setFormatter(
|
||||||
|
logging.Formatter(
|
||||||
|
"""
|
||||||
|
[start]
|
||||||
|
%(asctime)s
|
||||||
|
<%(name)s>
|
||||||
|
%(levelname)s
|
||||||
|
%(message)s
|
||||||
|
[end]
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
logger.addHandler(handler)
|
||||||
@@ -8,7 +8,12 @@ import redis
|
|||||||
|
|
||||||
from api.core.Gzip import gzip_zip
|
from api.core.Gzip import gzip_zip
|
||||||
from api.utils.skill_detail import SKILL_DETAIL_NORMALIZE_VERSION, normalize_skill_detail
|
from api.utils.skill_detail import SKILL_DETAIL_NORMALIZE_VERSION, normalize_skill_detail
|
||||||
from api.utils.openapi_data import logical_skill_detail_prefix
|
from api.utils.openapi_data import (
|
||||||
|
OPENAPI_CUSTOM_CN_PATH,
|
||||||
|
OPENAPI_DATA_DIR,
|
||||||
|
OPENAPI_SKILLS_PATH,
|
||||||
|
logical_skill_detail_prefix,
|
||||||
|
)
|
||||||
from api.utils.skill_tree import (
|
from api.utils.skill_tree import (
|
||||||
SKILL_TREE_NAME_MAP_VERSION,
|
SKILL_TREE_NAME_MAP_VERSION,
|
||||||
SKILL_TREE_WITH_DESC_SPECIAL_VERSION,
|
SKILL_TREE_WITH_DESC_SPECIAL_VERSION,
|
||||||
@@ -18,7 +23,7 @@ from api.utils.skill_tree import (
|
|||||||
from core.basic.character import createCharacter
|
from core.basic.character import createCharacter
|
||||||
from core.basic.equipment import get_equipment
|
from core.basic.equipment import get_equipment
|
||||||
from core.character.adventure import adv, get_adv_list
|
from core.character.adventure import adv, get_adv_list
|
||||||
from database.connect import BACKEND_DIR, RedisPool
|
from database.connect import RedisPool
|
||||||
|
|
||||||
|
|
||||||
PreheatLoader = Callable[[], object]
|
PreheatLoader = Callable[[], object]
|
||||||
@@ -28,12 +33,12 @@ PREHEAT_LOCK_EXPIRE_SECONDS = 15 * 60
|
|||||||
|
|
||||||
|
|
||||||
def _load_skills():
|
def _load_skills():
|
||||||
with (BACKEND_DIR / 'openapi' / 'data' / 'skills.json').open(encoding='utf-8') as file:
|
with OPENAPI_SKILLS_PATH.open(encoding='utf-8') as file:
|
||||||
return json.load(file)
|
return json.load(file)
|
||||||
|
|
||||||
|
|
||||||
def _load_custom_cn():
|
def _load_custom_cn():
|
||||||
with (BACKEND_DIR / 'openapi' / 'custom_cn.json').open(encoding='utf-8') as file:
|
with OPENAPI_CUSTOM_CN_PATH.open(encoding='utf-8') as file:
|
||||||
return json.load(file)
|
return json.load(file)
|
||||||
|
|
||||||
|
|
||||||
@@ -60,8 +65,7 @@ def _build_character_preheat_tasks(equ_version: str = '0') -> list[PreheatTask]:
|
|||||||
|
|
||||||
|
|
||||||
def _iter_skill_detail_files() -> list[Path]:
|
def _iter_skill_detail_files() -> list[Path]:
|
||||||
data_dir = BACKEND_DIR / 'openapi' / 'data'
|
return sorted(OPENAPI_DATA_DIR.glob('cn/*/*/skillDetail/*.json'))
|
||||||
return sorted(data_dir.glob('cn/*/*/skillDetail/*.json'))
|
|
||||||
|
|
||||||
|
|
||||||
def _skill_prefix_from_path(path: Path) -> str:
|
def _skill_prefix_from_path(path: Path) -> str:
|
||||||
@@ -144,9 +148,8 @@ def _build_skill_data_preheat_tasks(custom_cn: list[dict]) -> list[PreheatTask]:
|
|||||||
|
|
||||||
def _build_skill_tree_preheat_tasks() -> list[PreheatTask]:
|
def _build_skill_tree_preheat_tasks() -> list[PreheatTask]:
|
||||||
tasks: list[PreheatTask] = []
|
tasks: list[PreheatTask] = []
|
||||||
data_dir = BACKEND_DIR / 'openapi' / 'data'
|
for path in sorted(OPENAPI_DATA_DIR.glob('cn/*/*/skill_tree.json')):
|
||||||
for path in sorted(data_dir.glob('cn/*/*/skill_tree.json')):
|
relative = path.relative_to(OPENAPI_DATA_DIR)
|
||||||
relative = path.relative_to(data_dir)
|
|
||||||
job_id = relative.parts[1]
|
job_id = relative.parts[1]
|
||||||
job_grow_id = relative.parts[2]
|
job_grow_id = relative.parts[2]
|
||||||
tasks.append(
|
tasks.append(
|
||||||
|
|||||||
@@ -10,5 +10,5 @@ register_gzip_request(router)
|
|||||||
|
|
||||||
|
|
||||||
@router.post('/calc')
|
@router.post('/calc')
|
||||||
async def calc(state: AltersDep, config=Body(None)):
|
def calc(state: AltersDep, config=Body(None)):
|
||||||
return response(data=calculate(state, config))
|
return response(data=calculate(state, config))
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from api.core.Response import response
|
|||||||
from api.core.Redis import get_redis_info
|
from api.core.Redis import get_redis_info
|
||||||
from api.dp import RedisDep
|
from api.dp import RedisDep
|
||||||
from api.utils.skill_detail import SKILL_DETAIL_NORMALIZE_VERSION, normalize_skill_detail, skill_detail_for_level
|
from api.utils.skill_detail import SKILL_DETAIL_NORMALIZE_VERSION, normalize_skill_detail, skill_detail_for_level
|
||||||
from api.utils.openapi_data import openapi_locale_path
|
from api.utils.openapi_data import OPENAPI_SKILLS_PATH, OPENAPI_SUMMARY_DIR, openapi_locale_path
|
||||||
from api.utils.skill_tree import (
|
from api.utils.skill_tree import (
|
||||||
SKILL_TREE_WITH_DESC_SPECIAL_VERSION,
|
SKILL_TREE_WITH_DESC_SPECIAL_VERSION,
|
||||||
filter_skill_tree_by_desc_special,
|
filter_skill_tree_by_desc_special,
|
||||||
@@ -43,7 +43,7 @@ async def get_skills_list(
|
|||||||
def get_skills():
|
def get_skills():
|
||||||
# This function should retrieve the skills list from the data source
|
# This function should retrieve the skills list from the data source
|
||||||
# For now, we return a placeholder list
|
# For now, we return a placeholder list
|
||||||
with open('./openapi/data/skills.json', encoding='utf-8') as f:
|
with OPENAPI_SKILLS_PATH.open(encoding='utf-8') as f:
|
||||||
skills_data = json.load(f)
|
skills_data = json.load(f)
|
||||||
return skills_data
|
return skills_data
|
||||||
|
|
||||||
@@ -226,13 +226,12 @@ async def get_skill_data_summary(
|
|||||||
def get_skill_data_summary():
|
def get_skill_data_summary():
|
||||||
# This function should retrieve the skill data summary based on job
|
# This function should retrieve the skill data summary based on job
|
||||||
# For now, we return a placeholder dictionary
|
# For now, we return a placeholder dictionary
|
||||||
summary_dir = './openapi/summary/'
|
json_files = [f for f in os.listdir(OPENAPI_SUMMARY_DIR) if f.endswith('.json')]
|
||||||
json_files = [f for f in os.listdir(summary_dir) if f.endswith('.json')]
|
|
||||||
res = []
|
res = []
|
||||||
for i in json_files:
|
for i in json_files:
|
||||||
if job in i:
|
if job in i:
|
||||||
k = re.search(r'^\d+(?:-\d+)?(.*?)_', i)
|
k = re.search(r'^\d+(?:-\d+)?(.*?)_', i)
|
||||||
with open(os.path.join(summary_dir, i), encoding='utf-8') as f:
|
with (OPENAPI_SUMMARY_DIR / i).open(encoding='utf-8') as f:
|
||||||
skill_data = json.load(f)
|
skill_data = json.load(f)
|
||||||
weapon = i.split('_')[-1].replace('.json', '')
|
weapon = i.split('_')[-1].replace('.json', '')
|
||||||
res.append({'weapon': weapon if weapon else '通用', 'skills': skill_data, 'job': k.group(1) if k else ''})
|
res.append({'weapon': weapon if weapon else '通用', 'skills': skill_data, 'job': k.group(1) if k else ''})
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from database.connect import BACKEND_DIR
|
from config.main import BACKEND_DIR
|
||||||
|
|
||||||
|
|
||||||
OPENAPI_DATA_DIR = BACKEND_DIR / 'openapi' / 'data'
|
OPENAPI_DIR = BACKEND_DIR / 'openapi'
|
||||||
|
OPENAPI_STATIC_DIR = OPENAPI_DIR / 'static'
|
||||||
|
OPENAPI_DATA_DIR = OPENAPI_STATIC_DIR / 'data'
|
||||||
|
OPENAPI_SUMMARY_DIR = OPENAPI_STATIC_DIR / 'summary'
|
||||||
|
OPENAPI_SKILLS_PATH = OPENAPI_DATA_DIR / 'skills.json'
|
||||||
|
OPENAPI_CUSTOM_CN_PATH = OPENAPI_STATIC_DIR / 'custom_cn.json'
|
||||||
|
OPENAPI_RES_DIR = OPENAPI_STATIC_DIR / 'res'
|
||||||
LOCALES = {'cn', 'kr'}
|
LOCALES = {'cn', 'kr'}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
from functools import cache
|
from functools import cache
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from api.utils.openapi_data import openapi_locale_path
|
from api.utils.openapi_data import openapi_locale_path
|
||||||
from database.connect import BACKEND_DIR
|
|
||||||
|
|
||||||
|
|
||||||
SKILL_TREE_WITH_DESC_SPECIAL_VERSION = 'skillTreeWithDescSpecial:v1'
|
SKILL_TREE_WITH_DESC_SPECIAL_VERSION = 'skillTreeWithDescSpecial:v1'
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
import os
|
from pathlib import Path
|
||||||
import sys
|
|
||||||
|
|
||||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
|
|
||||||
try:
|
BACKEND_DIR = Path(__file__).resolve().parents[1]
|
||||||
os.chdir(os.path.dirname(sys.argv[0]))
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Config(BaseSettings):
|
class Config(BaseSettings):
|
||||||
DEBUG_MODE: bool = True
|
DEBUG_MODE: bool = True
|
||||||
WORKERS: int = 1
|
WORKERS: int = 1
|
||||||
PORT:int = 27173
|
PORT: int = 27173
|
||||||
MCPPORT: int = 27174
|
MCPPORT: int = 27174
|
||||||
REDIS_URL: str = 'redis://localhost:6379/1'
|
REDIS_URL: str = 'redis://localhost:6379/1'
|
||||||
COLG_TOKEN: str = ''
|
COLG_TOKEN: str = ''
|
||||||
@@ -19,7 +16,11 @@ class Config(BaseSettings):
|
|||||||
HELPER_ID: str = ''
|
HELPER_ID: str = ''
|
||||||
|
|
||||||
model_config = SettingsConfigDict(
|
model_config = SettingsConfigDict(
|
||||||
env_file=('config/.env', 'config/.env.dev', 'config/.env.prod'),
|
env_file=(
|
||||||
|
str(BACKEND_DIR / 'config' / '.env'),
|
||||||
|
str(BACKEND_DIR / 'config' / '.env.dev'),
|
||||||
|
str(BACKEND_DIR / 'config' / '.env.prod'),
|
||||||
|
),
|
||||||
extra='ignore',
|
extra='ignore',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -9,13 +9,17 @@ core.basic.character 包
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import importlib
|
import importlib
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from api.core.exception import ResponseException
|
||||||
from .jade import Jade
|
from .jade import Jade
|
||||||
from .calc_equip import CalcEquipMixin
|
from .calc_equip import CalcEquipMixin
|
||||||
from .calc_carry import CalcCarryMixin
|
from .calc_carry import CalcCarryMixin
|
||||||
from .calc_buffer import CalcBufferMixin
|
from .calc_buffer import CalcBufferMixin
|
||||||
from .info import CharacterInfoMixin
|
from .info import CharacterInfoMixin
|
||||||
|
|
||||||
|
logger = logging.getLogger('dcalc.character')
|
||||||
|
|
||||||
|
|
||||||
class Character(CalcEquipMixin, CalcCarryMixin, CalcBufferMixin, CharacterInfoMixin):
|
class Character(CalcEquipMixin, CalcCarryMixin, CalcBufferMixin, CharacterInfoMixin):
|
||||||
"""
|
"""
|
||||||
@@ -28,15 +32,17 @@ class Character(CalcEquipMixin, CalcCarryMixin, CalcBufferMixin, CharacterInfoMi
|
|||||||
|
|
||||||
|
|
||||||
def createCharacter(alter: str, equVersion: str = '0') -> 'Character':
|
def createCharacter(alter: str, equVersion: str = '0') -> 'Character':
|
||||||
character: Character = None
|
|
||||||
module_name = 'core.character.' + alter
|
module_name = 'core.character.' + alter
|
||||||
if equVersion is None or equVersion == '':
|
if equVersion is None or equVersion == '':
|
||||||
equVersion = '0'
|
equVersion = '0'
|
||||||
try:
|
try:
|
||||||
character = importlib.import_module(module_name).classChange(equVersion)
|
character = importlib.import_module(module_name).classChange(equVersion)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print(ex)
|
logger.exception('create character failed alter=%s equVersion=%s module=%s', alter, equVersion, module_name)
|
||||||
print('create character ' + module_name + ' error.')
|
raise ResponseException(f'职业加载失败: {alter}') from ex
|
||||||
|
if character is None:
|
||||||
|
logger.error('create character returned None alter=%s equVersion=%s module=%s', alter, equVersion, module_name)
|
||||||
|
raise ResponseException(f'职业加载失败: {alter}')
|
||||||
return character
|
return character
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,15 +5,14 @@ import json
|
|||||||
import re
|
import re
|
||||||
from typing import Any, Literal, TYPE_CHECKING
|
from typing import Any, Literal, TYPE_CHECKING
|
||||||
from .formula import 武器冷却惩罚
|
from .formula import 武器冷却惩罚
|
||||||
from database.connect import BACKEND_DIR, get_redis_client
|
from database.connect import get_redis_client
|
||||||
from api.core.Redis import get_redis_info
|
from api.core.Redis import get_redis_info
|
||||||
from api.utils.openapi_data import logical_skill_detail_path
|
from api.utils.openapi_data import OPENAPI_CUSTOM_CN_PATH, logical_skill_detail_path
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from core.basic.character import Character
|
from core.basic.character import Character
|
||||||
|
|
||||||
characterLv = 115 + 5
|
characterLv = 115 + 5
|
||||||
OPENAPI_DIR = BACKEND_DIR / 'openapi'
|
|
||||||
|
|
||||||
|
|
||||||
def _identity(value: Any) -> Any:
|
def _identity(value: Any) -> Any:
|
||||||
@@ -22,7 +21,7 @@ def _identity(value: Any) -> Any:
|
|||||||
|
|
||||||
@cache
|
@cache
|
||||||
def _load_custom_cn() -> list[dict[str, Any]]:
|
def _load_custom_cn() -> list[dict[str, Any]]:
|
||||||
with (OPENAPI_DIR / 'custom_cn.json').open(encoding='utf-8') as f:
|
with OPENAPI_CUSTOM_CN_PATH.open(encoding='utf-8') as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
from functools import cache
|
from functools import cache
|
||||||
from pathlib import Path
|
|
||||||
import redis
|
import redis
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from sqlalchemy.pool import NullPool
|
from sqlalchemy.pool import NullPool
|
||||||
from config.main import config
|
from config.main import BACKEND_DIR, config
|
||||||
|
|
||||||
RedisPool = redis.ConnectionPool.from_url(config.REDIS_URL)
|
RedisPool = redis.ConnectionPool.from_url(config.REDIS_URL)
|
||||||
BACKEND_DIR = Path(__file__).resolve().parents[1]
|
|
||||||
|
|
||||||
|
|
||||||
def get_redis_client():
|
def get_redis_client():
|
||||||
|
|||||||
@@ -7,9 +7,12 @@ import uvicorn
|
|||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
|
|
||||||
from api.core import Exceptions, Gzip, Middleware
|
from api.core import Exceptions, Gzip, Middleware
|
||||||
|
from api.core.Logging import configure_logging
|
||||||
from api.main import api_router
|
from api.main import api_router
|
||||||
from config.main import config
|
from config.main import config
|
||||||
|
|
||||||
|
configure_logging()
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(_: FastAPI):
|
async def lifespan(_: FastAPI):
|
||||||
|
|||||||
+1
-1
Submodule backend/openapi updated: 27e2652dd0...e82703643a
@@ -1,302 +0,0 @@
|
|||||||
# DCAlc 前端迁移探索分析
|
|
||||||
|
|
||||||
## 项目状态
|
|
||||||
- **frontend**:旧项目,功能完整
|
|
||||||
- Pages: Home, Character(Equipment/Skill/Forge)
|
|
||||||
- 组件库:自研 calc-* 组件库(UnoCSS)
|
|
||||||
- 样式:SCSS,暗黑 DNF 风格(棕金色)
|
|
||||||
- API: /info, /calc
|
|
||||||
|
|
||||||
- **frontendNew**:新项目,早期阶段
|
|
||||||
- Pages: Home, Dcalc(DnfHelper)
|
|
||||||
- 组件库:shadcn-vue + reka-ui + lucide-vue
|
|
||||||
- 样式:Tailwind CSS 4
|
|
||||||
- 仅实现了 DnfHelper 模块
|
|
||||||
|
|
||||||
## 主要技术栈差异
|
|
||||||
| 方面 | frontend | frontendNew |
|
|
||||||
|------|----------|-----------|
|
|
||||||
| Vue Router | 4.5.0 | 5.0.4 |
|
|
||||||
| TypeScript | ~5.8.0 | ~6.0.0 |
|
|
||||||
| 样式方案 | UnoCSS + SCSS | Tailwind CSS 4 |
|
|
||||||
| 组件库 | 自研 calc-* | shadcn-vue + reka-ui |
|
|
||||||
| 图标库 | 内置 | lucide-vue-next |
|
|
||||||
| 状态管理 | Pinia (同版本) | Pinia (同版本) |
|
|
||||||
|
|
||||||
## 业务模块复杂度评估
|
|
||||||
1. **Home 页面** - 简单
|
|
||||||
2. **DnfHelper** - 中等(已部分实现)
|
|
||||||
3. **Equipment 装备** - 高(复杂组件层级)
|
|
||||||
4. **Skill 技能** - 中等(树形/列表)
|
|
||||||
5. **Forge 打造** - 高(多标签页面)
|
|
||||||
|
|
||||||
## 迁移风险
|
|
||||||
- calc-* 组件完全重写(shadcn-vue)
|
|
||||||
- 样式系统从 UnoCSS 转换到 Tailwind
|
|
||||||
- 路由结构升级(Vue Router 5)
|
|
||||||
- 组件复用和嵌套关系复杂
|
|
||||||
|
|
||||||
|
|
||||||
## Equipment 完整文件清单
|
|
||||||
|
|
||||||
### Frontend 版本
|
|
||||||
- 页面层: `src/pages/Character/EquipmentDictionary/Index.vue` (主页面)
|
|
||||||
- 页面工具: `src/pages/Character/EquipmentDictionary/formatData.ts`
|
|
||||||
- 页面选项: `src/pages/Character/EquipmentDictionary/components/options.vue`
|
|
||||||
- 组件层:
|
|
||||||
- `src/components/dnf/Equipment/Icon/index.vue`
|
|
||||||
- `src/components/dnf/Equipment/Info/index.vue`
|
|
||||||
- `src/components/dnf/Equipment/List/index.vue`
|
|
||||||
- `src/components/dnf/Equipment/Oath/index.vue`
|
|
||||||
- `src/components/dnf/Equipment/PartSelect/index.vue`
|
|
||||||
- `src/components/dnf/Equipment/RaritySelect/index.vue`
|
|
||||||
- `src/components/dnf/Equipment/SuitInfo/index.vue`
|
|
||||||
|
|
||||||
### 已迁移模式分析 (DnfHelper)
|
|
||||||
- 位置: frontendNew/src/pages/Dcalc/DnfHelper/
|
|
||||||
- 使用 shadcn-vue 原生组件
|
|
||||||
- Tailwind CSS 4 样式
|
|
||||||
- useLocalStorage 本地存储模式
|
|
||||||
|
|
||||||
## Equipment 迁移状态
|
|
||||||
- **未迁移**:frontendNew 中没有对应的 Equipment 页面和组件
|
|
||||||
- **frontend 中的关键文件**:
|
|
||||||
- Pages: `/pages/Character/Equipment/` (已禁用), `/pages/Character/EquipmentDictionary/Index.vue` (当前使用)
|
|
||||||
- 组件库 (7 子组件): Icon, Info, List, Oath, PartSelect, RaritySelect, SuitInfo
|
|
||||||
- 工具函数: `formatData.ts` (formatEqu, formatOath)
|
|
||||||
- 数据处理: 复杂的排列、分组、筛选逻辑
|
|
||||||
|
|
||||||
## DnfHelper 迁移分析(已实现)
|
|
||||||
- **Pages**: `/pages/Dcalc/DnfHelper/`
|
|
||||||
- bind.vue: 表单组件 (InputGroup, Button, Field)
|
|
||||||
- list.vue: 网格列表 (Card, Grid)
|
|
||||||
- index.vue: 容器编排
|
|
||||||
- **Store**: `dnfhelper.ts` - 本地存储 + API 调用
|
|
||||||
- **UI 组件复用**:shadcn-vue Button/Card/Field 直接使用
|
|
||||||
- **样式**:Tailwind 原生类名(grid-cols, gap, padding等)
|
|
||||||
- **图标**:lucide-vue-next (RefreshCcw, GalleryVerticalEnd, Shuffle)
|
|
||||||
|
|
||||||
|
|
||||||
## 修正记录(2026-05-11)
|
|
||||||
- 纠正:frontendNew 已存在并接入 equipment 页面。
|
|
||||||
- 实际路径:frontendNew/src/pages/Dcalc/Character/EquipmentDictionary/Index.vue
|
|
||||||
- 实际路由:frontendNew/src/router/index.ts 中 character/equipment 子路由已配置。
|
|
||||||
- 结论:equipment 迁移状态应定义为“主体完成,后续做一致性优化”。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 详细模块分析
|
|
||||||
|
|
||||||
## 1. SKILL 模块详解
|
|
||||||
|
|
||||||
### Frontend 旧版结构
|
|
||||||
**文件清单**:
|
|
||||||
- `src/pages/Character/Skill/index.vue` (650+ 行)
|
|
||||||
- 子组件: SkillTree, SkillVP, SkillUP, SkillDetail
|
|
||||||
- `src/pages/Character/Skill/components/`:
|
|
||||||
- `SkillTree/index.tsx` - TSX 渲染函数,技能树面板(等级/技能筛选)
|
|
||||||
- `SkillTree/img/` - 美术资源
|
|
||||||
- `SkillVP/index.tsx` - TSX 技能进化选择(5个VP槽位)
|
|
||||||
- `SkillUP/index.tsx` - TSX 技能突破选择(多个UP槽位)
|
|
||||||
- `SkillDetail/index.tsx` - 技能详情弹窗
|
|
||||||
- 依赖 calc-* 组件(button/checkbox/tabs)
|
|
||||||
|
|
||||||
**职责分层**:
|
|
||||||
- **L0(展示层)**: index.vue 主容器 + 6 个 tab 组织
|
|
||||||
- **L1(交互层)**: SkillTree/SkillUP/SkillVP/SkillDetail
|
|
||||||
- **L2(核心)**: TSX 条件渲染 + 计算属性 + 样式定制
|
|
||||||
|
|
||||||
**关键依赖**:
|
|
||||||
- `useInfoStore.skills` - 技能数据源
|
|
||||||
- `useConfigStore.config.skills` - 技能配置 { lv, vp, up }
|
|
||||||
- `onClickOutside` VueUse 钩子
|
|
||||||
- Calc-UI 组件库
|
|
||||||
|
|
||||||
### FrontendNew 占位状态
|
|
||||||
- **文件**: `src/pages/Dcalc/Character/Skill/index.vue` (48 行)
|
|
||||||
- **内容**: 搜索框 + 技能卡片网格(展示级)
|
|
||||||
- **缺失**:
|
|
||||||
- SkillTree 树形选择
|
|
||||||
- SkillVP/SkillUP 配置面板
|
|
||||||
- 技能详情弹窗
|
|
||||||
- 数据编辑交互
|
|
||||||
|
|
||||||
### 迁移差距
|
|
||||||
| 功能 | 旧版 | 新版 | 缺失 |
|
|
||||||
|-----|------|------|------|
|
|
||||||
| 技能列表搜索 | SkillTree | 简单搜索框 | 树形结构 |
|
|
||||||
| VP 进化配置 | SkillVP | ❌ | 5 槽位选择 |
|
|
||||||
| UP 突破配置 | SkillUP | ❌ | 多槽选择 |
|
|
||||||
| 详情展示 | SkillDetail 弹窗 | ❌ | 弹窗界面 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. FORGE 模块详解
|
|
||||||
|
|
||||||
### Frontend 旧版结构
|
|
||||||
**文件清单**:
|
|
||||||
- `src/pages/Character/Forge/index.vue` (32 行) - 简洁容器
|
|
||||||
- `src/pages/Character/Forge/components/`:
|
|
||||||
- `Equipments.vue` - 装备打造子面板
|
|
||||||
- `Avatars.vue` - 时装打造子面板
|
|
||||||
- `Emblems.vue` - 徽章打造子面板
|
|
||||||
- `Oaths.vue` - 誓约打造子面板
|
|
||||||
- `Jades.vue` - 辟邪玉子面板
|
|
||||||
- `Sundry.vue` - 其他配置子面板
|
|
||||||
- 使用 calc-collapse/calc-tabs 容器化
|
|
||||||
|
|
||||||
**职责分层**:
|
|
||||||
- **L0(入口)**: Forge/index.vue - 6 个 collapse 分类
|
|
||||||
- **L1(子模块)**: 6 个 .vue 组件
|
|
||||||
- Equipments: 装备强化/升级/精炼/魔石编辑
|
|
||||||
- Avatars: 时装附魔/徽章选择
|
|
||||||
- Oaths: 誓约配置
|
|
||||||
- Jades: 玉石配置
|
|
||||||
- Sundry: 杂项选项(绑定觉醒等)
|
|
||||||
|
|
||||||
**关键依赖**:
|
|
||||||
- `useConfigStore` - 装备配置状态
|
|
||||||
- 各自与对应的 List 组件嵌套
|
|
||||||
- Calc-UI collapse/tabs
|
|
||||||
|
|
||||||
### FrontendNew 占位状态
|
|
||||||
- **文件**: `src/pages/Dcalc/Character/Forge/index.vue` (45 行)
|
|
||||||
- **内容**: 3 张 Card 统计(装备数、誓约数、时装徽章数)
|
|
||||||
- **缺失**:
|
|
||||||
- 所有 6 个子面板
|
|
||||||
- 装备编辑界面
|
|
||||||
- 誓约/玉石/时装编辑
|
|
||||||
- 数据交互
|
|
||||||
|
|
||||||
### 迁移差距
|
|
||||||
| 功能 | 旧版 | 新版 | 缺失 |
|
|
||||||
|-----|------|------|------|
|
|
||||||
| 装备编辑 | Equipments | 仅统计 | 完整编辑器 |
|
|
||||||
| 时装编辑 | Avatars | 仅统计 | 完整编辑器 |
|
|
||||||
| 徽章编辑 | Emblems | 仅统计 | 完整编辑器 |
|
|
||||||
| 誓约配置 | Oaths | ❌ | 完整面板 |
|
|
||||||
| 玉石配置 | Jades | ❌ | 完整面板 |
|
|
||||||
| 其他选项 | Sundry | ❌ | 完整面板 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. RESULT 模块详解
|
|
||||||
|
|
||||||
### Frontend 旧版结构
|
|
||||||
**文件清单**:
|
|
||||||
- `src/pages/Character/Result/Index.vue` (72 行) - 主容器,管理分类切换
|
|
||||||
- `src/pages/Character/Result/components/`:
|
|
||||||
- `Info.vue` (200+ 行) - 左侧信息面板
|
|
||||||
- EquList 子组件 (装备列表)
|
|
||||||
- Oath 子组件 (誓约信息)
|
|
||||||
- 基础属性数值展示
|
|
||||||
- `Skill.vue` (180+ 行) - 右侧技能伤害表
|
|
||||||
- 技能名/CD/次数/秒伤/总伤害/占比
|
|
||||||
- 可编辑技能次数
|
|
||||||
- 排序功能(按 dps/total 等)
|
|
||||||
- `BufferSkill.vue` (190+ 行) - 组队模式技能表
|
|
||||||
- 不同的表头字段(MAIN/ATK/STRINT 等)
|
|
||||||
- 不可编辑模式
|
|
||||||
|
|
||||||
**职责分层**:
|
|
||||||
- **L0(入口)**: Result/Index.vue - 切换 Equipment/Oath 视图 + 标准对比
|
|
||||||
- **L1(左侧)**: Info.vue - 装备信息 + 基础属性
|
|
||||||
- EquList: 装备图标列表
|
|
||||||
- Oath: 誓约信息
|
|
||||||
- 属性值网格
|
|
||||||
- **L1(右侧)**: Skill.vue / BufferSkill.vue - 技能伤害统计
|
|
||||||
- 表格渲染(固定列宽)
|
|
||||||
- 排序交互
|
|
||||||
- 伤害数值编辑
|
|
||||||
|
|
||||||
**关键依赖**:
|
|
||||||
- `useConfigStore.result.state` - 计算结果数据
|
|
||||||
- `useInfoStore.standard` - 标准对比数据
|
|
||||||
- `configStore.skillCountConfig` - 技能次数配置
|
|
||||||
- 复杂的数据处理和样式定制
|
|
||||||
|
|
||||||
### FrontendNew 缺失状态
|
|
||||||
- **不存在**: 零文件,没有 Result 模块
|
|
||||||
- **缺失**:
|
|
||||||
- 整个右侧结果面板
|
|
||||||
- 技能伤害表展示
|
|
||||||
- 标准对比功能
|
|
||||||
- 装备信息展示
|
|
||||||
|
|
||||||
### 完整缺失情况
|
|
||||||
Result 是 **三个模块中缺失最严重** 的:
|
|
||||||
- 没有代码框架
|
|
||||||
- 没有占位符
|
|
||||||
- 没有数据绑定
|
|
||||||
- 完全的新建工作
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 数据依赖图
|
|
||||||
|
|
||||||
### Skill 依赖
|
|
||||||
```
|
|
||||||
infoStore.skills (API)
|
|
||||||
↓
|
|
||||||
SkillTree/SkillVP/SkillUP
|
|
||||||
↓
|
|
||||||
configStore.config.skills
|
|
||||||
↓
|
|
||||||
Result 右侧 Skill 表展示
|
|
||||||
```
|
|
||||||
|
|
||||||
### Forge 依赖
|
|
||||||
```
|
|
||||||
configStore.config.equips/avatar/oaths
|
|
||||||
↓
|
|
||||||
6 个 Forge 子面板编辑
|
|
||||||
↓
|
|
||||||
Result 左侧 Info 面板展示
|
|
||||||
↓
|
|
||||||
API /calc 计算
|
|
||||||
↓
|
|
||||||
Result 右侧 Skill 表
|
|
||||||
```
|
|
||||||
|
|
||||||
### Result 依赖
|
|
||||||
```
|
|
||||||
configStore.result.state (来自 API /calc)
|
|
||||||
infoStore.standard (标准对比数据)
|
|
||||||
↓
|
|
||||||
Info.vue 左侧展示
|
|
||||||
Skill.vue / BufferSkill.vue 右侧表格
|
|
||||||
↓
|
|
||||||
setStandard() 保存对比基准
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 组件库差异影响
|
|
||||||
|
|
||||||
### Frontend 原生组件映射
|
|
||||||
|
|
||||||
**calc-* 组件** → **shadcn-vue 替代**:
|
|
||||||
- `calc-tabs` → `shadcn Tabs + TabsList + TabsTrigger`
|
|
||||||
- `calc-collapse` → `shadcn Accordion` 或自定义 collapsible
|
|
||||||
- `calc-button` → `shadcn Button`
|
|
||||||
- `calc-checkbox` → `shadcn Checkbox`
|
|
||||||
- `calc-input` → `shadcn Input`
|
|
||||||
- `calc-dialog` → `shadcn Dialog`
|
|
||||||
|
|
||||||
**样式转换**:
|
|
||||||
- UnoCSS shorthand (如 `h-40px`) → Tailwind (如 `h-10`)
|
|
||||||
- DNF 暗色主题 `#101012` → Tailwind `bg-black` + custom 深灰
|
|
||||||
- 金色强调 `#836832` / `#6C5E4A` → Tailwind 或 CSS variables
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 核心差异总结
|
|
||||||
|
|
||||||
| 方面 | Frontend | FrontendNew | 迁移工作 |
|
|
||||||
|------|----------|-----------|---------|
|
|
||||||
| **Skill** | 完整 TSX + UI | 占位(搜索框) | ★★★★ 高 |
|
|
||||||
| **Forge** | 6 个子面板 | 占位(统计卡) | ★★★★★ 最高 |
|
|
||||||
| **Result** | Info + Skill 双面板 | ❌ 不存在 | ★★★★★ 最高 |
|
|
||||||
| **路由** | `/character/xxx` | `/dcalc/character/xxx` | ★ 低 |
|
|
||||||
| **Store** | 全部就位 | 基础就位 | ★ 低 |
|
|
||||||
| **UI 库** | 自研 calc-* | shadcn-vue | ★★ 中 |
|
|
||||||
@@ -1,225 +0,0 @@
|
|||||||
# Frontend 装备流派/职业分支/打造阶段 完整交互与布局设计文档
|
|
||||||
|
|
||||||
## 1. 分析范围与假设
|
|
||||||
|
|
||||||
- 分析范围仅覆盖 frontend。
|
|
||||||
- 目标模块包含:装备、技能、打造、角色信息、计算结果展示。
|
|
||||||
- 术语固定:装备流派、职业分支、打造阶段。
|
|
||||||
- 输出重点:交互链路、分列布局、结果渲染、代码定位证据。
|
|
||||||
|
|
||||||
入口证据:
|
|
||||||
- frontend/src/router/index.ts:13
|
|
||||||
- frontend/src/router/index.ts:22
|
|
||||||
- frontend/src/router/index.ts:34
|
|
||||||
- frontend/src/router/index.ts:40
|
|
||||||
|
|
||||||
## 2. 模块清单与职责映射
|
|
||||||
|
|
||||||
### 2.1 路由与页面
|
|
||||||
|
|
||||||
- 角色主容器页:frontend/src/pages/Character/Index.vue:1
|
|
||||||
- 装备页:frontend/src/pages/Character/EquipmentDictionary/Index.vue:1
|
|
||||||
- 技能页:frontend/src/pages/Character/Skill/index.vue:1
|
|
||||||
- 打造页:frontend/src/pages/Character/Forge/index.vue:1
|
|
||||||
- 结果页:frontend/src/pages/Character/Result/Index.vue:1
|
|
||||||
|
|
||||||
### 2.2 状态管理
|
|
||||||
|
|
||||||
- 配置与计算入口:frontend/src/stores/config.ts:45
|
|
||||||
- 角色信息与职业上下文:frontend/src/stores/info.ts:9
|
|
||||||
- 角色创建与职业参数接入:frontend/src/stores/info.ts:165
|
|
||||||
|
|
||||||
### 2.3 API 与结果类型
|
|
||||||
|
|
||||||
- 计算结果结构定义:frontend/src/api/calc/type.ts:1
|
|
||||||
- 计算请求触发:frontend/src/stores/config.ts:236
|
|
||||||
|
|
||||||
## 3. 交互链路分解(装备流派/职业分支/打造阶段)
|
|
||||||
|
|
||||||
### 3.1 装备流派链路
|
|
||||||
|
|
||||||
1. 触发动作:点击装备图标。
|
|
||||||
- 证据:frontend/src/pages/Character/EquipmentDictionary/Index.vue:67
|
|
||||||
2. 状态变化:调用 chooseEqu 更新 config.equips。
|
|
||||||
- 证据:frontend/src/stores/config.ts:243
|
|
||||||
3. 请求与计算:watch config 变化后执行 result.execute -> api.calc。
|
|
||||||
- 证据:frontend/src/pages/Character/Index.vue:61
|
|
||||||
- 证据:frontend/src/pages/Character/Index.vue:70
|
|
||||||
- 证据:frontend/src/stores/config.ts:236
|
|
||||||
4. 结果渲染:Info 与 Skill/BufferSkill 刷新。
|
|
||||||
- 证据:frontend/src/pages/Character/Result/Index.vue:25
|
|
||||||
- 证据:frontend/src/pages/Character/Result/Index.vue:35
|
|
||||||
- 证据:frontend/src/pages/Character/Result/Index.vue:40
|
|
||||||
|
|
||||||
说明:当前“装备流派”主要通过装备组合隐式形成,未见独立流派枚举模型。
|
|
||||||
|
|
||||||
### 3.2 职业分支链路
|
|
||||||
|
|
||||||
1. 触发动作:路由 alter 参数进入职业上下文。
|
|
||||||
- 证据:frontend/src/router/index.ts:13
|
|
||||||
2. 状态变化:createCharacter(alter) 加载职业信息与配置。
|
|
||||||
- 证据:frontend/src/stores/info.ts:165
|
|
||||||
3. 请求与计算:技能加点/定制写入 config.skills,触发重算。
|
|
||||||
- 证据:frontend/src/pages/Character/Skill/index.vue:23
|
|
||||||
- 证据:frontend/src/pages/Character/Skill/index.vue:32
|
|
||||||
- 证据:frontend/src/pages/Character/Skill/index.vue:41
|
|
||||||
- 证据:frontend/src/pages/Character/Index.vue:61
|
|
||||||
4. 结果渲染:根据 buffer 分支渲染输出职业或辅助职业结果。
|
|
||||||
- 证据:frontend/src/pages/Character/Result/Index.vue:35
|
|
||||||
- 证据:frontend/src/pages/Character/Result/Index.vue:40
|
|
||||||
|
|
||||||
说明:当前“职业分支”语义主要依赖路由 alter 与角色数据,无单独分支选择器。
|
|
||||||
|
|
||||||
### 3.3 打造阶段链路
|
|
||||||
|
|
||||||
1. 触发动作:在打造页切换装备/誓约/时装/徽章并录入参数。
|
|
||||||
- 证据:frontend/src/pages/Character/Forge/index.vue:5
|
|
||||||
- 证据:frontend/src/pages/Character/Forge/index.vue:6
|
|
||||||
- 证据:frontend/src/pages/Character/Forge/index.vue:7
|
|
||||||
- 证据:frontend/src/pages/Character/Forge/index.vue:8
|
|
||||||
- 证据:frontend/src/pages/Character/Forge/index.vue:30
|
|
||||||
2. 状态变化:reinforce、upgrade 等字段写入 equips。
|
|
||||||
- 证据:frontend/src/stores/config.ts:21
|
|
||||||
- 证据:frontend/src/stores/config.ts:26
|
|
||||||
3. 请求与计算:配置变化统一触发 execute 与 calc。
|
|
||||||
- 证据:frontend/src/pages/Character/Index.vue:61
|
|
||||||
- 证据:frontend/src/stores/config.ts:236
|
|
||||||
4. 结果渲染:计算结果映射到 info 与 skills 区域。
|
|
||||||
- 证据:frontend/src/api/calc/type.ts:3
|
|
||||||
- 证据:frontend/src/api/calc/type.ts:4
|
|
||||||
- 证据:frontend/src/pages/Character/Result/Index.vue:25
|
|
||||||
- 证据:frontend/src/pages/Character/Result/Index.vue:35
|
|
||||||
|
|
||||||
说明:当前“打造阶段”以数值字段组合表达,未见显式阶段枚举。
|
|
||||||
|
|
||||||
## 4. 页面布局与分列展示逻辑
|
|
||||||
|
|
||||||
### 4.1 页面骨架
|
|
||||||
|
|
||||||
- 顶部:装备设置/技能设置/打造设置页签。
|
|
||||||
- 证据:frontend/src/pages/Character/Index.vue:4
|
|
||||||
- 中部:content 为 flex 双列结构。
|
|
||||||
- 左列:RouterView 承载配置页面。
|
|
||||||
- 右列:Result 承载结果面板。
|
|
||||||
- 证据:frontend/src/pages/Character/Index.vue:10
|
|
||||||
- 证据:frontend/src/pages/Character/Index.vue:15
|
|
||||||
- 证据:frontend/src/pages/Character/Index.vue:18
|
|
||||||
- 底部:系统奶、组队、导入按钮。
|
|
||||||
- 证据:frontend/src/pages/Character/Index.vue:21
|
|
||||||
|
|
||||||
### 4.2 分列与联动
|
|
||||||
|
|
||||||
- 主列联动:左侧输入变化驱动右侧结果更新。
|
|
||||||
- 证据:frontend/src/pages/Character/Index.vue:61
|
|
||||||
- 证据:frontend/src/pages/Character/Index.vue:70
|
|
||||||
- 结果区二级分列:Info 与 Skill/BufferSkill 并排。
|
|
||||||
- 证据:frontend/src/pages/Character/Result/Index.vue:1
|
|
||||||
- 装备页分列:装备列表区 + 右侧信息区(基础/套装/誓约)。
|
|
||||||
- 证据:frontend/src/pages/Character/EquipmentDictionary/Index.vue:1
|
|
||||||
- 技能页分列:技能树主区 + 技能详情侧浮层。
|
|
||||||
- 证据:frontend/src/pages/Character/Skill/index.vue:1
|
|
||||||
- 证据:frontend/src/pages/Character/Skill/index.vue:58
|
|
||||||
|
|
||||||
## 5. 角色信息与计算结果展示逻辑
|
|
||||||
|
|
||||||
### 5.1 数据源
|
|
||||||
|
|
||||||
- 角色基础数据:frontend/src/stores/info.ts:9
|
|
||||||
- 技能与装备源:frontend/src/stores/info.ts:21
|
|
||||||
- 计算输入配置:frontend/src/stores/config.ts:45
|
|
||||||
- 计算结果结构(skills/info/buffer):frontend/src/api/calc/type.ts:3
|
|
||||||
- 角色信息项结构:frontend/src/api/calc/type.ts:27
|
|
||||||
|
|
||||||
### 5.2 渲染链路
|
|
||||||
|
|
||||||
1. 输入端:装备、技能、打造等写入 config。
|
|
||||||
2. 计算端:result.execute 调用 calc,返回 IResult。
|
|
||||||
- 证据:frontend/src/stores/config.ts:260
|
|
||||||
- 证据:frontend/src/stores/config.ts:236
|
|
||||||
3. 展示端:
|
|
||||||
- 角色信息由 Info 消费 result.state.info。
|
|
||||||
- 证据:frontend/src/pages/Character/Result/Index.vue:25
|
|
||||||
- 结果明细由 Skill/BufferSkill 消费 result.state.skills。
|
|
||||||
- 证据:frontend/src/pages/Character/Result/Index.vue:35
|
|
||||||
- 证据:frontend/src/pages/Character/Result/Index.vue:40
|
|
||||||
|
|
||||||
## 6. 证据索引(按路由/store/API/页面组件分类)
|
|
||||||
|
|
||||||
### 路由
|
|
||||||
- frontend/src/router/index.ts:13
|
|
||||||
- frontend/src/router/index.ts:22
|
|
||||||
- frontend/src/router/index.ts:34
|
|
||||||
- frontend/src/router/index.ts:40
|
|
||||||
|
|
||||||
### Store
|
|
||||||
- frontend/src/stores/config.ts:21
|
|
||||||
- frontend/src/stores/config.ts:26
|
|
||||||
- frontend/src/stores/config.ts:45
|
|
||||||
- frontend/src/stores/config.ts:236
|
|
||||||
- frontend/src/stores/config.ts:243
|
|
||||||
- frontend/src/stores/config.ts:260
|
|
||||||
- frontend/src/stores/info.ts:9
|
|
||||||
- frontend/src/stores/info.ts:21
|
|
||||||
- frontend/src/stores/info.ts:165
|
|
||||||
|
|
||||||
### API
|
|
||||||
- frontend/src/api/calc/type.ts:3
|
|
||||||
- frontend/src/api/calc/type.ts:4
|
|
||||||
- frontend/src/api/calc/type.ts:10
|
|
||||||
- frontend/src/api/calc/type.ts:27
|
|
||||||
|
|
||||||
### 页面组件
|
|
||||||
- frontend/src/pages/Character/Index.vue:4
|
|
||||||
- frontend/src/pages/Character/Index.vue:15
|
|
||||||
- frontend/src/pages/Character/Index.vue:18
|
|
||||||
- frontend/src/pages/Character/Index.vue:61
|
|
||||||
- frontend/src/pages/Character/Index.vue:70
|
|
||||||
- frontend/src/pages/Character/EquipmentDictionary/Index.vue:13
|
|
||||||
- frontend/src/pages/Character/EquipmentDictionary/Index.vue:21
|
|
||||||
- frontend/src/pages/Character/EquipmentDictionary/Index.vue:30
|
|
||||||
- frontend/src/pages/Character/EquipmentDictionary/Index.vue:33
|
|
||||||
- frontend/src/pages/Character/EquipmentDictionary/Index.vue:38
|
|
||||||
- frontend/src/pages/Character/EquipmentDictionary/Index.vue:67
|
|
||||||
- frontend/src/pages/Character/Skill/index.vue:10
|
|
||||||
- frontend/src/pages/Character/Skill/index.vue:18
|
|
||||||
- frontend/src/pages/Character/Skill/index.vue:23
|
|
||||||
- frontend/src/pages/Character/Skill/index.vue:32
|
|
||||||
- frontend/src/pages/Character/Skill/index.vue:41
|
|
||||||
- frontend/src/pages/Character/Forge/index.vue:5
|
|
||||||
- frontend/src/pages/Character/Forge/index.vue:6
|
|
||||||
- frontend/src/pages/Character/Forge/index.vue:7
|
|
||||||
- frontend/src/pages/Character/Forge/index.vue:8
|
|
||||||
- frontend/src/pages/Character/Forge/index.vue:30
|
|
||||||
- frontend/src/pages/Character/Result/Index.vue:25
|
|
||||||
- frontend/src/pages/Character/Result/Index.vue:35
|
|
||||||
- frontend/src/pages/Character/Result/Index.vue:40
|
|
||||||
|
|
||||||
## 7. 问题清单与优先级改造建议
|
|
||||||
|
|
||||||
### P0
|
|
||||||
|
|
||||||
1. 装备流派缺少显式数据模型与入口。
|
|
||||||
- 影响:流派复用、分享、回放能力弱。
|
|
||||||
- 建议:新增流派实体(名称、标签、约束、快照),并在装备页增加流派管理入口。
|
|
||||||
|
|
||||||
2. 职业分支语义依赖路由参数,缺少可视化分支状态。
|
|
||||||
- 影响:评审和调试容易混淆当前分支上下文。
|
|
||||||
- 建议:在角色页头部补充职业与分支状态条。
|
|
||||||
|
|
||||||
### P1
|
|
||||||
|
|
||||||
1. 打造阶段没有统一阶段语义层。
|
|
||||||
- 影响:规则解释成本高,阶段对比困难。
|
|
||||||
- 建议:建立阶段映射(阶段名 -> 字段集合),并在打造页展示阶段摘要。
|
|
||||||
|
|
||||||
2. 结果展示缺少标准化加载态/错误态反馈。
|
|
||||||
- 影响:请求延迟或失败时可解释性不足。
|
|
||||||
- 建议:结果区增加执行中、失败重试、最近成功时间。
|
|
||||||
|
|
||||||
### P2
|
|
||||||
|
|
||||||
1. 多模块分列宽度与信息密度策略存在差异。
|
|
||||||
- 建议:统一主次列比例与标题层级规范。
|
|
||||||
|
|
||||||
2. 证据索引依赖人工维护。
|
|
||||||
- 建议:后续补充自动化证据采集脚本,降低维护成本。
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
# Frontend 角色信息与计算结果展示链路专项分析
|
|
||||||
|
|
||||||
## 1. 分析范围与假设
|
|
||||||
|
|
||||||
- 范围仅覆盖 frontend。
|
|
||||||
- 本文重点是角色信息与计算结果展示链路,补充分列布局、联动逻辑、异常反馈与改造建议。
|
|
||||||
- 职业上下文由路由参数 alter 驱动,计算请求统一走 /calc。
|
|
||||||
|
|
||||||
核心入口证据:
|
|
||||||
- 路由入口与职业参数:[frontend/src/router/index.ts](frontend/src/router/index.ts#L13)
|
|
||||||
- 结果面板挂载位置:[frontend/src/pages/Character/Index.vue](frontend/src/pages/Character/Index.vue#L18)
|
|
||||||
- 计算请求入口:[frontend/src/api/calc/index.ts](frontend/src/api/calc/index.ts#L7)
|
|
||||||
|
|
||||||
## 2. 模块清单与职责映射
|
|
||||||
|
|
||||||
### 2.1 主流程文件
|
|
||||||
|
|
||||||
- 角色页主容器(输入区 + 结果区 + 开关区):[frontend/src/pages/Character/Index.vue](frontend/src/pages/Character/Index.vue)
|
|
||||||
- 结果容器(Info 与 Skill/BufferSkill 分支):[frontend/src/pages/Character/Result/Index.vue](frontend/src/pages/Character/Result/Index.vue)
|
|
||||||
- 角色信息渲染组件: [frontend/src/pages/Character/Result/components/Info.vue](frontend/src/pages/Character/Result/components/Info.vue)
|
|
||||||
- 输出职业结果组件: [frontend/src/pages/Character/Result/components/Skill.vue](frontend/src/pages/Character/Result/components/Skill.vue)
|
|
||||||
- 辅助职业结果组件: [frontend/src/pages/Character/Result/components/BufferSkill.vue](frontend/src/pages/Character/Result/components/BufferSkill.vue)
|
|
||||||
|
|
||||||
### 2.2 数据与状态
|
|
||||||
|
|
||||||
- 配置输入与计算执行: [frontend/src/stores/config.ts](frontend/src/stores/config.ts#L236)
|
|
||||||
- 角色基础信息与职业上下文: [frontend/src/stores/info.ts](frontend/src/stores/info.ts#L9)
|
|
||||||
- 结果数据类型(skills/info/suits/buffer): [frontend/src/api/calc/type.ts](frontend/src/api/calc/type.ts#L3)
|
|
||||||
|
|
||||||
## 3. 角色信息与计算结果展示链路
|
|
||||||
|
|
||||||
### 3.1 触发到计算
|
|
||||||
|
|
||||||
1. 用户在装备、技能、打造区域改动配置后,主容器监听 config 变化。
|
|
||||||
- 证据: [frontend/src/pages/Character/Index.vue](frontend/src/pages/Character/Index.vue#L61)
|
|
||||||
|
|
||||||
2. 监听回调触发 execute,进入异步计算流程。
|
|
||||||
- 证据: [frontend/src/pages/Character/Index.vue](frontend/src/pages/Character/Index.vue#L70)
|
|
||||||
|
|
||||||
3. config store 调用 calc,向 /calc 提交 IConfig。
|
|
||||||
- 证据: [frontend/src/stores/config.ts](frontend/src/stores/config.ts#L236)
|
|
||||||
- 证据: [frontend/src/api/calc/index.ts](frontend/src/api/calc/index.ts#L7)
|
|
||||||
|
|
||||||
### 3.2 计算结果入仓与消费
|
|
||||||
|
|
||||||
1. 结果写入 result.state,结构包含 skills、info、suits、buffer。
|
|
||||||
- 证据: [frontend/src/stores/config.ts](frontend/src/stores/config.ts#L260)
|
|
||||||
- 证据: [frontend/src/api/calc/type.ts](frontend/src/api/calc/type.ts#L3)
|
|
||||||
- 证据: [frontend/src/api/calc/type.ts](frontend/src/api/calc/type.ts#L4)
|
|
||||||
- 证据: [frontend/src/api/calc/type.ts](frontend/src/api/calc/type.ts#L5)
|
|
||||||
- 证据: [frontend/src/api/calc/type.ts](frontend/src/api/calc/type.ts#L10)
|
|
||||||
|
|
||||||
2. 角色信息由 Info 组件消费并渲染 info 列表。
|
|
||||||
- 证据(绑定入口): [frontend/src/pages/Character/Result/Index.vue](frontend/src/pages/Character/Result/Index.vue#L25)
|
|
||||||
- 证据(渲染循环): [frontend/src/pages/Character/Result/components/Info.vue](frontend/src/pages/Character/Result/components/Info.vue#L13)
|
|
||||||
- 证据(字段定义): [frontend/src/api/calc/type.ts](frontend/src/api/calc/type.ts#L27)
|
|
||||||
|
|
||||||
3. 技能结果根据 buffer 分支渲染 Skill 或 BufferSkill。
|
|
||||||
- 证据(普通输出): [frontend/src/pages/Character/Result/Index.vue](frontend/src/pages/Character/Result/Index.vue#L35)
|
|
||||||
- 证据(辅助输出): [frontend/src/pages/Character/Result/Index.vue](frontend/src/pages/Character/Result/Index.vue#L40)
|
|
||||||
|
|
||||||
## 4. 分列布局与联动逻辑
|
|
||||||
|
|
||||||
### 4.1 顶层分列
|
|
||||||
|
|
||||||
- 角色页中部为双列:左列 RouterView 输入,右列 Result 输出。
|
|
||||||
- 证据: [frontend/src/pages/Character/Index.vue](frontend/src/pages/Character/Index.vue#L10)
|
|
||||||
- 证据: [frontend/src/pages/Character/Index.vue](frontend/src/pages/Character/Index.vue#L15)
|
|
||||||
- 证据: [frontend/src/pages/Character/Index.vue](frontend/src/pages/Character/Index.vue#L18)
|
|
||||||
|
|
||||||
### 4.2 结果区分列
|
|
||||||
|
|
||||||
- 结果容器内部再次分列:左侧 Info(角色信息与穿戴信息),右侧 Skill/BufferSkill(技能结果)。
|
|
||||||
- 证据: [frontend/src/pages/Character/Result/Index.vue](frontend/src/pages/Character/Result/Index.vue#L1)
|
|
||||||
- 证据: [frontend/src/pages/Character/Result/Index.vue](frontend/src/pages/Character/Result/Index.vue#L25)
|
|
||||||
- 证据: [frontend/src/pages/Character/Result/Index.vue](frontend/src/pages/Character/Result/Index.vue#L35)
|
|
||||||
|
|
||||||
### 4.3 联动规则
|
|
||||||
|
|
||||||
- 输入任意改动 -> config 变化 -> execute -> result.state 刷新 -> Info 与 Skill 重新渲染。
|
|
||||||
- 结果显示类型联动:buffer 布尔值驱动结果面板分支。
|
|
||||||
- 证据: [frontend/src/pages/Character/Index.vue](frontend/src/pages/Character/Index.vue#L61)
|
|
||||||
- 证据: [frontend/src/pages/Character/Index.vue](frontend/src/pages/Character/Index.vue#L70)
|
|
||||||
- 证据: [frontend/src/api/calc/type.ts](frontend/src/api/calc/type.ts#L10)
|
|
||||||
|
|
||||||
## 5. 异常反馈现状评估
|
|
||||||
|
|
||||||
### 5.1 已有反馈
|
|
||||||
|
|
||||||
- 结果组件存在数据门控:无数据时不渲染主表。
|
|
||||||
- 证据(Skill): [frontend/src/pages/Character/Result/components/Skill.vue](frontend/src/pages/Character/Result/components/Skill.vue#L4)
|
|
||||||
- 证据(BufferSkill): [frontend/src/pages/Character/Result/components/BufferSkill.vue](frontend/src/pages/Character/Result/components/BufferSkill.vue#L4)
|
|
||||||
|
|
||||||
### 5.2 缺口
|
|
||||||
|
|
||||||
- 缺少请求失败显式提示(无 toast/错误区/错误码展示)。
|
|
||||||
- 缺少结果区加载态反馈(执行中与完成时刻不可见)。
|
|
||||||
- 缺少重试按钮或自动降级策略。
|
|
||||||
- 缺少字段级异常占位(比如 info 值缺失时统一展示策略)。
|
|
||||||
|
|
||||||
侧面证据:
|
|
||||||
- execute 在主容器中仅做并发互斥,不包含错误反馈 UI: [frontend/src/pages/Character/Index.vue](frontend/src/pages/Character/Index.vue#L61)
|
|
||||||
- calc 直接返回接口数据,未见 UI 级异常转换: [frontend/src/api/calc/index.ts](frontend/src/api/calc/index.ts#L7)
|
|
||||||
|
|
||||||
## 6. 重点问题与改造建议
|
|
||||||
|
|
||||||
### P0
|
|
||||||
|
|
||||||
1. 结果计算缺少显式异常反馈链路。
|
|
||||||
- 影响:接口失败时用户难以判断是无结果还是请求异常。
|
|
||||||
- 建议:在 config store 增加 status 字段(idle/loading/success/error),并在 Result 区显示错误提示与重试按钮。
|
|
||||||
|
|
||||||
2. 结果刷新过程不可感知。
|
|
||||||
- 影响:连续编辑时体验抖动,用户无法确认是否正在计算。
|
|
||||||
- 建议:在 Result 容器增加轻量 loading 条或角标,绑定 execute 生命周期。
|
|
||||||
|
|
||||||
### P1
|
|
||||||
|
|
||||||
1. 角色信息面板缺少异常值占位规范。
|
|
||||||
- 影响:后端字段不全时易出现空白或排版错位。
|
|
||||||
- 建议:Info 渲染时对 name/value 增加兜底格式化层。
|
|
||||||
|
|
||||||
2. 分列宽度与自适应策略未统一抽象。
|
|
||||||
- 影响:不同分辨率下信息密度不稳定。
|
|
||||||
- 建议:将主容器与结果列宽抽为布局 token,统一响应式断点行为。
|
|
||||||
|
|
||||||
### P2
|
|
||||||
|
|
||||||
1. 结果组件中计算与展示逻辑耦合较高。
|
|
||||||
- 影响:后续扩展“多维对比/快照回放”时修改成本上升。
|
|
||||||
- 建议:提取结果计算 composable,将排序、汇总、同比逻辑从视图组件中抽离。
|
|
||||||
|
|
||||||
## 7. 证据索引
|
|
||||||
|
|
||||||
### 路由
|
|
||||||
- [frontend/src/router/index.ts](frontend/src/router/index.ts#L13)
|
|
||||||
|
|
||||||
### Store
|
|
||||||
- [frontend/src/stores/config.ts](frontend/src/stores/config.ts#L236)
|
|
||||||
- [frontend/src/stores/config.ts](frontend/src/stores/config.ts#L260)
|
|
||||||
- [frontend/src/stores/info.ts](frontend/src/stores/info.ts#L9)
|
|
||||||
|
|
||||||
### API
|
|
||||||
- [frontend/src/api/calc/index.ts](frontend/src/api/calc/index.ts#L7)
|
|
||||||
- [frontend/src/api/calc/type.ts](frontend/src/api/calc/type.ts#L3)
|
|
||||||
- [frontend/src/api/calc/type.ts](frontend/src/api/calc/type.ts#L4)
|
|
||||||
- [frontend/src/api/calc/type.ts](frontend/src/api/calc/type.ts#L5)
|
|
||||||
- [frontend/src/api/calc/type.ts](frontend/src/api/calc/type.ts#L10)
|
|
||||||
- [frontend/src/api/calc/type.ts](frontend/src/api/calc/type.ts#L27)
|
|
||||||
|
|
||||||
### 页面与组件
|
|
||||||
- [frontend/src/pages/Character/Index.vue](frontend/src/pages/Character/Index.vue#L10)
|
|
||||||
- [frontend/src/pages/Character/Index.vue](frontend/src/pages/Character/Index.vue#L18)
|
|
||||||
- [frontend/src/pages/Character/Index.vue](frontend/src/pages/Character/Index.vue#L22)
|
|
||||||
- [frontend/src/pages/Character/Index.vue](frontend/src/pages/Character/Index.vue#L23)
|
|
||||||
- [frontend/src/pages/Character/Index.vue](frontend/src/pages/Character/Index.vue#L61)
|
|
||||||
- [frontend/src/pages/Character/Index.vue](frontend/src/pages/Character/Index.vue#L70)
|
|
||||||
- [frontend/src/pages/Character/Result/Index.vue](frontend/src/pages/Character/Result/Index.vue#L25)
|
|
||||||
- [frontend/src/pages/Character/Result/Index.vue](frontend/src/pages/Character/Result/Index.vue#L35)
|
|
||||||
- [frontend/src/pages/Character/Result/Index.vue](frontend/src/pages/Character/Result/Index.vue#L40)
|
|
||||||
- [frontend/src/pages/Character/Result/components/Info.vue](frontend/src/pages/Character/Result/components/Info.vue#L13)
|
|
||||||
- [frontend/src/pages/Character/Result/components/Info.vue](frontend/src/pages/Character/Result/components/Info.vue#L166)
|
|
||||||
- [frontend/src/pages/Character/Result/components/Info.vue](frontend/src/pages/Character/Result/components/Info.vue#L212)
|
|
||||||
- [frontend/src/pages/Character/Result/components/Skill.vue](frontend/src/pages/Character/Result/components/Skill.vue#L4)
|
|
||||||
- [frontend/src/pages/Character/Result/components/Skill.vue](frontend/src/pages/Character/Result/components/Skill.vue#L47)
|
|
||||||
- [frontend/src/pages/Character/Result/components/Skill.vue](frontend/src/pages/Character/Result/components/Skill.vue#L150)
|
|
||||||
- [frontend/src/pages/Character/Result/components/Skill.vue](frontend/src/pages/Character/Result/components/Skill.vue#L166)
|
|
||||||
- [frontend/src/pages/Character/Result/components/Skill.vue](frontend/src/pages/Character/Result/components/Skill.vue#L173)
|
|
||||||
- [frontend/src/pages/Character/Result/components/BufferSkill.vue](frontend/src/pages/Character/Result/components/BufferSkill.vue#L4)
|
|
||||||
- [frontend/src/pages/Character/Result/components/BufferSkill.vue](frontend/src/pages/Character/Result/components/BufferSkill.vue#L89)
|
|
||||||
- [frontend/src/pages/Character/Result/components/BufferSkill.vue](frontend/src/pages/Character/Result/components/BufferSkill.vue#L159)
|
|
||||||
Reference in New Issue
Block a user