From cee25b63e1904e5447461bea84a3dee4cb2d133a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A5=BF=E7=93=9C=C2=B0?= Date: Mon, 6 Jul 2026 18:30:50 +0800 Subject: [PATCH] init --- AGENTS.md | 55 ---- backend/api/core/Exceptions.py | 61 +--- backend/api/core/Logging.py | 36 +++ backend/api/core/RedisPreheat.py | 21 +- backend/api/routes/calc.py | 2 +- backend/api/routes/openapi.py | 9 +- backend/api/utils/openapi_data.py | 10 +- backend/api/utils/skill_tree.py | 2 - backend/config/main.py | 17 +- backend/core/basic/character/__init__.py | 12 +- backend/core/basic/skill.py | 7 +- backend/database/connect.py | 4 +- backend/main.py | 3 + backend/openapi | 2 +- dcalc-migration-analysis.md | 302 ------------------ ...end-装备技能打造-完整交互与布局设计文档.md | 225 ------------- ...end-角色信息与计算结果展示链路-专项分析.md | 171 ---------- 17 files changed, 102 insertions(+), 837 deletions(-) delete mode 100644 AGENTS.md create mode 100644 backend/api/core/Logging.py delete mode 100644 dcalc-migration-analysis.md delete mode 100644 frontend-装备技能打造-完整交互与布局设计文档.md delete mode 100644 frontend-角色信息与计算结果展示链路-专项分析.md diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 5ae05f13..00000000 --- a/AGENTS.md +++ /dev/null @@ -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:链接现有文档,不重复长文说明。 diff --git a/backend/api/core/Exceptions.py b/backend/api/core/Exceptions.py index a13b36a6..098f6dfa 100644 --- a/backend/api/core/Exceptions.py +++ b/backend/api/core/Exceptions.py @@ -1,21 +1,12 @@ -from datetime import datetime -import logging -import traceback -from fastapi import Request, status -from fastapi.responses import JSONResponse -# from fastapi import Request, status - -from api.core.exception import ResponseException - -logFormatter = logging.Formatter( - """ -[start] -%(asctime)s -<%(name)s> -%(message)s -[end] -""" -) +import logging +import traceback +from fastapi import Request, status +from fastapi.responses import JSONResponse +# from fastapi import Request, status + +from api.core.exception import ResponseException + +logger = logging.getLogger('dcalc.exception') def format_exception() -> str: @@ -30,34 +21,12 @@ async def all_exception_handler(request: Request, ex: Exception): info = '' if not isinstance(ex, ResponseException): info = format_exception() - if request.client.host != '127.0.0.1': - from fastapi.logger import logger - logger.setLevel(logging.ERROR) - logger.name = 'dcalc' - 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: - info = ex.args[0] + client_host = request.client.host if request.client else '' + if client_host != '127.0.0.1': + logger.error('Unhandled exception for %s', request.url.path, exc_info=(type(ex), ex, ex.__traceback__)) + info = '服务器内部错误' + else: + info = ex.args[0] # traceback.print_last(sys.exc_info()) return JSONResponse( status_code=status.HTTP_200_OK, diff --git a/backend/api/core/Logging.py b/backend/api/core/Logging.py new file mode 100644 index 00000000..01c460c0 --- /dev/null +++ b/backend/api/core/Logging.py @@ -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) diff --git a/backend/api/core/RedisPreheat.py b/backend/api/core/RedisPreheat.py index e3f72f62..715038ce 100644 --- a/backend/api/core/RedisPreheat.py +++ b/backend/api/core/RedisPreheat.py @@ -8,7 +8,12 @@ import redis from api.core.Gzip import gzip_zip 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 ( SKILL_TREE_NAME_MAP_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.equipment import get_equipment from core.character.adventure import adv, get_adv_list -from database.connect import BACKEND_DIR, RedisPool +from database.connect import RedisPool PreheatLoader = Callable[[], object] @@ -28,12 +33,12 @@ PREHEAT_LOCK_EXPIRE_SECONDS = 15 * 60 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) 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) @@ -60,8 +65,7 @@ def _build_character_preheat_tasks(equ_version: str = '0') -> list[PreheatTask]: def _iter_skill_detail_files() -> list[Path]: - data_dir = BACKEND_DIR / 'openapi' / 'data' - return sorted(data_dir.glob('cn/*/*/skillDetail/*.json')) + return sorted(OPENAPI_DATA_DIR.glob('cn/*/*/skillDetail/*.json')) 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]: tasks: list[PreheatTask] = [] - data_dir = BACKEND_DIR / 'openapi' / 'data' - for path in sorted(data_dir.glob('cn/*/*/skill_tree.json')): - relative = path.relative_to(data_dir) + for path in sorted(OPENAPI_DATA_DIR.glob('cn/*/*/skill_tree.json')): + relative = path.relative_to(OPENAPI_DATA_DIR) job_id = relative.parts[1] job_grow_id = relative.parts[2] tasks.append( diff --git a/backend/api/routes/calc.py b/backend/api/routes/calc.py index cbbaf704..c2ec104d 100644 --- a/backend/api/routes/calc.py +++ b/backend/api/routes/calc.py @@ -10,5 +10,5 @@ register_gzip_request(router) @router.post('/calc') -async def calc(state: AltersDep, config=Body(None)): +def calc(state: AltersDep, config=Body(None)): return response(data=calculate(state, config)) diff --git a/backend/api/routes/openapi.py b/backend/api/routes/openapi.py index d1664c43..0fc30e8a 100644 --- a/backend/api/routes/openapi.py +++ b/backend/api/routes/openapi.py @@ -5,7 +5,7 @@ from api.core.Response import response from api.core.Redis import get_redis_info 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.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 ( SKILL_TREE_WITH_DESC_SPECIAL_VERSION, filter_skill_tree_by_desc_special, @@ -43,7 +43,7 @@ async def get_skills_list( def get_skills(): # This function should retrieve the skills list from the data source # 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) return skills_data @@ -226,13 +226,12 @@ async def get_skill_data_summary( def get_skill_data_summary(): # This function should retrieve the skill data summary based on job # For now, we return a placeholder dictionary - summary_dir = './openapi/summary/' - json_files = [f for f in os.listdir(summary_dir) if f.endswith('.json')] + json_files = [f for f in os.listdir(OPENAPI_SUMMARY_DIR) if f.endswith('.json')] res = [] for i in json_files: if job in 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) weapon = i.split('_')[-1].replace('.json', '') res.append({'weapon': weapon if weapon else '通用', 'skills': skill_data, 'job': k.group(1) if k else ''}) diff --git a/backend/api/utils/openapi_data.py b/backend/api/utils/openapi_data.py index 2ae3d811..4ccb6598 100644 --- a/backend/api/utils/openapi_data.py +++ b/backend/api/utils/openapi_data.py @@ -1,9 +1,15 @@ 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'} diff --git a/backend/api/utils/skill_tree.py b/backend/api/utils/skill_tree.py index 11d3f750..7b79ac16 100644 --- a/backend/api/utils/skill_tree.py +++ b/backend/api/utils/skill_tree.py @@ -1,9 +1,7 @@ import json from functools import cache -from pathlib import Path from api.utils.openapi_data import openapi_locale_path -from database.connect import BACKEND_DIR SKILL_TREE_WITH_DESC_SPECIAL_VERSION = 'skillTreeWithDescSpecial:v1' diff --git a/backend/config/main.py b/backend/config/main.py index c735c7b8..b2307f35 100644 --- a/backend/config/main.py +++ b/backend/config/main.py @@ -1,17 +1,14 @@ -import os -import sys +from pathlib import Path from pydantic_settings import BaseSettings, SettingsConfigDict -try: - os.chdir(os.path.dirname(sys.argv[0])) -except Exception: - pass +BACKEND_DIR = Path(__file__).resolve().parents[1] + class Config(BaseSettings): DEBUG_MODE: bool = True WORKERS: int = 1 - PORT:int = 27173 + PORT: int = 27173 MCPPORT: int = 27174 REDIS_URL: str = 'redis://localhost:6379/1' COLG_TOKEN: str = '' @@ -19,7 +16,11 @@ class Config(BaseSettings): HELPER_ID: str = '' 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', ) diff --git a/backend/core/basic/character/__init__.py b/backend/core/basic/character/__init__.py index 6106bb9a..2acafffc 100644 --- a/backend/core/basic/character/__init__.py +++ b/backend/core/basic/character/__init__.py @@ -9,13 +9,17 @@ core.basic.character 包 """ import importlib +import logging +from api.core.exception import ResponseException from .jade import Jade from .calc_equip import CalcEquipMixin from .calc_carry import CalcCarryMixin from .calc_buffer import CalcBufferMixin from .info import CharacterInfoMixin +logger = logging.getLogger('dcalc.character') + class Character(CalcEquipMixin, CalcCarryMixin, CalcBufferMixin, CharacterInfoMixin): """ @@ -28,15 +32,17 @@ class Character(CalcEquipMixin, CalcCarryMixin, CalcBufferMixin, CharacterInfoMi def createCharacter(alter: str, equVersion: str = '0') -> 'Character': - character: Character = None module_name = 'core.character.' + alter if equVersion is None or equVersion == '': equVersion = '0' try: character = importlib.import_module(module_name).classChange(equVersion) except Exception as ex: - print(ex) - print('create character ' + module_name + ' error.') + logger.exception('create character failed alter=%s equVersion=%s module=%s', alter, equVersion, module_name) + 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 diff --git a/backend/core/basic/skill.py b/backend/core/basic/skill.py index 90da307c..900a3606 100644 --- a/backend/core/basic/skill.py +++ b/backend/core/basic/skill.py @@ -5,15 +5,14 @@ import json import re from typing import Any, Literal, TYPE_CHECKING 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.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: from core.basic.character import Character characterLv = 115 + 5 -OPENAPI_DIR = BACKEND_DIR / 'openapi' def _identity(value: Any) -> Any: @@ -22,7 +21,7 @@ def _identity(value: Any) -> Any: @cache 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) diff --git a/backend/database/connect.py b/backend/database/connect.py index 3c009340..bb6fbab9 100644 --- a/backend/database/connect.py +++ b/backend/database/connect.py @@ -1,13 +1,11 @@ from functools import cache -from pathlib import Path import redis from sqlalchemy import create_engine from sqlalchemy.orm import Session 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) -BACKEND_DIR = Path(__file__).resolve().parents[1] def get_redis_client(): diff --git a/backend/main.py b/backend/main.py index 5f5145ae..b49faed1 100644 --- a/backend/main.py +++ b/backend/main.py @@ -7,9 +7,12 @@ import uvicorn from fastapi import FastAPI from api.core import Exceptions, Gzip, Middleware +from api.core.Logging import configure_logging from api.main import api_router from config.main import config +configure_logging() + @asynccontextmanager async def lifespan(_: FastAPI): diff --git a/backend/openapi b/backend/openapi index 27e2652d..e8270364 160000 --- a/backend/openapi +++ b/backend/openapi @@ -1 +1 @@ -Subproject commit 27e2652dd04cffd522a29b6813bce57de6f6d084 +Subproject commit e82703643a0d4338b816fa72523822d3c9c30a97 diff --git a/dcalc-migration-analysis.md b/dcalc-migration-analysis.md deleted file mode 100644 index f04b7623..00000000 --- a/dcalc-migration-analysis.md +++ /dev/null @@ -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 | ★★ 中 | diff --git a/frontend-装备技能打造-完整交互与布局设计文档.md b/frontend-装备技能打造-完整交互与布局设计文档.md deleted file mode 100644 index 61e105e8..00000000 --- a/frontend-装备技能打造-完整交互与布局设计文档.md +++ /dev/null @@ -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. 证据索引依赖人工维护。 -- 建议:后续补充自动化证据采集脚本,降低维护成本。 diff --git a/frontend-角色信息与计算结果展示链路-专项分析.md b/frontend-角色信息与计算结果展示链路-专项分析.md deleted file mode 100644 index 1c84d819..00000000 --- a/frontend-角色信息与计算结果展示链路-专项分析.md +++ /dev/null @@ -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)