Card.js:修订间差异
来自卡厄思梦境WIKI
创建页面,内容为“→* * MediaWiki卡牌编辑器 * 用于管理"模块:卡牌/角色名"页面 * 放置于 MediaWiki:card.js: (function() { 'use strict'; // 检查是否在正确的页面加载 if (mw.config.get('wgCanonicalSpecialPageName') !== 'Blankpage' || !window.location.href.includes('卡牌管理')) { return; } var CardEditor = { cards: [], currentCard: null, currentVariantIndex: null, api: new…” |
无编辑摘要 |
||
| (未显示同一用户的57个中间版本) | |||
| 第1行: | 第1行: | ||
(function() { | (function() { | ||
'use strict'; | 'use strict'; | ||
// | // 加载CSS | ||
mw.loader.load('/index.php?title=Mediawiki:Card.css&action=raw&ctype=text/css', 'text/css'); | |||
// 状态管理 | |||
const state = { | |||
currentFighter: '', | |||
fighters: [], | |||
cards: [], | cards: [], | ||
currentCard: null, | currentCard: null, | ||
defaultInfo: { | |||
order: '', | |||
ego: '' | |||
}, | }, | ||
editMode: 'base', // 'base', 'inspiration', 'god_inspiration' | |||
// | currentGod: '凯尔肯', | ||
currentInspirationIndex: null, | |||
var | 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: { | |||
凯尔肯: [], | |||
戴奥斯: [], | |||
尼希隆: [], | |||
赛克瑞德: [], | |||
维托: [] | |||
} | |||
} | |||
}; | |||
} | |||
// 创建空变体结构 | |||
function createEmptyVariant() { | |||
return { | |||
ap: '', | |||
type: '', | |||
desc_global: '', | |||
dict: '', | |||
sub: '' // 新增:变体卡牌的衍生卡牌字段 | |||
}; | |||
} | |||
// 创建元素辅助函数 | |||
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(); | |||
// | dropdown.style.top = rect.bottom + 'px'; | ||
dropdown.style.left = rect.left + 'px'; | |||
dropdown.style.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', ''); | |||
label.textContent = '是'; | |||
wrapper.addEventListener('click', () => { | |||
const newChecked = !checkbox.classList.contains('checked'); | |||
if (newChecked) { | |||
checkbox.classList.add('checked'); | |||
} else { | |||
checkbox.classList.remove('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'); | |||
const labelEl = createElement('div', 'form-label'); | |||
labelEl.textContent = label; | |||
group.appendChild(labelEl); | |||
group.appendChild(control); | |||
return group; | |||
} | |||
// 创建按钮 | |||
function createButton(text, className, onClick) { | |||
const btn = createElement('div', 'btn ' + className); | |||
btn.textContent = text; | |||
btn.addEventListener('click', onClick); | |||
return btn; | |||
} | |||
// 文本插入辅助函数 | |||
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(); | |||
const textNode = document.createTextNode(text.replace('选择文字', selectedText)); | |||
range.insertNode(textNode); | |||
return; | |||
} | |||
} | |||
document.execCommand('insertText', false, text); | |||
} | |||
// 辅助函数:提取字符串值 | |||
function extractStringValue(text, key) { | |||
const pattern = new RegExp(key + '\\s*=\\s*"([^"]*)"', 'm'); | |||
const match = text.match(pattern); | |||
return match ? match[1] : ''; | |||
} | |||
// 辅助函数:提取数值 | |||
function extractNumberValue(text, key) { | |||
const pattern = new RegExp(key + '\\s*=\\s*(\\d+)', 'm'); | |||
const match = text.match(pattern); | |||
return match ? parseInt(match[1]) : 0; | |||
} | |||
// 辅助函数:提取任意值(包括X、Ø等) | |||
function extractValue(text, key) { | |||
const pattern = new RegExp(key + '\\s*=\\s*([^,\\n]+)', 'm'); | |||
const match = text.match(pattern); | |||
if (match) { | |||
let value = match[1].trim(); | |||
value = value.replace(/^["']|["']$/g, ''); | |||
return value; | |||
} | |||
return ''; | |||
} | |||
// 解析灵光一闪/神光一闪列表 | |||
function parseInspirationList(content) { | |||
const inspirations = []; | |||
let depth = 0; | |||
let currentBlock = ''; | |||
let 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 = {}; | |||
const ap = extractValue(currentBlock, 'ap'); | |||
if (ap) inspiration.ap = ap; | |||
const type = extractStringValue(currentBlock, 'type'); | |||
if (type) inspiration.type = type; | |||
const desc = extractStringValue(currentBlock, 'desc_global'); | |||
if (desc) inspiration.desc_global = desc; | |||
const dict = extractStringValue(currentBlock, 'dict'); | |||
if (dict) inspiration.dict = dict; | |||
// 新增:解析变体的 sub 字段 | |||
const sub = extractStringValue(currentBlock, 'sub'); | |||
if (sub) inspiration.sub = sub; | |||
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; | |||
let endIdx = startIdx; | |||
let inString = false; | |||
for (let i = startIdx; i < content.length && depth > 0; i++) { | |||
const char = content[i]; | |||
const prevChar = i > 0 ? content[i - 1] : ''; | |||
if (char === '"' && prevChar !== '\\') { | |||
inString = !inString; | |||
} | |||
if (!inString) { | |||
if (char === '{') { | |||
depth++; | |||
} else if (char === '}') { | |||
depth--; | |||
if (depth === 0) { | |||
endIdx = i; | |||
break; | |||
} | |||
} | } | ||
.card | } | ||
.card | } | ||
. | |||
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 cardName = cardMatch[1]; | |||
const cardStartIdx = cardMatch.index + cardMatch[0].length; | |||
let depth = 1; | |||
let cardEndIdx = cardStartIdx; | |||
let inString = false; | |||
. | for (let i = cardStartIdx; i < luaText.length && depth > 0; i++) { | ||
const char = luaText[i]; | |||
const prevChar = i > 0 ? luaText[i - 1] : ''; | |||
if (char === '"' && prevChar !== '\\') { | |||
inString = !inString; | |||
} | |||
if (!inString) { | |||
if (char === '{') { | |||
depth++; | |||
} else if (char === '}') { | |||
depth--; | |||
if (depth === 0) { | |||
cardEndIdx = i; | |||
break; | |||
} | |||
} | |||
} | |||
} | } | ||
.card | |||
const cardContent = luaText.substring(cardStartIdx, cardEndIdx); | |||
const card = createEmptyCard(); | |||
card.name = cardName; | |||
const baseContent = extractBlock(cardContent, /base\s*=\s*\{/); | |||
if (baseContent) { | |||
card.base.displayname = extractStringValue(baseContent, 'displayname') || ''; | |||
card.base.art = extractStringValue(baseContent, 'art') || ''; | |||
card.base.group = extractStringValue(baseContent, 'group') || ''; | |||
card.base.rarity = extractStringValue(baseContent, 'rarity') || ''; | |||
card.base.god = extractStringValue(baseContent, 'god') || ''; | |||
card.base.ap = extractValue(baseContent, 'ap') || ''; | |||
card.base.type = extractStringValue(baseContent, 'type') || ''; | |||
card.base.dict = extractStringValue(baseContent, 'dict') || ''; | |||
card.base.desc_global = extractStringValue(baseContent, 'desc_global') || ''; | |||
card.base.sub = extractStringValue(baseContent, 'sub') || ''; | |||
card.base.isinspiration = extractNumberValue(baseContent, 'isinspiration') || 0; | |||
card.base.isgod_inspiration = extractNumberValue(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) { | |||
// 修改:使用中文键名解析神光一闪 | |||
['凯尔肯', '戴奥斯', '尼希隆', '赛克瑞德', '维托'].forEach(god => { | |||
const godPattern = new RegExp('\\["?' + god + '"?\\]\\s*=\\s*\\{'); | |||
const godContent = extractBlock(godInspirationContent, godPattern); | |||
if (godContent) { | |||
card.var.god_inspiration[god] = parseInspirationList(godContent); | |||
} | |||
}); | |||
} | |||
} | } | ||
.card | |||
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 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'; | |||
code += ` ego = "${escapeLuaString(state.defaultInfo.ego)}",\n`; | |||
code += '}\n\n'; | |||
} | |||
state.cards.forEach(card => { | |||
code += `card["${escapeLuaString(card.name)}"] = {\n`; | |||
code += ' base = {\n'; | |||
const base = card.base; | |||
if (base.displayname) code += ` displayname = "${escapeLuaString(base.displayname)}",\n`; | |||
if (base.art) code += ` art = "${escapeLuaString(base.art)}",\n`; | |||
if (base.group) code += ` group = "${escapeLuaString(base.group)}",\n`; | |||
if (base.rarity) code += ` rarity = "${escapeLuaString(base.rarity)}",\n`; | |||
if (base.god) code += ` god = "${escapeLuaString(base.god)}",\n`; | |||
if (base.ap !== '') { | |||
if (isNaN(base.ap)) { | |||
code += ` ap = "${escapeLuaString(base.ap)}",\n`; | |||
} else { | |||
code += ` ap = ${base.ap},\n`; | |||
} | } | ||
. | } | ||
if (base.type) code += ` type = "${escapeLuaString(base.type)}",\n`; | |||
if (base.dict) code += ` dict = "${escapeLuaString(base.dict)}",\n`; | |||
if (base.desc_global) code += ` desc_global = "${escapeLuaString(base.desc_global)}",\n`; | |||
if (base.sub) code += ` sub = "${escapeLuaString(base.sub)}",\n`; | |||
if (base.isinspiration) code += ` isinspiration = ${base.isinspiration},\n`; | |||
if (base.isgod_inspiration) code += ` isgod_inspiration = ${base.isgod_inspiration},\n`; | |||
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'; | |||
card.var.inspiration.forEach(insp => { | |||
code += ' {\n'; | |||
if (insp.ap !== '' && insp.ap !== undefined) { | |||
if (isNaN(insp.ap)) { | |||
code += ` ap = "${escapeLuaString(insp.ap)}",\n`; | |||
} else { | |||
code += ` ap = ${insp.ap},\n`; | |||
} | |||
} | |||
if (insp.type) code += ` type = "${escapeLuaString(insp.type)}",\n`; | |||
if (insp.desc_global) code += ` desc_global = "${escapeLuaString(insp.desc_global)}",\n`; | |||
if (insp.dict) code += ` dict = "${escapeLuaString(insp.dict)}",\n`; | |||
// 新增:输出变体的 sub 字段 | |||
if (insp.sub) code += ` sub = "${escapeLuaString(insp.sub)}",\n`; | |||
code += ' },\n'; | |||
}); | |||
code += ' },\n'; | |||
} | } | ||
.card | |||
if (hasGodInspiration) { | |||
code += ' god_inspiration = {\n'; | |||
Object.entries(card.var.god_inspiration).forEach(([god, inspList]) => { | |||
if (inspList.length > 0) { | |||
// 修改:使用中文键名格式 ["尼希隆"] | |||
code += ` ["${god}"] = {\n`; | |||
inspList.forEach(insp => { | |||
code += ' {\n'; | |||
if (insp.ap !== '' && insp.ap !== undefined) { | |||
if (isNaN(insp.ap)) { | |||
code += ` ap = "${escapeLuaString(insp.ap)}",\n`; | |||
} else { | |||
code += ` ap = ${insp.ap},\n`; | |||
} | |||
} | |||
if (insp.type) code += ` type = "${escapeLuaString(insp.type)}",\n`; | |||
if (insp.desc_global) code += ` desc_global = "${escapeLuaString(insp.desc_global)}",\n`; | |||
if (insp.dict) code += ` dict = "${escapeLuaString(insp.dict)}",\n`; | |||
// 新增:输出变体的 sub 字段 | |||
if (insp.sub) code += ` sub = "${escapeLuaString(insp.sub)}",\n`; | |||
code += ' },\n'; | |||
}); | |||
code += ' },\n'; | |||
} | |||
}); | |||
code += ' },\n'; | |||
} | } | ||
. | |||
code += ' },\n'; | |||
} | |||
code += '}\n\n'; | |||
}); | |||
code += 'return card'; | |||
return code; | |||
} | |||
// 加载战斗员列表 | |||
async function loadFighters() { | |||
try { | |||
const api = new mw.Api(); | |||
const result = await api.get({ | |||
action: 'query', | |||
list: 'allpages', | |||
apprefix: '卡牌/', | |||
apnamespace: 828, | |||
aplimit: 500 | |||
}); | |||
if (result.query && result.query.allpages) { | |||
state.fighters = result.query.allpages.map(page => { | |||
return page.title.replace('模块:卡牌/', ''); | |||
}); | |||
} | |||
} catch (error) { | |||
console.error('加载战斗员列表失败:', error); | |||
} | |||
} | |||
// 加载战斗员卡牌数据 | |||
async function loadFighterCards(fighter) { | |||
if (!fighter) return []; | |||
try { | |||
const api = new mw.Api(); | |||
const result = await api.get({ | |||
action: 'query', | |||
prop: 'revisions', | |||
titles: '模块:卡牌/' + fighter, | |||
rvprop: 'content', | |||
rvslots: 'main', | |||
formatversion: 2 | |||
}); | |||
if (!result.query || !result.query.pages || result.query.pages.length === 0) { | |||
console.error('未找到页面'); | |||
return []; | |||
} | |||
const page = result.query.pages[0]; | |||
if (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 renderVariantEditor(container) { | |||
if (!state.currentCard) return; | |||
if (state.editMode === 'inspiration' && state.currentInspirationIndex !== null) { | |||
const insp = state.currentCard.var.inspiration[state.currentInspirationIndex]; | |||
if (!insp) return; | |||
const variantSection = createElement('div', 'variant-edit-section'); | |||
const variantTitle = createElement('div', 'section-title'); | |||
variantTitle.textContent = `灵光一闪 #${state.currentInspirationIndex + 1}`; | |||
variantTitle.style.borderColor = '#9c27b0'; | |||
variantSection.appendChild(variantTitle); | |||
// 返回按钮 | |||
const backBtn = createButton('← 返回基础信息', 'btn', () => { | |||
state.editMode = 'base'; | |||
state.currentInspirationIndex = null; | |||
renderWithoutScroll(); | |||
}); | |||
backBtn.style.marginBottom = '15px'; | |||
backBtn.style.width = '100%'; | |||
variantSection.appendChild(backBtn); | |||
// AP输入 | |||
const apInput = createInput('text', insp.ap, (value) => { | |||
insp.ap = value; | |||
updateCodePreview(); | |||
}, 'AP (数字、X、Ø)'); | |||
variantSection.appendChild(createFormGroup('AP:', apInput)); | |||
// 类型选择 | |||
const typeSelect = createSelect( | |||
['', '攻击', '技能', '强化'], | |||
insp.type || '', | |||
(value) => { | |||
insp.type = value; | |||
updateCodePreview(); | |||
} | } | ||
. | ); | ||
variantSection.appendChild(createFormGroup('类型:', typeSelect.wrapper)); | |||
// 机制输入 | |||
const dictInput = createInput('text', insp.dict, (value) => { | |||
insp.dict = value; | |||
updateCodePreview(); | |||
}, '多个机制使用、隔开'); | |||
variantSection.appendChild(createFormGroup('机制:', dictInput)); | |||
// 描述区域 | |||
const descSection = createElement('div', 'form-group'); | |||
const descLabel = createElement('div', 'form-label'); | |||
descLabel.textContent = '描述:'; | |||
descSection.appendChild(descLabel); | |||
// 描述工具栏 | |||
const toolbar = createElement('div', 'button-group'); | |||
const blueTextBtn = createButton('蓝色文本', 'btn btn-blue', () => { | |||
insertTextAtCursor(descInput, '{{文本|蓝|选择文字}}', true); | |||
}); | |||
toolbar.appendChild(blueTextBtn); | |||
const greenTextBtn = createButton('绿色文本', 'btn btn-green', () => { | |||
insertTextAtCursor(descInput, '{{文本|绿|选择文字}}', true); | |||
}); | |||
toolbar.appendChild(greenTextBtn); | |||
const blueUnderlineBtn = createButton('蓝色下划线', 'btn btn-blue', () => { | |||
insertTextAtCursor(descInput, '{{文本|蓝|下划线|选择文字}}', true); | |||
}); | |||
toolbar.appendChild(blueUnderlineBtn); | |||
const greenUnderlineBtn = createButton('绿色下划线', 'btn btn-green', () => { | |||
insertTextAtCursor(descInput, '{{文本|绿|下划线|选择文字}}', true); | |||
}); | |||
toolbar.appendChild(greenUnderlineBtn); | |||
const strokeTextBtn = createButton('绿色描边', 'btn btn-green', () => { | |||
insertTextAtCursor(descInput, '{{描边|绿|选择文字}}', true); | |||
}); | |||
toolbar.appendChild(strokeTextBtn); | |||
const dictBtn = createButton('词典', 'btn', () => { | |||
insertTextAtCursor(descInput, '{{词典|选择文字}}', true); | |||
}); | |||
toolbar.appendChild(dictBtn); | |||
const wrapBtn = createButton('换行', 'btn', () => { | |||
insertTextAtCursor(descInput, '<br>'); | |||
}); | |||
toolbar.appendChild(wrapBtn); | |||
descSection.appendChild(toolbar); | |||
const descInput = createInput('textarea', insp.desc_global, (value) => { | |||
insp.desc_global = value; | |||
updateCodePreview(); | |||
}, '变体描述'); | |||
descInput.style.minHeight = '150px'; | |||
descSection.appendChild(descInput); | |||
variantSection.appendChild(descSection); | |||
// 新增:衍生卡牌输入框 | |||
const subInput = createInput('textarea', insp.sub || '', (value) => { | |||
insp.sub = value; | |||
updateCodePreview(); | |||
}, '衍生卡牌'); | |||
subInput.style.minHeight = '35px'; | |||
variantSection.appendChild(createFormGroup('衍生卡牌:', subInput)); | |||
// 删除按钮 | |||
const deleteBtn = createButton('删除此变体', 'btn-danger', () => { | |||
if (confirm('确定要删除这个灵光一闪吗?')) { | |||
state.currentCard.var.inspiration.splice(state.currentInspirationIndex, 1); | |||
state.editMode = 'base'; | |||
state.currentInspirationIndex = null; | |||
renderWithoutScroll(); | |||
} | } | ||
}); | |||
deleteBtn.style.marginTop = '20px'; | |||
deleteBtn.style.width = '100%'; | |||
variantSection.appendChild(deleteBtn); | |||
container.appendChild(variantSection); | |||
} else if (state.editMode === 'god_inspiration' && state.currentGodInspirationIndex !== null) { | |||
const insp = state.currentCard.var.god_inspiration[state.currentGod][state.currentGodInspirationIndex]; | |||
if (!insp) return; | |||
const variantSection = createElement('div', 'variant-edit-section'); | |||
const variantTitle = createElement('div', 'section-title'); | |||
variantTitle.textContent = `${state.currentGod} 神光 #${state.currentGodInspirationIndex + 1}`; | |||
variantTitle.style.borderColor = '#673ab7'; | |||
variantSection.appendChild(variantTitle); | |||
// 返回按钮 | |||
const backBtn = createButton('← 返回基础信息', 'btn', () => { | |||
state.editMode = 'base'; | |||
state.currentGodInspirationIndex = null; | |||
renderWithoutScroll(); | |||
}); | |||
backBtn.style.marginBottom = '15px'; | |||
backBtn.style.width = '100%'; | |||
variantSection.appendChild(backBtn); | |||
// AP输入 | |||
const apInput = createInput('text', insp.ap, (value) => { | |||
insp.ap = value; | |||
updateCodePreview(); | |||
}, 'AP (数字、X、Ø)'); | |||
variantSection.appendChild(createFormGroup('AP:', apInput)); | |||
// 类型选择 | |||
const typeSelect = createSelect( | |||
['', '攻击', '技能', '强化'], | |||
insp.type || '', | |||
(value) => { | |||
insp.type = value; | |||
updateCodePreview(); | |||
} | } | ||
); | |||
variantSection.appendChild(createFormGroup('类型:', typeSelect.wrapper)); | |||
// 机制输入 | |||
const dictInput = createInput('text', insp.dict, (value) => { | |||
insp.dict = value; | |||
updateCodePreview(); | |||
}, '多个机制使用、隔开'); | |||
variantSection.appendChild(createFormGroup('机制:', dictInput)); | |||
. | |||
// 描述区域 | |||
const descSection = createElement('div', 'form-group'); | |||
const descLabel = createElement('div', 'form-label'); | |||
descLabel.textContent = '描述:'; | |||
descSection.appendChild(descLabel); | |||
// 描述工具栏 | |||
const toolbar = createElement('div', 'button-group'); | |||
// | |||
const blueTextBtn = createButton('蓝色文本', 'btn btn-blue', () => { | |||
insertTextAtCursor(descInput, '{{文本|蓝|选择文字}}', true); | |||
}); | }); | ||
toolbar.appendChild(blueTextBtn); | |||
const greenTextBtn = createButton('绿色文本', 'btn btn-green', () => { | |||
insertTextAtCursor(descInput, '{{文本|绿|选择文字}}', true); | |||
}); | }); | ||
toolbar.appendChild(greenTextBtn); | |||
const blueUnderlineBtn = createButton('蓝色下划线', 'btn btn-blue', () => { | |||
insertTextAtCursor(descInput, '{{文本|蓝|下划线|选择文字}}', true); | |||
}); | |||
toolbar.appendChild(blueUnderlineBtn); | |||
const greenUnderlineBtn = createButton('绿色下划线', 'btn btn-green', () => { | |||
insertTextAtCursor(descInput, '{{文本|绿|下划线|选择文字}}', true); | |||
}); | |||
toolbar.appendChild(greenUnderlineBtn); | |||
const strokeTextBtn = createButton('绿色描边', 'btn btn-green', () => { | |||
insertTextAtCursor(descInput, '{{描边|绿|选择文字}}', true); | |||
}); | |||
toolbar.appendChild(strokeTextBtn); | |||
const dictBtn = createButton('词典', 'btn', () => { | |||
insertTextAtCursor(descInput, '{{词典|选择文字}}', true); | |||
}); | |||
toolbar.appendChild(dictBtn); | |||
const wrapBtn = createButton('换行', 'btn', () => { | |||
insertTextAtCursor(descInput, '<br>'); | |||
}); | |||
toolbar.appendChild(wrapBtn); | |||
const circenBtn = createButton('凯尔肯', 'btn', () => { | |||
insertTextAtCursor(descInput, '{{图标|凯尔肯}}'); | |||
}); | |||
toolbar.appendChild(circenBtn); | |||
const diallosBtn = createButton('戴奥斯', 'btn', () => { | |||
insertTextAtCursor(descInput, '{{图标|戴奥斯}}'); | |||
}); | |||
toolbar.appendChild(diallosBtn); | |||
const nihiluBtn = createButton('尼希隆', 'btn', () => { | |||
insertTextAtCursor(descInput, '{{图标|尼希隆}}'); | |||
}); | |||
toolbar.appendChild(nihiluBtn); | |||
const secredBtn = createButton('赛克瑞德', 'btn', () => { | |||
insertTextAtCursor(descInput, '{{图标|赛克瑞德}}'); | |||
}); | |||
toolbar.appendChild(secredBtn); | |||
const vitorBtn = createButton('维托', 'btn', () => { | |||
insertTextAtCursor(descInput, '{{图标|维托}}'); | |||
}); | |||
toolbar.appendChild(vitorBtn); | |||
descSection.appendChild(toolbar); | |||
const descInput = createInput('textarea', insp.desc_global, (value) => { | |||
insp.desc_global = value; | |||
updateCodePreview(); | |||
}, '变体描述'); | |||
descInput.style.minHeight = '150px'; | |||
descSection.appendChild(descInput); | |||
variantSection.appendChild(descSection); | |||
// 新增:衍生卡牌输入框 | |||
const subInput = createInput('textarea', insp.sub || '', (value) => { | |||
insp.sub = value; | |||
updateCodePreview(); | |||
}, '衍生卡牌'); | |||
subInput.style.minHeight = '35px'; | |||
variantSection.appendChild(createFormGroup('衍生卡牌:', subInput)); | |||
// 删除按钮 | |||
const deleteBtn = createButton('删除此变体', 'btn-danger', () => { | |||
if (confirm('确定要删除这个神光一闪吗?')) { | |||
state.currentCard.var.god_inspiration[state.currentGod].splice(state.currentGodInspirationIndex, 1); | |||
state.editMode = 'base'; | |||
state.currentGodInspirationIndex = null; | |||
renderWithoutScroll(); | |||
} | } | ||
} | }); | ||
deleteBtn.style.marginTop = '20px'; | |||
deleteBtn.style.width = '100%'; | |||
variantSection.appendChild(deleteBtn); | |||
container.appendChild(variantSection); | |||
} | |||
} | |||
// 不触发滚动的渲染函数 | |||
function renderWithoutScroll() { | |||
const container = document.getElementById('card-manager-container'); | |||
if (!container) return; | |||
// 保存当前滚动位置 | |||
const scrollPositions = { | |||
input: 0, | |||
list: 0, | |||
preview: 0 | |||
}; | |||
const inputSection = container.querySelector('.card-input-section'); | |||
const listSection = container.querySelector('.card-list-section'); | |||
const previewSection = container.querySelector('.card-preview-section'); | |||
if (inputSection) scrollPositions.input = inputSection.scrollTop; | |||
if (listSection) scrollPositions.list = listSection.scrollTop; | |||
if (previewSection) scrollPositions.preview = previewSection.scrollTop; | |||
// 执行渲染 | |||
render(); | |||
// | |||
// 恢复滚动位置 | |||
requestAnimationFrame(() => { | |||
const newInputSection = container.querySelector('.card-input-section'); | |||
const newListSection = container.querySelector('.card-list-section'); | |||
const newPreviewSection = container.querySelector('.card-preview-section'); | |||
} | |||
// | |||
// | |||
if (newInputSection) newInputSection.scrollTop = scrollPositions.input; | |||
if (newListSection) newListSection.scrollTop = scrollPositions.list; | |||
if (newPreviewSection) newPreviewSection.scrollTop = scrollPositions.preview; | |||
}); | |||
} | |||
// 渲染函数 | |||
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'); | |||
// 战斗员选择 | |||
const title = createElement('div', 'section-title'); | |||
title.textContent = '卡牌管理器'; | |||
inputSection.appendChild(title); | |||
const fighterSelect = createSelect(state.fighters, state.currentFighter, async (value) => { | |||
state.currentFighter = value; | |||
const loading = createElement('div', 'loading-indicator'); | |||
loading.textContent = '正在加载卡牌数据...'; | |||
document.body.appendChild(loading); | |||
try { | |||
const cards = await loadFighterCards(value); | |||
state.cards = cards; | |||
state.currentCard = cards.length > 0 ? cards[0] : null; | |||
state.editMode = 'base'; | |||
state.currentInspirationIndex = null; | |||
state.currentGodInspirationIndex = null; | |||
render(); | |||
} catch (error) { | |||
alert('加载失败:' + error.message); | |||
} finally { | |||
if (loading.parentNode) { | |||
document.body.removeChild(loading); | |||
} | |||
} | |||
}); | |||
inputSection.appendChild(createFormGroup('选择战斗员:', fighterSelect.wrapper)); | |||
// 默认信息区 | |||
if (state.currentFighter) { | |||
const defaultSection = createElement('div', 'default-info-section'); | |||
const defaultTitle = createElement('div', 'section-title'); | |||
defaultTitle.textContent = '默认信息'; | |||
defaultTitle.style.borderColor = '#ff9800'; | |||
defaultSection.appendChild(defaultTitle); | |||
const orderInput = createInput('text', state.defaultInfo.order, (value) => { | |||
state.defaultInfo.order = value; | |||
updateCodePreview(); | |||
}, 'card.order'); | |||
defaultSection.appendChild(createFormGroup('卡牌顺序:', orderInput)); | |||
const egoInput = createInput('text', state.defaultInfo.ego, (value) => { | |||
state.defaultInfo.ego = value; | |||
updateCodePreview(); | |||
}, 'card.info.ego'); | |||
defaultSection.appendChild(createFormGroup('属性:', egoInput)); | |||
}); | |||
if ( | inputSection.appendChild(defaultSection); | ||
} | |||
// 卡牌编辑区 - 根据editMode显示不同内容 | |||
' | if (state.currentCard) { | ||
if (state.editMode === 'base') { | |||
// 显示基础信息编辑 | |||
const cardSection = createElement('div', ''); | |||
. | cardSection.style.marginTop = '20px'; | ||
. | |||
const cardTitle = createElement('div', 'section-title'); | |||
cardTitle.textContent = '卡牌信息'; | |||
cardSection.appendChild(cardTitle); | |||
// 卡牌名称 | |||
const nameInput = createInput('text', state.currentCard.name, (value) => { | |||
state.currentCard.name = value; | |||
updateCodePreview(); | |||
// 更新列表显示 | |||
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 = value || '未命名卡牌'; | |||
} | |||
}); | |||
}, '卡牌名称'); | |||
cardSection.appendChild(createFormGroup('卡牌名称:', nameInput)); | |||
// 显示名称 | |||
const displaynameInput = createInput('text', state.currentCard.base.displayname, (value) => { | |||
state.currentCard.base.displayname = value; | |||
updateCodePreview(); | |||
}, '显示名称'); | |||
cardSection.appendChild(createFormGroup('显示名称:', displaynameInput)); | |||
// 图片 | |||
const artInput = createInput('text', state.currentCard.base.art, (value) => { | |||
state.currentCard.base.art = value; | |||
updateCodePreview(); | |||
}, '图片文件名'); | |||
cardSection.appendChild(createFormGroup('图片:', artInput)); | |||
// 分组 | |||
const groupInput = createInput('text', state.currentCard.base.group, (value) => { | |||
state.currentCard.base.group = value; | |||
updateCodePreview(); | |||
}, '分组'); | |||
cardSection.appendChild(createFormGroup('分组:', groupInput)); | |||
// 稀有度 | |||
const raritySelect = createSelect( | |||
['', '白', '蓝', '橙', '彩'], | |||
state.currentCard.base.rarity || '', | |||
(value) => { | |||
state.currentCard.base.rarity = value; | |||
updateCodePreview(); | |||
} | |||
); | |||
cardSection.appendChild(createFormGroup('稀有度:', raritySelect.wrapper)); | |||
// 神明 | |||
const godSelect = createSelect( | |||
['', '凯尔肯', '戴奥斯', '尼希隆', '赛克瑞德', '维托'], | |||
state.currentCard.base.god || '', | |||
(value) => { | |||
state.currentCard.base.god = value; | |||
updateCodePreview(); | |||
} | |||
); | |||
cardSection.appendChild(createFormGroup('神明:', godSelect.wrapper)); | |||
// AP | |||
const apInput = createInput('text', state.currentCard.base.ap, (value) => { | |||
state.currentCard.base.ap = value; | |||
updateCodePreview(); | |||
}, 'AP (可以是数字、X、Ø等)'); | |||
cardSection.appendChild(createFormGroup('AP:', apInput)); | |||
// 类型 | |||
const typeSelect = createSelect( | |||
['', '攻击', '技能', '强化'], | |||
state.currentCard.base.type || '', | |||
(value) => { | |||
state.currentCard.base.type = value; | |||
updateCodePreview(); | |||
} | |||
); | |||
cardSection.appendChild(createFormGroup('类型:', typeSelect.wrapper)); | |||
// 机制 | |||
const dictInput = createInput('text', state.currentCard.base.dict, (value) => { | |||
state.currentCard.base.dict = value; | |||
updateCodePreview(); | |||
}, '多个机制使用、隔开'); | |||
cardSection.appendChild(createFormGroup('机制:', dictInput)); | |||
// 描述 | |||
const descSection = createElement('div', 'form-group'); | |||
const descLabel = createElement('div', 'form-label'); | |||
descLabel.textContent = '描述:'; | |||
descSection.appendChild(descLabel); | |||
// 描述工具栏 | |||
const toolbar = createElement('div', 'button-group'); | |||
const blueTextBtn = createButton('蓝色文本', 'btn btn-blue', () => { | |||
insertTextAtCursor(descInput, '{{文本|蓝|选择文字}}', true); | |||
}); | |||
toolbar.appendChild(blueTextBtn); | |||
const greenTextBtn = createButton('绿色文本', 'btn btn-green', () => { | |||
insertTextAtCursor(descInput, '{{文本|绿|选择文字}}', true); | |||
}); | |||
toolbar.appendChild(greenTextBtn); | |||
const blueUnderlineBtn = createButton('蓝色下划线', 'btn btn-blue', () => { | |||
insertTextAtCursor(descInput, '{{文本|蓝|下划线|选择文字}}', true); | |||
}); | |||
toolbar.appendChild(blueUnderlineBtn); | |||
const greenUnderlineBtn = createButton('绿色下划线', 'btn btn-green', () => { | |||
insertTextAtCursor(descInput, '{{文本|绿|下划线|选择文字}}', true); | |||
}); | |||
toolbar.appendChild(greenUnderlineBtn); | |||
const strokeTextBtn = createButton('绿色描边', 'btn btn-green', () => { | |||
insertTextAtCursor(descInput, '{{描边|绿|选择文字}}', true); | |||
}); | |||
toolbar.appendChild(strokeTextBtn); | |||
const dictBtn = createButton('词典', 'btn', () => { | |||
insertTextAtCursor(descInput, '{{词典|选择文字}}', true); | |||
}); | |||
toolbar.appendChild(dictBtn); | |||
const wrapBtn = createButton('换行', 'btn', () => { | |||
insertTextAtCursor(descInput, '<br>'); | |||
}); | |||
toolbar.appendChild(wrapBtn); | |||
descSection.appendChild(toolbar); | |||
const descInput = createInput('textarea', state.currentCard.base.desc_global, (value) => { | |||
state.currentCard.base.desc_global = value; | |||
updateCodePreview(); | |||
}, '卡牌描述'); | |||
descInput.style.minHeight = '150px'; | |||
descSection.appendChild(descInput); | |||
cardSection.appendChild(descSection); | |||
// 衍生卡牌 | |||
const subInput = createInput('textarea', state.currentCard.base.sub, (value) => { | |||
state.currentCard.base.sub = value; | |||
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 = { | |||
凯尔肯: [], | |||
戴奥斯: [], | |||
尼希隆: [], | |||
赛克瑞德: [], | |||
维托: [] | |||
}; | |||
} | |||
updateCodePreview(); | |||
renderWithoutScroll(); | |||
}); | |||
cardSection.appendChild(createFormGroup('是否存在神光一闪:', godInspirationCheckbox)); | |||
inputSection.appendChild(cardSection); | |||
// 保存卡牌按钮 | |||
const saveCardBtn = createButton('保存卡牌', 'btn-primary', () => { | |||
updateCodePreview(); | |||
alert('卡牌已保存到代码预览!'); | |||
}); | }); | ||
saveCardBtn.style.marginTop = '20px'; | |||
saveCardBtn.style.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.marginTop = '20px'; | |||
addCardBtn.style.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'); | |||
const cardListTitle = createElement('div', 'list-title'); | |||
cardListTitle.textContent = '卡牌列表'; | |||
cardListHeader.appendChild(cardListTitle); | |||
cardListContainer.appendChild(cardListHeader); | |||
if (state.cards.length === 0) { | |||
const emptyState = createElement('div', 'empty-state'); | |||
const emptyIcon = createElement('div', 'empty-state-icon'); | |||
emptyIcon.textContent = '📋'; | |||
const emptyText = createElement('div', 'empty-state-text'); | |||
emptyText.textContent = '暂无卡牌,点击"新增卡牌"开始创建'; | |||
emptyState.appendChild(emptyIcon); | |||
emptyState.appendChild(emptyText); | |||
cardListContainer.appendChild(emptyState); | |||
} else { | |||
state.cards.forEach((card, index) => { | |||
const cardItem = createElement('div', 'card-item' + (state.currentCard === card && state.editMode === 'base' ? ' active' : '')); | |||
const cardName = createElement('div', 'card-item-name'); | |||
cardName.textContent = card.name || '未命名卡牌'; | |||
const cardInfo = createElement('div', 'card-item-info'); | |||
const infoText = []; | |||
if (card.base.type) infoText.push(card.base.type); | |||
if (card.base.ap !== '') infoText.push('AP:' + card.base.ap); | |||
if (card.base.rarity) infoText.push(card.base.rarity); | |||
cardInfo.textContent = infoText.join(' | ') || '暂无信息'; | |||
cardItem.appendChild(cardName); | |||
cardItem.appendChild(cardInfo); | |||
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'); | |||
const inspirationTitle = createElement('div', 'list-title'); | |||
inspirationTitle.textContent = '灵光一闪列表'; | |||
inspirationHeader.appendChild(inspirationTitle); | |||
// | |||
const addInspirationBtn = createButton('+ 添加', 'btn-primary', () => { | |||
const newIndex = state.currentCard.var.inspiration.length; | |||
state.currentCard.var.inspiration.push(createEmptyVariant()); | |||
state.editMode = 'inspiration'; | |||
state.currentInspirationIndex = newIndex; | |||
state.currentGodInspirationIndex = null; | |||
render(); | |||
}); | }); | ||
} | inspirationHeader.appendChild(addInspirationBtn); | ||
inspirationSection.appendChild(inspirationHeader); | |||
// | |||
if (state.currentCard.var.inspiration.length === 0) { | |||
const emptyState = createElement('div', 'empty-state'); | |||
emptyState.style.padding = '20px'; | |||
const emptyText = createElement('div', 'empty-state-text'); | |||
emptyText.textContent = '暂无灵光一闪,点击"添加"创建'; | |||
emptyState.appendChild(emptyText); | |||
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' : '') | |||
); | |||
const inspName = createElement('div', 'inspiration-item-name'); | |||
inspName.textContent = `灵光 #${idx + 1}`; | |||
const inspInfo = createElement('div', 'inspiration-item-info'); | |||
const infoText = []; | |||
if (insp.type) infoText.push(insp.type); | |||
if (insp.ap !== '') infoText.push('AP:' + insp.ap); | |||
inspInfo.textContent = infoText.join(' | ') || '点击编辑'; | |||
inspItem.appendChild(inspName); | |||
inspItem.appendChild(inspInfo); | |||
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'); | |||
const godInspirationTitle = createElement('div', 'list-title'); | |||
godInspirationTitle.textContent = '神光一闪列表'; | |||
godInspirationHeader.appendChild(godInspirationTitle); | |||
godInspirationSection.appendChild(godInspirationHeader); | |||
// 神明选择标签 | |||
const godSelectGroup = createElement('div', 'god-select-group'); | |||
['凯尔肯', '戴奥斯', '尼希隆', '赛克瑞德', '维托'].forEach(god => { | |||
const godTab = createElement('div', 'god-tab' + (state.currentGod === god ? ' active' : '')); | |||
godTab.textContent = god; | |||
godTab.addEventListener('click', () => { | |||
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(createEmptyVariant()); | |||
state.editMode = 'god_inspiration'; | |||
state.currentGodInspirationIndex = newIndex; | |||
state.currentInspirationIndex = null; | |||
render(); | |||
}); | }); | ||
addGodInspirationBtn.style.marginBottom = '10px'; | |||
addGodInspirationBtn.style.width = '100%'; | |||
godInspirationSection.appendChild(addGodInspirationBtn); | |||
if (currentGodInspirations.length === 0) { | |||
const emptyState = createElement('div', 'empty-state'); | |||
emptyState.style.padding = '20px'; | |||
const emptyText = createElement('div', 'empty-state-text'); | |||
emptyText.textContent = `暂无 ${state.currentGod} 神光一闪`; | |||
emptyState.appendChild(emptyText); | |||
godInspirationSection.appendChild(emptyState); | |||
} else { | |||
} | currentGodInspirations.forEach((insp, idx) => { | ||
const inspItem = createElement('div', | |||
'inspiration-item-simple' + | |||
(state.editMode === 'god_inspiration' && | |||
state.currentGodInspirationIndex === idx ? ' active' : '') | |||
); | |||
const inspName = createElement('div', 'inspiration-item-name'); | |||
inspName.textContent = `${state.currentGod} 神光 #${idx + 1}`; | |||
const inspInfo = createElement('div', 'inspiration-item-info'); | |||
const infoText = []; | |||
if (insp.type) infoText.push(insp.type); | |||
if (insp.ap !== '') infoText.push('AP:' + insp.ap); | |||
inspInfo.textContent = infoText.join(' | ') || '点击编辑'; | |||
inspItem.appendChild(inspName); | |||
inspItem.appendChild(inspInfo); | |||
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'); | |||
const previewTitle = createElement('div', 'section-title'); | |||
previewTitle.textContent = 'Lua 代码预览'; | |||
previewSection.appendChild(previewTitle); | |||
// 复制按钮 | |||
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.marginBottom = '10px'; | |||
copyBtn.style.width = '100%'; | |||
previewSection.appendChild(copyBtn); | |||
// 保存按钮 | |||
const saveBtn = createButton('保存到Wiki', 'btn-success', async () => { | |||
if (!state.currentFighter) { | |||
alert('请先选择战斗员!'); | |||
return; | |||
} | } | ||
const code = generateLuaCode(); | |||
const pageName = '模块:卡牌/' + state.currentFighter; | |||
const loading = createElement('div', 'loading-indicator'); | |||
loading.textContent = '正在保存...'; | |||
document.body.appendChild(loading); | |||
try { | |||
const api = new mw.Api(); | |||
await api.postWithToken('csrf', { | |||
action: 'edit', | |||
title: pageName, | |||
text: code, | |||
summary: '通过卡牌管理器更新卡牌数据', | |||
contentmodel: 'Scribunto' | |||
}); | |||
alert('保存成功!'); | |||
} catch (error) { | |||
console.error('保存失败:', error); | |||
alert('保存失败:' + error); | |||
} finally { | |||
if (loading.parentNode) { | |||
document.body.removeChild(loading); | |||
} | } | ||
} | } | ||
}); | |||
saveBtn.style.marginBottom = '10px'; | |||
} | saveBtn.style.width = '100%'; | ||
previewSection.appendChild(saveBtn); | |||
// 代码显示 | |||
const codePreview = createElement('div', 'code-preview'); | |||
codePreview.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'); | |||
loading.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]; | |||
if ( | |||
} | } | ||
} | } | ||
render(); | |||
} | } catch (error) { | ||
console.error('初始化失败:', error); | |||
alert('初始化失败:' + error.message); | |||
} finally { | |||
if (loading.parentNode) { | |||
document.body.removeChild(loading); | |||
} | } | ||
} | } | ||
} | |||
if (document.readyState === 'loading') { | |||
document.addEventListener('DOMContentLoaded', init); | |||
} else { | |||
init(); | |||
} | |||
window.CardManager = { | |||
init: init, | |||
render: render, | |||
state: state, | |||
createCard: createEmptyCard, | |||
generateCode: generateLuaCode | |||
}; | }; | ||
})(); | })(); | ||
2025年10月29日 (三) 09:52的最新版本
(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', // 'base', 'inspiration', 'god_inspiration'
currentGod: '凯尔肯',
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: {
凯尔肯: [],
戴奥斯: [],
尼希隆: [],
赛克瑞德: [],
维托: []
}
}
};
}
// 创建空变体结构
function createEmptyVariant() {
return {
ap: '',
type: '',
desc_global: '',
dict: '',
sub: '' // 新增:变体卡牌的衍生卡牌字段
};
}
// 创建元素辅助函数
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();
dropdown.style.top = rect.bottom + 'px';
dropdown.style.left = rect.left + 'px';
dropdown.style.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', '');
label.textContent = '是';
wrapper.addEventListener('click', () => {
const newChecked = !checkbox.classList.contains('checked');
if (newChecked) {
checkbox.classList.add('checked');
} else {
checkbox.classList.remove('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');
const labelEl = createElement('div', 'form-label');
labelEl.textContent = label;
group.appendChild(labelEl);
group.appendChild(control);
return group;
}
// 创建按钮
function createButton(text, className, onClick) {
const btn = createElement('div', 'btn ' + className);
btn.textContent = text;
btn.addEventListener('click', onClick);
return btn;
}
// 文本插入辅助函数
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();
const textNode = document.createTextNode(text.replace('选择文字', selectedText));
range.insertNode(textNode);
return;
}
}
document.execCommand('insertText', false, text);
}
// 辅助函数:提取字符串值
function extractStringValue(text, key) {
const pattern = new RegExp(key + '\\s*=\\s*"([^"]*)"', 'm');
const match = text.match(pattern);
return match ? match[1] : '';
}
// 辅助函数:提取数值
function extractNumberValue(text, key) {
const pattern = new RegExp(key + '\\s*=\\s*(\\d+)', 'm');
const match = text.match(pattern);
return match ? parseInt(match[1]) : 0;
}
// 辅助函数:提取任意值(包括X、Ø等)
function extractValue(text, key) {
const pattern = new RegExp(key + '\\s*=\\s*([^,\\n]+)', 'm');
const match = text.match(pattern);
if (match) {
let value = match[1].trim();
value = value.replace(/^["']|["']$/g, '');
return value;
}
return '';
}
// 解析灵光一闪/神光一闪列表
function parseInspirationList(content) {
const inspirations = [];
let depth = 0;
let currentBlock = '';
let 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 = {};
const ap = extractValue(currentBlock, 'ap');
if (ap) inspiration.ap = ap;
const type = extractStringValue(currentBlock, 'type');
if (type) inspiration.type = type;
const desc = extractStringValue(currentBlock, 'desc_global');
if (desc) inspiration.desc_global = desc;
const dict = extractStringValue(currentBlock, 'dict');
if (dict) inspiration.dict = dict;
// 新增:解析变体的 sub 字段
const sub = extractStringValue(currentBlock, 'sub');
if (sub) inspiration.sub = sub;
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;
let endIdx = startIdx;
let inString = false;
for (let i = startIdx; i < content.length && depth > 0; i++) {
const char = content[i];
const prevChar = i > 0 ? content[i - 1] : '';
if (char === '"' && prevChar !== '\\') {
inString = !inString;
}
if (!inString) {
if (char === '{') {
depth++;
} else if (char === '}') {
depth--;
if (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 cardName = cardMatch[1];
const cardStartIdx = cardMatch.index + cardMatch[0].length;
let depth = 1;
let cardEndIdx = cardStartIdx;
let inString = false;
for (let i = cardStartIdx; i < luaText.length && depth > 0; i++) {
const char = luaText[i];
const prevChar = i > 0 ? luaText[i - 1] : '';
if (char === '"' && prevChar !== '\\') {
inString = !inString;
}
if (!inString) {
if (char === '{') {
depth++;
} else if (char === '}') {
depth--;
if (depth === 0) {
cardEndIdx = i;
break;
}
}
}
}
const cardContent = luaText.substring(cardStartIdx, cardEndIdx);
const card = createEmptyCard();
card.name = cardName;
const baseContent = extractBlock(cardContent, /base\s*=\s*\{/);
if (baseContent) {
card.base.displayname = extractStringValue(baseContent, 'displayname') || '';
card.base.art = extractStringValue(baseContent, 'art') || '';
card.base.group = extractStringValue(baseContent, 'group') || '';
card.base.rarity = extractStringValue(baseContent, 'rarity') || '';
card.base.god = extractStringValue(baseContent, 'god') || '';
card.base.ap = extractValue(baseContent, 'ap') || '';
card.base.type = extractStringValue(baseContent, 'type') || '';
card.base.dict = extractStringValue(baseContent, 'dict') || '';
card.base.desc_global = extractStringValue(baseContent, 'desc_global') || '';
card.base.sub = extractStringValue(baseContent, 'sub') || '';
card.base.isinspiration = extractNumberValue(baseContent, 'isinspiration') || 0;
card.base.isgod_inspiration = extractNumberValue(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) {
// 修改:使用中文键名解析神光一闪
['凯尔肯', '戴奥斯', '尼希隆', '赛克瑞德', '维托'].forEach(god => {
const godPattern = new RegExp('\\["?' + god + '"?\\]\\s*=\\s*\\{');
const godContent = extractBlock(godInspirationContent, godPattern);
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 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';
code += ` ego = "${escapeLuaString(state.defaultInfo.ego)}",\n`;
code += '}\n\n';
}
state.cards.forEach(card => {
code += `card["${escapeLuaString(card.name)}"] = {\n`;
code += ' base = {\n';
const base = card.base;
if (base.displayname) code += ` displayname = "${escapeLuaString(base.displayname)}",\n`;
if (base.art) code += ` art = "${escapeLuaString(base.art)}",\n`;
if (base.group) code += ` group = "${escapeLuaString(base.group)}",\n`;
if (base.rarity) code += ` rarity = "${escapeLuaString(base.rarity)}",\n`;
if (base.god) code += ` god = "${escapeLuaString(base.god)}",\n`;
if (base.ap !== '') {
if (isNaN(base.ap)) {
code += ` ap = "${escapeLuaString(base.ap)}",\n`;
} else {
code += ` ap = ${base.ap},\n`;
}
}
if (base.type) code += ` type = "${escapeLuaString(base.type)}",\n`;
if (base.dict) code += ` dict = "${escapeLuaString(base.dict)}",\n`;
if (base.desc_global) code += ` desc_global = "${escapeLuaString(base.desc_global)}",\n`;
if (base.sub) code += ` sub = "${escapeLuaString(base.sub)}",\n`;
if (base.isinspiration) code += ` isinspiration = ${base.isinspiration},\n`;
if (base.isgod_inspiration) code += ` isgod_inspiration = ${base.isgod_inspiration},\n`;
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';
card.var.inspiration.forEach(insp => {
code += ' {\n';
if (insp.ap !== '' && insp.ap !== undefined) {
if (isNaN(insp.ap)) {
code += ` ap = "${escapeLuaString(insp.ap)}",\n`;
} else {
code += ` ap = ${insp.ap},\n`;
}
}
if (insp.type) code += ` type = "${escapeLuaString(insp.type)}",\n`;
if (insp.desc_global) code += ` desc_global = "${escapeLuaString(insp.desc_global)}",\n`;
if (insp.dict) code += ` dict = "${escapeLuaString(insp.dict)}",\n`;
// 新增:输出变体的 sub 字段
if (insp.sub) code += ` sub = "${escapeLuaString(insp.sub)}",\n`;
code += ' },\n';
});
code += ' },\n';
}
if (hasGodInspiration) {
code += ' god_inspiration = {\n';
Object.entries(card.var.god_inspiration).forEach(([god, inspList]) => {
if (inspList.length > 0) {
// 修改:使用中文键名格式 ["尼希隆"]
code += ` ["${god}"] = {\n`;
inspList.forEach(insp => {
code += ' {\n';
if (insp.ap !== '' && insp.ap !== undefined) {
if (isNaN(insp.ap)) {
code += ` ap = "${escapeLuaString(insp.ap)}",\n`;
} else {
code += ` ap = ${insp.ap},\n`;
}
}
if (insp.type) code += ` type = "${escapeLuaString(insp.type)}",\n`;
if (insp.desc_global) code += ` desc_global = "${escapeLuaString(insp.desc_global)}",\n`;
if (insp.dict) code += ` dict = "${escapeLuaString(insp.dict)}",\n`;
// 新增:输出变体的 sub 字段
if (insp.sub) code += ` sub = "${escapeLuaString(insp.sub)}",\n`;
code += ' },\n';
});
code += ' },\n';
}
});
code += ' },\n';
}
code += ' },\n';
}
code += '}\n\n';
});
code += 'return card';
return code;
}
// 加载战斗员列表
async function loadFighters() {
try {
const api = new mw.Api();
const result = await api.get({
action: 'query',
list: 'allpages',
apprefix: '卡牌/',
apnamespace: 828,
aplimit: 500
});
if (result.query && result.query.allpages) {
state.fighters = result.query.allpages.map(page => {
return page.title.replace('模块:卡牌/', '');
});
}
} catch (error) {
console.error('加载战斗员列表失败:', error);
}
}
// 加载战斗员卡牌数据
async function loadFighterCards(fighter) {
if (!fighter) return [];
try {
const api = new mw.Api();
const result = await api.get({
action: 'query',
prop: 'revisions',
titles: '模块:卡牌/' + fighter,
rvprop: 'content',
rvslots: 'main',
formatversion: 2
});
if (!result.query || !result.query.pages || result.query.pages.length === 0) {
console.error('未找到页面');
return [];
}
const page = result.query.pages[0];
if (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 renderVariantEditor(container) {
if (!state.currentCard) return;
if (state.editMode === 'inspiration' && state.currentInspirationIndex !== null) {
const insp = state.currentCard.var.inspiration[state.currentInspirationIndex];
if (!insp) return;
const variantSection = createElement('div', 'variant-edit-section');
const variantTitle = createElement('div', 'section-title');
variantTitle.textContent = `灵光一闪 #${state.currentInspirationIndex + 1}`;
variantTitle.style.borderColor = '#9c27b0';
variantSection.appendChild(variantTitle);
// 返回按钮
const backBtn = createButton('← 返回基础信息', 'btn', () => {
state.editMode = 'base';
state.currentInspirationIndex = null;
renderWithoutScroll();
});
backBtn.style.marginBottom = '15px';
backBtn.style.width = '100%';
variantSection.appendChild(backBtn);
// AP输入
const apInput = createInput('text', insp.ap, (value) => {
insp.ap = value;
updateCodePreview();
}, 'AP (数字、X、Ø)');
variantSection.appendChild(createFormGroup('AP:', apInput));
// 类型选择
const typeSelect = createSelect(
['', '攻击', '技能', '强化'],
insp.type || '',
(value) => {
insp.type = value;
updateCodePreview();
}
);
variantSection.appendChild(createFormGroup('类型:', typeSelect.wrapper));
// 机制输入
const dictInput = createInput('text', insp.dict, (value) => {
insp.dict = value;
updateCodePreview();
}, '多个机制使用、隔开');
variantSection.appendChild(createFormGroup('机制:', dictInput));
// 描述区域
const descSection = createElement('div', 'form-group');
const descLabel = createElement('div', 'form-label');
descLabel.textContent = '描述:';
descSection.appendChild(descLabel);
// 描述工具栏
const toolbar = createElement('div', 'button-group');
const blueTextBtn = createButton('蓝色文本', 'btn btn-blue', () => {
insertTextAtCursor(descInput, '{{文本|蓝|选择文字}}', true);
});
toolbar.appendChild(blueTextBtn);
const greenTextBtn = createButton('绿色文本', 'btn btn-green', () => {
insertTextAtCursor(descInput, '{{文本|绿|选择文字}}', true);
});
toolbar.appendChild(greenTextBtn);
const blueUnderlineBtn = createButton('蓝色下划线', 'btn btn-blue', () => {
insertTextAtCursor(descInput, '{{文本|蓝|下划线|选择文字}}', true);
});
toolbar.appendChild(blueUnderlineBtn);
const greenUnderlineBtn = createButton('绿色下划线', 'btn btn-green', () => {
insertTextAtCursor(descInput, '{{文本|绿|下划线|选择文字}}', true);
});
toolbar.appendChild(greenUnderlineBtn);
const strokeTextBtn = createButton('绿色描边', 'btn btn-green', () => {
insertTextAtCursor(descInput, '{{描边|绿|选择文字}}', true);
});
toolbar.appendChild(strokeTextBtn);
const dictBtn = createButton('词典', 'btn', () => {
insertTextAtCursor(descInput, '{{词典|选择文字}}', true);
});
toolbar.appendChild(dictBtn);
const wrapBtn = createButton('换行', 'btn', () => {
insertTextAtCursor(descInput, '<br>');
});
toolbar.appendChild(wrapBtn);
descSection.appendChild(toolbar);
const descInput = createInput('textarea', insp.desc_global, (value) => {
insp.desc_global = value;
updateCodePreview();
}, '变体描述');
descInput.style.minHeight = '150px';
descSection.appendChild(descInput);
variantSection.appendChild(descSection);
// 新增:衍生卡牌输入框
const subInput = createInput('textarea', insp.sub || '', (value) => {
insp.sub = value;
updateCodePreview();
}, '衍生卡牌');
subInput.style.minHeight = '35px';
variantSection.appendChild(createFormGroup('衍生卡牌:', subInput));
// 删除按钮
const deleteBtn = createButton('删除此变体', 'btn-danger', () => {
if (confirm('确定要删除这个灵光一闪吗?')) {
state.currentCard.var.inspiration.splice(state.currentInspirationIndex, 1);
state.editMode = 'base';
state.currentInspirationIndex = null;
renderWithoutScroll();
}
});
deleteBtn.style.marginTop = '20px';
deleteBtn.style.width = '100%';
variantSection.appendChild(deleteBtn);
container.appendChild(variantSection);
} else if (state.editMode === 'god_inspiration' && state.currentGodInspirationIndex !== null) {
const insp = state.currentCard.var.god_inspiration[state.currentGod][state.currentGodInspirationIndex];
if (!insp) return;
const variantSection = createElement('div', 'variant-edit-section');
const variantTitle = createElement('div', 'section-title');
variantTitle.textContent = `${state.currentGod} 神光 #${state.currentGodInspirationIndex + 1}`;
variantTitle.style.borderColor = '#673ab7';
variantSection.appendChild(variantTitle);
// 返回按钮
const backBtn = createButton('← 返回基础信息', 'btn', () => {
state.editMode = 'base';
state.currentGodInspirationIndex = null;
renderWithoutScroll();
});
backBtn.style.marginBottom = '15px';
backBtn.style.width = '100%';
variantSection.appendChild(backBtn);
// AP输入
const apInput = createInput('text', insp.ap, (value) => {
insp.ap = value;
updateCodePreview();
}, 'AP (数字、X、Ø)');
variantSection.appendChild(createFormGroup('AP:', apInput));
// 类型选择
const typeSelect = createSelect(
['', '攻击', '技能', '强化'],
insp.type || '',
(value) => {
insp.type = value;
updateCodePreview();
}
);
variantSection.appendChild(createFormGroup('类型:', typeSelect.wrapper));
// 机制输入
const dictInput = createInput('text', insp.dict, (value) => {
insp.dict = value;
updateCodePreview();
}, '多个机制使用、隔开');
variantSection.appendChild(createFormGroup('机制:', dictInput));
// 描述区域
const descSection = createElement('div', 'form-group');
const descLabel = createElement('div', 'form-label');
descLabel.textContent = '描述:';
descSection.appendChild(descLabel);
// 描述工具栏
const toolbar = createElement('div', 'button-group');
const blueTextBtn = createButton('蓝色文本', 'btn btn-blue', () => {
insertTextAtCursor(descInput, '{{文本|蓝|选择文字}}', true);
});
toolbar.appendChild(blueTextBtn);
const greenTextBtn = createButton('绿色文本', 'btn btn-green', () => {
insertTextAtCursor(descInput, '{{文本|绿|选择文字}}', true);
});
toolbar.appendChild(greenTextBtn);
const blueUnderlineBtn = createButton('蓝色下划线', 'btn btn-blue', () => {
insertTextAtCursor(descInput, '{{文本|蓝|下划线|选择文字}}', true);
});
toolbar.appendChild(blueUnderlineBtn);
const greenUnderlineBtn = createButton('绿色下划线', 'btn btn-green', () => {
insertTextAtCursor(descInput, '{{文本|绿|下划线|选择文字}}', true);
});
toolbar.appendChild(greenUnderlineBtn);
const strokeTextBtn = createButton('绿色描边', 'btn btn-green', () => {
insertTextAtCursor(descInput, '{{描边|绿|选择文字}}', true);
});
toolbar.appendChild(strokeTextBtn);
const dictBtn = createButton('词典', 'btn', () => {
insertTextAtCursor(descInput, '{{词典|选择文字}}', true);
});
toolbar.appendChild(dictBtn);
const wrapBtn = createButton('换行', 'btn', () => {
insertTextAtCursor(descInput, '<br>');
});
toolbar.appendChild(wrapBtn);
const circenBtn = createButton('凯尔肯', 'btn', () => {
insertTextAtCursor(descInput, '{{图标|凯尔肯}}');
});
toolbar.appendChild(circenBtn);
const diallosBtn = createButton('戴奥斯', 'btn', () => {
insertTextAtCursor(descInput, '{{图标|戴奥斯}}');
});
toolbar.appendChild(diallosBtn);
const nihiluBtn = createButton('尼希隆', 'btn', () => {
insertTextAtCursor(descInput, '{{图标|尼希隆}}');
});
toolbar.appendChild(nihiluBtn);
const secredBtn = createButton('赛克瑞德', 'btn', () => {
insertTextAtCursor(descInput, '{{图标|赛克瑞德}}');
});
toolbar.appendChild(secredBtn);
const vitorBtn = createButton('维托', 'btn', () => {
insertTextAtCursor(descInput, '{{图标|维托}}');
});
toolbar.appendChild(vitorBtn);
descSection.appendChild(toolbar);
const descInput = createInput('textarea', insp.desc_global, (value) => {
insp.desc_global = value;
updateCodePreview();
}, '变体描述');
descInput.style.minHeight = '150px';
descSection.appendChild(descInput);
variantSection.appendChild(descSection);
// 新增:衍生卡牌输入框
const subInput = createInput('textarea', insp.sub || '', (value) => {
insp.sub = value;
updateCodePreview();
}, '衍生卡牌');
subInput.style.minHeight = '35px';
variantSection.appendChild(createFormGroup('衍生卡牌:', subInput));
// 删除按钮
const deleteBtn = createButton('删除此变体', 'btn-danger', () => {
if (confirm('确定要删除这个神光一闪吗?')) {
state.currentCard.var.god_inspiration[state.currentGod].splice(state.currentGodInspirationIndex, 1);
state.editMode = 'base';
state.currentGodInspirationIndex = null;
renderWithoutScroll();
}
});
deleteBtn.style.marginTop = '20px';
deleteBtn.style.width = '100%';
variantSection.appendChild(deleteBtn);
container.appendChild(variantSection);
}
}
// 不触发滚动的渲染函数
function renderWithoutScroll() {
const container = document.getElementById('card-manager-container');
if (!container) return;
// 保存当前滚动位置
const scrollPositions = {
input: 0,
list: 0,
preview: 0
};
const inputSection = container.querySelector('.card-input-section');
const listSection = container.querySelector('.card-list-section');
const previewSection = container.querySelector('.card-preview-section');
if (inputSection) scrollPositions.input = inputSection.scrollTop;
if (listSection) scrollPositions.list = listSection.scrollTop;
if (previewSection) scrollPositions.preview = previewSection.scrollTop;
// 执行渲染
render();
// 恢复滚动位置
requestAnimationFrame(() => {
const newInputSection = container.querySelector('.card-input-section');
const newListSection = container.querySelector('.card-list-section');
const newPreviewSection = container.querySelector('.card-preview-section');
if (newInputSection) newInputSection.scrollTop = scrollPositions.input;
if (newListSection) newListSection.scrollTop = scrollPositions.list;
if (newPreviewSection) newPreviewSection.scrollTop = scrollPositions.preview;
});
}
// 渲染函数
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');
// 战斗员选择
const title = createElement('div', 'section-title');
title.textContent = '卡牌管理器';
inputSection.appendChild(title);
const fighterSelect = createSelect(state.fighters, state.currentFighter, async (value) => {
state.currentFighter = value;
const loading = createElement('div', 'loading-indicator');
loading.textContent = '正在加载卡牌数据...';
document.body.appendChild(loading);
try {
const cards = await loadFighterCards(value);
state.cards = cards;
state.currentCard = cards.length > 0 ? cards[0] : null;
state.editMode = 'base';
state.currentInspirationIndex = null;
state.currentGodInspirationIndex = null;
render();
} catch (error) {
alert('加载失败:' + error.message);
} finally {
if (loading.parentNode) {
document.body.removeChild(loading);
}
}
});
inputSection.appendChild(createFormGroup('选择战斗员:', fighterSelect.wrapper));
// 默认信息区
if (state.currentFighter) {
const defaultSection = createElement('div', 'default-info-section');
const defaultTitle = createElement('div', 'section-title');
defaultTitle.textContent = '默认信息';
defaultTitle.style.borderColor = '#ff9800';
defaultSection.appendChild(defaultTitle);
const orderInput = createInput('text', state.defaultInfo.order, (value) => {
state.defaultInfo.order = value;
updateCodePreview();
}, 'card.order');
defaultSection.appendChild(createFormGroup('卡牌顺序:', orderInput));
const egoInput = createInput('text', state.defaultInfo.ego, (value) => {
state.defaultInfo.ego = value;
updateCodePreview();
}, 'card.info.ego');
defaultSection.appendChild(createFormGroup('属性:', egoInput));
inputSection.appendChild(defaultSection);
}
// 卡牌编辑区 - 根据editMode显示不同内容
if (state.currentCard) {
if (state.editMode === 'base') {
// 显示基础信息编辑
const cardSection = createElement('div', '');
cardSection.style.marginTop = '20px';
const cardTitle = createElement('div', 'section-title');
cardTitle.textContent = '卡牌信息';
cardSection.appendChild(cardTitle);
// 卡牌名称
const nameInput = createInput('text', state.currentCard.name, (value) => {
state.currentCard.name = value;
updateCodePreview();
// 更新列表显示
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 = value || '未命名卡牌';
}
});
}, '卡牌名称');
cardSection.appendChild(createFormGroup('卡牌名称:', nameInput));
// 显示名称
const displaynameInput = createInput('text', state.currentCard.base.displayname, (value) => {
state.currentCard.base.displayname = value;
updateCodePreview();
}, '显示名称');
cardSection.appendChild(createFormGroup('显示名称:', displaynameInput));
// 图片
const artInput = createInput('text', state.currentCard.base.art, (value) => {
state.currentCard.base.art = value;
updateCodePreview();
}, '图片文件名');
cardSection.appendChild(createFormGroup('图片:', artInput));
// 分组
const groupInput = createInput('text', state.currentCard.base.group, (value) => {
state.currentCard.base.group = value;
updateCodePreview();
}, '分组');
cardSection.appendChild(createFormGroup('分组:', groupInput));
// 稀有度
const raritySelect = createSelect(
['', '白', '蓝', '橙', '彩'],
state.currentCard.base.rarity || '',
(value) => {
state.currentCard.base.rarity = value;
updateCodePreview();
}
);
cardSection.appendChild(createFormGroup('稀有度:', raritySelect.wrapper));
// 神明
const godSelect = createSelect(
['', '凯尔肯', '戴奥斯', '尼希隆', '赛克瑞德', '维托'],
state.currentCard.base.god || '',
(value) => {
state.currentCard.base.god = value;
updateCodePreview();
}
);
cardSection.appendChild(createFormGroup('神明:', godSelect.wrapper));
// AP
const apInput = createInput('text', state.currentCard.base.ap, (value) => {
state.currentCard.base.ap = value;
updateCodePreview();
}, 'AP (可以是数字、X、Ø等)');
cardSection.appendChild(createFormGroup('AP:', apInput));
// 类型
const typeSelect = createSelect(
['', '攻击', '技能', '强化'],
state.currentCard.base.type || '',
(value) => {
state.currentCard.base.type = value;
updateCodePreview();
}
);
cardSection.appendChild(createFormGroup('类型:', typeSelect.wrapper));
// 机制
const dictInput = createInput('text', state.currentCard.base.dict, (value) => {
state.currentCard.base.dict = value;
updateCodePreview();
}, '多个机制使用、隔开');
cardSection.appendChild(createFormGroup('机制:', dictInput));
// 描述
const descSection = createElement('div', 'form-group');
const descLabel = createElement('div', 'form-label');
descLabel.textContent = '描述:';
descSection.appendChild(descLabel);
// 描述工具栏
const toolbar = createElement('div', 'button-group');
const blueTextBtn = createButton('蓝色文本', 'btn btn-blue', () => {
insertTextAtCursor(descInput, '{{文本|蓝|选择文字}}', true);
});
toolbar.appendChild(blueTextBtn);
const greenTextBtn = createButton('绿色文本', 'btn btn-green', () => {
insertTextAtCursor(descInput, '{{文本|绿|选择文字}}', true);
});
toolbar.appendChild(greenTextBtn);
const blueUnderlineBtn = createButton('蓝色下划线', 'btn btn-blue', () => {
insertTextAtCursor(descInput, '{{文本|蓝|下划线|选择文字}}', true);
});
toolbar.appendChild(blueUnderlineBtn);
const greenUnderlineBtn = createButton('绿色下划线', 'btn btn-green', () => {
insertTextAtCursor(descInput, '{{文本|绿|下划线|选择文字}}', true);
});
toolbar.appendChild(greenUnderlineBtn);
const strokeTextBtn = createButton('绿色描边', 'btn btn-green', () => {
insertTextAtCursor(descInput, '{{描边|绿|选择文字}}', true);
});
toolbar.appendChild(strokeTextBtn);
const dictBtn = createButton('词典', 'btn', () => {
insertTextAtCursor(descInput, '{{词典|选择文字}}', true);
});
toolbar.appendChild(dictBtn);
const wrapBtn = createButton('换行', 'btn', () => {
insertTextAtCursor(descInput, '<br>');
});
toolbar.appendChild(wrapBtn);
descSection.appendChild(toolbar);
const descInput = createInput('textarea', state.currentCard.base.desc_global, (value) => {
state.currentCard.base.desc_global = value;
updateCodePreview();
}, '卡牌描述');
descInput.style.minHeight = '150px';
descSection.appendChild(descInput);
cardSection.appendChild(descSection);
// 衍生卡牌
const subInput = createInput('textarea', state.currentCard.base.sub, (value) => {
state.currentCard.base.sub = value;
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 = {
凯尔肯: [],
戴奥斯: [],
尼希隆: [],
赛克瑞德: [],
维托: []
};
}
updateCodePreview();
renderWithoutScroll();
});
cardSection.appendChild(createFormGroup('是否存在神光一闪:', godInspirationCheckbox));
inputSection.appendChild(cardSection);
// 保存卡牌按钮
const saveCardBtn = createButton('保存卡牌', 'btn-primary', () => {
updateCodePreview();
alert('卡牌已保存到代码预览!');
});
saveCardBtn.style.marginTop = '20px';
saveCardBtn.style.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.marginTop = '20px';
addCardBtn.style.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');
const cardListTitle = createElement('div', 'list-title');
cardListTitle.textContent = '卡牌列表';
cardListHeader.appendChild(cardListTitle);
cardListContainer.appendChild(cardListHeader);
if (state.cards.length === 0) {
const emptyState = createElement('div', 'empty-state');
const emptyIcon = createElement('div', 'empty-state-icon');
emptyIcon.textContent = '📋';
const emptyText = createElement('div', 'empty-state-text');
emptyText.textContent = '暂无卡牌,点击"新增卡牌"开始创建';
emptyState.appendChild(emptyIcon);
emptyState.appendChild(emptyText);
cardListContainer.appendChild(emptyState);
} else {
state.cards.forEach((card, index) => {
const cardItem = createElement('div', 'card-item' + (state.currentCard === card && state.editMode === 'base' ? ' active' : ''));
const cardName = createElement('div', 'card-item-name');
cardName.textContent = card.name || '未命名卡牌';
const cardInfo = createElement('div', 'card-item-info');
const infoText = [];
if (card.base.type) infoText.push(card.base.type);
if (card.base.ap !== '') infoText.push('AP:' + card.base.ap);
if (card.base.rarity) infoText.push(card.base.rarity);
cardInfo.textContent = infoText.join(' | ') || '暂无信息';
cardItem.appendChild(cardName);
cardItem.appendChild(cardInfo);
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');
const inspirationTitle = createElement('div', 'list-title');
inspirationTitle.textContent = '灵光一闪列表';
inspirationHeader.appendChild(inspirationTitle);
const addInspirationBtn = createButton('+ 添加', 'btn-primary', () => {
const newIndex = state.currentCard.var.inspiration.length;
state.currentCard.var.inspiration.push(createEmptyVariant());
state.editMode = 'inspiration';
state.currentInspirationIndex = newIndex;
state.currentGodInspirationIndex = null;
render();
});
inspirationHeader.appendChild(addInspirationBtn);
inspirationSection.appendChild(inspirationHeader);
if (state.currentCard.var.inspiration.length === 0) {
const emptyState = createElement('div', 'empty-state');
emptyState.style.padding = '20px';
const emptyText = createElement('div', 'empty-state-text');
emptyText.textContent = '暂无灵光一闪,点击"添加"创建';
emptyState.appendChild(emptyText);
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' : '')
);
const inspName = createElement('div', 'inspiration-item-name');
inspName.textContent = `灵光 #${idx + 1}`;
const inspInfo = createElement('div', 'inspiration-item-info');
const infoText = [];
if (insp.type) infoText.push(insp.type);
if (insp.ap !== '') infoText.push('AP:' + insp.ap);
inspInfo.textContent = infoText.join(' | ') || '点击编辑';
inspItem.appendChild(inspName);
inspItem.appendChild(inspInfo);
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');
const godInspirationTitle = createElement('div', 'list-title');
godInspirationTitle.textContent = '神光一闪列表';
godInspirationHeader.appendChild(godInspirationTitle);
godInspirationSection.appendChild(godInspirationHeader);
// 神明选择标签
const godSelectGroup = createElement('div', 'god-select-group');
['凯尔肯', '戴奥斯', '尼希隆', '赛克瑞德', '维托'].forEach(god => {
const godTab = createElement('div', 'god-tab' + (state.currentGod === god ? ' active' : ''));
godTab.textContent = god;
godTab.addEventListener('click', () => {
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(createEmptyVariant());
state.editMode = 'god_inspiration';
state.currentGodInspirationIndex = newIndex;
state.currentInspirationIndex = null;
render();
});
addGodInspirationBtn.style.marginBottom = '10px';
addGodInspirationBtn.style.width = '100%';
godInspirationSection.appendChild(addGodInspirationBtn);
if (currentGodInspirations.length === 0) {
const emptyState = createElement('div', 'empty-state');
emptyState.style.padding = '20px';
const emptyText = createElement('div', 'empty-state-text');
emptyText.textContent = `暂无 ${state.currentGod} 神光一闪`;
emptyState.appendChild(emptyText);
godInspirationSection.appendChild(emptyState);
} else {
currentGodInspirations.forEach((insp, idx) => {
const inspItem = createElement('div',
'inspiration-item-simple' +
(state.editMode === 'god_inspiration' &&
state.currentGodInspirationIndex === idx ? ' active' : '')
);
const inspName = createElement('div', 'inspiration-item-name');
inspName.textContent = `${state.currentGod} 神光 #${idx + 1}`;
const inspInfo = createElement('div', 'inspiration-item-info');
const infoText = [];
if (insp.type) infoText.push(insp.type);
if (insp.ap !== '') infoText.push('AP:' + insp.ap);
inspInfo.textContent = infoText.join(' | ') || '点击编辑';
inspItem.appendChild(inspName);
inspItem.appendChild(inspInfo);
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');
const previewTitle = createElement('div', 'section-title');
previewTitle.textContent = 'Lua 代码预览';
previewSection.appendChild(previewTitle);
// 复制按钮
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.marginBottom = '10px';
copyBtn.style.width = '100%';
previewSection.appendChild(copyBtn);
// 保存按钮
const saveBtn = createButton('保存到Wiki', 'btn-success', async () => {
if (!state.currentFighter) {
alert('请先选择战斗员!');
return;
}
const code = generateLuaCode();
const pageName = '模块:卡牌/' + state.currentFighter;
const loading = createElement('div', 'loading-indicator');
loading.textContent = '正在保存...';
document.body.appendChild(loading);
try {
const api = new mw.Api();
await api.postWithToken('csrf', {
action: 'edit',
title: pageName,
text: code,
summary: '通过卡牌管理器更新卡牌数据',
contentmodel: 'Scribunto'
});
alert('保存成功!');
} catch (error) {
console.error('保存失败:', error);
alert('保存失败:' + error);
} finally {
if (loading.parentNode) {
document.body.removeChild(loading);
}
}
});
saveBtn.style.marginBottom = '10px';
saveBtn.style.width = '100%';
previewSection.appendChild(saveBtn);
// 代码显示
const codePreview = createElement('div', 'code-preview');
codePreview.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');
loading.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 {
if (loading.parentNode) {
document.body.removeChild(loading);
}
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
window.CardManager = {
init: init,
render: render,
state: state,
createCard: createEmptyCard,
generateCode: generateLuaCode
};
})();