打开/关闭搜索
搜索
打开/关闭菜单
5
6
1
403
夜幕之下
导航
首页
最近更改
随机页面
MediaWiki帮助
特殊页面
上传文件
打开/关闭外观设置菜单
通知
打开/关闭个人菜单
未登录
未登录用户的IP地址会在进行任意编辑后公开展示。
user-interface-preferences
个人工具
创建账号
登录
查看“︁模块:CardData”︁的源代码
来自夜幕之下
查看
阅读
查看源代码
查看历史
associated-pages
模块
讨论
更多操作
←
模块:CardData
因为以下原因,您没有权限编辑该页面:
您请求的操作仅限属于该用户组的用户执行:
管理员
您可以查看和复制此页面的源代码。
-- Module:CardData -- 用法:{{#invoke:CardData|render|复仇童谣}} -- 通过 ExternalData 扩展从 Directus API 拉取数据,在服务端渲染完整 HTML -- 小传、邀约、映像、认知、属性等 API 暂无数据,保留骨架占位 local p = {} -- ══════════════════════════════════════════════ -- 工具函数 -- ══════════════════════════════════════════════ -- 安全取值:若为 nil 则返回 fallback(默认 "—") local function val(v, fallback) if v == nil or v == "" then return fallback or "—" end return v end -- 将稀有度数字转为星号字符串,例如 6 → "★★★★★★" local function rarityStars(n) n = tonumber(n) or 0 return string.rep("★", n) end -- 解析 JSON 字符串,失败时返回 nil 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 -- ────────────────────────────────────────────── -- 辅助:段落标题(如「战斗技能 Tactical」) -- ────────────────────────────────────────────── 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 -- ────────────────────────────────────────────── -- 辅助:被动触发条件文字 -- trigger_type: "normal_attack" 等 -- trigger_value: 数字 -- ────────────────────────────────────────────── 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 -- ────────────────────────────────────────────── -- 辅助:技能 tags 行 -- tagsJson: JSON 字符串数组,如 '["持续伤害","群攻"]' -- ────────────────────────────────────────────── 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 -- ────────────────────────────────────────────── -- 辅助:技能升级表格 -- levelsJson: JSON 数组,每项 { name, levels: [...] } -- 列数根据第一行 levels 数组长度 +1 决定(+1 是标签列) -- ────────────────────────────────────────────── 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 -- 计算列数:第一行的 levels 数组长度(即升级档位数) local firstRow = rows[1] local colCount = 0 if firstRow and firstRow.levels then colCount = #firstRow.levels end -- 总格数 = 标签列 + 数据列,对应 grid-template-columns local gridClass = "card_content_skill-upgrade-grid" if colCount == 9 then -- 终结技 9 档(1~9级) gridClass = gridClass -- 默认样式 elseif colCount >= 9 then -- 普攻/被动 10 档(1~10级,但第1格是标签) gridClass = gridClass .. " card_content_skill-upgrade-grid--10" end local grid = wrap:tag("div"):addClass(gridClass) -- 表头行:空格 + 2~N 级 grid:tag("div") -- 左上角空格 local startLv = 2 if colCount == 9 then startLv = 2 end -- 终结技从 Lv2 开始 for i = startLv, colCount 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("→") -- 数据列(从第 2 个值开始,第 1 个是基础值显示在描述里) if row.levels and type(row.levels) == "table" then for _, v in ipairs(row.levels) do local cell = grid:tag("div"):addClass("card_content_skill-upgrade-val") if v == nil or v == "" or v == "—" then cell:addClass("card_content_skill-upgrade-val--null"):wikitext("—") else cell:addClass("card_accent"):wikitext(tostring(v)) end end end end return wrap end -- ────────────────────────────────────────────── -- 辅助:生成一张技能卡 -- skillData: Lua table,包含技能字段 -- skillType: "normal_attack" | "passive" | "ultimate" -- ────────────────────────────────────────────── 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) .. "」") -- meta 行 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("欲火消耗 ") local costSpan = metaRow:tag("span"):addClass("card_content_skill-meta-val") costSpan:tag("span"):addClass("card_accent"):wikitext(val(skillData.desire_cost)) end -- tags(被动和终结技才有) 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 -- ────────────────────────────────────────────── -- 辅助:觉醒列表 -- stagesJson: JSON 字符串,feats[0].stages 数组 -- ────────────────────────────────────────────── 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") -- 左侧:STAGE + 序号 local stageWrap = row:tag("div"):addClass("card_content_feat-stage-wrap") stageWrap:tag("p"):tag("span"):addClass("card_content_feat-stage"):wikitext("STAGE") local stageNum = stage.stage or 0 stageWrap:tag("div"):addClass("card_content_feat-number") :wikitext(string.format("%02d", stageNum)) -- 右侧:效果文字 + 属性加成 + 觉醒名 local rightDiv = row:tag("div") -- 特殊效果(value[].description),用 card_accent 高亮 if stage.value and type(stage.value) == "table" and #stage.value > 0 then for _, v in ipairs(stage.value) do if v.description then rightDiv:tag("span"):addClass("card_accent"):wikitext(v.description) rightDiv:tag("br") end end end -- 属性加成(stat_boosts[]) if stage.stat_boosts and type(stage.stat_boosts) == "table" then for _, boost in ipairs(stage.stat_boosts) do rightDiv:wikitext((boost.type or "") .. "提升" .. (boost.value or "")) end end -- 觉醒名(extra_name) if stage.extra_name then rightDiv:tag("span") :addClass("card_content_feat-stage-cn") :wikitext("「" .. stage.extra_name .. "」") end end return ul 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 -- ── 1. 用 ExternalData 拉取数据 ── -- 构造请求字段列表(只取需要的字段,减少响应体积) local fields = table.concat({ "stylename", "rarity", "character.name", "profession.name", "desire.name", "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" }, ",") local encodedName = mw.uri.encode(name, "QUERY") local apiUrl = "https://data.saltedkiss.org/items/cards" .. "?fields=" .. mw.uri.encode(fields, "QUERY") .. "&filter[stylename][_eq]=" .. encodedName -- 调用 ExternalData 的 #get_web_data,将整个 JSON 存入变量 cardJson -- 注意:callParserFunction 要求至少一个无名参数(对应 wikitext 冒号后的位置),用空字符串占位 frame:callParserFunction("#get_web_data", { "", -- 无名参数占位,避免 "At least one unnamed parameter" 报错 url = apiUrl, format = "JSON", data = "cardJson=data.0" }) -- 读取变量(ExternalData 把结果存在全局) local rawJson = mw.ext.externalData.getVar("cardJson") or "" -- ── 2. 解析 JSON ── local card = parseJson(rawJson) -- 数据拉取失败时的错误提示 if not card then return '<span class="error">⚠ 无法加载卡片数据:' .. mw.text.nowiki(name) .. '</span>' end -- 取各子对象(可能是 table 或 nil) 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 {} -- feats 是数组,取第一项的 stages local featsStagesJson = "[]" if card.feats and type(card.feats) == "table" and card.feats[1] then local stagesRaw = card.feats[1].stages if type(stagesRaw) == "table" then featsStagesJson = mw.text.jsonEncode(stagesRaw) elseif type(stagesRaw) == "string" then featsStagesJson = stagesRaw end end -- 技能 levels:ExternalData 可能把它解析成 table,转回 JSON 字符串给 makeUpgradeTable 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) -- 技能 tags 同理 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) -- ── 3. 组装 HTML ── 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") :addClass("ron-card--rarity-" .. rarity) -- 星级样式钩子 :attr("data-rarity", rarity) -- 全屏卡面图(暂无 API 数据,留空占位) root:tag("div"):addClass("card_fullscreen-img") -- 英雄区占位 root:tag("div"):addClass("card_hero"):css("height", "100vh") -- 主内容容器 local container = root:tag("div"):addClass("card_content-container") local bg = container:tag("div"):addClass("card_content-background") local inner = bg:tag("div"):addClass("card_content-inner") local layout = inner:tag("div"):addClass("card_content card_content--layout") -- ════════════════════════════════ -- 左栏 -- ════════════════════════════════ local leftCol = layout:tag("div"):addClass("card_content_left") -- 1. 卡名区 local nameContainer = leftCol:tag("div"):addClass("card_content_name-container") local nameRow = nameContainer:tag("div"):addClass("card_content_name-row") -- 职业图标占位 + 职业名 nameRow:tag("div"):addClass("card_content_name-icon") nameRow:tag("div"):addClass("card_content_name-classtext"):wikitext(profName) -- 欲望图标占位 + 欲望名 nameRow:tag("div"):addClass("card_content_name-icon card_content_name-icon--ml") nameRow:tag("div"):addClass("card_content_name-classtext"):wikitext(desName) -- 风格名 · 角色名 local nameText = nameContainer:tag("div"):addClass("card_content_name-text") nameText:wikitext(stylename .. " ") nameText:tag("span"):addClass("card_accent"):wikitext("·") nameText:wikitext(" " .. charName) -- 2. 信息(稀有度) leftCol:node(makeTitle("信息", "Info")) leftCol:node(makeHr()) local infoGrid = leftCol:tag("div"):addClass("card_content_info") infoGrid:tag("div"):addClass("card_content_info-item") :tag("div"):wikitext("稀有度"):done() :tag("div"):wikitext(rarityStars(rarity)) -- 3. 满级属性(暂无数据) leftCol:node(makeTitle("等级 100", "Lv. Max")) leftCol:node(makeHr()) leftCol:tag("div"):addClass("card_content_attribute-item") :wikitext("(数据待补充)") -- 4. 映像(暂无数据) leftCol:node(makeTitle("映像", "Reflection")) leftCol:node(makeHr()) leftCol:tag("div"):addClass("card_content_attribute-item") :wikitext("(数据待补充)") -- 5. 认知(暂无数据) 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") -- 时间线骨架(暂无数据) local timeline = storyHeader:tag("div") :addClass("card_timeline card_content_story-timeline") timeline:tag("div"):addClass("card_timeline-line") timeline:tag("div"):addClass("card_timeline-item") :wikitext("(小传数据待补充)") -- ── 邀约(暂无数据,保留骨架) ── rightCol:tag("div"):css("height", "12px"):css("width", "100%") rightCol:node(makeTitle("邀约", "Date")) rightCol:node(makeHr("card_content-item-hr--mb12")) local dateWrap = rightCol:tag("div"):addClass("card_content_date") dateWrap:tag("div"):addClass("card_content_date-track") :tag("ul"):addClass("card_content_date-list") :tag("li"):addClass("card_content_date-item") :wikitext("(邀约数据待补充)") -- ── 完成,输出 HTML + CSS 模板 ── return tostring(root) .. frame:expandTemplate{ title = "cardcss" } end return p
该页面嵌入的页面:
模块:CardData/doc
(
查看源代码
)
返回
模块:CardData
。
查看“︁模块:CardData”︁的源代码
来自夜幕之下