Card.js:修订间差异
来自卡厄思梦境WIKI
无编辑摘要 标签:已被回退 |
无编辑摘要 标签:已被回退 |
||
| 第580行: | 第580行: | ||
} | } | ||
// | // 主渲染函数(续) | ||
function render() { | function render() { | ||
const container = document.getElementById('card-manager-container'); | const container = document.getElementById('card-manager-container'); | ||
| 第759行: | 第759行: | ||
const cardListContainer = createElement('div', 'list-container'); | const cardListContainer = createElement('div', 'list-container'); | ||
cardListContainer.style.minHeight = '250px'; | cardListContainer.style.minHeight = '250px'; | ||
const cardListHeader = createElement('div', 'list-header'); | |||
cardListHeader.appendChild(createElement('div', 'list-title', { textContent: '卡牌列表' })); | |||
cardListContainer.appendChild(cardListHeader); | |||
if (state.cards.length === 0) { | if (state.cards.length === 0) { | ||
2025年10月24日 (五) 15:45的版本
(function() {
'use strict';
// 加载CSS
mw.loader.load('/index.php?title=Mediawiki:Card.css&action=raw&ctype=text/css', 'text/css');
// 状态管理
const state = {
currentFighter: '',
fighters: [],
cards: [],
currentCard: null,
defaultInfo: { order: '', ego: '' },
editMode: 'base',
currentGod: 'circen',
currentInspirationIndex: null,
currentGodInspirationIndex: null
};
// 卡牌数据结构
function createEmptyCard() {
return {
name: '',
base: {
displayname: '', art: '', group: '', rarity: '', god: '',
ap: '', type: '', dict: '', desc_global: '', sub: '',
isinspiration: 0, isgod_inspiration: 0
},
var: {
inspiration: [],
god_inspiration: {
circen: [], diallos: [], nihilum: [], secred: [], vitor: []
}
}
};
}
// 创建元素辅助函数
function createElement(tag, className, attributes = {}) {
const el = document.createElement(tag);
if (className) el.className = className;
Object.entries(attributes).forEach(([key, value]) => {
if (key === 'textContent') el.textContent = value;
else if (key.startsWith('on')) el.addEventListener(key.substring(2).toLowerCase(), value);
else el.setAttribute(key, value);
});
return el;
}
// 创建自定义下拉选择器
function createSelect(options, selectedValue, onChange) {
const select = createElement('div', 'form-select');
select.textContent = selectedValue || options[0] || '请选择';
select.setAttribute('tabindex', '0');
const dropdown = createElement('div', 'dropdown-menu');
options.forEach(option => {
const item = createElement('div', 'dropdown-item');
item.textContent = option;
item.addEventListener('click', () => {
select.textContent = option;
dropdown.style.display = 'none';
if (onChange) onChange(option);
});
dropdown.appendChild(item);
});
select.addEventListener('click', (e) => {
e.stopPropagation();
dropdown.style.display = dropdown.style.display === 'none' ? 'block' : 'none';
const rect = select.getBoundingClientRect();
Object.assign(dropdown.style, {
top: rect.bottom + 'px',
left: rect.left + 'px',
width: rect.width + 'px'
});
});
document.addEventListener('click', () => dropdown.style.display = 'none');
const wrapper = createElement('div');
wrapper.style.position = 'relative';
wrapper.appendChild(select);
document.body.appendChild(dropdown);
return { wrapper, select, dropdown };
}
// 创建自定义输入框
function createInput(type, value, onChange, placeholder = '') {
const input = createElement('div', 'form-input');
input.setAttribute('contenteditable', 'true');
input.textContent = value || '';
input.setAttribute('data-placeholder', placeholder);
input.style.minHeight = type === 'textarea' ? '100px' : 'auto';
if (!value) {
input.style.color = '#999';
input.textContent = placeholder;
}
input.addEventListener('focus', () => {
if (input.textContent === placeholder) {
input.textContent = '';
input.style.color = '#333';
}
});
input.addEventListener('blur', () => {
if (!input.textContent.trim()) {
input.textContent = placeholder;
input.style.color = '#999';
}
if (onChange) onChange(input.textContent === placeholder ? '' : input.textContent);
});
input.addEventListener('input', () => {
if (onChange && input.textContent !== placeholder) {
onChange(input.textContent);
}
});
return input;
}
// 创建复选框
function createCheckbox(checked, onChange) {
const wrapper = createElement('div', 'checkbox-wrapper');
const checkbox = createElement('div', checked ? 'checkbox checked' : 'checkbox');
const label = createElement('div', '', { textContent: '是' });
wrapper.addEventListener('click', () => {
const newChecked = !checkbox.classList.contains('checked');
checkbox.classList.toggle('checked');
if (onChange) onChange(newChecked ? 1 : 0);
});
wrapper.appendChild(checkbox);
wrapper.appendChild(label);
return wrapper;
}
// 创建表单组
function createFormGroup(label, control) {
const group = createElement('div', 'form-group');
group.appendChild(createElement('div', 'form-label', { textContent: label }));
group.appendChild(control);
return group;
}
// 创建按钮
function createButton(text, className, onClick) {
return createElement('div', 'btn ' + className, { textContent: text, onclick: onClick });
}
// 文本插入辅助函数
function insertTextAtCursor(element, text, wrap = false) {
element.focus();
const selection = window.getSelection();
if (wrap && selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
const selectedText = range.toString();
if (selectedText) {
range.deleteContents();
range.insertNode(document.createTextNode(text.replace('选择文字', selectedText)));
return;
}
}
document.execCommand('insertText', false, text);
}
// 提取值辅助函数
function extractValue(text, key, isString = false) {
const pattern = isString
? new RegExp(key + '\\s*=\\s*"([^"]*)"', 'm')
: new RegExp(key + '\\s*=\\s*([^,\\n]+)', 'm');
const match = text.match(pattern);
if (!match) return isString ? '' : 0;
return isString ? match[1] : (parseInt(match[1]) || match[1].trim().replace(/^["']|["']$/g, ''));
}
// 解析灵光一闪/神光一闪列表
function parseInspirationList(content) {
const inspirations = [];
let depth = 0, currentBlock = '', inString = false;
for (let i = 0; i < content.length; i++) {
const char = content[i];
const prevChar = i > 0 ? content[i - 1] : '';
if (char === '"' && prevChar !== '\\') inString = !inString;
if (!inString) {
if (char === '{') {
if (depth > 0) currentBlock += char;
depth++;
} else if (char === '}') {
depth--;
if (depth === 0 && currentBlock) {
const inspiration = {};
['ap', 'type', 'desc_global', 'dict'].forEach(key => {
const val = extractValue(currentBlock, key, key !== 'ap');
if (val) inspiration[key] = val;
});
if (Object.keys(inspiration).length > 0) inspirations.push(inspiration);
currentBlock = '';
} else if (depth > 0) currentBlock += char;
} else if (depth > 0) currentBlock += char;
} else if (depth > 0) currentBlock += char;
}
return inspirations;
}
// 提取块内容
function extractBlock(content, startPattern) {
const match = content.match(startPattern);
if (!match) return null;
let startIdx = match.index + match[0].length;
let depth = 1, endIdx = startIdx, inString = false;
for (let i = startIdx; i < content.length && depth > 0; i++) {
const char = content[i];
if (char === '"' && content[i - 1] !== '\\') inString = !inString;
if (!inString) {
if (char === '{') depth++;
else if (char === '}' && --depth === 0) {
endIdx = i;
break;
}
}
}
return content.substring(startIdx, endIdx);
}
// 解析Lua代码
function parseLuaCode(luaText) {
const cards = [];
const defaultInfo = { order: '', ego: '' };
try {
const orderMatch = luaText.match(/card\.order\s*=\s*\{\s*"([^"]+)"\s*\}/);
if (orderMatch) defaultInfo.order = orderMatch[1];
const egoMatch = luaText.match(/card\.info\s*=\s*\{[^}]*ego\s*=\s*"([^"]+)"/);
if (egoMatch) defaultInfo.ego = egoMatch[1];
const cardPattern = /card\["([^"]+)"\]\s*=\s*\{/g;
let cardMatch;
while ((cardMatch = cardPattern.exec(luaText)) !== null) {
const cardContent = extractBlock(luaText.substring(cardMatch.index), /card\["[^"]+"\]\s*=\s*\{/);
if (!cardContent) continue;
const card = createEmptyCard();
card.name = cardMatch[1];
const baseContent = extractBlock(cardContent, /base\s*=\s*\{/);
if (baseContent) {
['displayname', 'art', 'group', 'rarity', 'god', 'type', 'dict', 'desc_global', 'sub'].forEach(key => {
card.base[key] = extractValue(baseContent, key, true);
});
card.base.ap = extractValue(baseContent, 'ap');
card.base.isinspiration = extractValue(baseContent, 'isinspiration') || 0;
card.base.isgod_inspiration = extractValue(baseContent, 'isgod_inspiration') || 0;
}
const varContent = extractBlock(cardContent, /var\s*=\s*\{/);
if (varContent) {
const inspirationContent = extractBlock(varContent, /inspiration\s*=\s*\{/);
if (inspirationContent) card.var.inspiration = parseInspirationList(inspirationContent);
const godInspirationContent = extractBlock(varContent, /god_inspiration\s*=\s*\{/);
if (godInspirationContent) {
['circen', 'diallos', 'nihilum', 'secred', 'vitor'].forEach(god => {
const godContent = extractBlock(godInspirationContent, new RegExp(god + '\\s*=\\s*\\{'));
if (godContent) card.var.god_inspiration[god] = parseInspirationList(godContent);
});
}
}
cards.push(card);
}
} catch (error) {
console.error('解析Lua代码失败:', error);
}
return { cards, defaultInfo };
}
// Lua字符串转义
function escapeLuaString(str) {
if (!str) return '';
return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
.replace(/\n/g, '\\n').replace(/\r/g, '\\r').replace(/\t/g, '\\t');
}
// 生成Lua属性行
function generateLuaProperty(key, value, indent = ' ') {
if (value === '' || value === undefined) return '';
const isNumber = !isNaN(value) && key !== 'ap';
return `${indent}${key} = ${isNumber ? value : `"${escapeLuaString(value)}"`},\n`;
}
// 生成灵光/神光代码
function generateInspirationCode(inspList, indent = ' ') {
let code = '';
inspList.forEach(insp => {
code += indent + '{\n';
['ap', 'type', 'desc_global', 'dict'].forEach(key => {
if (insp[key] !== '' && insp[key] !== undefined) {
code += generateLuaProperty(key, insp[key], indent + ' ');
}
});
code += indent + '},\n';
});
return code;
}
// 生成Lua代码
function generateLuaCode() {
let code = 'local card = {}\n\n';
if (state.defaultInfo.order)
code += `card.order = { "${escapeLuaString(state.defaultInfo.order)}" }\n\n`;
if (state.defaultInfo.ego)
code += `card.info = {\n ego = "${escapeLuaString(state.defaultInfo.ego)}",\n}\n\n`;
state.cards.forEach(card => {
code += `card["${escapeLuaString(card.name)}"] = {\n base = {\n`;
['displayname', 'art', 'group', 'rarity', 'god', 'ap', 'type', 'dict', 'desc_global', 'sub', 'isinspiration', 'isgod_inspiration']
.forEach(key => code += generateLuaProperty(key, card.base[key]));
code += ' },\n';
const hasInspiration = card.var.inspiration.length > 0;
const hasGodInspiration = Object.values(card.var.god_inspiration).some(arr => arr.length > 0);
if (hasInspiration || hasGodInspiration) {
code += ' var = {\n';
if (hasInspiration) {
code += ' inspiration = {\n';
code += generateInspirationCode(card.var.inspiration);
code += ' },\n';
}
if (hasGodInspiration) {
code += ' god_inspiration = {\n';
Object.entries(card.var.god_inspiration).forEach(([god, inspList]) => {
if (inspList.length > 0) {
code += ` ${god} = {\n`;
code += generateInspirationCode(inspList, ' ');
code += ' },\n';
}
});
code += ' },\n';
}
code += ' },\n';
}
code += '}\n\n';
});
return code + 'return card';
}
// API调用封装
async function apiCall(params) {
const api = new mw.Api();
return await api.get(params);
}
// 加载战斗员列表
async function loadFighters() {
try {
const result = await apiCall({
action: 'query',
list: 'allpages',
apprefix: '卡牌/',
apnamespace: 828,
aplimit: 500
});
if (result.query?.allpages) {
state.fighters = result.query.allpages.map(page => page.title.replace('模块:卡牌/', ''));
}
} catch (error) {
console.error('加载战斗员列表失败:', error);
}
}
// 加载战斗员卡牌数据
async function loadFighterCards(fighter) {
if (!fighter) return [];
try {
const result = await apiCall({
action: 'query',
prop: 'revisions',
titles: '模块:卡牌/' + fighter,
rvprop: 'content',
rvslots: 'main',
formatversion: 2
});
const page = result.query?.pages?.[0];
if (!page || page.missing) {
console.error('页面不存在');
return [];
}
const content = page.revisions[0].slots.main.content;
const parsed = parseLuaCode(content);
state.defaultInfo = parsed.defaultInfo;
return parsed.cards;
} catch (error) {
console.error('加载卡牌数据失败:', error);
return [];
}
}
// 更新代码预览
function updateCodePreview() {
const codePreview = document.querySelector('.code-preview');
if (codePreview) codePreview.textContent = generateLuaCode();
}
// 创建描述工具栏
function createDescToolbar(descInput) {
const toolbar = createElement('div', 'button-group');
const buttons = [
{ text: '蓝色文本', class: 'btn-blue', template: '{{文本|蓝|选择文字}}' },
{ text: '绿色文本', class: 'btn-green', template: '{{文本|绿|选择文字}}' },
{ text: '蓝色下划线', class: 'btn-blue', template: '{{文本|蓝|下划线|选择文字}}' },
{ text: '绿色下划线', class: 'btn-green', template: '{{文本|绿|下划线|选择文字}}' },
{ text: '绿色描边', class: 'btn-green', template: '{{描边|绿|选择文字}}' },
{ text: '换行', class: '', template: '<br>' }
];
buttons.forEach(btn => {
toolbar.appendChild(createButton(btn.text, btn.class, () => {
insertTextAtCursor(descInput, btn.template, true);
}));
});
toolbar.appendChild(createButton('词典', '', () => {
const keyword = prompt('请输入词典关键词:');
if (keyword) insertTextAtCursor(descInput, `{{词典|${keyword}}}`);
}));
return toolbar;
}
// 创建变体编辑表单
function createVariantEditForm(insp, title, color, onBack, onDelete) {
const section = createElement('div', 'variant-edit-section');
const titleEl = createElement('div', 'section-title', { textContent: title });
titleEl.style.borderColor = color;
section.appendChild(titleEl);
const backBtn = createButton('← 返回基础信息', 'btn', onBack);
backBtn.style.cssText = 'margin-bottom:15px;width:100%;';
section.appendChild(backBtn);
// AP输入
section.appendChild(createFormGroup('AP:',
createInput('text', insp.ap, v => { insp.ap = v; updateCodePreview(); }, 'AP (数字、X、Ø)')));
// 类型选择
const typeSelect = createSelect(['', '攻击', '技能', '强化'], insp.type || '',
v => { insp.type = v; updateCodePreview(); });
section.appendChild(createFormGroup('类型:', typeSelect.wrapper));
// 机制
const dictSection = createElement('div', 'form-group');
dictSection.appendChild(createElement('div', 'form-label', { textContent: '机制:' }));
const dictToolbar = createElement('div', 'button-group');
const dictInput = createInput('text', insp.dict, v => { insp.dict = v; updateCodePreview(); }, '多个机制使用、隔开');
dictToolbar.appendChild(createButton('词典', 'btn', () => insertTextAtCursor(dictInput, '{{词典|选择文字}}', true)));
dictSection.appendChild(dictToolbar);
dictSection.appendChild(dictInput);
section.appendChild(dictSection);
// 描述
const descSection = createElement('div', 'form-group');
descSection.appendChild(createElement('div', 'form-label', { textContent: '描述:' }));
const descInput = createInput('textarea', insp.desc_global, v => { insp.desc_global = v; updateCodePreview(); }, '变体描述');
descInput.style.minHeight = '150px';
descSection.appendChild(createDescToolbar(descInput));
descSection.appendChild(descInput);
section.appendChild(descSection);
// 删除按钮
const deleteBtn = createButton('删除此变体', 'btn-danger', onDelete);
deleteBtn.style.cssText = 'margin-top:20px;width:100%;';
section.appendChild(deleteBtn);
return section;
}
// 渲染变体编辑器
function renderVariantEditor(container) {
if (!state.currentCard) return;
if (state.editMode === 'inspiration' && state.currentInspirationIndex !== null) {
const insp = state.currentCard.var.inspiration[state.currentInspirationIndex];
if (!insp) return;
container.appendChild(createVariantEditForm(
insp,
`灵光一闪 #${state.currentInspirationIndex + 1}`,
'#9c27b0',
() => { state.editMode = 'base'; state.currentInspirationIndex = null; renderWithoutScroll(); },
() => {
if (confirm('确定要删除这个灵光一闪吗?')) {
state.currentCard.var.inspiration.splice(state.currentInspirationIndex, 1);
state.editMode = 'base';
state.currentInspirationIndex = null;
renderWithoutScroll();
}
}
));
} else if (state.editMode === 'god_inspiration' && state.currentGodInspirationIndex !== null) {
const insp = state.currentCard.var.god_inspiration[state.currentGod][state.currentGodInspirationIndex];
if (!insp) return;
container.appendChild(createVariantEditForm(
insp,
`${state.currentGod} 神光 #${state.currentGodInspirationIndex + 1}`,
'#673ab7',
() => { state.editMode = 'base'; state.currentGodInspirationIndex = null; renderWithoutScroll(); },
() => {
if (confirm('确定要删除这个神光一闪吗?')) {
state.currentCard.var.god_inspiration[state.currentGod].splice(state.currentGodInspirationIndex, 1);
state.editMode = 'base';
state.currentGodInspirationIndex = null;
renderWithoutScroll();
}
}
));
}
}
// 不触发滚动的渲染函数
function renderWithoutScroll() {
const container = document.getElementById('card-manager-container');
if (!container) return;
const scrollPositions = {};
['input', 'list', 'preview'].forEach(type => {
const section = container.querySelector(`.card-${type}-section`);
if (section) scrollPositions[type] = section.scrollTop;
});
render();
requestAnimationFrame(() => {
['input', 'list', 'preview'].forEach(type => {
const section = container.querySelector(`.card-${type}-section`);
if (section) section.scrollTop = scrollPositions[type];
});
});
}
// 主渲染函数(续)
function render() {
const container = document.getElementById('card-manager-container');
if (!container) return;
container.innerHTML = '';
const manager = createElement('div', 'card-manager');
// 左侧输入区
const inputSection = createElement('div', 'card-input-section');
inputSection.appendChild(createElement('div', 'section-title', { textContent: '卡牌管理器' }));
// 战斗员选择
const fighterSelect = createSelect(state.fighters, state.currentFighter, async (value) => {
state.currentFighter = value;
const loading = createElement('div', 'loading-indicator', { textContent: '正在加载卡牌数据...' });
document.body.appendChild(loading);
try {
state.cards = await loadFighterCards(value);
state.currentCard = state.cards[0] || null;
state.editMode = 'base';
state.currentInspirationIndex = null;
state.currentGodInspirationIndex = null;
render();
} catch (error) {
alert('加载失败:' + error.message);
} finally {
loading.remove();
}
});
inputSection.appendChild(createFormGroup('选择战斗员:', fighterSelect.wrapper));
// 默认信息区
if (state.currentFighter) {
const defaultSection = createElement('div', 'default-info-section');
const defaultTitle = createElement('div', 'section-title', { textContent: '默认信息' });
defaultTitle.style.borderColor = '#ff9800';
defaultSection.appendChild(defaultTitle);
['order', 'ego'].forEach(key => {
const label = key === 'order' ? '卡牌顺序:' : '属性:';
const placeholder = key === 'order' ? 'card.order' : 'card.info.ego';
defaultSection.appendChild(createFormGroup(label,
createInput('text', state.defaultInfo[key], v => { state.defaultInfo[key] = v; updateCodePreview(); }, placeholder)));
});
inputSection.appendChild(defaultSection);
}
// 卡牌编辑区
if (state.currentCard) {
if (state.editMode === 'base') {
const cardSection = createElement('div', '');
cardSection.style.marginTop = '20px';
cardSection.appendChild(createElement('div', 'section-title', { textContent: '卡牌信息' }));
// 基础属性
const fields = [
{ key: 'name', label: '卡牌名称:', type: 'input', placeholder: '卡牌名称', obj: state.currentCard },
{ key: 'displayname', label: '显示名称:', type: 'input', placeholder: '显示名称' },
{ key: 'art', label: '图片:', type: 'input', placeholder: '图片文件名' },
{ key: 'group', label: '分组:', type: 'input', placeholder: '分组' },
{ key: 'rarity', label: '稀有度:', type: 'select', options: ['', '白', '蓝', '橙', '彩'] },
{ key: 'god', label: '神明:', type: 'select', options: ['', 'circen', 'diallos', 'nihilum', 'secred', 'vitor'] },
{ key: 'ap', label: 'AP:', type: 'input', placeholder: 'AP (可以是数字、X、Ø等)' },
{ key: 'type', label: '类型:', type: 'select', options: ['', '攻击', '技能', '强化'] }
];
fields.forEach(field => {
const obj = field.obj || state.currentCard.base;
if (field.type === 'select') {
const select = createSelect(field.options, obj[field.key] || '', v => { obj[field.key] = v; updateCodePreview(); });
cardSection.appendChild(createFormGroup(field.label, select.wrapper));
} else {
const input = createInput('text', obj[field.key], v => {
obj[field.key] = v;
updateCodePreview();
if (field.key === 'name') {
const cardItems = document.querySelectorAll('.card-item');
cardItems.forEach((item, idx) => {
if (state.cards[idx] === state.currentCard) {
const nameEl = item.querySelector('.card-item-name');
if (nameEl) nameEl.textContent = v || '未命名卡牌';
}
});
}
}, field.placeholder);
cardSection.appendChild(createFormGroup(field.label, input));
}
});
// 机制
const dictSection = createElement('div', 'form-group');
dictSection.appendChild(createElement('div', 'form-label', { textContent: '机制:' }));
const dictToolbar = createElement('div', 'button-group');
const dictInput = createInput('text', state.currentCard.base.dict, v => { state.currentCard.base.dict = v; updateCodePreview(); }, '多个机制使用、隔开');
dictToolbar.appendChild(createButton('词典', 'btn', () => insertTextAtCursor(dictInput, '{{词典|选择文字}}', true)));
dictSection.appendChild(dictToolbar);
dictSection.appendChild(dictInput);
cardSection.appendChild(dictSection);
// 描述
const descSection = createElement('div', 'form-group');
descSection.appendChild(createElement('div', 'form-label', { textContent: '描述:' }));
const descInput = createInput('textarea', state.currentCard.base.desc_global,
v => { state.currentCard.base.desc_global = v; updateCodePreview(); }, '卡牌描述');
descInput.style.minHeight = '150px';
descSection.appendChild(createDescToolbar(descInput));
descSection.appendChild(descInput);
cardSection.appendChild(descSection);
// 衍生卡牌
const subInput = createInput('textarea', state.currentCard.base.sub,
v => { state.currentCard.base.sub = v; updateCodePreview(); }, '衍生卡牌');
subInput.style.minHeight = '35px';
cardSection.appendChild(createFormGroup('衍生卡牌:', subInput));
// 是否存在灵光一闪
const inspirationCheckbox = createCheckbox(state.currentCard.base.isinspiration, value => {
state.currentCard.base.isinspiration = value;
if (!value) state.currentCard.var.inspiration = [];
updateCodePreview();
renderWithoutScroll();
});
cardSection.appendChild(createFormGroup('是否存在灵光一闪:', inspirationCheckbox));
// 是否存在神光一闪
const godInspirationCheckbox = createCheckbox(state.currentCard.base.isgod_inspiration, value => {
state.currentCard.base.isgod_inspiration = value;
if (!value) {
state.currentCard.var.god_inspiration = {
circen: [], diallos: [], nihilum: [], secred: [], vitor: []
};
}
updateCodePreview();
renderWithoutScroll();
});
cardSection.appendChild(createFormGroup('是否存在神光一闪:', godInspirationCheckbox));
inputSection.appendChild(cardSection);
// 保存卡牌按钮
const saveCardBtn = createButton('保存卡牌', 'btn-primary', () => {
updateCodePreview();
alert('卡牌已保存到代码预览!');
});
saveCardBtn.style.cssText = 'margin-top:20px;width:100%;';
inputSection.appendChild(saveCardBtn);
} else {
renderVariantEditor(inputSection);
}
}
// 添加新卡牌按钮
if (state.currentFighter && state.editMode === 'base') {
const addCardBtn = createButton('+ 新增卡牌', 'btn-success', () => {
const newCard = createEmptyCard();
newCard.name = '新卡牌' + (state.cards.length + 1);
state.cards.push(newCard);
state.currentCard = newCard;
state.editMode = 'base';
state.currentInspirationIndex = null;
state.currentGodInspirationIndex = null;
renderWithoutScroll();
});
addCardBtn.style.cssText = 'margin-top:20px;width:100%;';
inputSection.appendChild(addCardBtn);
}
manager.appendChild(inputSection);
// 中间列表区
const listSection = createElement('div', 'card-list-section');
// 卡牌列表
const cardListContainer = createElement('div', 'list-container');
cardListContainer.style.minHeight = '250px';
const cardListHeader = createElement('div', 'list-header');
cardListHeader.appendChild(createElement('div', 'list-title', { textContent: '卡牌列表' }));
cardListContainer.appendChild(cardListHeader);
if (state.cards.length === 0) {
const emptyState = createElement('div', 'empty-state');
emptyState.appendChild(createElement('div', 'empty-state-icon', { textContent: '📋' }));
emptyState.appendChild(createElement('div', 'empty-state-text', { textContent: '暂无卡牌,点击"新增卡牌"开始创建' }));
cardListContainer.appendChild(emptyState);
} else {
state.cards.forEach((card, index) => {
const cardItem = createElement('div', 'card-item' + (state.currentCard === card && state.editMode === 'base' ? ' active' : ''));
cardItem.appendChild(createElement('div', 'card-item-name', { textContent: card.name || '未命名卡牌' }));
const infoText = [card.base.type, card.base.ap !== '' ? 'AP:' + card.base.ap : '', card.base.rarity]
.filter(Boolean).join(' | ') || '暂无信息';
cardItem.appendChild(createElement('div', 'card-item-info', { textContent: infoText }));
cardItem.addEventListener('click', () => {
state.currentCard = card;
state.editMode = 'base';
state.currentInspirationIndex = null;
state.currentGodInspirationIndex = null;
renderWithoutScroll();
});
const deleteBtn = createButton('删除', 'btn-danger', e => {
e.stopPropagation();
if (confirm(`确定要删除卡牌"${card.name}"吗?`)) {
state.cards.splice(index, 1);
if (state.currentCard === card) {
state.currentCard = state.cards[0] || null;
state.editMode = 'base';
state.currentInspirationIndex = null;
state.currentGodInspirationIndex = null;
}
renderWithoutScroll();
}
});
deleteBtn.style.cssText = 'margin-top:8px;width:100%;';
cardItem.appendChild(deleteBtn);
cardListContainer.appendChild(cardItem);
});
}
listSection.appendChild(cardListContainer);
// 灵光一闪列表
if (state.currentCard && state.currentCard.base.isinspiration) {
const inspirationSection = createElement('div', 'inspiration-section');
inspirationSection.style.minHeight = '250px';
const inspirationHeader = createElement('div', 'list-header');
inspirationHeader.appendChild(createElement('div', 'list-title', { textContent: '灵光一闪列表' }));
inspirationHeader.appendChild(createButton('+ 添加', 'btn-primary', () => {
const newIndex = state.currentCard.var.inspiration.length;
state.currentCard.var.inspiration.push({ ap: '', type: '', desc_global: '', dict: '' });
state.editMode = 'inspiration';
state.currentInspirationIndex = newIndex;
state.currentGodInspirationIndex = null;
render();
}));
inspirationSection.appendChild(inspirationHeader);
if (state.currentCard.var.inspiration.length === 0) {
const emptyState = createElement('div', 'empty-state');
emptyState.style.padding = '20px';
emptyState.appendChild(createElement('div', 'empty-state-text', { textContent: '暂无灵光一闪,点击"添加"创建' }));
inspirationSection.appendChild(emptyState);
} else {
state.currentCard.var.inspiration.forEach((insp, idx) => {
const inspItem = createElement('div', 'inspiration-item-simple' +
(state.editMode === 'inspiration' && state.currentInspirationIndex === idx ? ' active' : ''));
inspItem.appendChild(createElement('div', 'inspiration-item-name', { textContent: `灵光 #${idx + 1}` }));
const infoText = [insp.type, insp.ap !== '' ? 'AP:' + insp.ap : '']
.filter(Boolean).join(' | ') || '点击编辑';
inspItem.appendChild(createElement('div', 'inspiration-item-info', { textContent: infoText }));
inspItem.addEventListener('click', () => {
state.editMode = 'inspiration';
state.currentInspirationIndex = idx;
state.currentGodInspirationIndex = null;
renderWithoutScroll();
});
inspirationSection.appendChild(inspItem);
});
}
listSection.appendChild(inspirationSection);
}
// 神光一闪列表
if (state.currentCard && state.currentCard.base.isgod_inspiration) {
const godInspirationSection = createElement('div', 'inspiration-section');
godInspirationSection.style.minHeight = '250px';
const godInspirationHeader = createElement('div', 'list-header');
godInspirationHeader.appendChild(createElement('div', 'list-title', { textContent: '神光一闪列表' }));
godInspirationSection.appendChild(godInspirationHeader);
// 神明选择标签
const godSelectGroup = createElement('div', 'god-select-group');
['circen', 'diallos', 'nihilum', 'secred', 'vitor'].forEach(god => {
const godTab = createElement('div', 'god-tab' + (state.currentGod === god ? ' active' : ''),
{ textContent: god, onclick: () => { state.currentGod = god; renderWithoutScroll(); } });
godSelectGroup.appendChild(godTab);
});
godInspirationSection.appendChild(godSelectGroup);
// 当前神明的神光一闪列表
const currentGodInspirations = state.currentCard.var.god_inspiration[state.currentGod];
const addGodInspirationBtn = createButton(`+ 添加 ${state.currentGod} 神光`, 'btn-primary', () => {
const newIndex = currentGodInspirations.length;
currentGodInspirations.push({ ap: '', type: '', desc_global: '', dict: '' });
state.editMode = 'god_inspiration';
state.currentGodInspirationIndex = newIndex;
state.currentInspirationIndex = null;
render();
});
addGodInspirationBtn.style.cssText = 'margin-bottom:10px;width:100%;';
godInspirationSection.appendChild(addGodInspirationBtn);
if (currentGodInspirations.length === 0) {
const emptyState = createElement('div', 'empty-state');
emptyState.style.padding = '20px';
emptyState.appendChild(createElement('div', 'empty-state-text', { textContent: `暂无 ${state.currentGod} 神光一闪` }));
godInspirationSection.appendChild(emptyState);
} else {
currentGodInspirations.forEach((insp, idx) => {
const inspItem = createElement('div', 'inspiration-item-simple' +
(state.editMode === 'god_inspiration' && state.currentGodInspirationIndex === idx ? ' active' : ''));
inspItem.appendChild(createElement('div', 'inspiration-item-name', { textContent: `${state.currentGod} 神光 #${idx + 1}` }));
const infoText = [insp.type, insp.ap !== '' ? 'AP:' + insp.ap : '']
.filter(Boolean).join(' | ') || '点击编辑';
inspItem.appendChild(createElement('div', 'inspiration-item-info', { textContent: infoText }));
inspItem.addEventListener('click', () => {
state.editMode = 'god_inspiration';
state.currentGodInspirationIndex = idx;
state.currentInspirationIndex = null;
renderWithoutScroll();
});
godInspirationSection.appendChild(inspItem);
});
}
listSection.appendChild(godInspirationSection);
}
manager.appendChild(listSection);
// 右侧预览区
const previewSection = createElement('div', 'card-preview-section');
previewSection.appendChild(createElement('div', 'section-title', { textContent: 'Lua 代码预览' }));
// 复制按钮
const copyBtn = createButton('复制代码', 'btn-primary', () => {
const code = generateLuaCode();
navigator.clipboard.writeText(code).then(() => {
alert('代码已复制到剪贴板!');
}).catch(err => {
console.error('复制失败:', err);
const textarea = document.createElement('textarea');
textarea.value = code;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
alert('代码已复制到剪贴板!');
});
});
copyBtn.style.cssText = 'margin-bottom:10px;width:100%;';
previewSection.appendChild(copyBtn);
// 保存按钮
const saveBtn = createButton('保存到Wiki', 'btn-success', async () => {
if (!state.currentFighter) {
alert('请先选择战斗员!');
return;
}
const code = generateLuaCode();
const loading = createElement('div', 'loading-indicator', { textContent: '正在保存...' });
document.body.appendChild(loading);
try {
const api = new mw.Api();
await api.postWithToken('csrf', {
action: 'edit',
title: '模块:卡牌/' + state.currentFighter,
text: code,
summary: '通过卡牌管理器更新卡牌数据',
contentmodel: 'Scribunto'
});
alert('保存成功!');
} catch (error) {
console.error('保存失败:', error);
alert('保存失败:' + error);
} finally {
loading.remove();
}
});
saveBtn.style.cssText = 'margin-bottom:10px;width:100%;';
previewSection.appendChild(saveBtn);
// 代码显示
const codePreview = createElement('div', 'code-preview', { textContent: generateLuaCode() });
previewSection.appendChild(codePreview);
manager.appendChild(previewSection);
container.appendChild(manager);
}
// 初始化
async function init() {
let container = document.getElementById('card-manager-container');
if (!container) {
container = createElement('div', '');
container.id = 'card-manager-container';
const content = document.getElementById('mw-content-text');
if (content) content.insertBefore(container, content.firstChild);
else document.body.appendChild(container);
}
const loading = createElement('div', 'loading-indicator', { textContent: '正在初始化...' });
document.body.appendChild(loading);
try {
await loadFighters();
if (state.fighters.length > 0) {
state.currentFighter = state.fighters[0];
state.cards = await loadFighterCards(state.currentFighter);
if (state.cards.length > 0) state.currentCard = state.cards[0];
}
render();
} catch (error) {
console.error('初始化失败:', error);
alert('初始化失败:' + error.message);
} finally {
loading.remove();
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
window.CardManager = {
init, render, state,
createCard: createEmptyCard,
generateCode: generateLuaCode
};
})();