模块:Db:修订间差异
MJ Hamster(留言 | 贡献) 小无编辑摘要 |
MJ Hamster(留言 | 贡献) 小无编辑摘要 |
||
| (未显示同一用户的19个中间版本) | |||
| 第11行: | 第11行: | ||
['error_no_type'] = '<span class="error">Db:调用时必须提供type参数。</span>', | ['error_no_type'] = '<span class="error">Db:调用时必须提供type参数。</span>', | ||
['error_find_keys_args'] = '<span class="error">Db:findKeysByValue必须提供field和value参数。</span>', | ['error_find_keys_args'] = '<span class="error">Db:findKeysByValue必须提供field和value参数。</span>', | ||
['error_generate_table_args'] = '<span class="error"> | ['error_generate_table_args'] = '<span class="error">Db:generateListTable调用缺少field或columns参数。</span>', | ||
} | } | ||
| 第18行: | 第18行: | ||
-- [[------------------------------------------------------------------]] | -- [[------------------------------------------------------------------]] | ||
local displayNames = { | local displayNames = { | ||
['title'] = '歌曲标题', ['composer'] = '艺术家', ['bpm'] = 'BPM', ['singer'] = '歌手', ['genre'] = '流派', | |||
['title'] = '歌曲标题', | ['id'] = '歌曲ID', ['key'] = 'KEY音', ['bga'] = 'BGA作者', ['duration'] = '时长', | ||
['pack'] = '曲包', | |||
['4b_nm'] = '4B NM', ['4b_nm_notes'] = '4B NM', ['4b_hd'] = '4B HD', ['4b_hd_notes'] = '4B HD', | |||
['4b_mx'] = '4B MX', ['4b_mx_notes'] = '4B MX', ['4b_sc'] = '4B SC', ['4b_sc_notes'] = '4B SC', | |||
['5b_nm'] = '5B NM', ['5b_nm_notes'] = '5B NM', ['5b_hd'] = '5B HD', ['5b_hd_notes'] = '5B HD', | |||
['id'] = '歌曲ID', | ['5b_mx'] = '5B MX', ['5b_mx_notes'] = '5B MX', ['5b_sc'] = '5B SC', ['5b_sc_notes'] = '5B SC', | ||
[' | ['6b_nm'] = '6B NM', ['6b_nm_notes'] = '6B NM', ['6b_hd'] = '6B HD', ['6b_hd_notes'] = '6B HD', | ||
[' | ['6b_mx'] = '6B MX', ['6b_mx_notes'] = '6B MX', ['6b_sc'] = '6B SC', ['6b_sc_notes'] = '6B SC', | ||
['8b_nm'] = '8B NM', ['8b_nm_notes'] = '8B NM', ['8b_hd'] = '8B HD', ['8b_hd_notes'] = '8B HD', | |||
['8b_mx'] = '8B MX', ['8b_mx_notes'] = '8B MX', ['8b_sc'] = '8B SC', ['8b_sc_notes'] = '8B SC', | |||
} | |||
-- **Pack 缩写映射表(用户输入缩写 -> 完整包名,键使用大写以便不区分大小写查询)** | |||
[' | local packAbbreviations = { | ||
['VL'] = 'V LIBERTY', | |||
['VL2'] = 'V LIBERTY 2', | |||
[' | ['VL3'] = 'V LIBERTY 3', | ||
[' | ['VE'] = 'V EXTENSION', | ||
[' | ['VE2'] = 'V EXTENSION 2', | ||
[' | ['VE3'] = 'V EXTENSION 3', | ||
[' | ['VE4'] = 'V EXTENSION 4', | ||
[' | ['VE5'] = 'V EXTENSION 5', | ||
[' | ['P1'] = 'PORTABLE 1', | ||
[' | ['P2'] = 'PORTABLE 2', | ||
['P3'] = 'PORTABLE 3', | |||
['CE'] = 'CLAZZIQUAI', | |||
['BS'] = 'BLACK SQUARE', | |||
['TR'] = 'TRILOGY', | |||
['T1'] = 'TECHNIKA', | |||
['T2'] = 'TECHNIKA 2', | |||
['T3'] = 'TECHNIKA 3', | |||
['R'] = 'RESPECT', | |||
['RV'] = 'RESPECT V', | |||
['ARC'] = 'ARCAEA', | |||
['BA'] = 'BLUE ARCHIVE', | |||
['CY'] = 'CYTUS', | |||
['CHU'] = 'CHUNITHM', | |||
['DE'] = 'DEEMO', | |||
['EZ'] = 'EZ2ON', | |||
['GC'] = 'GROOVE COASTER', | |||
['MD'] = 'MUSE DASH', | |||
['ESTI'] = 'ESTIMATE', | |||
['FC'] = 'FALCOM', | |||
['GF'] = 'GIRL\'S FRONTLINE', | |||
['GG'] = 'GUILTY GEAR', | |||
['MAP'] = 'MAPLESTORY', | |||
['NXN'] = 'NEXON', | |||
['TK'] = 'TEKKEN', | |||
['TB1'] = 'PLI: TRIBUTE VOL.1', | |||
['645141'] = 'PLI: 64514 VOL.1', | |||
} | } | ||
| 第56行: | 第85行: | ||
end | end | ||
-- | -- **Pack 缩写解析** | ||
local function resolvePackValue(input) | |||
if type(input) ~= 'string' then | |||
return input | |||
end | |||
-- 尝试查找缩写(不区分大小写,因为 packAbbreviations 的键是大写) | |||
local upperInput = string.upper(input) | |||
if packAbbreviations[upperInput] then | |||
return packAbbreviations[upperInput] | |||
end | |||
-- 找不到缩写,返回原始输入 | |||
return input | |||
end | |||
local function formatSingleValue(value, fieldType) | local function formatSingleValue(value, fieldType) | ||
if value == nil then | if value == nil then | ||
| 第69行: | 第111行: | ||
end | end | ||
local function formatBPMValue(valueString) | local function formatBPMValue(valueString) | ||
if string.find(valueString, '-') then | if string.find(valueString, '-') then | ||
| 第77行: | 第118行: | ||
end | end | ||
-- **格式化 key 值 (仅在表格中使用)** | |||
local function formatKeyValue(value) | |||
if type(value) == 'boolean' then | |||
local color = value and 'green' or 'red' | |||
local text = value and i18n['true'] or i18n['false'] | |||
return '<span style="color:' .. color .. ';">' .. text .. '</span>' | |||
end | |||
return tostring(value) | |||
end | |||
local function getDifficultyDetails(field) | local function getDifficultyDetails(field) | ||
local checkField = field and string.lower(mw.text.trim(field)) or '' | local checkField = field and string.lower(mw.text.trim(field)) or '' | ||
| 第90行: | 第139行: | ||
if difficultyFields[checkField] == true then | if difficultyFields[checkField] == true then | ||
local mode = checkField:sub(-2) | local mode = checkField:sub(-2) | ||
return checkField:match("^(%d+)b"), mode | return checkField:match("^(%d+)b"), mode | ||
end | end | ||
return nil, nil | return nil, nil | ||
end | end | ||
-- **核心:难度样式应用 ( | -- **核心:难度样式应用 (仅用于表格,并处理可能存在的 'sc' 前缀)** | ||
local function formatDifficultyStyles(valueString, mode) | local function formatDifficultyStyles(valueString, mode) | ||
if valueString == '-' or valueString == i18n['none'] then | |||
return valueString | |||
end | |||
local displayString = valueString | |||
local numValue = tonumber(valueString) | local numValue = tonumber(valueString) | ||
if mode == 'sc' and numValue == nil then | |||
local stripped = valueString:match("^[Ss][Cc](%d+)$") | |||
if stripped then | |||
numValue = tonumber(stripped) | |||
displayString = stripped | |||
end | |||
end | |||
if numValue == nil then | if numValue == nil then | ||
return valueString | return valueString | ||
| 第112行: | 第170行: | ||
if mode == 'sc' then | if mode == 'sc' then | ||
local blueGlow = ' text-shadow: 0 0 5px #3D66FF, 0 0 8px #3D66FF;' | local blueGlow = ' text-shadow: 0 0 5px #3D66FF, 0 0 8px #3D66FF;' | ||
style = 'font-weight: bold;' | style = 'font-weight: bold;' | ||
| 第130行: | 第187行: | ||
end | end | ||
elseif (mode == 'nm' or mode == 'hd' or mode == 'mx') then | elseif (mode == 'nm' or mode == 'hd' or mode == 'mx') then | ||
if numValue == 14 then | if numValue == 14 then | ||
style = 'font-weight: bold; color: orange;' | style = 'font-weight: bold; color: orange;' | ||
| 第139行: | 第195行: | ||
if style ~= '' then | if style ~= '' then | ||
-- | return '<span style="' .. style .. '">' .. displayString .. '</span>' | ||
end | |||
return displayString | |||
end | |||
-- **NOTES 字段加粗/变色逻辑** | |||
local function formatNotesValue(valueString, context) | |||
local numValue = tonumber(valueString) | |||
if numValue and numValue >= 2000 then | |||
local content = valueString | |||
if context == 'table' then | |||
-- --- 表格环境 (高优先级颜色 + 加粗) --- | |||
if numValue >= 3000 then | |||
-- Table, >= 3000: 紫色加粗 | |||
return '<span style="font-weight: bold; color: purple;">' .. content .. '</span>' | |||
elseif numValue >= 2000 then | |||
-- Table, 2000 <= X < 3000: 红色加粗 | |||
return '<span style="font-weight: bold; color: red;">' .. content .. '</span>' | |||
end | |||
else | |||
-- --- 非表格环境 (仅加粗) --- | |||
return '<b>' .. content .. '</b>' | |||
end | |||
end | end | ||
return valueString | -- 如果小于 2000,返回原始字符串 | ||
return valueString | |||
end | end | ||
-- **SC 前缀应用 (仅用于 p.value)** | -- **SC 前缀应用 (仅用于 p.value)** | ||
| 第150行: | 第232行: | ||
local isSC = string.match(fieldType, '_sc$') | local isSC = string.match(fieldType, '_sc$') | ||
if isSC and valueString ~= i18n['none'] and valueString ~= '-' then | if isSC and valueString ~= i18n['none'] and valueString ~= '-' then | ||
return 'sc' .. valueString | if not valueString:lower():find('sc') then | ||
return 'sc' .. valueString | |||
end | |||
end | end | ||
| 第169行: | 第252行: | ||
return value | return value | ||
end), | end), | ||
-- key 字段在非表格环境中的默认处理 (例如 p.value 调用) | |||
['key'] = (function(value) | |||
if type(value) == 'boolean' then | |||
return value and i18n['true'] or i18n['false'] | |||
end | |||
return tostring(value) | |||
end) | |||
} | } | ||
| 第211行: | 第301行: | ||
local value = ValueFromValuesByKey(songEntry, argType) | local value = ValueFromValuesByKey(songEntry, argType) | ||
-- | -- 使用 formatSingleValue 处理 BPM/Duration/Key(boolean->text) 等映射 | ||
local formattedValue = formatSingleValue(value, argType) | local formattedValue = formatSingleValue(value, argType) | ||
formattedValue = applySCPrefix(formattedValue, argType) | formattedValue = applySCPrefix(formattedValue, argType) | ||
-- 非表格环境下应用 NOTES 格式化(仅加粗) | |||
if string.match(argType, '_notes$') then | |||
formattedValue = formatNotesValue(formattedValue, 'text') | |||
end | |||
table.insert(resultTable, formattedValue) | table.insert(resultTable, formattedValue) | ||
end | end | ||
return table.concat(resultTable, ' / ') | return table.concat(resultTable, ' / ') | ||
end | end | ||
| 第265行: | 第325行: | ||
local displayFieldsString = mw.text.trim(args.columns or '') | local displayFieldsString = mw.text.trim(args.columns or '') | ||
-- 预处理 conditionValue | local isPackFilter = (conditionField == 'pack') -- 定义 pack 筛选标记 | ||
-- ** 预处理 conditionValue ** | |||
if type(conditionValue) == 'string' then | if type(conditionValue) == 'string' then | ||
local trimmedValue = mw.text.trim(conditionValue) | local trimmedValue = mw.text.trim(conditionValue) | ||
local lowerValue = string.lower(trimmedValue) | local lowerValue = string.lower(trimmedValue) | ||
if lowerValue == 'true' or trimmedValue == '是' then | if lowerValue == 'true' or trimmedValue == '是' then | ||
conditionValue = true | conditionValue = true | ||
| 第276行: | 第339行: | ||
else | else | ||
local num = tonumber(trimmedValue) | local num = tonumber(trimmedValue) | ||
if num ~= nil then | -- 核心修复:如果是 Pack 筛选,即使能转为数字也不转,保持字符串以便后续缩写解析和不区分大小写比较 | ||
if num ~= nil and not isPackFilter then | |||
conditionValue = num | conditionValue = num | ||
else | else | ||
| 第284行: | 第348行: | ||
end | end | ||
if conditionField == '' | if conditionField == '' or displayFieldsString == '' then | ||
return i18n['error_generate_table_args'] | return i18n['error_generate_table_args'] | ||
end | end | ||
| 第292行: | 第356行: | ||
local matchingKeys = {} | local matchingKeys = {} | ||
local shouldFilter = conditionValue ~= nil | |||
-- 如果是 pack 筛选,提前解析 conditionValue 以支持缩写 | |||
local resolvedConditionValue = nil | |||
if isPackFilter and type(conditionValue) == 'string' then | |||
resolvedConditionValue = resolvePackValue(conditionValue) | |||
end | |||
for songId, songEntry in pairs(songData) do | for songId, songEntry in pairs(songData) do | ||
if not shouldFilter then | |||
if | |||
table.insert(matchingKeys, songId) | table.insert(matchingKeys, songId) | ||
else | |||
local currentValue = ValueFromValuesByKey(songEntry, conditionField) | |||
local match = false | |||
-- 1. 数字和布尔值的精确匹配 | |||
if currentValue == conditionValue then | |||
match = true | |||
-- 2. 字符串匹配 | |||
elseif type(currentValue) == 'string' and type(conditionValue) == 'string' then | |||
-- 2a. Pack 字段: 缩写解析后的精确匹配 (Case-insensitive Exact Match) | |||
if isPackFilter then | |||
local targetValue = resolvedConditionValue -- 这会是完整的包名或原始输入 | |||
if string.upper(currentValue) == string.upper(targetValue) then | |||
match = true | |||
end | |||
-- 2b. 其他字符串字段: 模糊匹配 (Case-insensitive Fuzzy Match) | |||
else | |||
local searchString = conditionValue | |||
-- 模糊匹配:只要 currentValue 包含 searchString(不区分大小写) | |||
if string.find(string.upper(currentValue), string.upper(searchString), 1, true) then | |||
match = true | |||
end | |||
end | |||
end | |||
if match then | |||
table.insert(matchingKeys, songId) | |||
end | |||
end | end | ||
end | end | ||
| 第307行: | 第412行: | ||
local output = {} | local output = {} | ||
-- 表格样式 | -- 检查是否需要禁用宽度设置 | ||
local disableWidths = (#displayFields <= 8) | |||
-- 表格样式 | |||
table.insert(output, '{| class="wikitable sortable" style="width: 100%; margin: 0 auto; text-align: center;"') | table.insert(output, '{| class="wikitable sortable" style="width: 100%; margin: 0 auto; text-align: center;"') | ||
-- | -- 构造表格头部:添加宽度样式控制 | ||
table.insert(output, '|-') | table.insert(output, '|-') | ||
for _, field in ipairs(displayFields) do | for _, field in ipairs(displayFields) do | ||
field = mw.text.trim(field) | field = mw.text.trim(field) | ||
table.insert(output, '! ' .. | |||
local _, mode = getDifficultyDetails(field) | |||
local headerContent = displayNames[field] or field | |||
local headerStyle = '' | |||
local separator = ' ' -- 默认分隔符是空格 | |||
if not disableWidths then -- 宽度要求失效检查 | |||
-- BPM/Key/Duration 字段,固定宽度 6% | |||
if field == 'bpm' or field == 'key' or field == 'duration' then | |||
headerStyle = ' style="width: 6%;"' | |||
separator = ' | ' | |||
-- Pack 字段,固定宽度 10% | |||
elseif field == 'pack' then | |||
headerStyle = ' style="width: 10%;"' | |||
separator = ' | ' | |||
elseif mode then -- 谱面难度字段 (3.2% 固定宽度) | |||
headerStyle = ' style="width: 3.2%;"' | |||
separator = ' | ' | |||
elseif string.match(field, '_notes$') then -- NOTES 字段 (4% 固定宽度) | |||
headerStyle = ' style="width: 4%;"' | |||
separator = ' | ' | |||
end | |||
end | |||
-- MediaWiki 表格头单元格: ! style | Content | |||
table.insert(output, '!' .. headerStyle .. separator .. headerContent) | |||
end | end | ||
| 第320行: | 第453行: | ||
for _, songId in ipairs(matchingKeys) do | for _, songId in ipairs(matchingKeys) do | ||
local songEntry = songData[songId] | local songEntry = songData[songId] | ||
table.insert(output, '|-') | table.insert(output, '|-') | ||
for _, field in ipairs(displayFields) do | for _, field in ipairs(displayFields) do | ||
field = mw.text.trim(field) | field = mw.text.trim(field) | ||
| 第328行: | 第460行: | ||
local rawValue = ValueFromValuesByKey(songEntry, field) | local rawValue = ValueFromValuesByKey(songEntry, field) | ||
-- | -- 获取格式化的值 | ||
local formattedValue = formatSingleValue(rawValue, field) | local formattedValue = formatSingleValue(rawValue, field) | ||
-- | -- 应用内容样式 (BPM/Key) | ||
if field == 'bpm' then | if field == 'bpm' then | ||
formattedValue = formatBPMValue(formattedValue) | formattedValue = formatBPMValue(formattedValue) | ||
elseif field == 'key' then | |||
formattedValue = formatKeyValue(rawValue) -- 对 key 字段应用颜色逻辑,使用原始值 (rawValue) | |||
end | end | ||
local _, mode = getDifficultyDetails(field) | local _, mode = getDifficultyDetails(field) | ||
if mode then | if mode then | ||
formattedValue = formatDifficultyStyles(formattedValue, mode) | formattedValue = formatDifficultyStyles(formattedValue, mode) | ||
end | end | ||
-- | -- *** NOTES 字段样式逻辑 (包含 _sc_notes 默认颜色) *** | ||
table.insert(output, '|' .. | if string.match(field, '_notes$') then | ||
local numValue = tonumber(rawValue) | |||
-- 1. 检查是否为高优先级(>= 2000/3000) | |||
if numValue and numValue >= 2000 then | |||
-- 高优先级:应用红/紫色的 formatNotesValue | |||
formattedValue = formatNotesValue(formattedValue, 'table') | |||
-- 2. 检查是否为所有 _sc_notes 字段且为低优先级(< 2000) | |||
elseif string.match(field, '_sc_notes$') then | |||
-- sc_notes 默认颜色:3d66ff (如果值不是高优先级) | |||
formattedValue = '<span style="color: #3d66ff;">' .. formattedValue .. '</span>' | |||
end | |||
end | |||
local cellContent = formattedValue | |||
-- CRITICAL FIX: 如果内容是单个 '-', 增加空格以避免被解析为行分隔符 '|-'. | |||
if cellContent == '-' then | |||
cellContent = ' -' | |||
end | |||
table.insert(output, '|' .. cellContent) | |||
end | end | ||
end | end | ||
| 第352行: | 第503行: | ||
table.insert(output, '|}') | table.insert(output, '|}') | ||
return table.concat(output, '\n') | return table.concat(output, '\n') | ||
end | end | ||
return p | return p | ||