每日运势小程序
This commit is contained in:
62
DailyFortuneGuide/utils/adService.js
Normal file
62
DailyFortuneGuide/utils/adService.js
Normal file
@@ -0,0 +1,62 @@
|
||||
const config = require("../config/index");
|
||||
|
||||
function showRewardedVideoAd() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!config.adUnitId) {
|
||||
console.warn("adUnitId 未配置,跳过广告流程(仅开发阶段)");
|
||||
resolve("ad_skipped");
|
||||
return;
|
||||
}
|
||||
if (typeof tt === "undefined" || !tt.createRewardedVideoAd) {
|
||||
reject(new Error("rewarded_ad_unavailable"));
|
||||
return;
|
||||
}
|
||||
|
||||
const ad = tt.createRewardedVideoAd({
|
||||
adUnitId: config.adUnitId
|
||||
});
|
||||
|
||||
const cleanup = () => {
|
||||
if (!ad) {
|
||||
return;
|
||||
}
|
||||
if (ad.offLoad) {
|
||||
ad.offLoad();
|
||||
}
|
||||
if (ad.offError) {
|
||||
ad.offError();
|
||||
}
|
||||
if (ad.offClose) {
|
||||
ad.offClose();
|
||||
}
|
||||
};
|
||||
|
||||
ad.onError((error) => {
|
||||
cleanup();
|
||||
reject(error);
|
||||
});
|
||||
|
||||
ad.onClose((result = {}) => {
|
||||
cleanup();
|
||||
if (result.isEnded) {
|
||||
resolve();
|
||||
} else {
|
||||
const err = new Error("ad_not_completed");
|
||||
err.code = "ad_not_completed";
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
|
||||
ad
|
||||
.load()
|
||||
.then(() => ad.show())
|
||||
.catch((error) => {
|
||||
cleanup();
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
showRewardedVideoAd
|
||||
};
|
||||
27
DailyFortuneGuide/utils/date.js
Normal file
27
DailyFortuneGuide/utils/date.js
Normal file
@@ -0,0 +1,27 @@
|
||||
function pad(num) {
|
||||
return `${num}`.padStart(2, "0");
|
||||
}
|
||||
|
||||
function formatDate(date) {
|
||||
const target = date instanceof Date ? date : new Date(date);
|
||||
return `${target.getFullYear()}-${pad(target.getMonth() + 1)}-${pad(target.getDate())}`;
|
||||
}
|
||||
|
||||
function formatDisplayDateTime(value) {
|
||||
if (!value) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
const target = new Date(value);
|
||||
return `${target.getFullYear()}-${pad(target.getMonth() + 1)}-${pad(target.getDate())} ${pad(target.getHours())}:${pad(
|
||||
target.getMinutes()
|
||||
)}`;
|
||||
} catch (error) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
formatDate,
|
||||
formatDisplayDateTime
|
||||
};
|
||||
150
DailyFortuneGuide/utils/fortuneFormatter.js
Normal file
150
DailyFortuneGuide/utils/fortuneFormatter.js
Normal file
@@ -0,0 +1,150 @@
|
||||
const dimensionMeta = require("../constants/dimensions");
|
||||
const { formatDisplayDateTime } = require("./date");
|
||||
|
||||
const dimensionMap = dimensionMeta.reduce((acc, item) => {
|
||||
acc[item.key] = item;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
function pickValue(source, keys, fallback) {
|
||||
if (!source) {
|
||||
return fallback;
|
||||
}
|
||||
for (const key of keys) {
|
||||
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
||||
const value = source[key];
|
||||
if (value !== undefined && value !== null) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function mapTrendText(trend) {
|
||||
if (trend === "up") {
|
||||
return "回升";
|
||||
}
|
||||
if (trend === "down") {
|
||||
return "波动";
|
||||
}
|
||||
return "平稳";
|
||||
}
|
||||
|
||||
function normalizePillars(pillars) {
|
||||
if (!Array.isArray(pillars)) {
|
||||
return [];
|
||||
}
|
||||
return pillars
|
||||
.map((item) => ({
|
||||
label: pickValue(item, ["label", "Label"], ""),
|
||||
value: pickValue(item, ["value", "Value"], "")
|
||||
}))
|
||||
.filter((item) => item.label || item.value);
|
||||
}
|
||||
|
||||
function formatListText(list, separator = " · ") {
|
||||
if (!Array.isArray(list) || !list.length) {
|
||||
return "";
|
||||
}
|
||||
return list.join(separator);
|
||||
}
|
||||
|
||||
function normalizeFortunePayload(fortune) {
|
||||
if (!fortune) {
|
||||
return null;
|
||||
}
|
||||
const dimensionListRaw = pickValue(fortune, ["dimensions", "Dimensions"], []);
|
||||
const dimensionList = Array.isArray(dimensionListRaw) ? dimensionListRaw : [];
|
||||
const dimensions = dimensionList.map((item) => {
|
||||
const key = pickValue(item, ["key", "Key"], "");
|
||||
const meta = dimensionMap[key] || {};
|
||||
const trend = pickValue(item, ["trend", "Trend"], "steady");
|
||||
return {
|
||||
key,
|
||||
title: pickValue(item, ["title", "Title"], meta.title || key),
|
||||
score: pickValue(item, ["score", "Score"], 0),
|
||||
trend,
|
||||
insight: pickValue(item, ["insight", "Insight"], ""),
|
||||
suggestion: pickValue(item, ["suggestion", "Suggestion"], ""),
|
||||
icon: meta.icon || "⭐",
|
||||
accent: meta.accent || "#ffb85c",
|
||||
trendText: mapTrendText(trend)
|
||||
};
|
||||
});
|
||||
const total = dimensions.reduce((acc, current) => acc + (current.score || 0), 0);
|
||||
const overallScore = dimensions.length ? Math.round(total / dimensions.length) : 0;
|
||||
|
||||
return {
|
||||
fortuneDate: pickValue(fortune, ["fortuneDate", "FortuneDate"], ""),
|
||||
summary: pickValue(fortune, ["summary", "Summary"], ""),
|
||||
narrative: pickValue(fortune, ["narrative", "Narrative"], ""),
|
||||
dimensions,
|
||||
overallScore,
|
||||
luckyGuide: normalizeGuide(pickValue(fortune, ["luckyGuide", "LuckyGuide"], null)),
|
||||
profile: normalizeProfile(pickValue(fortune, ["profile", "Profile"], null))
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeProfile(profile) {
|
||||
if (!profile) {
|
||||
return null;
|
||||
}
|
||||
const fiveElements = pickValue(profile, ["fiveElementDistribution", "FiveElementDistribution"], []);
|
||||
const total = fiveElements.reduce((acc, item) => acc + (pickValue(item, ["count", "Count"], 0) || 0), 0) || 1;
|
||||
const distribution = fiveElements.map((item) => {
|
||||
const count = pickValue(item, ["count", "Count"], 0);
|
||||
const percent = Math.round(((count || 0) / total) * 100);
|
||||
return {
|
||||
element: pickValue(item, ["element", "Element"], "未知"),
|
||||
count,
|
||||
percent,
|
||||
width: `${Math.max(percent, 6)}%`
|
||||
};
|
||||
});
|
||||
const birthDateTimeRaw = pickValue(profile, ["birthDateTime", "BirthDateTime"], "");
|
||||
return {
|
||||
birthCity: pickValue(profile, ["birthCity", "BirthCity"], ""),
|
||||
birthProvince: pickValue(profile, ["birthProvince", "BirthProvince"], ""),
|
||||
birthDateTime: birthDateTimeRaw ? formatDisplayDateTime(birthDateTimeRaw) : "",
|
||||
birthPillars: normalizePillars(pickValue(profile, ["birthPillars", "BirthPillars"], [])),
|
||||
todayPillars: normalizePillars(pickValue(profile, ["todayPillars", "TodayPillars"], [])),
|
||||
distribution,
|
||||
weakElements: pickValue(profile, ["weakElements", "WeakElements"], []),
|
||||
strongElements: pickValue(profile, ["strongElements", "StrongElements"], [])
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeGuide(guide) {
|
||||
if (!guide) {
|
||||
return null;
|
||||
}
|
||||
const slots = pickValue(guide, ["bestTimeSlots", "BestTimeSlots"], []);
|
||||
const colors = pickValue(guide, ["colors", "Colors"], []);
|
||||
const directions = pickValue(guide, ["directions", "Directions"], []);
|
||||
const props = pickValue(guide, ["props", "Props"], []);
|
||||
const activities = pickValue(guide, ["activities", "Activities"], []);
|
||||
return {
|
||||
element: pickValue(guide, ["element", "Element"], "木"),
|
||||
colors,
|
||||
colorText: formatListText(colors),
|
||||
directions,
|
||||
directionText: formatListText(directions),
|
||||
props,
|
||||
propsText: formatListText(props),
|
||||
activities,
|
||||
activitiesText: formatListText(activities, " / "),
|
||||
bestTimeSlots: Array.isArray(slots)
|
||||
? slots.map((slot) => ({
|
||||
label: pickValue(slot, ["label", "Label"], "时段"),
|
||||
period: pickValue(slot, ["period", "Period"], ""),
|
||||
reason: pickValue(slot, ["reason", "Reason"], "")
|
||||
}))
|
||||
: []
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
normalizeFortunePayload
|
||||
};
|
||||
|
||||
81
DailyFortuneGuide/utils/region.js
Normal file
81
DailyFortuneGuide/utils/region.js
Normal file
@@ -0,0 +1,81 @@
|
||||
const MAX_REGION_LENGTH = 3;
|
||||
|
||||
function sanitizeSegment(value) {
|
||||
if (typeof value !== "string") {
|
||||
return "";
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed || trimmed === "全部") {
|
||||
return "";
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
function normalizeRegionArray(region) {
|
||||
if (!Array.isArray(region)) {
|
||||
return [];
|
||||
}
|
||||
const sanitized = region.map(sanitizeSegment);
|
||||
const hasValue = sanitized.some(Boolean);
|
||||
if (!hasValue) {
|
||||
return [];
|
||||
}
|
||||
const normalized = sanitized.slice(0, MAX_REGION_LENGTH);
|
||||
while (normalized.length < MAX_REGION_LENGTH) {
|
||||
normalized.push("");
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function deriveRegionFromParts(parts = {}) {
|
||||
const segments = [];
|
||||
if (parts.province) {
|
||||
segments.push(parts.province);
|
||||
}
|
||||
if (parts.city) {
|
||||
segments.push(parts.city);
|
||||
}
|
||||
if (parts.district && parts.district !== parts.city) {
|
||||
segments.push(parts.district);
|
||||
}
|
||||
if (!segments.length && parts.city) {
|
||||
segments.push(parts.city);
|
||||
}
|
||||
return normalizeRegionArray(segments);
|
||||
}
|
||||
|
||||
function getRegionParts(region, fallback = {}) {
|
||||
const normalized = normalizeRegionArray(region);
|
||||
if (!normalized.length) {
|
||||
return {
|
||||
province: sanitizeSegment(fallback.province),
|
||||
city: sanitizeSegment(fallback.city),
|
||||
district: sanitizeSegment(fallback.district)
|
||||
};
|
||||
}
|
||||
const [province = "", city = "", district = ""] = normalized;
|
||||
return {
|
||||
province: province || sanitizeSegment(fallback.province),
|
||||
city: city || province || sanitizeSegment(fallback.city),
|
||||
district: district || sanitizeSegment(fallback.district)
|
||||
};
|
||||
}
|
||||
|
||||
function formatRegionDisplay(region, fallbackList = []) {
|
||||
const normalized = normalizeRegionArray(region);
|
||||
const source = normalized.length ? normalized : fallbackList;
|
||||
if (!source || !source.length) {
|
||||
return "";
|
||||
}
|
||||
return source
|
||||
.filter(sanitizeSegment)
|
||||
.filter((item, index, arr) => arr.indexOf(item) === index)
|
||||
.join(" · ");
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
normalizeRegionArray,
|
||||
deriveRegionFromParts,
|
||||
getRegionParts,
|
||||
formatRegionDisplay
|
||||
};
|
||||
52
DailyFortuneGuide/utils/safeArea.js
Normal file
52
DailyFortuneGuide/utils/safeArea.js
Normal file
@@ -0,0 +1,52 @@
|
||||
const DEFAULT_PADDING = 64;
|
||||
const EXTRA_SPACING = 12;
|
||||
|
||||
function getMenuPadding(extraSpacing) {
|
||||
if (typeof tt === "undefined" || typeof tt.getMenuButtonBoundingClientRect !== "function") {
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
const rect = tt.getMenuButtonBoundingClientRect();
|
||||
if (!rect) {
|
||||
return 0;
|
||||
}
|
||||
if (typeof rect.bottom === "number") {
|
||||
return rect.bottom + extraSpacing;
|
||||
}
|
||||
if (typeof rect.top === "number" && typeof rect.height === "number") {
|
||||
return rect.top + rect.height + extraSpacing;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("safe-area: menu rect unavailable", error);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function getStatusBarPadding(extraSpacing) {
|
||||
if (typeof tt === "undefined" || typeof tt.getSystemInfoSync !== "function") {
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
const info = tt.getSystemInfoSync();
|
||||
return (info && info.statusBarHeight ? info.statusBarHeight : 0) + extraSpacing;
|
||||
} catch (error) {
|
||||
console.warn("safe-area: system info unavailable", error);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function getPageSafeTop(extraSpacing = EXTRA_SPACING, fallback = DEFAULT_PADDING) {
|
||||
const menuPadding = getMenuPadding(extraSpacing);
|
||||
if (menuPadding) {
|
||||
return Math.round(menuPadding);
|
||||
}
|
||||
const statusPadding = getStatusBarPadding(extraSpacing);
|
||||
if (statusPadding) {
|
||||
return Math.round(statusPadding);
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getPageSafeTop
|
||||
};
|
||||
31
DailyFortuneGuide/utils/storage.js
Normal file
31
DailyFortuneGuide/utils/storage.js
Normal file
@@ -0,0 +1,31 @@
|
||||
function safeGetStorage(key, fallback) {
|
||||
if (typeof tt === "undefined" || !tt.getStorageSync) {
|
||||
return fallback;
|
||||
}
|
||||
try {
|
||||
const value = tt.getStorageSync(key);
|
||||
if (value === undefined || value === null) {
|
||||
return fallback;
|
||||
}
|
||||
return value;
|
||||
} catch (error) {
|
||||
console.warn(`getStorageSync failed: ${key}`, error);
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
function safeSetStorage(key, value) {
|
||||
if (typeof tt === "undefined" || !tt.setStorageSync) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
tt.setStorageSync(key, value);
|
||||
} catch (error) {
|
||||
console.warn(`setStorageSync failed: ${key}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
safeGetStorage,
|
||||
safeSetStorage
|
||||
};
|
||||
Reference in New Issue
Block a user