This commit is contained in:
Your Name
2026-08-11 17:41:36 +08:00
parent cfe4c82c90
commit 03fe4ddf9d
18771 changed files with 3617239 additions and 0 deletions
@@ -0,0 +1,86 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
if (!Math) {
TongjiIcon();
}
const TongjiIcon = () => "./TongjiIcon.js";
const _sfc_main = {
__name: "CelebrateBurst",
props: {
show: { type: Boolean, default: false },
title: { type: String, default: "" },
subtitle: { type: String, default: "" }
},
setup(__props) {
const props = __props;
const particles = common_vendor.ref([]);
const cardPop = common_vendor.ref(false);
const COLORS = ["#204E2B", "#386641", "#AFE2B3", "#FBBF24", "#727970", "#DC2626"];
function buildParticles() {
const list = [];
for (let i = 0; i < 18; i++) {
const left = 8 + Math.random() * 84;
const delay = Math.random() * 0.35;
const hue = COLORS[i % COLORS.length];
const size = 10 + Math.floor(Math.random() * 14);
list.push({
id: `${Date.now()}_${i}`,
style: {
left: `${left}%`,
width: `${size}rpx`,
height: `${size}rpx`,
background: hue,
animationDelay: `${delay}s`
}
});
}
return list;
}
common_vendor.watch(
() => props.show,
(v) => {
if (v) {
particles.value = buildParticles();
cardPop.value = false;
setTimeout(() => {
cardPop.value = true;
}, 30);
} else {
particles.value = [];
cardPop.value = false;
}
}
);
return (_ctx, _cache) => {
return common_vendor.e({
a: __props.show
}, __props.show ? common_vendor.e({
b: common_vendor.f(particles.value, (p, k0, i0) => {
return {
a: p.id,
b: common_vendor.s(p.style)
};
}),
c: __props.title
}, __props.title ? common_vendor.e({
d: common_vendor.p({
name: "sparkles",
size: "lg",
color: "#204E2B"
}),
e: common_vendor.t(__props.title),
f: __props.subtitle
}, __props.subtitle ? {
g: common_vendor.t(__props.subtitle)
} : {}, {
h: cardPop.value ? 1 : ""
}) : {}, {
i: common_vendor.o(() => {
}, "49")
}) : {});
};
}
};
const Component = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-313f45d6"]]);
wx.createComponent(Component);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/components/CelebrateBurst.js.map
@@ -0,0 +1,6 @@
{
"component": true,
"usingComponents": {
"tongji-icon": "./TongjiIcon"
}
}
@@ -0,0 +1 @@
<view wx:if="{{a}}" class="celebrate-root data-v-313f45d6" catchtouchmove="{{i}}"><view wx:for="{{b}}" wx:for-item="p" wx:key="a" class="celebrate-particle data-v-313f45d6" style="{{p.b}}"/><view wx:if="{{c}}" class="{{['celebrate-card', 'data-v-313f45d6', h && 'pop']}}"><view class="celebrate-card-glow data-v-313f45d6"/><tongji-icon wx:if="{{d}}" class="data-v-313f45d6" u-i="313f45d6-0" bind:__l="__l" u-p="{{d}}"/><text class="celebrate-card-title data-v-313f45d6">{{e}}</text><text wx:if="{{f}}" class="celebrate-card-sub data-v-313f45d6">{{g}}</text></view></view>
@@ -0,0 +1,95 @@
/**
* 这里是uni-app内置的常用样式变量
*
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
*
*/
/**
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
*
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
*/
/* 颜色变量 */
/* 行为相关颜色 */
/* 文字基本颜色 */
/* 背景颜色 */
/* 边框颜色 */
/* 尺寸变量 */
/* 文字尺寸 */
/* 图片尺寸 */
/* Border Radius */
/* 水平间距 */
/* 垂直间距 */
/* 透明度 */
/* 文章场景相关 */
.celebrate-root.data-v-313f45d6 {
position: fixed;
inset: 0;
z-index: 9999;
pointer-events: none;
overflow: hidden;
}
.celebrate-particle.data-v-313f45d6 {
position: absolute;
top: -20rpx;
border-radius: 4rpx;
opacity: 0.9;
animation: celebrate-fall-313f45d6 1.6s ease-in forwards;
}
@keyframes celebrate-fall-313f45d6 {
0% {
transform: translateY(0) rotate(0deg) scale(1);
opacity: 1;
}
100% {
transform: translateY(110vh) rotate(540deg) scale(0.4);
opacity: 0;
}
}
.celebrate-card.data-v-313f45d6 {
position: absolute;
left: 50%;
top: 38%;
transform: translate(-50%, -50%) scale(0.82);
width: 78%;
max-width: 560rpx;
padding: 36rpx 32rpx 32rpx;
background: rgba(255, 255, 255, 0.96);
border-radius: 28rpx;
box-shadow: 0 20rpx 60rpx rgba(8, 145, 178, 0.22);
border: 2rpx solid rgba(8, 145, 178, 0.12);
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
opacity: 0;
transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.25s ease;
}
.celebrate-card.pop.data-v-313f45d6 {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
.celebrate-card-glow.data-v-313f45d6 {
position: absolute;
inset: -20rpx;
border-radius: 36rpx;
background: radial-gradient(circle, rgba(34, 211, 238, 0.2), transparent 70%);
pointer-events: none;
}
.celebrate-card-title.data-v-313f45d6 {
position: relative;
z-index: 1;
margin-top: 16rpx;
font-size: 36rpx;
font-weight: 800;
color: #1b1c1a;
}
.celebrate-card-sub.data-v-313f45d6 {
position: relative;
z-index: 1;
margin-top: 10rpx;
font-size: 26rpx;
color: #475569;
line-height: 1.45;
}
@@ -0,0 +1,100 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const tongji_utils_svgDataUrl = require("../utils/svgDataUrl.js");
const tongji_utils_treeLevels = require("../utils/treeLevels.js");
const _sfc_main = {
__name: "SugarTreeGraphic",
props: {
level: { type: Number, default: 0 },
size: { type: String, default: "md" },
watering: { type: Boolean, default: false }
},
setup(__props) {
const MAX_LEVEL = tongji_utils_treeLevels.TREE_MAX_LEVEL;
const treeUseFallback = common_vendor.ref(true);
const props = __props;
const clampedLevel = common_vendor.computed(() => Math.min(MAX_LEVEL, Math.max(0, Number(props.level) || 0)));
const visualTier = common_vendor.computed(() => {
const lv = clampedLevel.value;
if (lv <= 0)
return 0;
if (lv <= 2)
return 1;
if (lv <= 4)
return 2;
if (lv <= 6)
return 3;
if (lv <= 8)
return 4;
return 5;
});
const treeEmoji = common_vendor.computed(() => (tongji_utils_treeLevels.TREE_LEVELS[clampedLevel.value] || tongji_utils_treeLevels.TREE_LEVELS[0]).emoji);
function buildTreeSvg(level) {
const pot = `
<ellipse cx="24" cy="50" rx="15" ry="3" fill="#204e2b" opacity="0.12"/>
<path d="M11 46h26c1.2 0 2 1 2 2.2v3.8c0 1-.8 1.8-1.8 1.8H10.8c-1 0-1.8-.8-1.8-1.8v-3.8c0-1.2.8-2.2 2-2.2z" fill="#E7E5E4"/>
<path d="M12.5 46h23c.8 0 1.5.7 1.5 1.5v2.2c0 .6-.5 1.1-1.1 1.1H12.1c-.6 0-1.1-.5-1.1-1.1v-2.2c0-.8.7-1.5 1.5-1.5z" fill="#D6D3D1"/>
<ellipse cx="24" cy="46.5" rx="9" ry="1.6" fill="#A8A29E" opacity="0.35"/>
`;
const soil = `<ellipse cx="24" cy="44.5" rx="8" ry="2.2" fill="#386641" opacity="0.18"/>`;
if (level <= 0) {
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 56" fill="none">
${pot}${soil}
<circle cx="24" cy="41.5" r="2.2" fill="#a8a29e" opacity="0.7"/>
<path d="M24 41.5v3.5" stroke="#78716c" stroke-width="1" stroke-linecap="round"/>
</svg>`;
}
const trunkH = level >= 6 ? 16 : level >= 3 ? 14 : 10;
const trunkY = 46 - trunkH;
const trunk = `<rect x="22.2" y="${trunkY}" width="3.6" height="${trunkH}" rx="1.8" fill="#78716C"/>
<rect x="22.6" y="${trunkY + 1}" width="2.8" height="${trunkH - 1}" rx="1.4" fill="#A8A29E" opacity="0.35"/>`;
const leaf = (cx, cy, r, fill, opacity = 1) => `<circle cx="${cx}" cy="${cy}" r="${r}" fill="${fill}" opacity="${opacity}"/>
<circle cx="${cx - r * 0.25}" cy="${cy - r * 0.2}" r="${r * 0.35}" fill="#eef6ef" opacity="0.55"/>`;
const bloom = level >= 7 ? leaf(17, 18, 3, "#fda4af", 0.95) + leaf(31, 17, 2.8, "#f9a8d4", 0.9) + leaf(24, 13, 3.2, "#fb7185", 0.95) + `<circle cx="24" cy="12" r="1.3" fill="#fef3c7"/>` : "";
const crown = level >= 9 ? leaf(12, 22, 6, "#34d399", 0.9) + leaf(36, 22, 6, "#34d399", 0.9) + leaf(24, 10, 7, "#386641", 0.95) + bloom : bloom;
let canopy = "";
if (level === 1) {
canopy = leaf(24, 38, 4, "#afe2b3") + `<path d="M24 38v-5" stroke="#386641" stroke-width="1.2" stroke-linecap="round"/>`;
} else if (level === 2) {
canopy = leaf(24, 32, 5.5, "#34d399") + leaf(20, 34, 3.5, "#6ee7b7", 0.9);
} else if (level <= 4) {
canopy = leaf(24, 28, 7, "#34d399") + leaf(17, 30, 5, "#6ee7b7", 0.9) + leaf(31, 30, 5, "#6ee7b7", 0.9);
} else if (level <= 6) {
canopy = leaf(24, 24, 9, "#22c55e") + leaf(14, 26, 7, "#4ade80", 0.92) + leaf(34, 26, 7, "#4ade80", 0.92) + leaf(24, 16, 6, "#386641", 0.88);
} else {
canopy = leaf(24, 20, 10, "#204e2b") + leaf(13, 24, 8, "#34d399", 0.95) + leaf(35, 24, 8, "#34d399", 0.95) + crown;
}
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 56" fill="none">${pot}${soil}${trunk}${canopy}</svg>`;
}
const treeImageSrc = common_vendor.computed(() => tongji_utils_svgDataUrl.svgToDataUrl(buildTreeSvg(clampedLevel.value)));
return (_ctx, _cache) => {
return common_vendor.e({
a: clampedLevel.value >= 7
}, clampedLevel.value >= 7 ? {} : {}, {
b: !treeUseFallback.value
}, !treeUseFallback.value ? {
c: treeImageSrc.value,
d: common_vendor.o(($event) => treeUseFallback.value = true, "20")
} : {
e: common_vendor.t(treeEmoji.value)
}, {
f: clampedLevel.value >= 7
}, clampedLevel.value >= 7 ? {} : {}, {
g: clampedLevel.value >= 8
}, clampedLevel.value >= 8 ? {} : {}, {
h: clampedLevel.value >= common_vendor.unref(MAX_LEVEL)
}, clampedLevel.value >= common_vendor.unref(MAX_LEVEL) ? {} : {}, {
i: common_vendor.n(`lv-${clampedLevel.value}`),
j: common_vendor.n(`tier-${visualTier.value}`),
k: common_vendor.n(`size-${__props.size}`),
l: common_vendor.n({
watering: __props.watering,
"is-max": clampedLevel.value >= common_vendor.unref(MAX_LEVEL)
})
});
};
}
};
const Component = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-d91cb5e7"]]);
wx.createComponent(Component);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/components/SugarTreeGraphic.js.map
@@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}
@@ -0,0 +1 @@
<view class="{{['sugar-tree-graphic', 'data-v-d91cb5e7', i, j, k, l]}}"><view class="stg-glow data-v-d91cb5e7"/><view wx:if="{{a}}" class="stg-aura data-v-d91cb5e7"/><image wx:if="{{b}}" class="stg-image data-v-d91cb5e7" src="{{c}}" mode="aspectFit" binderror="{{d}}"/><text wx:else class="stg-emoji data-v-d91cb5e7">{{e}}</text><view wx:if="{{f}}" class="stg-sparkle stg-sparkle-a data-v-d91cb5e7"/><view wx:if="{{g}}" class="stg-sparkle stg-sparkle-b data-v-d91cb5e7"/><view wx:if="{{h}}" class="stg-sparkle stg-sparkle-c data-v-d91cb5e7"/></view>
@@ -0,0 +1,153 @@
/**
* 这里是uni-app内置的常用样式变量
*
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
*
*/
/**
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
*
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
*/
/* 颜色变量 */
/* 行为相关颜色 */
/* 文字基本颜色 */
/* 背景颜色 */
/* 边框颜色 */
/* 尺寸变量 */
/* 文字尺寸 */
/* 图片尺寸 */
/* Border Radius */
/* 水平间距 */
/* 垂直间距 */
/* 透明度 */
/* 文章场景相关 */
.sugar-tree-graphic.data-v-d91cb5e7 {
position: relative;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
}
.size-sm.data-v-d91cb5e7 {
width: 64rpx;
height: 72rpx;
}
.size-md.data-v-d91cb5e7 {
width: 80rpx;
height: 88rpx;
}
.size-lg.data-v-d91cb5e7 {
width: 112rpx;
height: 124rpx;
}
.stg-glow.data-v-d91cb5e7 {
position: absolute;
inset: 6%;
border-radius: 50%;
background: radial-gradient(circle, rgba(32, 78, 43, 0.22) 0%, transparent 68%);
pointer-events: none;
}
.lv-0 .stg-glow.data-v-d91cb5e7 {
background: radial-gradient(circle, rgba(148, 163, 184, 0.25) 0%, transparent 70%);
}
.tier-4 .stg-glow.data-v-d91cb5e7,
.tier-5 .stg-glow.data-v-d91cb5e7,
.is-max .stg-glow.data-v-d91cb5e7 {
background: radial-gradient(circle, rgba(251, 191, 36, 0.3) 0%, rgba(32, 78, 43, 0.15) 55%, transparent 72%);
}
.stg-aura.data-v-d91cb5e7 {
position: absolute;
inset: -8%;
border-radius: 50%;
border: 2rpx solid rgba(253, 224, 71, 0.35);
animation: stg-aura-pulse-d91cb5e7 2.4s ease-in-out infinite;
pointer-events: none;
}
@keyframes stg-aura-pulse-d91cb5e7 {
0%, 100% {
transform: scale(0.92);
opacity: 0.5;
}
50% {
transform: scale(1.05);
opacity: 1;
}
}
.stg-image.data-v-d91cb5e7 {
position: relative;
z-index: 1;
width: 100%;
height: 100%;
transition: transform 0.35s ease;
}
.sugar-tree-graphic.watering .stg-image.data-v-d91cb5e7,
.sugar-tree-graphic.watering .stg-emoji.data-v-d91cb5e7 {
animation: stg-water-bounce-d91cb5e7 0.65s ease;
}
.stg-emoji.data-v-d91cb5e7 {
position: relative;
z-index: 1;
line-height: 1;
font-size: 72rpx;
}
.size-sm .stg-emoji.data-v-d91cb5e7 {
font-size: 48rpx;
}
.size-md .stg-emoji.data-v-d91cb5e7 {
font-size: 60rpx;
}
.size-lg .stg-emoji.data-v-d91cb5e7 {
font-size: 88rpx;
}
@keyframes stg-water-bounce-d91cb5e7 {
0%, 100% {
transform: scale(1);
}
35% {
transform: scale(1.1) translateY(-6rpx);
}
60% {
transform: scale(0.96) translateY(2rpx);
}
}
.stg-sparkle.data-v-d91cb5e7 {
position: absolute;
z-index: 2;
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background: #fde68a;
box-shadow: 0 0 6rpx rgba(253, 224, 71, 0.8);
pointer-events: none;
animation: stg-sparkle-twinkle-d91cb5e7 1.8s ease-in-out infinite;
}
.stg-sparkle-a.data-v-d91cb5e7 {
top: 4%;
right: 16%;
}
.stg-sparkle-b.data-v-d91cb5e7 {
top: 12%;
left: 10%;
width: 6rpx;
height: 6rpx;
animation-delay: 0.4s;
}
.stg-sparkle-c.data-v-d91cb5e7 {
top: 22%;
right: 28%;
width: 10rpx;
height: 10rpx;
animation-delay: 0.8s;
}
@keyframes stg-sparkle-twinkle-d91cb5e7 {
0%, 100% {
opacity: 0.4;
transform: scale(0.8);
}
50% {
opacity: 1;
transform: scale(1.2);
}
}
@@ -0,0 +1,129 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const tongji_utils_svgDataUrl = require("../utils/svgDataUrl.js");
const _sfc_main = {
__name: "TongjiIcon",
props: {
name: { type: String, required: true },
size: { type: String, default: "md" },
color: { type: String, default: "#204E2B" }
},
setup(__props) {
const props = __props;
const useFallback = common_vendor.ref(false);
const ICON_PATHS = {
view: '<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/>',
ticket: '<path d="M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"/><path d="M13 5v2"/><path d="M13 17v2"/><path d="M13 11v2"/>',
calendar: '<rect width="18" height="18" x="3" y="4" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/>',
flame: '<path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/>',
"check-circle": '<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><path d="m9 11 3 3L22 4"/>',
glucose: '<path d="M12 2.69l5.66 5.66a8 8 0 1 1-11.31 0z"/>',
heart: '<path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"/>',
activity: '<path d="M22 12h-4l-3 9L9 3l-3 9H2"/>',
users: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
user: '<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>',
person: '<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>',
send: '<path d="m22 2-7 20-4-9-9-4Z"/><path d="M22 2 11 13"/>',
"alert-triangle": '<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/>',
minus: '<path d="M5 12h14"/>',
droplet: '<path d="M12 22a7 7 0 0 0 7-7c0-2-1-3.5-2.5-5.5C15 7 12 2 12 2S9 7 7.5 9.5 5 13 5 15a7 7 0 0 0 7 7z"/>',
sparkles: '<path d="m12 3-1.9 5.8L4 12l5.8 1.9L12 21l1.9-5.8L20 12l-5.8-1.9L12 3Z"/><path d="M5 3v4M19 17v4M3 5h4M17 19h4"/>',
trophy: '<path d="M6 9H4.5a2.5 2.5 0 0 1 0-5H6M18 9h1.5a2.5 2.5 0 0 0 0-5H18M4 22h16M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20 7 22M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20 17 22M18 2H6v7a6 6 0 0 0 12 0V2Z"/>',
share: '<path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8M16 6l-4-4-4 4M12 2v13"/>',
plus: '<path d="M5 12h14M12 5v14"/>',
refresh: '<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8M3 3v5h5M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16M21 21v-5h-5"/>',
volume: '<path d="M11 5 6 9H2v6h4l5 4V5zM15.54 8.46a5 5 0 0 1 0 7.07M19.07 4.93a10 10 0 0 1 0 14.14"/>',
pause: '<rect width="4" height="16" x="14" y="4" rx="1"/><rect width="4" height="16" x="6" y="4" rx="1"/>',
play: '<polygon points="6 3 20 12 6 21 6 3"/>',
info: '<circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/>',
sun: '<circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41-1.41M17.66 6.34l1.41-1.41M6.34 4.93l1.41 1.41"/>',
moon: '<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>',
sunset: '<path d="M12 10V2M18.364 5.636l-2.12 2.12M5.636 18.364l2.12-2.12M22 18h-3M5 18H2M18.364 18.364l-2.12-2.12M5.636 5.636l2.12 2.12M16 12a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"/>',
mic: '<path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" x2="12" y1="19" y2="22"/>',
camera: '<path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z"/><circle cx="12" cy="13" r="3"/>',
bulb: '<path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1.3.5 2.6 1.5 3.5.8.8 1.3 1.5 1.5 2.5"/><path d="M9 18h6"/><path d="M10 22h4"/>',
leaf: '<path d="M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10Z"/><path d="M2 21c0-3 1.85-5.36 5.08-6"/>',
utensils: '<path d="M3 2v7c0 1.1.9 2 2 2a2 2 0 0 0 2-2V2"/><path d="M7 2v20"/><path d="M21 15V2a5 5 0 0 0-3 4.5v6a2 2 0 0 0 2 2h1Z"/><path d="M18 15v7"/>',
egg: '<path d="M12 22c4.97 0 8-3.27 8-7.31C20 9.65 16.42 2 12 2S4 9.65 4 14.69C4 18.73 7.03 22 12 22Z"/>',
home: '<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/>',
"plus-circle": '<circle cx="12" cy="12" r="10"/><path d="M8 12h8M12 8v8"/>',
"chevron-right": '<path d="m9 18 6-6-6-6"/>',
"chevron-left": '<path d="m15 18-6-6 6-6"/>',
check: '<path d="M20 6 9 17l-5-5"/>',
settings: '<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/>',
syringe: '<path d="m18 2 4 4"/><path d="m17 7 3-3"/><path d="M19 9 8.7 19.3c-1 1-2.5 1-3.4 0l-.6-.6c-1-1-1-2.5 0-3.4L15 5"/><path d="m9 11 4 4"/><path d="m5 19-3 3"/><path d="m14 4 6 6"/>',
zap: '<path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"/>'
};
const ICON_FALLBACK = {
view: "👁",
ticket: "🎫",
calendar: "📅",
flame: "🔥",
"check-circle": "✓",
glucose: "💧",
heart: "❤",
activity: "🏃",
users: "👥",
user: "👤",
person: "👤",
send: "➤",
"alert-triangle": "⚠",
minus: "—",
droplet: "💧",
sparkles: "✨",
trophy: "🏆",
share: "↗",
plus: "+",
refresh: "↻",
volume: "🔊",
pause: "⏸",
play: "▶",
info: "!",
sun: "☀",
moon: "🌙",
sunset: "☀",
mic: "🎤",
camera: "📷",
bulb: "💡",
leaf: "🥬",
utensils: "🍴",
egg: "🍳",
home: "🏠",
"plus-circle": "⊕",
"chevron-right": "›",
"chevron-left": "‹",
check: "✓",
settings: "⚙",
syringe: "💉",
zap: "⚡"
};
const strokeColor = common_vendor.computed(() => {
const c = String(props.color || "#204E2B").trim();
return /^#[0-9A-Fa-f]{3,8}$/.test(c) ? c : "#204E2B";
});
const iconSrc = common_vendor.computed(() => {
const path = ICON_PATHS[props.name] || ICON_PATHS.view;
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="${strokeColor.value}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${path}</svg>`;
return tongji_utils_svgDataUrl.svgToDataUrl(svg);
});
common_vendor.computed(() => ICON_FALLBACK[props.name] || ICON_FALLBACK.view);
function onImageError() {
useFallback.value = true;
}
return (_ctx, _cache) => {
return common_vendor.e({
a: !useFallback.value
}, !useFallback.value ? {
b: common_vendor.n(`tj-icon--${__props.name}`),
c: common_vendor.n(`tj-icon--${__props.size}`),
d: iconSrc.value,
e: common_vendor.o(onImageError, "4d")
} : {}, {
f: common_vendor.n(`tj-icon-wrap--${__props.size}`)
});
};
}
};
const Component = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-f41c6626"]]);
wx.createComponent(Component);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/components/TongjiIcon.js.map
@@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}
@@ -0,0 +1 @@
<view class="{{['tj-icon-wrap', 'data-v-f41c6626', f]}}"><image wx:if="{{a}}" class="{{['tj-icon', 'data-v-f41c6626', b, c]}}" src="{{d}}" mode="aspectFit" binderror="{{e}}"/></view>
@@ -0,0 +1,75 @@
/**
* 这里是uni-app内置的常用样式变量
*
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
*
*/
/**
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
*
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
*/
/* 颜色变量 */
/* 行为相关颜色 */
/* 文字基本颜色 */
/* 背景颜色 */
/* 边框颜色 */
/* 尺寸变量 */
/* 文字尺寸 */
/* 图片尺寸 */
/* Border Radius */
/* 水平间距 */
/* 垂直间距 */
/* 透明度 */
/* 文章场景相关 */
.tj-icon-wrap.data-v-f41c6626 {
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.tj-icon-wrap--sm.data-v-f41c6626 {
width: 32rpx;
height: 32rpx;
}
.tj-icon-wrap--md.data-v-f41c6626 {
width: 40rpx;
height: 40rpx;
}
.tj-icon-wrap--lg.data-v-f41c6626 {
width: 48rpx;
height: 48rpx;
}
.tj-icon-wrap--xl.data-v-f41c6626 {
width: 56rpx;
height: 56rpx;
}
.tj-icon.data-v-f41c6626 {
display: block;
width: 100%;
height: 100%;
}
/* Lucide send 路径重心偏右上,微调使圆形按钮内视觉居中 */
.tj-icon--send.data-v-f41c6626 {
transform: translate(-10%, 10%);
}
.tj-icon-fallback.data-v-f41c6626 {
display: block;
line-height: 1;
font-weight: 700;
text-align: center;
}
.tj-icon-fallback--sm.data-v-f41c6626 {
font-size: 24rpx;
}
.tj-icon-fallback--md.data-v-f41c6626 {
font-size: 30rpx;
}
.tj-icon-fallback--lg.data-v-f41c6626 {
font-size: 36rpx;
}
.tj-icon-fallback--xl.data-v-f41c6626 {
font-size: 42rpx;
}
@@ -0,0 +1,28 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const _sfc_main = {
__name: "VoiceSpeakMascot",
props: {
/** 正在按住说话 / 录音中 */
active: { type: Boolean, default: false },
/** 深色条背景上使用浅色卡通 */
onDark: { type: Boolean, default: false },
size: { type: String, default: "md" }
},
setup(__props) {
return (_ctx, _cache) => {
return common_vendor.e({
a: __props.active
}, __props.active ? {} : {}, {
b: common_vendor.n(`vsm--${__props.size}`),
c: common_vendor.n({
"vsm--active": __props.active,
"vsm--on-dark": __props.onDark
})
});
};
}
};
const Component = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-e1de3b0a"]]);
wx.createComponent(Component);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/components/VoiceSpeakMascot.js.map
@@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}
@@ -0,0 +1 @@
<view class="{{['vsm', 'data-v-e1de3b0a', b, c]}}"><view class="vsm-body data-v-e1de3b0a"><view class="vsm-leaf data-v-e1de3b0a" aria-hidden="true"/><view class="vsm-face data-v-e1de3b0a"><view class="vsm-eye vsm-eye--l data-v-e1de3b0a"/><view class="vsm-eye vsm-eye--r data-v-e1de3b0a"/><view class="vsm-blush vsm-blush--l data-v-e1de3b0a"/><view class="vsm-blush vsm-blush--r data-v-e1de3b0a"/><view class="vsm-mouth data-v-e1de3b0a"/></view><view wx:if="{{a}}" class="vsm-waves data-v-e1de3b0a" aria-hidden="true"><view class="vsm-wave vsm-wave--1 data-v-e1de3b0a"/><view class="vsm-wave vsm-wave--2 data-v-e1de3b0a"/><view class="vsm-wave vsm-wave--3 data-v-e1de3b0a"/></view></view></view>
@@ -0,0 +1,230 @@
/**
* 这里是uni-app内置的常用样式变量
*
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
*
*/
/**
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
*
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
*/
/* 颜色变量 */
/* 行为相关颜色 */
/* 文字基本颜色 */
/* 背景颜色 */
/* 边框颜色 */
/* 尺寸变量 */
/* 文字尺寸 */
/* 图片尺寸 */
/* Border Radius */
/* 水平间距 */
/* 垂直间距 */
/* 透明度 */
/* 文章场景相关 */
.vsm.data-v-e1de3b0a {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.vsm--md.data-v-e1de3b0a {
width: 72rpx;
height: 72rpx;
}
.vsm--sm.data-v-e1de3b0a {
width: 64rpx;
height: 64rpx;
}
.vsm-body.data-v-e1de3b0a {
position: relative;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.vsm-leaf.data-v-e1de3b0a {
position: absolute;
top: 2rpx;
left: 50%;
width: 16rpx;
height: 16rpx;
margin-left: -8rpx;
border-radius: 0 100% 0 100%;
background: #34d399;
transform: rotate(-18deg);
z-index: 2;
}
.vsm--on-dark .vsm-leaf.data-v-e1de3b0a {
background: #a7f3d0;
}
.vsm-face.data-v-e1de3b0a {
position: relative;
width: 52rpx;
height: 52rpx;
border-radius: 50%;
background: #fef9c3;
border: 3rpx solid #047857;
box-shadow: 0 4rpx 10rpx rgba(4, 120, 87, 0.18);
z-index: 1;
}
.vsm--sm .vsm-face.data-v-e1de3b0a {
width: 46rpx;
height: 46rpx;
}
.vsm--on-dark .vsm-face.data-v-e1de3b0a {
background: #fffbeb;
border-color: rgba(255, 255, 255, 0.85);
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.12);
}
.vsm-eye.data-v-e1de3b0a {
position: absolute;
top: 16rpx;
width: 6rpx;
height: 8rpx;
border-radius: 50%;
background: #064e3b;
}
.vsm--sm .vsm-eye.data-v-e1de3b0a {
top: 14rpx;
width: 5rpx;
height: 7rpx;
}
.vsm--on-dark .vsm-eye.data-v-e1de3b0a {
background: #134e4a;
}
.vsm-eye--l.data-v-e1de3b0a {
left: 12rpx;
}
.vsm-eye--r.data-v-e1de3b0a {
right: 12rpx;
}
.vsm--sm .vsm-eye--l.data-v-e1de3b0a {
left: 10rpx;
}
.vsm--sm .vsm-eye--r.data-v-e1de3b0a {
right: 10rpx;
}
.vsm-blush.data-v-e1de3b0a {
position: absolute;
top: 24rpx;
width: 10rpx;
height: 6rpx;
border-radius: 50%;
background: rgba(251, 113, 133, 0.45);
opacity: 0.85;
}
.vsm--sm .vsm-blush.data-v-e1de3b0a {
top: 21rpx;
width: 8rpx;
height: 5rpx;
}
.vsm-blush--l.data-v-e1de3b0a {
left: 6rpx;
}
.vsm-blush--r.data-v-e1de3b0a {
right: 6rpx;
}
.vsm--on-dark .vsm-blush.data-v-e1de3b0a {
background: rgba(255, 255, 255, 0.35);
}
.vsm-mouth.data-v-e1de3b0a {
position: absolute;
left: 50%;
bottom: 10rpx;
width: 18rpx;
height: 8rpx;
margin-left: -9rpx;
border-radius: 0 0 18rpx 18rpx;
background: #047857;
transform-origin: center top;
}
.vsm--sm .vsm-mouth.data-v-e1de3b0a {
bottom: 9rpx;
width: 16rpx;
height: 7rpx;
margin-left: -8rpx;
}
.vsm--on-dark .vsm-mouth.data-v-e1de3b0a {
background: #ecfdf5;
}
.vsm-waves.data-v-e1de3b0a {
position: absolute;
right: -2rpx;
top: 50%;
display: flex;
align-items: flex-end;
gap: 4rpx;
height: 28rpx;
margin-top: -14rpx;
z-index: 0;
}
.vsm-wave.data-v-e1de3b0a {
width: 5rpx;
border-radius: 4rpx;
background: #047857;
animation: vsm-wave-jump-e1de3b0a 0.72s ease-in-out infinite;
}
.vsm--on-dark .vsm-wave.data-v-e1de3b0a {
background: rgba(255, 255, 255, 0.9);
}
.vsm-wave--1.data-v-e1de3b0a {
height: 10rpx;
animation-delay: 0s;
}
.vsm-wave--2.data-v-e1de3b0a {
height: 18rpx;
animation-delay: 0.12s;
}
.vsm-wave--3.data-v-e1de3b0a {
height: 12rpx;
animation-delay: 0.24s;
}
.vsm--active .vsm-body.data-v-e1de3b0a {
animation: vsm-bob-e1de3b0a 0.9s ease-in-out infinite;
}
.vsm--active .vsm-mouth.data-v-e1de3b0a {
animation: vsm-talk-e1de3b0a 0.32s ease-in-out infinite alternate;
}
.vsm--active .vsm-eye.data-v-e1de3b0a {
animation: vsm-blink-e1de3b0a 2.4s ease-in-out infinite;
}
@keyframes vsm-bob-e1de3b0a {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-4rpx);
}
}
@keyframes vsm-talk-e1de3b0a {
0% {
transform: scaleY(0.45);
border-radius: 0 0 18rpx 18rpx;
}
100% {
transform: scaleY(1.15);
height: 12rpx;
border-radius: 50%;
}
}
@keyframes vsm-blink-e1de3b0a {
0%, 42%, 46%, 100% {
transform: scaleY(1);
}
44% {
transform: scaleY(0.15);
}
}
@keyframes vsm-wave-jump-e1de3b0a {
0%, 100% {
transform: scaleY(0.45);
opacity: 0.55;
}
50% {
transform: scaleY(1);
opacity: 1;
}
}
@@ -0,0 +1,307 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const tongji_utils_aiStreamRequest = require("../utils/aiStreamRequest.js");
const DIET_AI_PREFILL_KEY = "tongji_diet_ai_prefill";
const emptyDietAiRecommend = () => ({
breakfast: "",
drinks: "",
lunch: "",
dinner: "",
tips: "",
avoid: [],
analysis: [],
exercise: null,
disclaimer: "",
rules_summary: "",
source: "",
date: ""
});
function useDietAi(proxy, { diagnosisId, showUserToast, formatUserMessage }) {
const dietAiRecommend = common_vendor.ref(emptyDietAiRecommend());
const dietAiLoading = common_vendor.ref(false);
const dietAiStreaming = common_vendor.ref(false);
const dietAiReplacing = common_vendor.ref(false);
const dietAiStreamField = common_vendor.ref("");
let dietStreamBuffer = "";
const dietAiAskText = common_vendor.ref("");
const dietAiAsking = common_vendor.ref(false);
const dietAiAskResult = common_vendor.ref({ advice: "", level: "", level_label: "", portion: "", food: "" });
const hasDietAiMealContent = common_vendor.computed(() => {
const r = dietAiRecommend.value;
return !!(r.breakfast || r.drinks || r.lunch || r.dinner || r.tips);
});
function detectDietStreamField(text) {
const tail = String(text || "").match(/(?:^|\n)(早餐|喝的|午餐|晚餐|提示|少碰)[::]\s*([^\n]*)$/);
if (!tail)
return "breakfast";
const map = { "早餐": "breakfast", "喝的": "drinks", "午餐": "lunch", "晚餐": "dinner", "提示": "tips", "少碰": "tips" };
return map[tail[1]] || "breakfast";
}
function mergePartialDietFromStream() {
const partial = tongji_utils_aiStreamRequest.parsePartialDietPlainText(dietStreamBuffer);
const next = { ...dietAiRecommend.value };
for (const key of ["breakfast", "drinks", "lunch", "dinner", "tips"]) {
if (partial[key] != null)
next[key] = partial[key];
}
if (Array.isArray(partial.avoid))
next.avoid = partial.avoid;
dietAiRecommend.value = next;
dietAiStreamField.value = detectDietStreamField(dietStreamBuffer);
}
function applyDietRecommendPayload(payload) {
if (!payload)
return;
const prevAnalysis = dietAiRecommend.value.analysis;
const prevExercise = dietAiRecommend.value.exercise;
dietAiRecommend.value = {
...emptyDietAiRecommend(),
...payload,
avoid: Array.isArray(payload.avoid) ? payload.avoid : [],
analysis: Array.isArray(payload.analysis) && payload.analysis.length ? payload.analysis : Array.isArray(prevAnalysis) ? prevAnalysis : [],
exercise: payload.exercise && typeof payload.exercise === "object" ? payload.exercise : prevExercise || null
};
}
function resolveDietDonePayload(payload) {
if (!payload)
return payload;
if (payload.source !== "rule" || !dietStreamBuffer)
return payload;
const streamed = tongji_utils_aiStreamRequest.parsePartialDietPlainText(dietStreamBuffer);
if (!streamed.breakfast)
return payload;
return {
...emptyDietAiRecommend(),
...payload,
breakfast: streamed.breakfast,
drinks: streamed.drinks || payload.drinks || "",
lunch: streamed.lunch || payload.lunch || "",
dinner: streamed.dinner || payload.dinner || "",
tips: streamed.tips || payload.tips || "",
avoid: Array.isArray(streamed.avoid) && streamed.avoid.length ? streamed.avoid : payload.avoid || [],
source: "ai",
disclaimer: "AI 建议供参考,仍请结合医嘱与血糖监测。"
};
}
function onDietAiStreamEvent(event, payload) {
if (event === "analysis") {
if (Array.isArray(payload == null ? void 0 : payload.analysis)) {
dietAiRecommend.value = { ...dietAiRecommend.value, analysis: payload.analysis };
}
} else if (event === "exercise") {
if ((payload == null ? void 0 : payload.exercise) && typeof payload.exercise === "object") {
dietAiRecommend.value = { ...dietAiRecommend.value, exercise: payload.exercise };
}
} else if (event === "delta") {
dietAiStreaming.value = true;
if (payload.buffer != null) {
dietStreamBuffer = payload.buffer;
} else {
dietStreamBuffer += payload.text || "";
}
mergePartialDietFromStream();
} else if (event === "done") {
applyDietRecommendPayload(resolveDietDonePayload(payload));
dietStreamBuffer = "";
dietAiStreamField.value = "";
} else if (event === "error") {
showUserToast(formatUserMessage(payload == null ? void 0 : payload.message, "饮食建议加载失败"));
}
}
async function fetchDietAiRecommendFallback(refresh) {
const res = await proxy.apiUrl({
url: "/api/tcm/dailyDietAiRecommend",
method: "GET",
timeout: 35e3,
data: {
diagnosis_id: diagnosisId.value,
refresh: refresh ? 1 : 0,
...refresh ? { _t: Date.now() } : {}
}
}, false);
if ((res == null ? void 0 : res.code) === 1 && res.data) {
applyDietRecommendPayload(res.data);
} else if (res == null ? void 0 : res.msg) {
showUserToast(res.msg);
}
}
async function fetchDietAiRecommend(refresh = false) {
if (!diagnosisId.value || dietAiLoading.value)
return;
dietAiLoading.value = true;
dietAiStreaming.value = false;
dietAiReplacing.value = !!refresh;
dietStreamBuffer = "";
dietAiStreamField.value = "";
if (refresh) {
applyDietRecommendPayload(emptyDietAiRecommend());
}
try {
await tongji_utils_aiStreamRequest.requestAiStream({
baseUrl: proxy.$url,
url: "/api/tcm/dailyDietAiRecommendStream",
method: "GET",
data: {
diagnosis_id: diagnosisId.value,
refresh: refresh ? 1 : 0,
...refresh ? { _t: Date.now() } : {}
},
onEvent: onDietAiStreamEvent,
fallback: () => fetchDietAiRecommendFallback(refresh)
});
} catch (e) {
try {
await fetchDietAiRecommendFallback(refresh);
} catch (e2) {
showUserToast("饮食建议加载失败");
}
} finally {
dietAiLoading.value = false;
dietAiStreaming.value = false;
dietAiReplacing.value = false;
dietAiStreamField.value = "";
}
}
function refreshDietAiRecommend() {
if (dietAiLoading.value)
return;
fetchDietAiRecommend(true);
}
async function askDietAiFoodFallback(q) {
const res = await proxy.apiUrl({
url: "/api/tcm/dailyDietAiAsk",
method: "POST",
timeout: 35e3,
data: {
diagnosis_id: diagnosisId.value,
question: q
}
}, false);
if ((res == null ? void 0 : res.code) === 1 && res.data) {
dietAiAskResult.value = {
advice: res.data.advice || "",
level: res.data.level || "",
level_label: res.data.level_label || "",
portion: res.data.portion || "",
food: res.data.food || q
};
} else {
showUserToast(formatUserMessage(res == null ? void 0 : res.msg, "咨询失败"));
}
}
async function askDietAiFood() {
const q = String(dietAiAskText.value || "").trim();
if (!q) {
showUserToast("请输入想咨询的食物");
return;
}
if (!diagnosisId.value)
return;
dietAiAskText.value = "";
dietAiAsking.value = true;
dietAiAskResult.value = { advice: "", level: "", level_label: "", portion: "", food: "" };
try {
await tongji_utils_aiStreamRequest.requestAiStream({
baseUrl: proxy.$url,
url: "/api/tcm/dailyDietAiAskStream",
method: "POST",
data: {
diagnosis_id: diagnosisId.value,
question: q
},
onEvent: (event, payload) => {
if (event === "start" && !dietAiAskResult.value.advice) {
dietAiAskResult.value = { ...dietAiAskResult.value, advice: payload.message || "正在想…" };
} else if (event === "delta") {
dietAiAskResult.value = {
...dietAiAskResult.value,
advice: payload.advice || dietAiAskResult.value.advice
};
} else if (event === "done") {
dietAiAskResult.value = {
advice: payload.advice || "",
level: payload.level || "",
level_label: payload.level_label || "",
portion: payload.portion || "",
food: payload.food || q
};
} else if (event === "error") {
showUserToast(formatUserMessage(payload == null ? void 0 : payload.message, "咨询失败"));
}
},
fallback: () => askDietAiFoodFallback(q)
});
} catch (e) {
try {
await askDietAiFoodFallback(q);
} catch (e2) {
showUserToast("网络异常,请稍后再试");
}
} finally {
dietAiAsking.value = false;
}
}
function onDietAiAskInput(e) {
var _a;
dietAiAskText.value = ((_a = e == null ? void 0 : e.detail) == null ? void 0 : _a.value) ?? "";
}
function saveDietPrefillToStorage() {
const r = dietAiRecommend.value;
if (!r.breakfast)
return;
try {
common_vendor.index.setStorageSync(DIET_AI_PREFILL_KEY, {
breakfast: r.breakfast,
drinks: r.drinks || "",
lunch: r.lunch,
dinner: r.dinner,
tips: r.tips || ""
});
} catch (e) {
}
}
function goMoreDietForm(withPrefill = false) {
if (withPrefill)
saveDietPrefillToStorage();
common_vendor.index.navigateTo({ url: "/tongji/pages/more?openDiet=1" });
}
async function applyDietAiToForm() {
if (!dietAiRecommend.value.breakfast) {
await fetchDietAiRecommend(false);
}
goMoreDietForm(true);
}
function openDietForm() {
goMoreDietForm(false);
}
return {
dietAiRecommend,
dietAiLoading,
dietAiStreaming,
dietAiReplacing,
dietAiStreamField,
dietAiAskText,
dietAiAsking,
dietAiAskResult,
hasDietAiMealContent,
fetchDietAiRecommend,
refreshDietAiRecommend,
askDietAiFood,
onDietAiAskInput,
applyDietAiToForm,
openDietForm
};
}
function consumeDietAiPrefill() {
try {
const data = common_vendor.index.getStorageSync(DIET_AI_PREFILL_KEY);
common_vendor.index.removeStorageSync(DIET_AI_PREFILL_KEY);
if (data && typeof data === "object" && data.breakfast)
return data;
} catch (e) {
}
return null;
}
exports.consumeDietAiPrefill = consumeDietAiPrefill;
exports.useDietAi = useDietAi;
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/composables/useDietAi.js.map
@@ -0,0 +1,291 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const MIN_HOLD_MS = 280;
function isBenignSttError(msg, retcode) {
const m = String(msg || "").toLowerCase();
if (/internal voice data failed/i.test(m))
return true;
if (/voice data failed/i.test(m))
return true;
if (/recordfailed|record failed|record manager/i.test(m))
return true;
if (/please stop after start/i.test(m))
return true;
if (/no recognition|not start|未开始|无识别/i.test(m))
return true;
const code = Number(retcode);
if (code === -30002 || code === -30003 || code === -30012)
return true;
return false;
}
function friendlySttError(msg) {
const m = String(msg || "").trim();
if (!m || isBenignSttError(m))
return "";
if (/not allowed|auth|permission|权限|麦克风/i.test(m))
return "请允许使用麦克风";
if (/network|网络|timeout/i.test(m))
return "网络异常,请稍后再试";
return "语音识别失败,请重试";
}
let sharedManager = null;
let sharedManagerInited = false;
let sharedRecording = false;
let activeCtl = null;
function getSharedRecordManager() {
if (sharedManagerInited)
return sharedManager;
sharedManagerInited = true;
try {
const plugin = requirePlugin("WechatSI");
sharedManager = plugin.getRecordRecognitionManager();
} catch (e) {
common_vendor.index.__f__("warn", "at tongji/composables/useSpeechToText.js:44", "WechatSI record recognition not available", e);
sharedManager = null;
return null;
}
if (!sharedManager)
return null;
sharedManager.onStart = () => {
sharedRecording = true;
if (activeCtl)
activeCtl.handleStart();
};
sharedManager.onRecognize = (res) => {
if (activeCtl)
activeCtl.handleRecognize(res);
};
sharedManager.onStop = (res) => {
sharedRecording = false;
if (activeCtl)
activeCtl.handleStop(res);
};
sharedManager.onError = (res) => {
sharedRecording = false;
if (activeCtl)
activeCtl.handleError(res);
};
return sharedManager;
}
function useSpeechToText({ onResult, onSettle, onPartial, onError, showToast } = {}) {
const sttListening = common_vendor.ref(false);
const sttHolding = common_vendor.ref(false);
const sttSupported = common_vendor.ref(false);
let wxRecordManager = null;
let holdStartTs = 0;
let holdSessionId = 0;
let startRequested = false;
let recordAuthorized = null;
let pendingStartSession = 0;
function notify(msg) {
if (!msg)
return;
if (typeof showToast === "function") {
showToast(msg);
return;
}
common_vendor.index.showToast({ title: msg, icon: "none", duration: 2e3 });
}
function emitResult(text) {
const t = String(text || "").trim();
const heldMs = Date.now() - holdStartTs;
if (typeof onSettle === "function") {
const handled = onSettle(t, { heldMs });
if (handled === true)
return;
}
if (!t) {
if (heldMs < MIN_HOLD_MS)
return;
notify("没听清,请再试一次");
return;
}
if (typeof onResult === "function") {
onResult(t);
}
}
function stopListening() {
const ownsRecording = sharedRecording && activeCtl === controller;
if (wxRecordManager && (sttListening.value || ownsRecording)) {
try {
wxRecordManager.stop();
} catch (e) {
}
}
startRequested = false;
sttHolding.value = false;
sttListening.value = false;
}
function ensureRecordAuth(onGranted) {
if (recordAuthorized === true) {
onGranted();
return;
}
common_vendor.index.authorize({
scope: "scope.record",
success() {
recordAuthorized = true;
onGranted();
},
fail() {
recordAuthorized = false;
sttHolding.value = false;
common_vendor.index.showModal({
title: "需要麦克风权限",
content: "请在设置中允许使用麦克风,以便长按说话",
confirmText: "去设置",
success(res) {
if (res.confirm) {
common_vendor.index.openSetting({});
}
}
});
}
});
}
function actuallyStartWechat(session) {
if (!wxRecordManager || session !== holdSessionId || !sttHolding.value) {
return;
}
try {
startRequested = true;
wxRecordManager.start({
duration: 6e4,
lang: "zh_CN"
});
} catch (e) {
startRequested = false;
sharedRecording = false;
sttHolding.value = false;
notify("无法开始录音");
}
}
function startWechatRecord(session) {
if (!wxRecordManager || session !== holdSessionId || !sttHolding.value) {
return;
}
if (sharedRecording || startRequested) {
pendingStartSession = session;
try {
wxRecordManager.stop();
} catch (e) {
}
return;
}
actuallyStartWechat(session);
}
function beginHoldSpeech() {
if (!sttSupported.value) {
notify("当前环境不支持语音输入");
return;
}
if (sttHolding.value)
return;
const session = ++holdSessionId;
holdStartTs = Date.now();
sttHolding.value = true;
activeCtl = controller;
ensureRecordAuth(() => startWechatRecord(session));
}
function endHoldSpeech(force = false) {
let recording = false;
recording = sharedRecording && activeCtl === controller;
if (!force && !sttHolding.value && !sttListening.value && !startRequested && !recording) {
return;
}
sttHolding.value = false;
pendingStartSession = 0;
if (sttListening.value || recording) {
stopListening();
return;
}
if (startRequested) {
startRequested = false;
holdSessionId += 1;
return;
}
holdSessionId += 1;
}
const controller = {
handleStart() {
if (!sttHolding.value) {
startRequested = false;
sttListening.value = false;
return;
}
startRequested = false;
sttListening.value = true;
},
handleRecognize(res) {
if (typeof onPartial !== "function")
return;
const t = String((res == null ? void 0 : res.result) || "").trim();
if (t)
onPartial(t);
},
handleStop(res) {
sttListening.value = false;
startRequested = false;
if (pendingStartSession && pendingStartSession === holdSessionId && sttHolding.value) {
const s = pendingStartSession;
pendingStartSession = 0;
actuallyStartWechat(s);
return;
}
pendingStartSession = 0;
emitResult(res == null ? void 0 : res.result);
},
handleError(res) {
sttListening.value = false;
startRequested = false;
const msg = res && res.msg || "";
const retcode = res && (res.retcode ?? res.errCode ?? res.code);
if (/please stop after start/i.test(msg)) {
try {
wxRecordManager.stop();
} catch (e) {
}
pendingStartSession = sttHolding.value ? holdSessionId : 0;
return;
}
pendingStartSession = 0;
sttHolding.value = false;
const heldMs = Date.now() - holdStartTs;
if (isBenignSttError(msg, retcode) || heldMs < MIN_HOLD_MS) {
if (typeof onError === "function")
onError(msg || "");
return;
}
if (typeof onError === "function") {
const handled = onError(msg || "");
if (handled === true)
return;
}
const tip = friendlySttError(msg);
if (tip)
notify(tip);
}
};
wxRecordManager = getSharedRecordManager();
sttSupported.value = !!wxRecordManager;
common_vendor.onUnmounted(() => {
sttHolding.value = false;
holdSessionId += 1;
pendingStartSession = 0;
stopListening();
if (activeCtl === controller) {
activeCtl = null;
}
});
return {
sttListening,
sttHolding,
sttSupported,
beginHoldSpeech,
endHoldSpeech,
stopSpeechToText: endHoldSpeech
};
}
exports.isBenignSttError = isBenignSttError;
exports.useSpeechToText = useSpeechToText;
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/composables/useSpeechToText.js.map
@@ -0,0 +1,64 @@
"use strict";
const common_vendor = require("../../../common/vendor.js");
function useGameAuth(proxy) {
let loginPromise = null;
function hasToken() {
return !!String(common_vendor.index.getStorageSync("token") || "").trim();
}
function clearToken() {
common_vendor.index.removeStorageSync("token");
}
function wxLoginCode() {
return new Promise((resolve, reject) => {
common_vendor.index.login({
provider: "weixin",
success: (res) => (res == null ? void 0 : res.code) ? resolve(res.code) : reject(new Error("微信登录未返回 code")),
fail: reject
});
});
}
async function loginWithCode(code) {
var _a;
const res = await proxy.apiUrl({
url: "/api/login/mnpLogin",
method: "POST",
data: { code }
}, false);
if ((res == null ? void 0 : res.code) !== 1 || !((_a = res.data) == null ? void 0 : _a.token)) {
clearToken();
throw new Error((res == null ? void 0 : res.msg) || "登录失败");
}
common_vendor.index.setStorageSync("token", res.data.token);
common_vendor.index.setStorageSync("userData", res.data);
return true;
}
async function verifyOrLogin() {
if (hasToken()) {
try {
const res = await proxy.apiUrl({ url: "/api/user/info", method: "POST" }, false);
if ((res == null ? void 0 : res.code) === 1 && res.data) {
common_vendor.index.setStorageSync("userData", res.data);
return true;
}
if ((res == null ? void 0 : res.code) !== -1)
return true;
} catch (_) {
return true;
}
clearToken();
}
const code = await wxLoginCode();
return loginWithCode(code);
}
async function ensureLoggedIn() {
if (loginPromise)
return loginPromise;
loginPromise = verifyOrLogin().then((ok) => !!ok).catch(() => false).finally(() => {
loginPromise = null;
});
return loginPromise;
}
return { ensureLoggedIn };
}
exports.useGameAuth = useGameAuth;
//# sourceMappingURL=../../../../.sourcemap/mp-weixin/tongji/endless-game/composables/useGameAuth.js.map
@@ -0,0 +1,240 @@
"use strict";
const common_vendor = require("../../../common/vendor.js");
const EMPTY_BOARD = {
week_start: "",
week_end: "",
sex_label: "同行",
group_size: 7,
member_count: 0,
players: [],
me: { count: 0, rank: 1, best_score: 0, distance: 0, is_first: true },
invite_code: ""
};
const PENDING_SYNC_KEY = "tongji_endless_pending_sync_v1";
function createSessionKey() {
const random = Math.random().toString(36).slice(2, 12);
return `game_${Date.now().toString(36)}_${random}`;
}
function readPendingPayloads() {
try {
const stored = common_vendor.index.getStorageSync(PENDING_SYNC_KEY);
if (!Array.isArray(stored))
return [];
return stored.filter((item) => item && /^[A-Za-z0-9_-]{16,64}$/.test(String(item.session_key || "")) && Number(item.learned_count) >= 0).slice(-8);
} catch (_) {
return [];
}
}
function useGamePlatform(proxy, ensureLoggedIn) {
const connected = common_vendor.ref(false);
const loading = common_vendor.ref(false);
const syncStatus = common_vendor.ref("idle");
const leaderboard = common_vendor.ref({ ...EMPTY_BOARD });
const confirmedSessionLearned = common_vendor.ref(0);
let sessionKey = createSessionKey();
let syncTimer = null;
let syncing = false;
let queuedPayloads = readPendingPayloads();
let connectPromise = null;
function persistPendingPayloads() {
try {
common_vendor.index.setStorageSync(PENDING_SYNC_KEY, queuedPayloads.slice(-8));
} catch (_) {
}
}
async function api(request) {
if (!(proxy == null ? void 0 : proxy.apiUrl))
throw new Error("平台接口未初始化");
return proxy.apiUrl(request, false);
}
function applyLeaderboard(data, confirmedForSession = "") {
if (!data || !Array.isArray(data.players))
return;
const inactiveSessionResponse = confirmedForSession && confirmedForSession !== sessionKey;
if (!inactiveSessionResponse) {
leaderboard.value = {
...EMPTY_BOARD,
...data,
me: { ...EMPTY_BOARD.me, ...data.me || {} },
players: data.players
};
}
if (confirmedForSession === sessionKey && data.confirmed_session_learned != null) {
confirmedSessionLearned.value = Number(data.confirmed_session_learned) || 0;
}
connected.value = true;
syncStatus.value = "synced";
}
async function refreshLeaderboard() {
const res = await api({
url: "/api/tcm/gameWeeklyLeaderboard",
method: "GET"
});
if ((res == null ? void 0 : res.code) !== 1 || !res.data) {
throw new Error((res == null ? void 0 : res.msg) || "同行榜加载失败");
}
applyLeaderboard(res.data);
return res.data;
}
async function acceptShare(inviteCode) {
const code = String(inviteCode || "").trim().toUpperCase();
if (!code)
return;
try {
await api({
url: "/api/tcm/gameAcceptShare",
method: "POST",
data: { invite_code: code }
});
} catch (_) {
}
}
async function connect(inviteCode = "") {
if (connectPromise)
return connectPromise;
connectPromise = (async () => {
loading.value = true;
try {
const loggedIn = await ensureLoggedIn();
if (!loggedIn)
throw new Error("登录失败");
await acceptShare(inviteCode);
await refreshLeaderboard();
if (queuedPayloads.length)
flushProgress();
return true;
} catch (_) {
connected.value = false;
syncStatus.value = "offline";
return false;
} finally {
loading.value = false;
connectPromise = null;
}
})();
return connectPromise;
}
function beginSession() {
if (syncTimer)
clearTimeout(syncTimer);
sessionKey = createSessionKey();
confirmedSessionLearned.value = 0;
syncStatus.value = queuedPayloads.length ? "pending" : connected.value ? "synced" : "offline";
return sessionKey;
}
function queueProgress(learnedCount, score, ended = false) {
const nextPayload = {
session_key: sessionKey,
learned_count: Math.max(0, Number(learnedCount) || 0),
score: Math.max(0, Number(score) || 0),
ended: ended ? 1 : 0
};
const existingIndex = queuedPayloads.findIndex((item) => item.session_key === sessionKey);
if (existingIndex >= 0) {
const existing = queuedPayloads[existingIndex];
queuedPayloads[existingIndex] = {
...nextPayload,
learned_count: Math.max(existing.learned_count, nextPayload.learned_count),
score: Math.max(existing.score, nextPayload.score),
ended: Math.max(existing.ended, nextPayload.ended)
};
} else {
queuedPayloads.push(nextPayload);
}
persistPendingPayloads();
syncStatus.value = "pending";
if (syncTimer)
clearTimeout(syncTimer);
if (ended) {
flushProgress();
} else {
syncTimer = setTimeout(flushProgress, 1e3);
}
}
async function flushProgress() {
if (syncTimer)
clearTimeout(syncTimer);
syncTimer = null;
if (syncing || !queuedPayloads.length)
return;
const payload = queuedPayloads[0];
syncing = true;
syncStatus.value = "syncing";
try {
if (!connected.value) {
const loggedIn = await ensureLoggedIn();
if (!loggedIn)
throw new Error("未登录");
}
const res = await api({
url: "/api/tcm/gameSubmitProgress",
method: "POST",
data: payload
});
if ((res == null ? void 0 : res.code) !== 1 || !res.data) {
throw new Error((res == null ? void 0 : res.msg) || "成绩保存失败");
}
applyLeaderboard(res.data, payload.session_key);
queuedPayloads = queuedPayloads.filter((item) => item.session_key !== payload.session_key || Number(item.learned_count) > Number(payload.learned_count) || Number(item.score) > Number(payload.score) || Number(item.ended) > Number(payload.ended));
persistPendingPayloads();
} catch (_) {
connected.value = false;
syncStatus.value = "offline";
persistPendingPayloads();
} finally {
syncing = false;
if (queuedPayloads.length && connected.value) {
setTimeout(flushProgress, 80);
}
}
}
async function syncAndRefresh(learnedCount, score) {
queueProgress(learnedCount, score, false);
await flushProgress();
if (!connected.value) {
await connect();
if (queuedPayloads.length)
await flushProgress();
} else {
try {
await refreshLeaderboard();
} catch (_) {
}
}
}
async function recordShare() {
var _a;
try {
if (!connected.value && !await connect())
return;
const res = await api({ url: "/api/tcm/gameRecordShare", method: "POST" });
if ((res == null ? void 0 : res.code) === 1 && ((_a = res.data) == null ? void 0 : _a.invite_code)) {
leaderboard.value = { ...leaderboard.value, invite_code: res.data.invite_code };
}
} catch (_) {
}
}
function dispose() {
if (syncTimer)
clearTimeout(syncTimer);
syncTimer = null;
persistPendingPayloads();
}
return {
connected,
loading,
syncStatus,
leaderboard,
confirmedSessionLearned,
connect,
beginSession,
queueProgress,
flushProgress,
syncAndRefresh,
refreshLeaderboard,
recordShare,
dispose
};
}
exports.useGamePlatform = useGamePlatform;
//# sourceMappingURL=../../../../.sourcemap/mp-weixin/tongji/endless-game/composables/useGamePlatform.js.map
@@ -0,0 +1,366 @@
"use strict";
const common_vendor = require("../../../common/vendor.js");
const STORAGE_KEY = "tongji_game_sfx_enabled";
const COS = "https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/uploads/file";
const SRC = {
tap: `${COS}/20260526/20260526105128557ef8669.mp3`,
swoosh: `${COS}/20260527/202605271539371ebe13672.mp3`,
pop: `${COS}/20260526/202605261051282a0a94508.mp3`,
buzz: `${COS}/20260527/202605271539376ff628472.mp3`,
chime: `${COS}/20260528/202605280937077bad76716.mp3`,
fanfare: `${COS}/20260528/20260528093707b021f5794.mp3`,
blast: `${COS}/20260528/20260528093707ebd4d5733.mp3`,
sparkle: `${COS}/20260528/20260528093707016f07427.mp3`,
stinger: `${COS}/20260528/20260528093709c669f4659.mp3`
};
const SFX = {
// —— 基础素材别名(供分层音效直接调用)——
tap: { src: SRC.tap, volume: 0.32, rate: 1, pool: 3 },
pop: { src: SRC.pop, volume: 0.3, rate: 1, pool: 4 },
fanfare: { src: SRC.fanfare, volume: 0.56, rate: 1, pool: 2 },
blast: { src: SRC.blast, volume: 0.62, rate: 1, pool: 2 },
sparkle: { src: SRC.sparkle, volume: 0.5, rate: 1, pool: 2 },
// —— 交互 ——
select: { src: SRC.tap, volume: 0.32, rate: 1.05, pool: 3 },
deselect: { src: SRC.tap, volume: 0.22, rate: 0.82, pool: 2 },
hint: { src: SRC.tap, volume: 0.26, rate: 1.28, pool: 2 },
swap: { src: SRC.swoosh, volume: 0.38, rate: 1, pool: 3 },
swapFail: { src: SRC.buzz, volume: 0.36, rate: 1.18, pool: 2 },
drop: { src: SRC.pop, volume: 0.3, rate: 0.88, pool: 4 },
dropLight: { src: SRC.pop, volume: 0.22, rate: 1.22, pool: 2 },
// —— 消除(按连数)——
match3: { src: SRC.chime, volume: 0.48, rate: 1, pool: 4 },
match4: { src: SRC.fanfare, volume: 0.54, rate: 1.06, pool: 3 },
match5: { src: SRC.blast, volume: 0.62, rate: 1, pool: 3 },
// —— 消除(按 GI 主色)——
matchLowGi: { src: SRC.chime, volume: 0.42, rate: 0.86, pool: 2 },
matchMidGi: { src: SRC.chime, volume: 0.46, rate: 1.02, pool: 2 },
matchHighGi: { src: SRC.buzz, volume: 0.44, rate: 0.92, pool: 2 },
// —— 连消 ——
combo2: { src: SRC.fanfare, volume: 0.56, rate: 1.12, pool: 2 },
combo3: { src: SRC.blast, volume: 0.64, rate: 1.08, pool: 2 },
comboMega: { src: SRC.blast, volume: 0.72, rate: 1.22, pool: 2 },
// —— 道具 ——
insulin: { src: SRC.sparkle, volume: 0.5, rate: 1.15, pool: 2 },
fiber: { src: SRC.swoosh, volume: 0.48, rate: 1.32, pool: 2 },
meal: { src: SRC.stinger, volume: 0.42, rate: 1.35, pool: 1 },
reshuffle: { src: SRC.swoosh, volume: 0.46, rate: 0.72, pool: 2 },
// —— 反馈 ——
score: { src: SRC.tap, volume: 0.28, rate: 1.38, pool: 2 },
goalTick: { src: SRC.chime, volume: 0.36, rate: 1.45, pool: 2 },
meterWarn: { src: SRC.buzz, volume: 0.5, rate: 1, pool: 2 },
meterDanger: { src: SRC.buzz, volume: 0.58, rate: 0.78, pool: 2 },
meterStable: { src: SRC.pop, volume: 0.24, rate: 1.05, pool: 1 },
win: { src: SRC.fanfare, volume: 0.66, rate: 1, pool: 1 },
winStinger: { src: SRC.stinger, volume: 0.38, rate: 1.5, pool: 1 },
lose: { src: SRC.buzz, volume: 0.52, rate: 0.68, pool: 1 },
// 兼容旧名
match: { src: SRC.chime, volume: 0.48, rate: 1, pool: 4 },
combo: { src: SRC.fanfare, volume: 0.56, rate: 1.1, pool: 3 },
heal: { src: SRC.pop, volume: 0.44, rate: 1.18, pool: 2 },
boost: { src: SRC.sparkle, volume: 0.52, rate: 1, pool: 2 },
invalid: { src: SRC.buzz, volume: 0.4, rate: 1.15, pool: 2 },
danger: { src: SRC.blast, volume: 0.58, rate: 0.95, pool: 2 }
};
const WARMUP_SFX = [
"select",
"swap",
"swapFail",
"drop",
"dropLight",
"match3",
"match4",
"match5",
"combo2",
"combo3",
"comboMega",
"insulin",
"sparkle",
"reshuffle",
"lose"
];
function createAudioContext() {
if (typeof Audio !== "undefined") {
const audio = new Audio();
audio.preload = "auto";
return {
get src() {
return audio.src;
},
set src(value) {
audio.src = value;
},
get volume() {
return audio.volume;
},
set volume(value) {
audio.volume = value;
},
get playbackRate() {
return audio.playbackRate;
},
set playbackRate(value) {
audio.playbackRate = value;
},
play() {
const promise = audio.play();
if (promise == null ? void 0 : promise.catch)
promise.catch(() => {
});
},
stop() {
audio.pause();
try {
audio.currentTime = 0;
} catch (_) {
}
},
seek(time) {
try {
audio.currentTime = time;
} catch (_) {
}
},
destroy() {
audio.pause();
audio.removeAttribute("src");
}
};
}
return common_vendor.index.createInnerAudioContext();
}
class SfxPool {
constructor(src, size, volume, rate = 1) {
this.src = src;
this.size = size;
this.volume = volume;
this.rate = rate;
this.list = [];
this.cursor = 0;
this.warmedUp = false;
}
createOne() {
const ctx = createAudioContext();
ctx.src = this.src;
ctx.obeyMuteSwitch = false;
ctx.autoplay = false;
ctx.volume = this.volume;
try {
ctx.playbackRate = this.rate;
} catch (_) {
}
this.list.push(ctx);
return ctx;
}
create() {
while (this.list.length < this.size)
this.createOne();
}
warmUp() {
if (this.warmedUp)
return;
const ctx = this.list[0] || this.createOne();
try {
ctx.volume = 0;
ctx.play();
setTimeout(() => {
try {
ctx.stop();
ctx.volume = this.volume;
} catch (_) {
}
}, 60);
} catch (_) {
}
if (this.size > 1)
this.cursor = 1;
this.warmedUp = true;
}
play(scale = 1, rateMul = 1) {
if (this.list.length < this.size)
this.create();
const ctx = this.list[this.cursor % this.list.length];
this.cursor += 1;
const vol = Math.min(1, this.volume * scale);
const rate = Math.min(2, Math.max(0.5, this.rate * rateMul));
try {
ctx.volume = vol;
try {
ctx.playbackRate = rate;
} catch (_) {
}
ctx.stop();
ctx.seek(0);
ctx.play();
} catch (_) {
try {
ctx.play();
} catch (e) {
}
}
}
destroy() {
this.list.forEach((ctx) => {
try {
ctx.stop();
ctx.destroy();
} catch (_) {
}
});
this.list = [];
this.warmedUp = false;
}
}
function dominantGi(tiles = []) {
const cnt = { low: 0, mid: 0, high: 0 };
tiles.forEach((t) => {
var _a;
const g = (_a = t == null ? void 0 : t.type) == null ? void 0 : _a.gi;
if (g && cnt[g] !== void 0)
cnt[g] += 1;
});
let best = "mid";
let max = 0;
Object.entries(cnt).forEach(([k, v]) => {
if (v > max) {
max = v;
best = k;
}
});
return best;
}
function useGameSfx() {
const enabled = common_vendor.ref(true);
const pools = {};
try {
const stored = common_vendor.index.getStorageSync(STORAGE_KEY);
if (stored === false || stored === "0" || stored === 0) {
enabled.value = false;
}
} catch (_) {
}
function ensureAudioOption() {
try {
common_vendor.index.setInnerAudioOption({
obeyMuteSwitch: false,
mixWithOther: true
});
} catch (_) {
}
}
function getPool(name) {
const cfg = SFX[name];
if (!cfg)
return null;
if (!pools[name]) {
pools[name] = new SfxPool(cfg.src, cfg.pool, cfg.volume, cfg.rate ?? 1);
}
return pools[name];
}
function warmUp() {
if (!enabled.value)
return;
ensureAudioOption();
const names = typeof Audio !== "undefined" ? ["select"] : WARMUP_SFX;
names.forEach((name) => {
var _a;
(_a = getPool(name)) == null ? void 0 : _a.warmUp();
});
}
function play(name, scale = 1, rateMul = 1) {
var _a;
if (!enabled.value)
return;
(_a = getPool(name)) == null ? void 0 : _a.play(scale, rateMul);
}
function playLayer(primary, secondary, delayMs = 70, secScale = 0.6) {
play(primary);
if (secondary) {
setTimeout(() => play(secondary, secScale), delayMs);
}
}
function playMatch({ matchCount = 3, chain = 0, tiles = [] } = {}) {
if (!enabled.value)
return;
const giTrack = (() => {
const gi = dominantGi(tiles);
if (gi === "low")
return "matchLowGi";
if (gi === "high")
return "matchHighGi";
return "matchMidGi";
})();
if (chain >= 3) {
playLayer("comboMega", giTrack, 90, 0.55);
return;
}
if (chain === 2) {
playLayer("combo3", giTrack, 80, 0.5);
return;
}
if (chain === 1) {
playLayer("combo2", giTrack, 70, 0.45);
return;
}
if (matchCount >= 5) {
playLayer("match5", "matchHighGi", 85, 0.5);
} else if (matchCount >= 4) {
playLayer("match4", giTrack, 65, 0.48);
} else {
play(giTrack, 1, 1);
setTimeout(() => play("match3", 0.85), 40);
}
}
function playDrop(count = 1) {
if (!enabled.value || count <= 0)
return;
const name = count >= 4 ? "drop" : "dropLight";
play(name, Math.min(1.2, 0.85 + count * 0.04), count >= 6 ? 0.9 : 1);
}
function playMeter(delta, level) {
if (!enabled.value)
return;
if (level > 70)
play("meterDanger", 1 + (level - 70) * 0.02);
else if (level < 30)
play("meterWarn", 0.9, 0.95);
else if (delta > 8)
play("meterWarn", 0.75);
else if (delta < -5)
play("meterStable", 1.1);
}
function toggleEnabled() {
enabled.value = !enabled.value;
try {
common_vendor.index.setStorageSync(STORAGE_KEY, enabled.value ? "1" : "0");
} catch (_) {
}
if (enabled.value) {
warmUp();
play("select");
}
}
function destroyAll() {
Object.keys(pools).forEach((key) => {
var _a;
(_a = pools[key]) == null ? void 0 : _a.destroy();
delete pools[key];
});
}
common_vendor.onUnmounted(() => {
destroyAll();
});
return {
enabled,
play,
playLayer,
playMatch,
playDrop,
playMeter,
warmUp,
toggleEnabled,
destroyAll
};
}
exports.useGameSfx = useGameSfx;
//# sourceMappingURL=../../../../.sourcemap/mp-weixin/tongji/endless-game/composables/useGameSfx.js.map
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,11 @@
{
"navigationStyle": "custom",
"navigationBarTitleText": "识糖小课堂",
"backgroundColor": "#eefbf4",
"disableScroll": false,
"enableShareAppMessage": true,
"enableShareTimeline": true,
"usingComponents": {
"tongji-icon": "../components/TongjiIcon"
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,17 @@
{
"navigationBarTitleText": "血糖记录",
"navigationBarBackgroundColor": "#204e2b",
"navigationBarTextStyle": "white",
"backgroundColor": "#faf9f5",
"enablePullDownRefresh": true,
"usingPlugins": {
"WechatSI": {
"version": "0.3.5",
"provider": "wx069ba97219f66d99"
}
},
"usingComponents": {
"sugar-tree-graphic": "../components/SugarTreeGraphic",
"tongji-icon": "../components/TongjiIcon"
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,18 @@
{
"navigationStyle": "custom",
"navigationBarTitleText": "日常护理",
"backgroundColor": "#f4fbf4",
"enablePullDownRefresh": true,
"usingPlugins": {
"WechatSI": {
"version": "0.3.5",
"provider": "wx069ba97219f66d99"
}
},
"usingComponents": {
"sugar-tree-graphic": "../components/SugarTreeGraphic",
"tongji-icon": "../components/TongjiIcon",
"celebrate-burst": "../components/CelebrateBurst",
"voice-speak-mascot": "../components/VoiceSpeakMascot"
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,17 @@
{
"navigationStyle": "custom",
"navigationBarTitleText": "血糖管理",
"backgroundColor": "#f4fbf4",
"enablePullDownRefresh": true,
"usingPlugins": {
"WechatSI": {
"version": "0.3.5",
"provider": "wx069ba97219f66d99"
}
},
"usingComponents": {
"tongji-icon": "../components/TongjiIcon",
"tab-bar-dock": "../../components/app-tab-bar/tab-bar-dock",
"dev-training-entry": "../../components/dev-training-entry/index"
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,141 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
function joinApiUrl(base, path) {
const b = String(base || "").replace(/\/+$/, "");
const p = String(path || "").replace(/^\/+/, "");
return p ? `${b}/${p}` : b;
}
function appendQuery(url, data) {
if (!data || typeof data !== "object")
return url;
const keys = Object.keys(data);
if (!keys.length)
return url;
const qs = keys.map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(data[k] ?? "")}`).join("&");
return url + (url.includes("?") ? "&" : "?") + qs;
}
function decodeChunkData(data) {
if (typeof data === "string")
return data;
if (!data)
return "";
if (typeof TextDecoder !== "undefined" && data instanceof ArrayBuffer) {
return new TextDecoder("utf-8").decode(new Uint8Array(data));
}
if (data instanceof ArrayBuffer) {
const bytes = new Uint8Array(data);
let str = "";
for (let i = 0; i < bytes.length; i++)
str += String.fromCharCode(bytes[i]);
try {
return decodeURIComponent(escape(str));
} catch (e) {
return str;
}
}
return "";
}
function consumeSseBuffer(buffer, onEvent) {
let rest = buffer;
let idx = rest.indexOf("\n\n");
while (idx !== -1) {
const block = rest.slice(0, idx);
rest = rest.slice(idx + 2);
let event = "message";
let dataLine = "";
block.split("\n").forEach((line) => {
if (line.startsWith("event:"))
event = line.slice(6).trim();
else if (line.startsWith("data:"))
dataLine += line.slice(5).trim();
});
if (dataLine) {
try {
onEvent(event, JSON.parse(dataLine));
} catch (e) {
onEvent(event, dataLine);
}
}
idx = rest.indexOf("\n\n");
}
return rest;
}
function parsePartialDietPlainText(text) {
const out = {};
if (!text)
return out;
const lineRe = /^(早餐|喝的|午餐|晚餐|提示|少碰)[::]\s*(.*)$/gm;
let match;
while ((match = lineRe.exec(text)) !== null) {
const key = { "早餐": "breakfast", "喝的": "drinks", "午餐": "lunch", "晚餐": "dinner", "提示": "tips", "少碰": "avoid" }[match[1]];
const val = (match[2] || "").trim();
if (key === "avoid") {
out.avoid = val.split(/[、,,;;\s]+/).map((s) => s.trim()).filter(Boolean);
} else {
out[key] = val;
}
}
const tail = text.match(/(?:^|\n)(早餐|喝的|午餐|晚餐|提示|少碰)[::]\s*([^\n]*)$/);
if (tail) {
const key = { "早餐": "breakfast", "喝的": "drinks", "午餐": "lunch", "晚餐": "dinner", "提示": "tips", "少碰": "avoid" }[tail[1]];
const val = (tail[2] || "").trim();
if (key === "avoid") {
if (val)
out.avoid = val.split(/[、,,;;\s]+/).map((s) => s.trim()).filter(Boolean);
} else {
out[key] = val;
}
}
return out;
}
function requestAiStream(opts) {
const {
baseUrl,
url,
method = "GET",
data = {},
onEvent,
fallback
} = opts;
return new Promise((resolve, reject) => {
const token = common_vendor.index.getStorageSync("token") || "";
let sseBuffer = "";
let requestUrl = joinApiUrl(baseUrl, url);
if (method === "GET") {
requestUrl = appendQuery(requestUrl, data);
}
const task = common_vendor.wx$1.request({
url: requestUrl,
method,
data: method === "POST" ? data : {},
enableChunked: true,
timeout: 6e4,
header: {
token,
"content-type": "application/x-www-form-urlencoded",
"Cache-Control": "no-cache"
},
success: (res) => {
if (res && res.data) {
sseBuffer += decodeChunkData(res.data);
sseBuffer = consumeSseBuffer(sseBuffer, onEvent);
}
resolve(res);
},
fail: (err) => reject(err)
});
if (task && typeof task.onChunkReceived === "function") {
task.onChunkReceived((res) => {
sseBuffer += decodeChunkData(res.data);
sseBuffer = consumeSseBuffer(sseBuffer, onEvent);
});
} else if (typeof fallback === "function") {
fallback().then(resolve).catch(reject);
} else {
reject(new Error("当前环境不支持流式请求"));
}
});
}
exports.parsePartialDietPlainText = parsePartialDietPlainText;
exports.requestAiStream = requestAiStream;
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/utils/aiStreamRequest.js.map
@@ -0,0 +1,48 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
function utf8ToBytes(str) {
const encoded = encodeURIComponent(str);
const bytes = [];
for (let i = 0; i < encoded.length; i++) {
if (encoded.charCodeAt(i) === 37) {
bytes.push(parseInt(encoded.substring(i + 1, i + 3), 16));
i += 2;
} else {
bytes.push(encoded.charCodeAt(i));
}
}
return new Uint8Array(bytes);
}
function bytesToBase64(bytes) {
if (typeof common_vendor.index !== "undefined" && typeof common_vendor.index.arrayBufferToBase64 === "function") {
return common_vendor.index.arrayBufferToBase64(bytes.buffer);
}
if (typeof btoa !== "undefined") {
let binary = "";
for (let i = 0; i < bytes.length; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let result = "";
for (let i = 0; i < bytes.length; i += 3) {
const a = bytes[i];
const b = i + 1 < bytes.length ? bytes[i + 1] : 0;
const c = i + 2 < bytes.length ? bytes[i + 2] : 0;
result += chars[a >> 2];
result += chars[(a & 3) << 4 | b >> 4];
result += i + 1 < bytes.length ? chars[(b & 15) << 2 | c >> 6] : "=";
result += i + 2 < bytes.length ? chars[c & 63] : "=";
}
return result;
}
function svgToDataUrl(svg) {
const normalized = String(svg || "").trim();
if (!normalized)
return "";
const base64 = bytesToBase64(utf8ToBytes(normalized));
return `data:image/svg+xml;base64,${base64}`;
}
exports.svgToDataUrl = svgToDataUrl;
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/utils/svgDataUrl.js.map
@@ -0,0 +1,60 @@
"use strict";
const TREE_MAX_LEVEL = 9;
const TREE_XP_PER_LEVEL = 50;
const TREE_LEVELS = [
{ level: 0, name: "种子眠", emoji: "🫘", mood: "困困", desc: "稳糖种子在土里打盹" },
{ level: 1, name: "破土芽", emoji: "🌱", mood: "探头", desc: "探出第一抹新绿" },
{ level: 2, name: "展两叶", emoji: "🌿", mood: "好奇", desc: "两片嫩叶迎风展" },
{ level: 3, name: "小树苗", emoji: "🪴", mood: "精神", desc: "身子骨硬朗起来" },
{ level: 4, name: "青枝繁", emoji: "🌳", mood: "茁壮", desc: "枝叶渐密,元气足" },
{ level: 5, name: "拔节高", emoji: "🌲", mood: "挺拔", desc: "一节一节往上蹿" },
{ level: 6, name: "稳糖冠", emoji: "💚", mood: "沉稳", desc: "树冠成形,习惯成自然" },
{ level: 7, name: "初绽香", emoji: "🌸", mood: "开心", desc: "枝头冒出第一朵花" },
{ level: 8, name: "漫开花", emoji: "🌺", mood: "灿烂", desc: "花开满枝,越记越稳" },
{ level: 9, name: "圆满树", emoji: "🏆", mood: "荣耀", desc: "满级大树,习惯大师" }
];
const WHISPERS_BY_LEVEL = {
0: ["种子在睡觉,记一笔就醒啦。", "今天浇第一滴水,芽就要冒出来。"],
1: ["破土啦!再坚持几天就长高。", "小芽最喜欢规律的记录了。"],
2: ["两片叶子为你鼓掌。", "空腹餐后都记全,我会长得更快。"],
3: ["我已经是一棵小树啦。", "家人点赞的时候,我也会发光。"],
4: ["枝叶越来越密,您真棒。", "连续记录,我会开出更多叶子。"],
5: ["拔节中!习惯比完美更重要。", "再浇一点水,我就更高啦。"],
6: ["习惯成自然,树冠成形啦。", "您今天的坚持,小树都记得。"],
7: ["开花啦!闻到春天的味道了吗?", "稳糖花只开给坚持的人。"],
8: ["满树花香,您已是控糖达人。", "明天继续来,花儿会更艳。"],
9: ["满级大树陪您一路稳糖。", "圆满不是终点,习惯才是。"]
};
function calcTreeFromPoints(points) {
const pts = Math.max(0, Number(points) || 0);
const level = Math.min(TREE_MAX_LEVEL, Math.floor(pts / TREE_XP_PER_LEVEL));
const xpInLevel = level >= TREE_MAX_LEVEL ? TREE_XP_PER_LEVEL : pts % TREE_XP_PER_LEVEL;
const progress = level >= TREE_MAX_LEVEL ? 100 : Math.round(xpInLevel / TREE_XP_PER_LEVEL * 100);
const meta = TREE_LEVELS[level] || TREE_LEVELS[0];
const nextMeta = level < TREE_MAX_LEVEL ? TREE_LEVELS[level + 1] : null;
const pointsToNext = level >= TREE_MAX_LEVEL ? 0 : TREE_XP_PER_LEVEL - xpInLevel;
return {
level,
progress,
name: meta.name,
emoji: meta.emoji,
mood: meta.mood,
desc: meta.desc,
xpInLevel,
xpNeed: TREE_XP_PER_LEVEL,
pointsToNext,
nextName: (nextMeta == null ? void 0 : nextMeta.name) || "",
isMax: level >= TREE_MAX_LEVEL
};
}
function pickTreeWhisper(level) {
const lv = Math.min(TREE_MAX_LEVEL, Math.max(0, Number(level) || 0));
const list = WHISPERS_BY_LEVEL[lv] || WHISPERS_BY_LEVEL[0];
return list[Math.floor(Math.random() * list.length)];
}
exports.TREE_LEVELS = TREE_LEVELS;
exports.TREE_MAX_LEVEL = TREE_MAX_LEVEL;
exports.TREE_XP_PER_LEVEL = TREE_XP_PER_LEVEL;
exports.calcTreeFromPoints = calcTreeFromPoints;
exports.pickTreeWhisper = pickTreeWhisper;
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/utils/treeLevels.js.map
@@ -0,0 +1,68 @@
"use strict";
const tongji_utils_treeLevels = require("./treeLevels.js");
const TREE_SPECIES_LIST = [
{
id: "classic",
name: "稳糖灵树",
tagline: "经典路线,从种子到圆满",
stages: tongji_utils_treeLevels.TREE_LEVELS.map((l) => l.emoji)
},
{
id: "bonsai",
name: "雅韵盆景",
tagline: "小巧精致,书桌旁的温柔陪伴",
stages: ["🫘", "🌱", "🪴", "🎋", "🪴", "🌳", "🎍", "🌸", "🏵️", "🏆"]
},
{
id: "pine",
name: "苍劲青松",
tagline: "挺拔向上,越记越稳",
stages: ["🫘", "🌱", "🌿", "🌲", "🌲", "🌲", "⛰️", "🌲", "🏔️", "🏆"]
},
{
id: "sakura",
name: "樱花小树",
tagline: "坚持打卡,枝头渐开",
stages: ["🫘", "🌱", "🌿", "🌳", "🌸", "🌸", "🌸", "🌸", "🌺", "🏆"]
},
{
id: "ginkgo",
name: "金秋银杏",
tagline: "叶色渐变,见证每一天",
stages: ["🫘", "🌱", "🍃", "🌳", "🍂", "🍂", "🌳", "🍁", "🌟", "🏆"]
}
];
const DAILY_MAX_TASK_POINTS = 40;
function getTreeSpecies(id) {
return TREE_SPECIES_LIST.find((s) => s.id === id) || TREE_SPECIES_LIST[0];
}
function speciesEmojiAtLevel(speciesId, level) {
const sp = getTreeSpecies(speciesId);
const lv = Math.min(tongji_utils_treeLevels.TREE_MAX_LEVEL, Math.max(0, Number(level) || 0));
return sp.stages[lv] || sp.stages[0] || "🌱";
}
function speciesGrowthRoadmap(speciesId) {
const sp = getTreeSpecies(speciesId);
return tongji_utils_treeLevels.TREE_LEVELS.map((meta, i) => {
const xpTotal = i * tongji_utils_treeLevels.TREE_XP_PER_LEVEL;
const daysHint = i === 0 ? 0 : Math.max(1, Math.ceil(xpTotal / DAILY_MAX_TASK_POINTS));
return {
level: i,
name: meta.name,
emoji: sp.stages[i] || meta.emoji,
desc: meta.desc,
xpTotal,
daysHint
};
});
}
function estimateDaysToMaxLevel() {
const totalXp = tongji_utils_treeLevels.TREE_MAX_LEVEL * tongji_utils_treeLevels.TREE_XP_PER_LEVEL;
return Math.max(1, Math.ceil(totalXp / DAILY_MAX_TASK_POINTS));
}
exports.TREE_SPECIES_LIST = TREE_SPECIES_LIST;
exports.estimateDaysToMaxLevel = estimateDaysToMaxLevel;
exports.getTreeSpecies = getTreeSpecies;
exports.speciesEmojiAtLevel = speciesEmojiAtLevel;
exports.speciesGrowthRoadmap = speciesGrowthRoadmap;
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/utils/treeSpecies.js.map
@@ -0,0 +1,310 @@
"use strict";
const CN_DIGIT = {
零: 0,
"〇": 0,
一: 1,
壹: 1,
幺: 1,
二: 2,
贰: 2,
两: 2,
三: 3,
叁: 3,
四: 4,
肆: 4,
五: 5,
伍: 5,
六: 6,
陆: 6,
七: 7,
柒: 7,
八: 8,
捌: 8,
九: 9,
玖: 9
};
const CN_UNIT = { 十: 10, 拾: 10, 百: 100, 佰: 100, 千: 1e3, 仟: 1e3, 万: 1e4, 亿: 1e8 };
function cnIntToNumber(s) {
let total = 0;
let section = 0;
let number = 0;
let hadUnit = false;
let lastUnit = 0;
let sawZeroAfterUnit = false;
for (const ch of s) {
if (CN_DIGIT[ch] !== void 0) {
number = CN_DIGIT[ch];
if (number === 0)
sawZeroAfterUnit = true;
} else if (CN_UNIT[ch] !== void 0) {
hadUnit = true;
const unit = CN_UNIT[ch];
lastUnit = unit;
sawZeroAfterUnit = false;
if (unit >= 1e4) {
section = (section + number) * unit;
total += section;
section = 0;
} else {
if (number === 0)
number = 1;
section += number * unit;
}
number = 0;
}
}
if (!hadUnit && s.length > 1) {
let joined = "";
for (const ch of s) {
if (CN_DIGIT[ch] !== void 0)
joined += CN_DIGIT[ch];
}
if (joined)
return Number(joined);
}
if (number > 0 && lastUnit >= 100 && !sawZeroAfterUnit) {
number = number * (lastUnit / 10);
}
return total + section + number;
}
function cnSeqToNumber(seq) {
if (seq.includes("点")) {
const parts = seq.split("点");
const intPart = parts[0];
const decPart = parts.slice(1).join("");
const intVal = intPart ? cnIntToNumber(intPart) : 0;
let decStr = "";
for (const ch of decPart) {
if (CN_DIGIT[ch] !== void 0)
decStr += CN_DIGIT[ch];
}
if (!decStr)
return intVal;
return Number(`${intVal}.${decStr}`);
}
return cnIntToNumber(seq);
}
function hasCnDigit(seq) {
for (const ch of seq) {
if (CN_DIGIT[ch] !== void 0 || CN_UNIT[ch] !== void 0)
return true;
}
return false;
}
function normalizeNumbers(text) {
const raw = String(text || "");
return raw.replace(/[零〇一壹幺二贰两三叁四肆五伍六陆七柒八捌九玖十拾百佰千仟万亿点]+/g, (m) => {
if (!hasCnDigit(m))
return m;
const n = cnSeqToNumber(m);
return Number.isFinite(n) ? String(n) : m;
});
}
function numAfter(text, keys) {
for (const k of keys) {
const i = text.indexOf(k);
if (i >= 0) {
const rest = text.slice(i + k.length);
const m = rest.match(/-?\d+(?:\.\d+)?/);
if (m)
return m[0];
}
}
return "";
}
function textAfter(text, keys, stopKeys = []) {
for (const k of keys) {
const i = text.indexOf(k);
if (i >= 0) {
let rest = text.slice(i + k.length);
rest = rest.replace(/^[是为吃了喝了吃的喝的有打了用了::,,、。\s]+/, "");
let end = rest.length;
const mStop = rest.match(/[,,。.;;!!??\n]/);
if (mStop && mStop.index < end)
end = mStop.index;
for (const sk of stopKeys) {
const si = rest.indexOf(sk);
if (si >= 0 && si < end)
end = si;
}
const seg = rest.slice(0, end).trim();
if (seg)
return seg;
}
}
return "";
}
function parseGlucose(raw) {
const t = normalizeNumbers(raw);
const result = {};
const fasting = numAfter(t, ["空腹"]);
const post = numAfter(t, ["餐后", "饭后"]);
const other = numAfter(t, ["其他", "随机", "睡前", "凌晨", "夜间", "晚上"]);
if (fasting)
result.fasting_blood_sugar = fasting;
if (post)
result.postprandial_blood_sugar = post;
if (other)
result.other_blood_sugar = other;
if (!fasting && !post && !other) {
const nums = t.match(/\d+(?:\.\d+)?/g);
if (nums && nums.length === 1)
result.other_blood_sugar = nums[0];
}
return result;
}
function parseBloodPressure(raw) {
const t = normalizeNumbers(raw);
const result = {};
let sys = numAfter(t, ["高压", "收缩压", "收缩"]);
let dia = numAfter(t, ["低压", "舒张压", "舒张"]);
if (!sys || !dia) {
const nums = (t.match(/\d{2,3}/g) || []).map(Number).filter((n) => n >= 30 && n <= 300);
if (!sys && !dia && nums.length >= 2) {
sys = String(nums[0]);
dia = String(nums[1]);
} else if (!sys && nums.length === 1 && nums[0] >= 90) {
sys = String(nums[0]);
}
}
if (sys && dia && Number(sys) < Number(dia)) {
const tmp = sys;
sys = dia;
dia = tmp;
}
if (sys)
result.systolic_pressure = sys;
if (dia)
result.diastolic_pressure = dia;
const insulin = textAfter(raw, ["胰岛素"], ["高压", "低压", "血压", "西药", "备注"]);
if (insulin)
result.insulin = insulin;
const western = textAfter(raw, ["西药", "降糖药", "口服药"], ["胰岛素", "高压", "低压", "血压", "备注"]);
if (western)
result.western_medicine = western;
return result;
}
const DIET_MARKERS = [
{ keys: ["早餐", "早饭", "早上", "早点", "早晨"], field: "breakfast_foods" },
{ keys: ["午餐", "午饭", "中午"], field: "lunch_foods" },
{ keys: ["晚餐", "晚饭", "晚上", "夜宵", "夜里"], field: "dinner_foods" }
];
function cleanFoodSeg(seg) {
return String(seg || "").replace(/^[是为吃了吃的喝了喝的有::,,、。\s]+/, "").replace(/[。.\s]+$/, "").trim();
}
function parseDiet(raw) {
const t = String(raw || "").trim();
const result = {};
const hits = [];
DIET_MARKERS.forEach((m) => {
for (const k of m.keys) {
const idx = t.indexOf(k);
if (idx >= 0) {
hits.push({ idx, field: m.field, klen: k.length });
break;
}
}
});
if (!hits.length) {
if (t)
result.note = t;
return result;
}
hits.sort((a, b) => a.idx - b.idx);
hits.forEach((h, i) => {
const start = h.idx + h.klen;
const end = i + 1 < hits.length ? hits[i + 1].idx : t.length;
const seg = cleanFoodSeg(t.slice(start, end));
if (seg && !result[h.field])
result[h.field] = seg;
});
const head = cleanFoodSeg(t.slice(0, hits[0].idx));
if (head)
result.note = head;
return result;
}
const EXERCISE_FILLERS = [
"今天",
"我",
"做了",
"做",
"进行了",
"进行",
"运动了",
"运动",
"锻炼了",
"锻炼",
"了",
"大概",
"左右",
"持续",
"差不多",
"一共",
"总共",
"的",
"走了",
"打了",
"跑了"
];
function parseExercise(raw) {
const t = normalizeNumbers(raw);
const result = {};
const durMatch = t.match(/(\d+(?:\.\d+)?)\s*(个小时|小时|钟头|时|分钟|分)/);
if (durMatch) {
const val = parseFloat(durMatch[1]);
const isHour = /个小时|小时|钟头|时/.test(durMatch[2]);
result.duration = String(Math.round(isHour ? val * 60 : val));
}
if (/高强度|剧烈|很累|大汗|气喘/.test(t))
result.intensity = 3;
else if (/中强度|中等强度|中等|有点累|微微出汗|微汗/.test(t))
result.intensity = 2;
else if (/低强度|轻松|轻微|溜达|缓慢/.test(t))
result.intensity = 1;
let type = t;
if (durMatch)
type = type.replace(durMatch[0], "");
type = type.replace(/高强度|中强度|中等强度|低强度|剧烈|很累|大汗|气喘|有点累|微微出汗|微汗|轻松|轻微|缓慢/g, "").replace(/\d+(?:\.\d+)?/g, "").replace(/[,,。.;;、\s]+/g, "");
EXERCISE_FILLERS.forEach((f) => {
type = type.split(f).join("");
});
type = type.trim();
if (type && type.length <= 20)
result.exercise_type = type;
return result;
}
const FIELD_LABELS = {
fasting_blood_sugar: "空腹",
postprandial_blood_sugar: "餐后",
other_blood_sugar: "其他血糖",
systolic_pressure: "高压",
diastolic_pressure: "低压",
western_medicine: "西药",
insulin: "胰岛素",
breakfast_foods: "早餐",
lunch_foods: "午餐",
dinner_foods: "晚餐",
note: "备注",
exercise_type: "运动",
duration: "时长",
intensity: "强度"
};
const INTENSITY_LABELS = { 1: "低强度", 2: "中强度", 3: "高强度" };
function summarizeParsed(parsed) {
const parts = [];
Object.keys(parsed).forEach((k) => {
const label = FIELD_LABELS[k] || k;
let val = parsed[k];
if (k === "intensity")
val = INTENSITY_LABELS[val] || val;
parts.push(`${label}${val}`);
});
return parts.join(" · ");
}
exports.normalizeNumbers = normalizeNumbers;
exports.parseBloodPressure = parseBloodPressure;
exports.parseDiet = parseDiet;
exports.parseExercise = parseExercise;
exports.parseGlucose = parseGlucose;
exports.summarizeParsed = summarizeParsed;
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/utils/voiceParse.js.map