卡厄思
梦
境
菜单
首页
回到首页
WIKI工具
全站样式
全站JS
修改导航栏
测试
沙盒
可视化管理器
战斗员管理器
卡牌管理器
伙伴管理器
装备管理器
词典管理器
图鉴
战斗员
伙伴
装备
怪物卡牌
中立卡牌
词典
小工具
配队模拟器
节奏榜生成器
搜索
链入页面
相关更改
特殊页面
页面信息
最近更改
登录
MediaWiki
查看“︁Card.js”︁的源代码
←
MediaWiki:Card.js
因为以下原因,您没有权限编辑该页面:
您请求的操作仅限属于该用户组的用户执行:
用户
此页面为本wiki上的软件提供界面文本,并受到保护以防止滥用。 如欲修改所有wiki的翻译,请访问
translatewiki.net
上的MediaWiki本地化项目。
您无权编辑此JavaScript页面,因为编辑此页面可能会影响所有访问者。
您可以查看和复制此页面的源代码。
(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 }; })();
该页面使用的模板:
模板:图标
(
查看源代码
)
模板:描边
(
查看源代码
)
模板:描边/颜色
(
查看源代码
)
模板:文本
(
查看源代码
)
模板:词典
(
查看源代码
)
模块:文本
(
查看源代码
)
模块:词典
(
查看源代码
)
模块:词典/data
(
查看源代码
)
返回
MediaWiki:Card.js
。