打开/关闭菜单
打开/关闭外观设置菜单
打开/关闭个人菜单
未登录
未登录用户的IP地址会在进行任意编辑后公开展示。

模块:CardData

来自夜幕之下
Rin留言 | 贡献2026年3月11日 (三) 21:13的版本

此模块的文档可以在模块: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 makeStoryTimeline(stories, cardTitle)

    local timeline = mw.html.create("div")
        :addClass("card_timeline card_content_story-timeline")

    timeline:tag("div")
        :addClass("card_timeline-line")

    -- 更安全获取 entry
    local entry
    if type(stories) == "table" then
        for _,v in pairs(stories) do
            entry = v
            break
        end
    end

    local chapters = entry and entry.story or {}

    if not chapters or type(chapters) ~= "table" then
        chapters = {}
    end

    if next(chapters) == nil 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 解锁"
    }

    local idx = 1

    for _, chapter in pairs(chapters) do

        local item = timeline:tag("div")
            :addClass("card_timeline-item")

        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 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

            local lines = mw.text.split(text,"\n")

            for i,line in ipairs(lines) do

                if line ~= "" then
                    bodyDiv:wikitext(line)
                end

                if i < #lines then
                    bodyDiv:tag("br")
                end

            end

        end

        idx = idx + 1

    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">错误:请提供卡片名称</span>'
    end


    -- 关键:stories.story.*
    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",

        -- 修复 stories
        "stories.*",
        "stories.story.*"

    },",")


    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


    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">⚠ 无法加载卡片数据</span>'
    end


    local stories = card.stories or {}

    local root = mw.html.create("div")
        :addClass("ron-card")

    local rightCol = root:tag("div")
        :addClass("card_content_right")


    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"))

    storyHeader:node(
        makeStoryTimeline(stories,card.stylename)
    )


    return tostring(root)

end

return p