模块:CardData
来自夜幕之下
更多操作
此模块的文档可以在模块:CardData/doc创建
-- Module:CardData
-- 用法:{{#invoke:CardData|render|复仇童谣}}
local p = {}
local function val(v, fallback)
if v == nil or v == "" then return fallback or "—" end
return v
end
local function rarityStars(n)
n = tonumber(n) or 0
return string.rep("★", n)
end
local function parseJson(str)
if not str or str == "" then return nil end
local ok, result = pcall(mw.text.jsonDecode, str)
if ok then return result else return nil end
end
local function makeTitle(titleText, subtitleText, extraClass)
local div = mw.html.create("div")
:addClass("card_content-item-title")
:addClass("card_content-item-title--mt")
if extraClass then div:addClass(extraClass) end
div:wikitext(titleText)
div:tag("span"):addClass("card_content-item-subtitle"):wikitext(subtitleText)
return div
end
local function makeHr(extraClass)
local div = mw.html.create("div"):addClass("card_content-item-hr")
if extraClass then div:addClass(extraClass) end
return div
end
-- 单条属性行:图标 + 标签 + 值
local function makeAttrRow(label, value)
local item = mw.html.create("div"):addClass("card_content_attribute-item")
local titleDiv = item:tag("div"):addClass("card_content_attribute-title")
titleDiv:tag("div"):addClass("card_content_attribute-icon")
titleDiv:tag("div"):wikitext(" " .. label)
item:tag("div"):wikitext(value or "—")
return item
end
-- 信息栏:稀有度 + 四维
local function makeInfoSection(intel, supply, execute, strategy, rarity)
local infoDiv = mw.html.create("div"):addClass("card_content_info")
local rarityItem = infoDiv:tag("div"):addClass("card_content_info-item")
rarityItem:tag("div"):wikitext("稀有度")
rarityItem:tag("div"):wikitext(rarityStars(rarity))
local dims = {
{ label = "情报", value = intel },
{ label = "物资", value = supply },
{ label = "执行", value = execute },
{ label = "策略", value = strategy },
}
for _, dim in ipairs(dims) do
local item = infoDiv:tag("div"):addClass("card_content_info-item")
item:tag("div"):wikitext(dim.label)
local n = tonumber(dim.value)
item:tag("div"):wikitext(n and ("※" .. n) or "—")
end
return infoDiv
end
local function makeTriggerLabel(triggerType, triggerValue)
local span = mw.html.create("span"):addClass("card_content_skill-meta-val")
if triggerType == "normal_attack" then
span:wikitext("每进行")
span:tag("span"):addClass("card_accent"):wikitext(tostring(triggerValue or "?"))
span:wikitext("次普攻")
else
span:wikitext(val(triggerType))
end
return span
end
local function makeTagsRow(tagsJson)
local div = mw.html.create("div"):addClass("card_content_skill-tags")
local tags = parseJson(tagsJson)
if tags and type(tags) == "table" then
for _, tag in ipairs(tags) do
div:tag("div"):addClass("card_content_skill-tag"):wikitext(tag)
end
end
return div
end
local function makeUpgradeTable(levelsJson)
local wrap = mw.html.create("div"):addClass("card_content_skill-upgrade")
local rows = parseJson(levelsJson)
if not rows or type(rows) ~= "table" or #rows == 0 then return wrap end
local maxCols = 0
for _, row in ipairs(rows) do
if type(row.levels) == "table" and #row.levels > maxCols then
maxCols = #row.levels
end
end
local DATA_COLS, gridClass
if maxCols > 8 then
DATA_COLS = 9
gridClass = "card_content_skill-upgrade-grid card_content_skill-upgrade-grid--10"
else
DATA_COLS = 8
gridClass = "card_content_skill-upgrade-grid"
end
local grid = wrap:tag("div"):addClass(gridClass)
grid:tag("div")
for i = 2, DATA_COLS + 1 do
grid:tag("div"):addClass("card_content_skill-upgrade-lv"):wikitext(tostring(i))
end
for idx, row in ipairs(rows) do
local labelCell = grid:tag("div"):addClass("card_content_skill-upgrade-row-label")
labelCell:tag("span"):addClass("card_content_skill-upgrade-badge"):wikitext(tostring(idx))
labelCell:tag("span"):addClass("card_content_skill-upgrade-badge-text"):wikitext(val(row.name, ""))
labelCell:tag("span"):addClass("card_content_skill-upgrade-arrow"):wikitext("→")
local levels = (type(row.levels) == "table") and row.levels or {}
for col = 1, DATA_COLS do
local v = levels[col]
local cell = grid:tag("div"):addClass("card_content_skill-upgrade-val")
if v == nil or v == "" then
cell:addClass("card_content_skill-upgrade-val--null"):wikitext("—")
else
cell:addClass("card_accent"):wikitext(tostring(v))
end
end
end
return wrap
end
local function makeSkillCard(skillData, skillType)
local card = mw.html.create("div"):addClass("card_content_skill-card")
local left = card:tag("div"):addClass("card_content_skill-left")
local titleRow = left:tag("div"):addClass("card_content_skill-title")
titleRow:tag("div"):addClass("card_content_skill-icon")
local titleText = titleRow:tag("div"):addClass("card_content_skill-title-text")
local nameRow = titleText:tag("div")
nameRow:tag("span"):addClass("card_content_skill-name"):wikitext(val(skillData.name))
nameRow:tag("span"):addClass("card_content_skill-type card_accent--type")
:wikitext("「" .. val(skillData.type) .. "」")
local metaRow = titleText:tag("div"):addClass("card_content_skill-meta")
if skillType == "normal_attack" then
metaRow:wikitext("使用武器 ")
metaRow:tag("span"):addClass("card_content_skill-meta-val card_accent"):wikitext(val(skillData.weapon))
elseif skillType == "passive" then
metaRow:wikitext("触发条件 ")
metaRow:node(makeTriggerLabel(skillData.trigger_type, skillData.trigger_value))
elseif skillType == "ultimate" then
metaRow:wikitext("欲火消耗 ")
metaRow:tag("span"):addClass("card_content_skill-meta-val")
:tag("span"):addClass("card_accent"):wikitext(val(skillData.desire_cost))
end
if skillType == "passive" or skillType == "ultimate" then
left:node(makeTagsRow(skillData.tags))
end
left:tag("div"):addClass("card_content_skill_effect"):wikitext(val(skillData.description))
card:node(makeUpgradeTable(skillData.levels))
return card
end
local function makeFeatList(stagesJson)
local ul = mw.html.create("ul"):addClass("card_content_feat-ul")
local stages = parseJson(stagesJson)
if not stages or type(stages) ~= "table" then
ul:tag("li"):wikitext("—")
return ul
end
for _, stage in ipairs(stages) do
local li = ul:tag("li"):addClass("card_content_feat-list")
local row = li:tag("div"):addClass("card_content_feat-row")
local sw = row:tag("div"):addClass("card_content_feat-stage-wrap")
sw:tag("p"):tag("span"):addClass("card_content_feat-stage"):wikitext("STAGE")
sw:tag("div"):addClass("card_content_feat-number")
:wikitext(string.format("%02d", stage.stage or 0))
local rd = row:tag("div")
if stage.value and type(stage.value) == "table" then
for _, v in ipairs(stage.value) do
if v.description then
rd:tag("span"):addClass("card_accent"):wikitext(v.description)
rd:tag("br")
end
end
end
if stage.stat_boosts and type(stage.stat_boosts) == "table" then
for _, boost in ipairs(stage.stat_boosts) do
rd:wikitext((boost.type or "") .. "提升" .. (boost.value or ""))
end
end
if stage.extra_name then
rd:tag("span"):addClass("card_content_feat-stage-cn")
:wikitext("「" .. stage.extra_name .. "」")
end
end
return ul
end
local function makeStoryTimeline(stories, cardTitle)
local timeline = mw.html.create("div")
:addClass("card_timeline card_content_story-timeline")
timeline:tag("div"):addClass("card_timeline-line")
if not stories or type(stories) ~= "table" or #stories == 0 then
timeline:tag("div"):addClass("card_timeline-item"):wikitext("(小传数据待补充)")
return timeline
end
local romans = { "Ⅰ", "Ⅱ", "Ⅲ", "Ⅳ", "Ⅴ" }
local unlockLevels = { "Lv.20 解锁", "Lv.40 解锁", "Lv.60 解锁", "Lv.80 解锁", "Lv.100 解锁" }
for idx, chapter in ipairs(stories) do
local item = timeline:tag("div"):addClass("card_timeline-item")
if idx == #stories then item:addClass("card_timeline-item--last") end
item:tag("div"):addClass("card_timeline-dot")
item:tag("div"):addClass("card_timeline-unlock")
:wikitext(unlockLevels[idx] or ("Lv." .. (idx * 20) .. " 解锁"))
local titleDiv = item:tag("div"):addClass("card_timeline-title")
titleDiv:tag("span"):addClass("card_timeline-numeral"):wikitext(romans[idx] or tostring(idx))
titleDiv:tag("span"):addClass("card_timeline-chapter"):wikitext(cardTitle or "")
titleDiv:tag("span"):addClass("card_timeline-label"):wikitext("/ Story")
local bodyDiv = item:tag("div"):addClass("card_timeline-body")
local text = chapter.text or ""
if text ~= "" then
text = text:gsub("\r\n", "\n")
local paragraphs = mw.text.split(text, "\n+")
for _, para in ipairs(paragraphs) do
para = mw.text.trim(para)
if para ~= "" then bodyDiv:tag("p"):wikitext(para) end
end
end
end
return timeline
end
function p.render(frame)
local name = mw.text.trim(frame.args[1] or frame.args.name or "")
if name == "" then
return '<span class="error">错误:请提供卡片名称,例如 {{#invoke:CardData|render|复仇童谣}}</span>'
end
local encodedName = mw.uri.encode(name, "QUERY")
local apiUrl = "https://data.saltedkiss.org/items/cards"
.. "?fields=stylename,rarity,character.name,profession.name,desire.name"
.. ",intel,supply,execute,strategy"
.. ",skill_normal_attack.name,skill_normal_attack.type,skill_normal_attack.weapon"
.. ",skill_normal_attack.description,skill_normal_attack.levels.*"
.. ",skill_passive.name,skill_passive.type,skill_passive.trigger_type"
.. ",skill_passive.trigger_value,skill_passive.tags,skill_passive.description"
.. ",skill_passive.levels.*"
.. ",skill_ultimate.name,skill_ultimate.type,skill_ultimate.desire_cost"
.. ",skill_ultimate.tags,skill_ultimate.description,skill_ultimate.levels.*"
.. ",feats.stages"
.. ",stories.text"
.. "&filter[stylename][_eq]=" .. encodedName
local rawResponse = frame:preprocess(
'{{#get_web_data:url=' .. apiUrl
.. '|format=text|data=responseText=__text}}'
.. '{{#external_value:responseText}}'
)
rawResponse = mw.text.trim(rawResponse or "")
local parsed = parseJson(rawResponse)
local card = parsed and type(parsed.data) == "table" and parsed.data[1] or nil
if not card then
return '<span class="error">⚠ 无法加载卡片数据:' .. mw.text.nowiki(name)
.. '(response=' .. mw.text.nowiki(rawResponse) .. ')</span>'
end
local charData = card.character or {}
local profData = card.profession or {}
local desireData = card.desire or {}
local skillNA = card.skill_normal_attack or {}
local skillPA = card.skill_passive or {}
local skillUL = card.skill_ultimate or {}
local stories = card.stories or {}
local featsStagesJson = "[]"
if card.feats and type(card.feats) == "table" and card.feats[1] then
local sr = card.feats[1].stages
featsStagesJson = type(sr) == "table" and mw.text.jsonEncode(sr)
or type(sr) == "string" and sr or "[]"
end
local function levelsToJson(skill)
if not skill.levels then return "[]" end
if type(skill.levels) == "string" then return skill.levels end
if type(skill.levels) == "table" then return mw.text.jsonEncode(skill.levels) end
return "[]"
end
skillNA.levels = levelsToJson(skillNA)
skillPA.levels = levelsToJson(skillPA)
skillUL.levels = levelsToJson(skillUL)
local function tagsToJson(skill)
if not skill.tags then return "[]" end
if type(skill.tags) == "string" then return skill.tags end
if type(skill.tags) == "table" then return mw.text.jsonEncode(skill.tags) end
return "[]"
end
skillPA.tags = tagsToJson(skillPA)
skillUL.tags = tagsToJson(skillUL)
local rarity = tonumber(card.rarity) or 0
local stylename = val(card.stylename)
local charName = val(charData.name)
local profName = val(profData.name)
local desName = val(desireData.name)
local root = mw.html.create("div")
:addClass("ron-card ron-card--rarity-" .. rarity)
:attr("data-rarity", rarity)
-- 全屏背景图,文件命名规则:卡面_卡面名.png
root:tag("div"):addClass("card_fullscreen-img")
:wikitext("[[文件:卡面_" .. stylename .. ".png]]")
root:tag("div"):addClass("card_hero"):css("height", "100vh")
local layout = root:tag("div"):addClass("card_content-container")
:tag("div"):addClass("card_content-background")
:tag("div"):addClass("card_content-inner")
:tag("div"):addClass("card_content card_content--layout")
-- 左栏
local leftCol = layout:tag("div"):addClass("card_content_left")
-- 卡名
local nc = leftCol:tag("div"):addClass("card_content_name-container")
local nr = nc:tag("div"):addClass("card_content_name-row")
nr:tag("div"):addClass("card_content_name-icon")
nr:tag("div"):addClass("card_content_name-classtext"):wikitext(profName)
nr:tag("div"):addClass("card_content_name-icon card_content_name-icon--ml")
nr:tag("div"):addClass("card_content_name-classtext"):wikitext(desName)
local nt = nc:tag("div"):addClass("card_content_name-text")
nt:wikitext(stylename .. " ")
nt:tag("span"):addClass("card_accent"):wikitext("·")
nt:wikitext(" " .. charName)
-- 信息(稀有度 + 四维)
leftCol:node(makeTitle("信息", "Info"))
leftCol:node(makeHr())
leftCol:node(makeInfoSection(card.intel, card.supply, card.execute, card.strategy, rarity))
-- 等级 100 属性
leftCol:node(makeTitle("等级 100", "Lv. Max"))
leftCol:node(makeHr())
leftCol:node(makeAttrRow("生命", "—"))
leftCol:node(makeAttrRow("攻击", "—"))
leftCol:node(makeAttrRow("防御", "—"))
leftCol:node(makeAttrRow("暴击率", "—"))
leftCol:node(makeAttrRow("暴击伤害", "—"))
-- 映像
leftCol:node(makeTitle("映像", "Reflection"))
leftCol:node(makeHr())
leftCol:node(makeAttrRow("固定攻击", "—"))
leftCol:node(makeAttrRow("固定生命", "—"))
leftCol:node(makeAttrRow("伤害加成", "—"))
-- 认知
leftCol:node(makeTitle("认知", "Cognition"))
leftCol:node(makeHr())
leftCol:tag("div"):addClass("card_content_attribute-item"):wikitext("(数据待补充)")
-- 右栏
local rightCol = layout:tag("div"):addClass("card_content_right")
rightCol:node(makeTitle("战斗技能", "Tactical"))
rightCol:node(makeHr("card_content-item-hr--mb12"))
rightCol:node(makeSkillCard(skillNA, "normal_attack"))
rightCol:tag("div"):addClass("card_content_skill-gap")
rightCol:node(makeSkillCard(skillPA, "passive"))
rightCol:tag("div"):addClass("card_content_skill-gap")
rightCol:node(makeSkillCard(skillUL, "ultimate"))
rightCol:node(makeTitle("觉醒", "Feat"))
rightCol:node(makeHr("card_content-item-hr--mb12"))
rightCol:node(makeFeatList(featsStagesJson))
-- 小传
local storySection = rightCol:tag("div"):addClass("card_content_story")
local storyHeader = storySection:tag("div"):addClass("card_content_story-header")
local storyLeft = storyHeader:tag("div"):addClass("card_content_story-header-left")
storyLeft:node(makeTitle("小传", "Story"))
storyLeft:node(makeHr("card_content-item-hr--mb12"))
storyLeft:tag("div"):addClass("card_content_story-toggle")
:attr("data-collapsed", "false")
:tag("span"):addClass("story-toggle-text"):wikitext("收起"):done()
:tag("span"):addClass("story-toggle-icon"):wikitext("-")
storyHeader:tag("div"):addClass("card_content_story-header-spacer")
storyHeader:node(makeStoryTimeline(stories, stylename))
-- 邀约
rightCol:tag("div"):css("height", "12px"):css("width", "100%")
rightCol:node(makeTitle("邀约", "Date"))
rightCol:node(makeHr("card_content-item-hr--mb12"))
rightCol:tag("div"):addClass("card_content_date")
:tag("div"):addClass("card_content_date-track")
:tag("ul"):addClass("card_content_date-list")
:tag("li"):addClass("card_content_date-item"):wikitext("(邀约数据待补充)")
return tostring(root)
end
return p