diff --git a/lib/pages/people/person_detail_page.dart b/lib/pages/people/person_detail_page.dart index 9b0fc3f..2708d00 100644 --- a/lib/pages/people/person_detail_page.dart +++ b/lib/pages/people/person_detail_page.dart @@ -415,6 +415,7 @@ class _PersonDetailPageState extends State { 'director' => '导演', 'writer' => '编剧', 'actor' => '演员', + 'voiceActor' => '配音', 'author' => '作者', 'translator' => '译者', 'developer' => '开发者', diff --git a/lib/widgets/person_info_sheet.dart b/lib/widgets/person_info_sheet.dart index fe61512..f28cfc9 100644 --- a/lib/widgets/person_info_sheet.dart +++ b/lib/widgets/person_info_sheet.dart @@ -421,6 +421,7 @@ class _PersonInfoSheetState extends State { 'director' => '导演', 'writer' => '编剧', 'actor' => '演员', + 'voiceActor' => '配音', 'author' => '作者', 'translator' => '译者', 'developer' => '开发者', diff --git a/lib/widgets/work_people_section.dart b/lib/widgets/work_people_section.dart index 4c8ba3c..4f316ca 100644 --- a/lib/widgets/work_people_section.dart +++ b/lib/widgets/work_people_section.dart @@ -48,24 +48,28 @@ class _WorkPeopleSectionState extends State { final person = people.where((p) => p.id == personId).firstOrNull; if (person == null) return; final existing = byPerson[personId]; + if (withCharacter) { + final c = r.characterName as String?; + if (c != null && c.isNotEmpty) { + existing?.characters.putIfAbsent(roleType, () => []).add(c); + } + } if (existing != null) { existing.roleTypes.add(roleType); - if (withCharacter) { - final c = r.characterName as String?; - if (c != null && c.isNotEmpty) existing.characterNames.add(c); - } - } else { - final characterNames = []; - if (withCharacter) { - final c = r.characterName as String?; - if (c != null && c.isNotEmpty) characterNames.add(c); - } - byPerson[personId] = _PersonRole( - person: person, - roleTypes: [roleType], - characterNames: characterNames, - ); + return; } + final characters = >{}; + if (withCharacter) { + final c = r.characterName as String?; + if (c != null && c.isNotEmpty) { + characters[roleType] = [c]; + } + } + byPerson[personId] = _PersonRole( + person: person, + roleTypes: [roleType], + characters: characters, + ); } switch (widget.workType) { @@ -109,6 +113,7 @@ class _WorkPeopleSectionState extends State { 'director' => '导演', 'writer' => '编剧', 'actor' => '演员', + 'voiceActor' => '配音', 'author' => '作者', 'translator' => '译者', 'developer' => '开发者', @@ -116,7 +121,7 @@ class _WorkPeopleSectionState extends State { }; } - /// 角色排序权重:演员放最后 + /// 角色排序权重:演员/配音放最后 int _roleWeight(String roleType) { return switch (roleType) { 'director' => 0, @@ -125,6 +130,7 @@ class _WorkPeopleSectionState extends State { 'translator' => 3, 'developer' => 4, 'actor' => 99, + 'voiceActor' => 99, _ => 50, }; } @@ -134,19 +140,22 @@ class _WorkPeopleSectionState extends State { /// 「导演 / 演员 饰 唐僧 / 演员 饰 孙悟空」 String _buildRoleText(_PersonRole item) { final parts = []; - // 非演员角色:去重后按权重排序 - final nonActor = item.roleTypes.where((r) => r != 'actor').toSet().toList() + final perfRoleTypes = ['actor', 'voiceActor']; + // 非演员/配音角色:去重后按权重排序 + final nonPerf = item.roleTypes.where((r) => !perfRoleTypes.contains(r)).toSet().toList() ..sort((a, b) => _roleWeight(a).compareTo(_roleWeight(b))); - parts.addAll(nonActor.map(_roleLabel)); + parts.addAll(nonPerf.map(_roleLabel)); - // 演员角色:每个饰演角色名单独成段 - final isActor = item.roleTypes.contains('actor'); - if (isActor) { - if (item.characterNames.isEmpty) { - parts.add('演员'); + // 演员/配音角色:每个饰演角色名单独成段 + for (final roleType in perfRoleTypes) { + if (!item.roleTypes.contains(roleType)) continue; + final label = _roleLabel(roleType); + final names = item.characters[roleType] ?? const []; + if (names.isEmpty) { + parts.add(label); } else { - for (final c in item.characterNames) { - parts.add('演员 饰 $c'); + for (final c in names) { + parts.add('$label 饰 $c'); } } } @@ -282,11 +291,11 @@ class _WorkPeopleSectionState extends State { class _PersonRole { final Person person; final List roleTypes; - final List characterNames; + final Map> characters; // roleType → 角色名列表(actor/voiceActor) _PersonRole({ required this.person, required this.roleTypes, - required this.characterNames, + required this.characters, }); } diff --git a/lib/widgets/work_selector_page.dart b/lib/widgets/work_selector_page.dart index f88c510..3280749 100644 --- a/lib/widgets/work_selector_page.dart +++ b/lib/widgets/work_selector_page.dart @@ -85,7 +85,7 @@ class _WorkSelectorPageState extends State { List _matchedPeople = []; bool _searching = false; - static const _movieRoles = [('director', '导演'), ('writer', '编剧'), ('actor', '演员')]; + static const _movieRoles = [('director', '导演'), ('writer', '编剧'), ('actor', '演员'), ('voiceActor', '配音')]; static const _bookRoles = [('author', '作者'), ('translator', '译者')]; static const _gameRoles = [('developer', '开发者')]; @@ -151,7 +151,7 @@ class _WorkSelectorPageState extends State { workId: entry.workId, workType: entry.workType, roleType: roleType, - characterName: entry.workType == 'movie' && roleType == 'actor' ? entry.characterName : null, + characterName: entry.workType == 'movie' && (roleType == 'actor' || roleType == 'voiceActor') ? entry.characterName : null, ); }); } @@ -556,6 +556,9 @@ class _WorkSelectorPageState extends State { } void _showCharacterNameDialog(_WorkRoleEntry entry, String? current) { + final isVoiceActor = entry.roleType == 'voiceActor'; + final title = isVoiceActor ? '配音角色' : '饰演角色'; + final hint = isVoiceActor ? '如:米老鼠' : '如:关羽'; final ctrl = TextEditingController(text: current ?? ''); showDialog( context: context, @@ -564,14 +567,14 @@ class _WorkSelectorPageState extends State { return AlertDialog( backgroundColor: colors.surface, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - title: Text('饰演角色', style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600, color: colors.onSurface)), + title: Text(title, style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600, color: colors.onSurface)), content: TextField( controller: ctrl, autofocus: true, style: TextStyle(fontSize: 15, color: colors.onSurface), cursorColor: colors.primary, decoration: InputDecoration( - hintText: '如:关羽', + hintText: hint, hintStyle: TextStyle(color: colors.onSurface.withValues(alpha: 0.3)), filled: true, fillColor: colors.surfaceContainerHigh, @@ -699,8 +702,8 @@ class _WorkSelectorPageState extends State { // 职业标签/下拉 _buildRoleDropdown(entry, colors), const SizedBox(width: 8), - // 角色名(仅影视演员) - if (entry.workType == 'movie' && entry.roleType == 'actor') ...[ + // 角色名(影视演员/配音) + if (entry.workType == 'movie' && (entry.roleType == 'actor' || entry.roleType == 'voiceActor')) ...[ GestureDetector( onTap: () => _showCharacterNameDialog(entry, entry.characterName), child: Container(