影视添加配音角色

This commit is contained in:
DelLevin-Home
2026-08-09 12:24:09 +08:00
parent 034453e3f8
commit 7af892c760
4 changed files with 48 additions and 34 deletions

View File

@@ -415,6 +415,7 @@ class _PersonDetailPageState extends State<PersonDetailPage> {
'director' => '导演', 'director' => '导演',
'writer' => '编剧', 'writer' => '编剧',
'actor' => '演员', 'actor' => '演员',
'voiceActor' => '配音',
'author' => '作者', 'author' => '作者',
'translator' => '译者', 'translator' => '译者',
'developer' => '开发者', 'developer' => '开发者',

View File

@@ -421,6 +421,7 @@ class _PersonInfoSheetState extends State<PersonInfoSheet> {
'director' => '导演', 'director' => '导演',
'writer' => '编剧', 'writer' => '编剧',
'actor' => '演员', 'actor' => '演员',
'voiceActor' => '配音',
'author' => '作者', 'author' => '作者',
'translator' => '译者', 'translator' => '译者',
'developer' => '开发者', 'developer' => '开发者',

View File

@@ -48,24 +48,28 @@ class _WorkPeopleSectionState extends State<WorkPeopleSection> {
final person = people.where((p) => p.id == personId).firstOrNull; final person = people.where((p) => p.id == personId).firstOrNull;
if (person == null) return; if (person == null) return;
final existing = byPerson[personId]; 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) { if (existing != null) {
existing.roleTypes.add(roleType); existing.roleTypes.add(roleType);
if (withCharacter) { return;
final c = r.characterName as String?;
if (c != null && c.isNotEmpty) existing.characterNames.add(c);
}
} else {
final characterNames = <String>[];
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,
);
} }
final characters = <String, List<String>>{};
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) { switch (widget.workType) {
@@ -109,6 +113,7 @@ class _WorkPeopleSectionState extends State<WorkPeopleSection> {
'director' => '导演', 'director' => '导演',
'writer' => '编剧', 'writer' => '编剧',
'actor' => '演员', 'actor' => '演员',
'voiceActor' => '配音',
'author' => '作者', 'author' => '作者',
'translator' => '译者', 'translator' => '译者',
'developer' => '开发者', 'developer' => '开发者',
@@ -116,7 +121,7 @@ class _WorkPeopleSectionState extends State<WorkPeopleSection> {
}; };
} }
/// 角色排序权重:演员放最后 /// 角色排序权重:演员/配音放最后
int _roleWeight(String roleType) { int _roleWeight(String roleType) {
return switch (roleType) { return switch (roleType) {
'director' => 0, 'director' => 0,
@@ -125,6 +130,7 @@ class _WorkPeopleSectionState extends State<WorkPeopleSection> {
'translator' => 3, 'translator' => 3,
'developer' => 4, 'developer' => 4,
'actor' => 99, 'actor' => 99,
'voiceActor' => 99,
_ => 50, _ => 50,
}; };
} }
@@ -134,19 +140,22 @@ class _WorkPeopleSectionState extends State<WorkPeopleSection> {
/// 「导演 / 演员 饰 唐僧 / 演员 饰 孙悟空」 /// 「导演 / 演员 饰 唐僧 / 演员 饰 孙悟空」
String _buildRoleText(_PersonRole item) { String _buildRoleText(_PersonRole item) {
final parts = <String>[]; final parts = <String>[];
// 非演员角色:去重后按权重排序 final perfRoleTypes = ['actor', 'voiceActor'];
final nonActor = item.roleTypes.where((r) => r != 'actor').toSet().toList() // 非演员/配音角色:去重后按权重排序
final nonPerf = item.roleTypes.where((r) => !perfRoleTypes.contains(r)).toSet().toList()
..sort((a, b) => _roleWeight(a).compareTo(_roleWeight(b))); ..sort((a, b) => _roleWeight(a).compareTo(_roleWeight(b)));
parts.addAll(nonActor.map(_roleLabel)); parts.addAll(nonPerf.map(_roleLabel));
// 演员角色:每个饰演角色名单独成段 // 演员/配音角色:每个饰演角色名单独成段
final isActor = item.roleTypes.contains('actor'); for (final roleType in perfRoleTypes) {
if (isActor) { if (!item.roleTypes.contains(roleType)) continue;
if (item.characterNames.isEmpty) { final label = _roleLabel(roleType);
parts.add('演员'); final names = item.characters[roleType] ?? const [];
if (names.isEmpty) {
parts.add(label);
} else { } else {
for (final c in item.characterNames) { for (final c in names) {
parts.add('演员$c'); parts.add('$label$c');
} }
} }
} }
@@ -282,11 +291,11 @@ class _WorkPeopleSectionState extends State<WorkPeopleSection> {
class _PersonRole { class _PersonRole {
final Person person; final Person person;
final List<String> roleTypes; final List<String> roleTypes;
final List<String> characterNames; final Map<String, List<String>> characters; // roleType → 角色名列表actor/voiceActor
_PersonRole({ _PersonRole({
required this.person, required this.person,
required this.roleTypes, required this.roleTypes,
required this.characterNames, required this.characters,
}); });
} }

View File

@@ -85,7 +85,7 @@ class _WorkSelectorPageState extends State<WorkSelectorPage> {
List<Person> _matchedPeople = []; List<Person> _matchedPeople = [];
bool _searching = false; bool _searching = false;
static const _movieRoles = [('director', '导演'), ('writer', '编剧'), ('actor', '演员')]; static const _movieRoles = [('director', '导演'), ('writer', '编剧'), ('actor', '演员'), ('voiceActor', '配音')];
static const _bookRoles = [('author', '作者'), ('translator', '译者')]; static const _bookRoles = [('author', '作者'), ('translator', '译者')];
static const _gameRoles = [('developer', '开发者')]; static const _gameRoles = [('developer', '开发者')];
@@ -151,7 +151,7 @@ class _WorkSelectorPageState extends State<WorkSelectorPage> {
workId: entry.workId, workId: entry.workId,
workType: entry.workType, workType: entry.workType,
roleType: roleType, 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<WorkSelectorPage> {
} }
void _showCharacterNameDialog(_WorkRoleEntry entry, String? current) { void _showCharacterNameDialog(_WorkRoleEntry entry, String? current) {
final isVoiceActor = entry.roleType == 'voiceActor';
final title = isVoiceActor ? '配音角色' : '饰演角色';
final hint = isVoiceActor ? '如:米老鼠' : '如:关羽';
final ctrl = TextEditingController(text: current ?? ''); final ctrl = TextEditingController(text: current ?? '');
showDialog( showDialog(
context: context, context: context,
@@ -564,14 +567,14 @@ class _WorkSelectorPageState extends State<WorkSelectorPage> {
return AlertDialog( return AlertDialog(
backgroundColor: colors.surface, backgroundColor: colors.surface,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), 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( content: TextField(
controller: ctrl, controller: ctrl,
autofocus: true, autofocus: true,
style: TextStyle(fontSize: 15, color: colors.onSurface), style: TextStyle(fontSize: 15, color: colors.onSurface),
cursorColor: colors.primary, cursorColor: colors.primary,
decoration: InputDecoration( decoration: InputDecoration(
hintText: '如:关羽', hintText: hint,
hintStyle: TextStyle(color: colors.onSurface.withValues(alpha: 0.3)), hintStyle: TextStyle(color: colors.onSurface.withValues(alpha: 0.3)),
filled: true, filled: true,
fillColor: colors.surfaceContainerHigh, fillColor: colors.surfaceContainerHigh,
@@ -699,8 +702,8 @@ class _WorkSelectorPageState extends State<WorkSelectorPage> {
// 职业标签/下拉 // 职业标签/下拉
_buildRoleDropdown(entry, colors), _buildRoleDropdown(entry, colors),
const SizedBox(width: 8), const SizedBox(width: 8),
// 角色名(影视演员) // 角色名(影视演员/配音
if (entry.workType == 'movie' && entry.roleType == 'actor') ...[ if (entry.workType == 'movie' && (entry.roleType == 'actor' || entry.roleType == 'voiceActor')) ...[
GestureDetector( GestureDetector(
onTap: () => _showCharacterNameDialog(entry, entry.characterName), onTap: () => _showCharacterNameDialog(entry, entry.characterName),
child: Container( child: Container(