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

模块:CardData

来自夜幕之下
Rin留言 | 贡献2026年3月10日 (二) 23:32的版本 (重写:用 mw.html.create 生成完整骨架,对应 Sandbox/2.0 HTML 结构 (via update-page on MediaWiki MCP Server))

此模块的文档可以在模块:CardData/doc创建

-- Module:CardData
-- 生成卡片页面完整 HTML 骨架,数据由前端 JS 从 Directus API 获取后填充
-- 用法:{{#invoke:CardData|render|复仇童谣}}
--
-- 骨架结构对应 Sandbox/2.0,分为左栏(基础信息)和右栏(技能/觉醒/小传/邀约)
-- JS 会找到所有 [data-cardname] 元素,请求 Directus 后将数据注入各 [data-card-field] 占位节点

local p = {}

-- ──────────────────────────────────────────────
-- 辅助:创建"标题行"(如「战斗技能 Tactical」)
-- ──────────────────────────────────────────────
local function makeTitle(titleText, subtitleText, extraClass)
    local div = mw.html.create("div")
        :addClass("card_content-item-title")
    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

-- ──────────────────────────────────────────────
-- 辅助:创建一个技能卡片骨架
-- skillKey = "normal_attack" | "passive" | "ultimate"
-- ──────────────────────────────────────────────
local function makeSkillCard(skillKey)
    -- 外层卡片
    local card = mw.html.create("div")
        :addClass("card_content_skill-card")

    -- 左侧内容区
    local left = card:tag("div"):addClass("card_content_skill-left")

    -- 技能标题行
    local titleWrap = left:tag("div"):addClass("card_content_skill-title")
    titleWrap:tag("div"):addClass("card_content_skill-icon")  -- 图标占位
    local titleText = titleWrap:tag("div"):addClass("card_content_skill-title-text")

    -- 技能名 + 类型 行
    local nameRow = titleText:tag("div")
    nameRow:tag("span")
        :addClass("card_content_skill-name")
        :attr("data-card-field", "skill_" .. skillKey .. ".name")
        :wikitext("—")
    nameRow:tag("span")
        :addClass("card_content_skill-type card_accent--type")
        :attr("data-card-field", "skill_" .. skillKey .. ".type_label")
        :wikitext("—")

    -- meta 行(普攻显示武器;被动显示触发条件;终结技显示欲火消耗)
    local metaRow = titleText:tag("div"):addClass("card_content_skill-meta")
    if skillKey == "normal_attack" then
        metaRow:wikitext("使用武器 ")
        metaRow:tag("span")
            :addClass("card_content_skill-meta-val card_accent")
            :attr("data-card-field", "skill_normal_attack.weapon")
            :wikitext("—")
    elseif skillKey == "passive" then
        metaRow:wikitext("触发条件 ")
        metaRow:tag("span")
            :addClass("card_content_skill-meta-val")
            :attr("data-card-field", "skill_passive.trigger_label")
            :wikitext("—")
    elseif skillKey == "ultimate" then
        metaRow:wikitext("欲火消耗 ")
        metaRow:tag("span")
            :addClass("card_content_skill-meta-val")
            :tag("span")
                :addClass("card_accent")
                :attr("data-card-field", "skill_ultimate.desire_cost")
                :wikitext("—")
    end

    -- 被动/终结技才有 tags 行
    if skillKey == "passive" or skillKey == "ultimate" then
        left:tag("div")
            :addClass("card_content_skill-tags")
            :attr("data-card-field", "skill_" .. skillKey .. ".tags")
            -- JS 会把 tag 列表渲染到这里
    end

    -- 技能描述
    left:tag("div")
        :addClass("card_content_skill_effect")
        :attr("data-card-field", "skill_" .. skillKey .. ".description_html")
        :wikitext("—")

    -- 右侧升级表格占位(JS 负责生成具体列数和内容)
    card:tag("div")
        :addClass("card_content_skill-upgrade")
        :attr("data-card-field", "skill_" .. skillKey .. ".levels_table")

    return card
end

-- ──────────────────────────────────────────────
-- 主渲染函数
-- ──────────────────────────────────────────────
function p.render(frame)
    local name = frame.args[1] or frame.args.name or ""
    name = mw.text.trim(name)

    if name == "" then
        return '<span class="error">错误:请提供卡片名称,例如 {{#invoke:CardData|render|复仇童谣}}</span>'
    end

    -- ── 最外层容器,JS 以此为根节点 ──
    local root = mw.html.create("div")
        :addClass("ron-card")
        :attr("data-cardname", name)

    -- 全屏卡面图
    root:tag("div")
        :addClass("card_fullscreen-img")
        :attr("data-card-field", "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")

    -- ════════════════════════════════
    -- 左栏(sticky)
    -- ════════════════════════════════
    local left = layout:tag("div"):addClass("card_content_left")

    -- 1. 卡名区
    local nameContainer = left: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")
        :attr("data-card-field", "profession_icon")
    nameRow:tag("div")
        :addClass("card_content_name-classtext")
        :attr("data-card-field", "profession.name")
        :wikitext("—")

    -- 欲望图标 + 文字
    nameRow:tag("div")
        :addClass("card_content_name-icon card_content_name-icon--ml")
        :attr("data-card-field", "desire_icon")
    nameRow:tag("div")
        :addClass("card_content_name-classtext")
        :attr("data-card-field", "desire.name")
        :wikitext("—")

    -- 卡名 + 角色名
    nameContainer:tag("div")
        :addClass("card_content_name-text")
        :attr("data-card-field", "card_title")
        -- JS 会把「风格名 · 角色名」填进来
        :wikitext("—")

    -- 2. 信息(稀有度等,目前 API 只有 rarity,其他待补)
    left:node(makeTitle("信息", "Info", "card_content-item-title--mt"))
    left:node(makeHr())
    local infoGrid = left:tag("div"):addClass("card_content_info")
    -- 四项情报:JS 填充(data-card-field="info_grid")
    infoGrid:attr("data-card-field", "info_grid")

    -- 3. 满级属性(API 暂无,先留骨架)
    left:node(makeTitle("等级 100", "Lv. Max", "card_content-item-title--mt"))
    left:node(makeHr())
    left:tag("div")
        :addClass("card_content_attributes")
        :attr("data-card-field", "attributes")
        :wikitext("—")

    -- 4. 映像(API 暂无,先留骨架)
    left:node(makeTitle("映像", "Reflection", "card_content-item-title--mt"))
    left:node(makeHr())
    left:tag("div")
        :addClass("card_content_reflection")
        :attr("data-card-field", "reflection")
        :wikitext("—")

    -- 5. 认知(API 暂无,先留骨架)
    left:node(makeTitle("认知", "Cognition", "card_content-item-title--mt"))
    left:node(makeHr())
    left:tag("div")
        :addClass("card_content_cognition")
        :attr("data-card-field", "cognition")
        :wikitext("—")

    -- ════════════════════════════════
    -- 右栏
    -- ════════════════════════════════
    local right = layout:tag("div"):addClass("card_content_right")

    -- ── 2-1. 战斗技能 ──
    right:node(makeTitle("战斗技能", "Tactical", "card_content-item-title--mt"))
    right:node(makeHr("card_content-item-hr--mb12"))

    -- 普攻
    right:node(makeSkillCard("normal_attack"))
    right:tag("div"):addClass("card_content_skill-gap")

    -- 被动
    right:node(makeSkillCard("passive"))
    right:tag("div"):addClass("card_content_skill-gap")

    -- 终结技
    right:node(makeSkillCard("ultimate"))

    -- ── 2-2. 觉醒 ──
    right:node(makeTitle("觉醒", "Feat", "card_content-item-title--mt"))
    right:node(makeHr("card_content-item-hr--mb12"))

    -- 觉醒列表,JS 根据 feats.stages 数组填充
    right:tag("ul")
        :addClass("card_content_feat-ul")
        :attr("data-card-field", "feats_list")

    -- ── 2-3. 小传 ──
    local storySection = right: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", "card_content-item-title--mt"))
    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")

    -- 时间线,JS 根据 story 数据填充
    storyHeader:tag("div")
        :addClass("card_timeline card_content_story-timeline")
        :attr("data-card-field", "story_timeline")
        :tag("div"):addClass("card_timeline-line")

    -- ── 2-4. 邀约 ──
    right:tag("div"):css("height", "12px"):css("width", "100%")
    right:node(makeTitle("邀约", "Date", "card_content-item-title--mt"))
    right:node(makeHr("card_content-item-hr--mb12"))

    -- 邀约卡片轨道,JS 填充
    local dateWrap = right:tag("div"):addClass("card_content_date")
    dateWrap:tag("div")
        :addClass("card_content_date-track")
        :tag("ul")
            :addClass("card_content_date-list")
            :attr("data-card-field", "date_list")

    -- 邀约故事面板(点击卡片后展开)
    local dateStory = right:tag("div")
        :addClass("card_date_story")
        :attr("id", "date-story-panel")
        :attr("aria-live", "polite")
    dateStory:tag("div")
        :addClass("card_date_story-scene")
        :attr("id", "date-story-scene")
        :attr("data-card-field", "date_story_scene")
    dateStory:tag("div")
        :addClass("card_date_story-body")
        :attr("id", "date-story-body")
        :attr("data-card-field", "date_story_body")

    -- ── 加载提示(JS 完成后移除)──
    root:tag("div")
        :addClass("ron-card-loading")
        :wikitext("⏳ 加载中…")

    -- 输出 HTML + CSS 模板
    return tostring(root) .. frame:expandTemplate{ title = "cardcss" }
end

return p