数据源:百度百科开放接口 BaikeLemmaCardApi(无反爬,返回结构化 JSON) 与「历史上的今天」(维基)完全解耦,维基代码原样保留可随时恢复: 服务端: - WikiEntry 增加 image(配图)和 extra(结构化字段 JSON)字段 - 新建 BaikeTermSyncLog 独立日志表(不污染维基 WikiSyncLog) - 新建 BaikeTermSyncService 独立同步服务(24 节气,间隔 1.2s) - 启动时缺详情自动补齐,之后按配置间隔定时增量刷新 - 管理 API:GET/POST /admin/api/wiki/term-sync* 管理后台: - 百科条目 Tab 顶部加节气同步卡片(X/24 + 状态 + 同步按钮) - 条目编辑器加配图 URL(含预览)和节气扩展字段编辑 小程序: - 百科页展开详情图文展示:配图 + 简介 + 物候/习俗/养生等分块 本地验证:24/24 节气抓取成功,image/extra 全部填充,维基数据不受影响
221 lines
10 KiB
JavaScript
221 lines
10 KiB
JavaScript
const { SOLAR_TERMS, LUNAR_FESTIVALS, SOLAR_FESTIVALS, HEAVEN_STEMS, EARTH_BRANCHES, ZODIACS } = require('../../utils/core/data.js');
|
||
const { request } = require('../../utils/request.js');
|
||
|
||
const CATEGORIES = [
|
||
{ key: 'all', name: '全部' },
|
||
{ key: 'term', name: '二十四节气' },
|
||
{ key: 'lunarFest', name: '农历节日' },
|
||
{ key: 'solarFest', name: '公历节日' },
|
||
{ key: 'ganzhi', name: '干支生肖' },
|
||
{ key: 'termExplain', name: '黄历术语' },
|
||
{ key: 'other', name: '其他' }
|
||
];
|
||
|
||
// 服务端分类 key 到展示名的映射
|
||
const CAT_NAMES = {
|
||
term: '节气', lunarFest: '农历节日', solarFest: '公历节日',
|
||
ganzhi: '干支生肖', termExplain: '术语', other: '其他'
|
||
};
|
||
|
||
const TERM_BRIEFS = {
|
||
'立春': '春季开始,万物复苏', '雨水': '降雨增多,气温回升', '惊蛰': '春雷惊醒蛰伏动物',
|
||
'春分': '昼夜平分,春季过半', '清明': '天气晴朗,草木繁茂', '谷雨': '雨生百谷,播种时节',
|
||
'立夏': '夏季开始,万物生长', '小满': '麦类籽粒开始饱满', '芒种': '有芒作物成熟,忙于播种',
|
||
'夏至': '白昼最长,盛夏来临', '小暑': '天气开始炎热', '大暑': '一年中最热的时期',
|
||
'立秋': '秋季开始,暑去凉来', '处暑': '暑气结束,秋意渐浓', '白露': '天气转凉,出现露水',
|
||
'秋分': '昼夜平分,秋季过半', '寒露': '露水更凉,秋意更浓', '霜降': '开始降霜,天气渐冷',
|
||
'立冬': '冬季开始,万物收藏', '小雪': '开始降雪,气温下降', '大雪': '降雪增多,天气更冷',
|
||
'冬至': '白昼最短,数九开始', '小寒': '天气寒冷,但未到极点', '大寒': '一年中最冷的时期'
|
||
};
|
||
|
||
const TERM_EXPLAINS = [
|
||
{ title: '干支纪年', brief: '天干地支组合纪年法', content: '天干有十:甲乙丙丁戊己庚辛壬癸;地支有十二:子丑寅卯辰巳午未申酉戌亥。两者按顺序两两组合,形成六十甲子循环,用于纪年、纪月、纪日、纪时。' },
|
||
{ title: '建除十二神', brief: '建、除、满、平、定、执、破、危、成、收、开、闭', content: '建除十二神又称十二值日,是古代择日的重要依据。以月建起建,按地支顺序排列。建日宜出行,除日宜疗病,满日宜嫁娶,平日宜修造,定日宜祈福,执日宜祭祀,破日宜破屋,危日宜安床,成日宜开市,收日宜纳财,开日宜开光,闭日宜安葬。' },
|
||
{ title: '黄道吉日', brief: '青龙、明堂、金匮、天德、玉堂、司命为黄道', content: '黄道黑道是古代择日术语。黄道日(青龙、明堂、金匮、天德、玉堂、司命值日)诸事皆宜;黑道日(天刑、朱雀、白虎、天牢、玄武、勾陈值日)宜避凶就吉。' },
|
||
{ title: '二十八宿', brief: '古代天文学家将黄道附近的星宿分为二十八组', content: '二十八宿分东方青龙(角亢氐房心尾箕)、北方玄武(斗牛女虚危室壁)、西方白虎(奎娄胃昴毕觜参)、南方朱雀(井鬼柳星张翼轸)四象,每象七宿。古人用二十八宿来判断日子的吉凶。' },
|
||
{ title: '六曜', brief: '先胜、友引、先负、佛灭、大安、赤口', content: '六曜起源于中国,后传入日本并广泛流行。大安日诸事皆吉;先胜日上午吉;友引日早晚吉;先负日上午凶;佛灭日大凶;赤口日午凶。以农历月日之和除以六取余数确定。' },
|
||
{ title: '彭祖百忌', brief: '每日天干地支对应的禁忌事项', content: '彭祖百忌是古代择日学的重要内容,根据每日的天干地支组合给出相应的禁忌。如"甲不开仓财物耗散,子不问卜自惹祸殃"等,共六十条对应六十甲子。' },
|
||
{ title: '冲煞', brief: '日支与某生肖相冲,该生肖之人需谨慎', content: '冲煞指日支与某地支相冲(如子午相冲、丑未相冲等),对应的生肖之人在当日需谨慎行事。煞方指当日凶煞所在的方位,不宜在该方位动土或远行。' },
|
||
{ title: '纳音五行', brief: '六十甲子与五行的配合', content: '纳音是将六十甲子与五行相配,每两组干支合为一种纳音,共三十种。如甲子乙丑海中金、丙寅丁卯炉中火等,用于命理分析。' },
|
||
{ title: '月相', brief: '朔、上弦、望、下弦的月相变化', content: '农历初一为朔(新月),初七初八为上弦月,十五为望(满月),廿二廿三为下弦月。月相变化影响潮汐,也被古人用于判断吉凶。' },
|
||
{ title: '胎神方位', brief: '孕妇需避开的方位', content: '胎神是传统民俗中保护胎儿的神灵,每日所在方位不同。孕妇不宜在胎神方位动土、钉钉或搬动重物,以免惊动胎神。' }
|
||
];
|
||
|
||
// 节气扩展字段的展示顺序与中文标签
|
||
const EXTRA_LABELS = [
|
||
{ key: 'alias', name: '别名' },
|
||
{ key: 'english', name: '外文名' },
|
||
{ key: 'meaning', name: '涵义' },
|
||
{ key: 'solarDate', name: '公历时间' },
|
||
{ key: 'ecliptic', name: '黄道位置' },
|
||
{ key: 'climate', name: '气候特点' },
|
||
{ key: 'phenology', name: '物候现象' },
|
||
{ key: 'farming', name: '农事活动' },
|
||
{ key: 'customs', name: '传统习俗' },
|
||
{ key: 'health', name: '起居养生' },
|
||
{ key: 'flower', name: '花信' }
|
||
];
|
||
|
||
// buildExtraList 把服务端 extra JSON 字符串解析为 [{name, value}] 展示列表(仅保留非空项,按定义顺序)
|
||
function buildExtraList(extraStr) {
|
||
if (!extraStr) return [];
|
||
let obj = {};
|
||
try { obj = JSON.parse(extraStr); } catch (e) { return []; }
|
||
const list = [];
|
||
EXTRA_LABELS.forEach(f => {
|
||
const v = (obj[f.key] || '').trim();
|
||
if (v) list.push({ name: f.name, value: v });
|
||
});
|
||
return list;
|
||
}
|
||
|
||
function buildEntries() {
|
||
const entries = [];
|
||
|
||
// 二十四节气
|
||
SOLAR_TERMS.forEach(name => {
|
||
entries.push({
|
||
cat: 'term', catName: '节气', title: name,
|
||
brief: TERM_BRIEFS[name] || '二十四节气之一',
|
||
content: TERM_BRIEFS[name] || '二十四节气之一,是古人根据太阳在黄道上的位置划分的时令节点。',
|
||
open: false
|
||
});
|
||
});
|
||
|
||
// 农历节日
|
||
const seenLunar = new Set();
|
||
Object.entries(LUNAR_FESTIVALS).forEach(([key, name]) => {
|
||
if (seenLunar.has(name)) return;
|
||
seenLunar.add(name);
|
||
const [m, d] = key.split('-');
|
||
entries.push({
|
||
cat: 'lunarFest', catName: '农历节日', title: name,
|
||
brief: `农历${m}月${d}日`,
|
||
content: `${name},农历${m}月${d}日,是中国传统农历节日。`,
|
||
open: false
|
||
});
|
||
});
|
||
|
||
// 公历节日
|
||
const seenSolar = new Set();
|
||
Object.entries(SOLAR_FESTIVALS).forEach(([key, name]) => {
|
||
if (seenSolar.has(name)) return;
|
||
seenSolar.add(name);
|
||
const [m, d] = key.split('-');
|
||
entries.push({
|
||
cat: 'solarFest', catName: '公历节日', title: name,
|
||
brief: `公历${m}月${d}日`,
|
||
content: `${name},公历${m}月${d}日。`,
|
||
open: false
|
||
});
|
||
});
|
||
|
||
// 干支生肖
|
||
HEAVEN_STEMS.forEach((s, i) => {
|
||
entries.push({
|
||
cat: 'ganzhi', catName: '天干', title: s,
|
||
brief: `十天干之第${i + 1}位`,
|
||
content: `${s}为十天干之第${i + 1}位,五行属${['木','木','火','火','土','土','金','金','水','水'][i]},属${i % 2 === 0 ? '阳' : '阴'}。`,
|
||
open: false
|
||
});
|
||
});
|
||
EARTH_BRANCHES.forEach((b, i) => {
|
||
entries.push({
|
||
cat: 'ganzhi', catName: '地支', title: `${b}(${ZODIACS[i]})`,
|
||
brief: `十二地支之第${i + 1}位,生肖属${ZODIACS[i]}`,
|
||
content: `${b}为十二地支之第${i + 1}位,生肖属${ZODIACS[i]},五行属${['水','土','木','木','土','火','火','土','金','金','土','水'][i]}。`,
|
||
open: false
|
||
});
|
||
});
|
||
|
||
// 黄历术语
|
||
TERM_EXPLAINS.forEach(t => {
|
||
entries.push({ cat: 'termExplain', catName: '术语', ...t, open: false });
|
||
});
|
||
|
||
return entries;
|
||
}
|
||
|
||
Page({
|
||
data: {
|
||
categories: CATEGORIES,
|
||
activeCat: 'all',
|
||
keyword: '',
|
||
entries: [],
|
||
filteredEntries: []
|
||
},
|
||
|
||
onLoad() {
|
||
const entries = buildEntries();
|
||
this.setData({ entries, filteredEntries: entries });
|
||
this.loadServerEntries();
|
||
},
|
||
|
||
/** 从服务端加载百科条目(管理后台可维护),与本地数据合并:同标题以服务端为准,新增条目追加 */
|
||
loadServerEntries() {
|
||
request({ url: '/api/wiki/entries' })
|
||
.then(res => {
|
||
if (!res || res.code !== 0 || !res.data || !res.data.list) return;
|
||
const merged = [...this.data.entries];
|
||
const titleIndex = {};
|
||
merged.forEach((e, i) => { titleIndex[e.title] = i; });
|
||
|
||
res.data.list.forEach(s => {
|
||
const entry = {
|
||
cat: s.category,
|
||
catName: CAT_NAMES[s.category] || s.category,
|
||
title: s.title,
|
||
brief: s.brief,
|
||
content: s.content,
|
||
image: s.image || '',
|
||
extraList: buildExtraList(s.extra),
|
||
open: false
|
||
};
|
||
if (titleIndex[s.title] !== undefined) {
|
||
merged[titleIndex[s.title]] = entry; // 服务端内容覆盖本地
|
||
} else {
|
||
merged.push(entry);
|
||
}
|
||
});
|
||
|
||
this.setData({ entries: merged }, () => this.filter());
|
||
})
|
||
.catch(() => { /* 网络失败时使用本地内置数据 */ });
|
||
},
|
||
|
||
switchCat(e) {
|
||
this.setData({ activeCat: e.currentTarget.dataset.key }, () => this.filter());
|
||
},
|
||
|
||
onSearch(e) {
|
||
this.setData({ keyword: e.detail.value.trim() }, () => this.filter());
|
||
},
|
||
|
||
filter() {
|
||
const { entries, activeCat, keyword } = this.data;
|
||
let list = entries;
|
||
if (activeCat !== 'all') list = list.filter(t => t.cat === activeCat);
|
||
if (keyword) list = list.filter(t => t.title.includes(keyword) || t.brief.includes(keyword) || t.content.includes(keyword));
|
||
this.setData({ filteredEntries: list });
|
||
},
|
||
|
||
toggleEntry(e) {
|
||
const index = e.currentTarget.dataset.index;
|
||
const key = `filteredEntries[${index}].open`;
|
||
this.setData({ [key]: !this.data.filteredEntries[index].open });
|
||
},
|
||
|
||
onShareAppMessage() {
|
||
return {
|
||
title: '黄历百科 - 传统文化知识',
|
||
path: '/pages/wiki/wiki'
|
||
};
|
||
},
|
||
|
||
onShareTimeline() {
|
||
return {
|
||
title: '黄历百科 - 传统文化知识'
|
||
};
|
||
}
|
||
});
|