跳转到内容

Module:曲包列表

来自Bleap Wiki
Admin​(留言 | 贡献)2026年9月26日 (六) 21:54的版本 (接入 模块:数据页校验:数据页结构错误时显红字并归入 分类:数据页异常(不再静默渲染空表))

此模块的文档可以在Module:曲包列表/doc创建

local p = {}

-- 单一数据源: 曲目列表.json (曲包信息由 songs 的 "曲包" 字段派生)
local DATA_TITLE = '模板:曲目列表.json'
local validator = require('模块:数据页校验')

-- 曲包展示顺序 (游戏内上架顺序), 不在表中的曲包按名称排序追加到末尾
local PACK_ORDER = {
	'夏日序曲 第一章', '夏日序曲 第二章', '夏日序曲 第三章',
	'泡沫假日1号包', '数据突围1号包', '旅行伊始1号包', '神秘国度1号包',
	'冬日欢歌1号包', '新年快乐1号包', '闪音集结1号包', '五光十色1号包',
	'万象春来1号包', 'ChillMe!1号包', '时序织梦1号包',
	'中国BOY1号包', 'Nanatsukaze1号包',
	'免费单曲(商店)', '免费单曲', 'CORE基础卡',
}

local function esc(s)
	return (s:gsub('|', '|'))
end

-- 链接目标: # 是 MediaWiki 的锚点分隔符, 标题里一律换成全角 #
-- 优先用「链接名」字段; 没有就回退到曲名并自动替换 (双保险)
-- 注意: 光用 <nowiki>&#35;</nowiki> 之类的转义救不了 —— # 在标题解析阶段就被切走了
local function pageName(song, suffix)
	local n = song['链接名'] or song['曲名']
	if suffix then
		n = n .. '(' .. suffix .. ')'
	end
	return (n:gsub('#', '#'))
end

local function norm(s)
	-- 图片文件名规范: 空格 -> 下划线
	return (s:gsub(' ', '_'))
end

local function langWrap(song)
	local lang = song['lang']
	if lang == 'ja' then
		return '{{lang|ja|' .. esc(song['曲名']) .. '}}'
	elseif lang == 'zh-Hant' then
		return '{{lang|zh-Hant|' .. esc(song['曲名']) .. '}}'
	end
	return esc(song['曲名'])
end

function p.render(frame)
	local demomode = frame and frame.args['demo']
	local data, dataErr = validator.load(DATA_TITLE)
	if not data then
		return dataErr
	end

	-- 按曲包分组 (保持 songs 数组顺序)
	local packs = {}
	for _, song in ipairs(data.songs or {}) do
		local pk = song['曲包']
		if pk and pk ~= '' then
			packs[pk] = packs[pk] or {}
			table.insert(packs[pk], song)
		end
	end

	-- 排序: PACK_ORDER 优先, 未知曲包按名称追加
	local orderMap = {}
	for i, name in ipairs(PACK_ORDER) do
		orderMap[name] = i
	end
	local keys = {}
	for pk in pairs(packs) do
		table.insert(keys, pk)
	end
	table.sort(keys, function(a, b)
		local ra = orderMap[a] or (#PACK_ORDER + 1)
		local rb = orderMap[b] or (#PACK_ORDER + 1)
		if ra ~= rb then
			return ra < rb
		end
		return a < b
	end)

	local out = {}
	table.insert(out, '{| class="wikitable sortable" style="width:100%"')
	table.insert(out, '! 图片 !! 曲包 !! 曲目数 !! 包含曲目')
	for _, pk in ipairs(keys) do
		local songs = packs[pk]
		-- 图片: 曲包物品-<曲包名>.png, 不存在则显示 —
		local imgFile = '曲包物品-' .. norm(pk) .. '.png'
		local imgTitle = mw.title.new('File:' .. imgFile)
		local imgCell
		if imgTitle and imgTitle.exists then
			imgCell = '[[File:' .. esc(imgFile) .. '|80px]]'
		else
			imgCell = '—'
		end
		local links = {}
		for _, song in ipairs(songs) do
            table.insert(links, '[[' .. '曲目:' .. esc(pageName(song)) .. '|' .. langWrap(song) .. ']]')
		end
		-- 锚点: 其他页面可 [[曲包列表#<曲包名>]] 跳转到对应行
		table.insert(out, '|-\n| id=\"' .. esc(pk) .. '\" | ' .. imgCell .. ' || ' .. esc(pk)
			.. ' || ' .. tostring(#songs)
			.. ' || ' .. table.concat(links, ' / '))
	end
	table.insert(out, '|}')

	if not demomode then
		table.insert(out, '\n[[Category:曲包]]')
	end
	-- Scribunto 返回文本中的 {{模板}} 不会被自动展开, 必须显式 preprocess
	return frame:preprocess(table.concat(out, '\n'))
end

return p