欢迎来到《帝国时代Mobile》WIKI频道!
本WIKI开放编辑权限,欢迎收藏起来防止迷路,也希望有爱的小伙伴和我们一起把WIKI做大做强~!
编辑置顶公告 • WIKI编辑帮助 • BWIKI反馈
全站通知:
模块:EventCalendar
刷
历
编
跳到导航
跳到搜索
此模块的文档可以在模块:EventCalendar/doc创建
local p = {}
-- ============================================================================
-- 辅助逻辑函数
-- ============================================================================
local function parseDate(dateStr)
if not dateStr then return os.time() end
local p = "(%d+)-(%d+)-(%d+)"
local y, m, d = dateStr:match(p)
if not y then return os.time() end
return os.time({year=y, month=m, day=d})
end
local function formatDate(timestamp)
return os.date("%m/%d", timestamp)
end
local function getChineseWeekName(timestamp)
local wday = os.date("*t", timestamp).wday
local names = {"周日", "周一", "周二", "周三", "周四", "周五", "周六"}
return names[wday]
end
local function isEventActive(evt, checkTime)
if not evt then return false end
local checkDate = os.date("*t", checkTime)
checkTime = os.time({year=checkDate.year, month=checkDate.month, day=checkDate.day})
local startTime = parseDate(evt.start)
if evt.type == 'special' then
local endTime = parseDate(evt['end'])
return checkTime >= startTime and checkTime <= endTime
elseif evt.type == 'cycle' then
if checkTime < startTime then return false end
local duration = tonumber(evt.duration) or 1
local interval = tonumber(evt.interval) or 0
local cycleLen = duration + interval
if cycleLen <= 0 then return false end
local diffDays = math.floor(os.difftime(checkTime, startTime) / 86400)
return (diffDays % cycleLen) < duration
end
return false
end
-- ============================================================================
-- 主函数
-- ============================================================================
function p.main(frame)
-- 1. 数据准备
local offsetWeek = tonumber(frame.args.week_offset) or 0
local maxHeight = frame.args.height
local success, rawData = pcall(mw.loadData, 'Module:EventCalendar/Data')
if not success then rawData = { events={}, schedule={} } end
local now = os.time()
local nowTable = os.date("*t", now)
local todayStart = os.time({year=nowTable.year, month=nowTable.month, day=nowTable.day})
-- 视图锚点:前2后4
local startDate = todayStart - (2 * 86400) + (offsetWeek * 7 * 86400)
local weekDates = {}
for i = 0, 6 do
table.insert(weekDates, startDate + (i * 86400))
end
local viewCenterDate = weekDates[3]
-- 2. 数据计算与排序
local displayList = {}
for _, item in ipairs(rawData.schedule) do
local meta = rawData.events[item.eventId]
if meta then
local isActiveInWeek = false
local sIdx, eIdx = -1, -1
for i = 1, 7 do
if isEventActive(item, weekDates[i]) then
if not isActiveInWeek then isActiveInWeek = true; sIdx = i end
eIdx = i
end
end
if isActiveInWeek then
local isExpired = eIdx < 3
table.insert(displayList, {
id = item.eventId,
priority = meta.priority or 0,
name = meta.name,
link = meta.link,
icon = meta.icon,
sched = item,
sIdx = sIdx,
eIdx = eIdx,
isExpired = isExpired
})
end
end
end
table.sort(displayList, function(a, b)
if a.isExpired ~= b.isExpired then return not a.isExpired end
if a.sIdx ~= b.sIdx then return a.sIdx < b.sIdx end
if a.priority ~= b.priority then return a.priority > b.priority end
return a.id < b.id
end)
-- 3. 主题配色
local theme = {
bg_container = 'rgba(40, 44, 60, 0.95)',
border_outer = '1px solid rgba(244, 218, 136, 0.8)',
border_col = '1px solid rgba(244, 218, 136, 0.15)',
border_header= '1px solid rgba(244, 218, 136, 0.3)',
text_title = '#ffdf91',
text_tips = 'rgba(255, 223, 145, 0.6)',
text_white = 'rgba(255, 255, 255, 0.9)',
bg_title = 'rgba(0, 0, 0, 0.25)',
bg_header = 'rgba(40, 44, 60, 0.95)', -- 不透明背景
today_bg = 'rgba(220, 179, 101, 0.15)',
today_shadow = 'inset 0 0 15px rgba(220, 179, 101, 0.1)',
today_line = '#ffdf91',
bar_bg = 'linear-gradient(to bottom, rgba(68, 80, 110, 0.95), rgba(48, 59, 85, 1))',
bar_border = '1px solid rgba(244, 218, 136, 0.5)',
bar_text = '#ffdf91',
expired_bg = 'linear-gradient(to bottom, rgba(45, 50, 60, 0.9), rgba(35, 40, 50, 0.9))',
expired_border = '1px solid rgba(244, 218, 136, 0.15)',
expired_text = 'rgba(255, 255, 255, 0.4)'
}
-- 4. 结构构建
local outerBox = mw.html.create('div')
:css({
['font-family'] = '"Microsoft YaHei", Arial, sans-serif',
['background'] = theme.bg_container,
['border'] = theme.border_outer,
['border-radius'] = '6px',
['box-shadow'] = '0 5px 15px rgba(0, 0, 0, 0.6)',
['backdrop-filter'] = 'blur(5px)',
['color'] = theme.text_white,
['max-width'] = '800px',
['margin'] = '10px auto',
['overflow'] = 'hidden',
['display'] = 'flex',
['flex-direction'] = 'column',
['box-sizing'] = 'border-box'
})
-- 4.1 标题栏
local titleBar = outerBox:tag('div'):css({
['display'] = 'flex',
['justify-content'] = 'space-between',
['align-items'] = 'center',
['padding'] = '10px 15px',
['background'] = theme.bg_title,
['border-bottom'] = theme.border_header,
['flex-shrink'] = '0'
})
titleBar:tag('div'):css({['font-size']='16px',['font-weight']='bold',['color']=theme.text_title,['text-shadow']='0 1px 2px rgba(0,0,0,0.8)',['display']='flex',['align-items']='center',['gap']='6px'}):wikitext('✦ 活动日历')
titleBar:tag('div'):css({['font-size']='12px',['color']=theme.text_tips,['font-family']='Arial, sans-serif'}):wikitext(os.date("%Y / %m", viewCenterDate))
-- 4.2 滚动视口 (使用原生滚动条)
local scrollWindow = outerBox:tag('div')
:css({
['position'] = 'relative',
['overflow-y'] = 'auto',
['overflow-x'] = 'hidden',
['width'] = '100%'
})
if maxHeight and maxHeight ~= "" then
scrollWindow:css('max-height', maxHeight)
else
scrollWindow:css('min-height', 'auto')
end
-- 4.3 内部大容器
local innerWrapper = scrollWindow:tag('div'):css({
['position'] = 'relative',
['width'] = '100%',
['box-sizing'] = 'border-box'
})
-- 层1: 背景网格 (修复错位逻辑:自动避开滚动条)
local bgGrid = innerWrapper:tag('div'):css({
['position'] = 'absolute',
['top'] = '0',
['left'] = '0',
['right'] = '0',
['bottom'] = '0',
['display'] = 'grid',
['grid-template-columns'] = 'repeat(7, 1fr)',
['z-index'] = '0',
['pointer-events'] = 'none',
['box-sizing'] = 'border-box'
})
for i = 1, 7 do
local cell = bgGrid:tag('div'):css({['border-right']=theme.border_col, ['box-sizing']='border-box'})
if os.date("%Y%m%d", weekDates[i]) == os.date("%Y%m%d", now) then cell:css('background', 'rgba(220, 179, 101, 0.05)') end
if i == 7 then cell:css('border-right', 'none') end
end
-- 层2: 表头 (Sticky)
local header = innerWrapper:tag('div')
:css({
['display'] = 'grid',
['grid-template-columns'] = 'repeat(7, 1fr)',
['background'] = theme.bg_header,
['color'] = theme.text_title,
['text-align'] = 'center',
['font-size'] = '14px',
['border-bottom'] = theme.border_col,
['gap'] = '0',
['position'] = 'sticky',
['top'] = '0',
['z-index'] = '10',
['width'] = '100%',
['box-sizing'] = 'border-box'
})
for i = 0, 6 do
local d = weekDates[i+1]
local isToday = os.date("%Y%m%d", d) == os.date("%Y%m%d", now)
local col = header:tag('div'):css({['padding']='8px 0', ['border-right']=theme.border_col, ['position']='relative', ['box-sizing']='border-box'})
if i == 6 then col:css('border-right', 'none') end
if isToday then
col:css({['background']=theme.today_bg,['box-shadow']=theme.today_shadow})
col:tag('div'):css({['position']='absolute', ['bottom']='0', ['left']='0', ['width']='100%', ['height']='2px', ['background']=theme.today_line})
end
col:tag('div'):wikitext(getChineseWeekName(d))
col:tag('div'):css({['font-size']='12px', ['opacity']='0.7', ['margin-top']='2px'}):wikitext(formatDate(d))
end
-- 层3: 活动内容
local contentLayer = innerWrapper:tag('div'):css({
['position'] = 'relative',
['z-index'] = '1',
['padding'] = '10px 0',
['width'] = '100%',
['box-sizing'] = 'border-box'
})
if #displayList > 0 then
for _, evt in ipairs(displayList) do
local row = contentLayer:tag('div'):css({['display']='grid', ['grid-template-columns']='repeat(7, 1fr)', ['margin']='6px 0', ['padding']='0', ['gap']='0', ['width']='100%'})
local posWrapper = row:tag('div'):addClass('plainlinks'):css({['grid-column']=evt.sIdx .. ' / span ' .. (evt.eIdx - evt.sIdx + 1), ['margin']='0 2px'})
local currentBg = evt.isExpired and theme.expired_bg or theme.bar_bg
local currentBorder = evt.isExpired and theme.expired_border or theme.bar_border
local currentText = evt.isExpired and theme.expired_text or theme.bar_text
local styleStr = table.concat({
'background:' .. currentBg,
'border:' .. currentBorder,
'color:' .. currentText,
'font-size: 13px',
'padding: 5px 8px',
'border-radius: 4px',
'box-shadow: 0 2px 5px rgba(0,0,0,0.4)',
'white-space: nowrap',
'overflow: hidden',
'display: flex',
'align-items: center',
'width: auto',
'height: 100%',
'box-sizing: border-box',
'cursor: default'
}, ';')
local hasLink = (evt.link and evt.link ~= "")
if hasLink and not evt.isExpired then styleStr = styleStr .. ';cursor:pointer' end
if evt.sIdx == 1 and isEventActive(evt.sched, weekDates[1] - 86400) then styleStr = styleStr .. ';border-top-left-radius:0;border-bottom-left-radius:0;border-left:none' end
if evt.eIdx == 7 and isEventActive(evt.sched, weekDates[7] + 86400) then styleStr = styleStr .. ';border-top-right-radius:0;border-bottom-right-radius:0;border-right:none' end
local contentTxt = evt.name
if hasLink then contentTxt = '<span style="font-weight:bold;">' .. evt.name .. '</span>' end
if evt.icon and evt.icon ~= '' then
contentTxt = '[[File:' .. evt.icon .. '|18px|link=]] ' .. contentTxt
end
-- 保留了 class 接口,但删除了 CSS 注入代码。
-- 悬浮特效:如果你以后在 Common.css 里加了 .aoem-hover-anim,这里会自动生效。
-- 如果不加,就是无特效,但不报错。
if hasLink and not evt.isExpired then
posWrapper:wikitext('[[' .. evt.link .. '|<span class="aoem-hover-anim" style="' .. styleStr .. '">' .. contentTxt .. '</span>]]')
elseif hasLink then
posWrapper:wikitext('[[' .. evt.link .. '|<span style="' .. styleStr .. '">' .. contentTxt .. '</span>]]')
else
posWrapper:tag('div'):attr('style', styleStr):wikitext(contentTxt)
end
end
else
contentLayer:tag('div'):css({['text-align']='center', ['color']='rgba(255,255,255,0.3)', ['padding']='30px', ['font-style']='italic'}):wikitext('暂无活动')
end
return tostring(outerBox)
end
return p

沪公网安备 31011002002714 号