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