Compare commits

..
17 Commits
Author SHA1 Message Date
Your Name ed040025e4 i
Merge branch 'master' of git1.zhenyangtang.com.cn:ZYT/zyt
2026-08-17 13:59:32 +08:00
Your Name 49309b7c57 gengx 2026-08-17 13:59:12 +08:00
gr 18b92f1767 geng 2026-08-17 09:17:42 +08:00
Your Name 79601ea176 更新 2026-08-17 09:09:20 +08:00
Your Name c1eee2d28a 更新bug 2026-08-17 09:06:49 +08:00
Your Name 2fc8048844 更新 2026-08-14 14:52:42 +08:00
Your Name 18c15d1262 更新 2026-08-14 14:37:30 +08:00
Your Name 21790e35f4 新增 2026-08-13 16:37:37 +08:00
Your Name cbe71905c0 更新 2026-08-13 14:22:19 +08:00
Your Name 72a5300b08 更新 2026-08-13 11:54:00 +08:00
Your Name 07188e82f8 更新 2026-08-13 09:41:55 +08:00
Your Name c3713a8785 更新 2026-08-13 09:39:16 +08:00
Your Name dddf2348d3 更新 2026-08-12 18:30:00 +08:00
Your Name 18ed3c48fa 更新 2026-08-12 18:26:42 +08:00
Your Name 26839c2957 更新 2026-08-12 18:18:33 +08:00
Your Name b2eb18c75f 更新 2026-08-12 18:09:57 +08:00
Your Name a1092c02c3 更新 2026-08-12 17:27:14 +08:00
649 changed files with 37789 additions and 56562 deletions
+1 -164
View File
@@ -1,165 +1,2 @@
"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const common_vendor = require("./common/vendor.js");
if (!Math) {
"./tongji/pages/weekly.js";
"./pages/user/user.js";
"./pages/user/userinfo.js";
"./pages/Card/Card.js";
"./pages/Card/edit_card.js";
"./pages/Card/contact.js";
"./tongji/pages/index.js";
"./tongji/pages/more.js";
"./tongji/endless-game/index.js";
"./training/pages/grip-ring.js";
"./training/pages/pilates-ring.js";
"./training/pages/foot-pedal.js";
"./training/pages/dumbbell.js";
"./training/pages/metronome.js";
}
const _sfc_main = {
__name: "App",
setup(__props) {
const { proxy } = common_vendor.getCurrentInstance();
common_vendor.onLaunch(() => {
try {
common_vendor.index.setInnerAudioOption({
obeyMuteSwitch: false,
mixWithOther: true
});
} catch (e) {
common_vendor.index.__f__("warn", "at App.vue:16", "setInnerAudioOption failed:", e);
}
common_vendor.index.login({
provider: "weixin",
success: function(loginRes) {
if (common_vendor.index.getStorageSync("token")) {
userinfo(loginRes.code);
} else {
wxcode(loginRes.code);
}
}
});
});
const wxcode = async (code) => {
try {
const res = await proxy.apiUrl(
{
url: "/api/login/mnpLogin",
method: "POST",
data: {
code
}
},
false
);
if (res && res.data && res.data.token) {
common_vendor.index.setStorageSync("token", res.data.token);
common_vendor.index.setStorageSync("userData", res.data);
}
} catch (err) {
}
};
const userinfo = async (code) => {
try {
const res = await proxy.apiUrl(
{
url: "/api/user/info",
method: "POST"
},
false
);
if (res.code == -1) {
wxcode(code);
return;
}
if (res && res.data) {
common_vendor.index.setStorageSync("userData", res.data);
}
} catch (err) {
}
};
return () => {
};
}
};
var baseUrl = "https://admin.zhenyangtang.com.cn/";
function joinApiUrl(base, path) {
const b = String(base || "").replace(/\/+$/, "");
const p = String(path || "").replace(/^\/+/, "");
return p ? `${b}/${p}` : b;
}
function apiUrl(promise, Loading = true) {
const token = common_vendor.index.getStorageSync("token");
const header = {
token,
"content-type": "application/x-www-form-urlencoded",
...promise.header || {}
};
const data = promise.data || {};
const url = joinApiUrl(baseUrl, promise.url || "");
return new Promise((resolve, reject) => {
if (Loading) {
common_vendor.index.showLoading({ title: "加载中...", mask: true });
}
common_vendor.index.request({
url,
data,
timeout: promise.timeout != null ? promise.timeout : 15e3,
method: promise.method || "POST",
header,
sslVerify: false,
success: (res) => {
const data2 = res.data;
if (data2.code === -1) {
common_vendor.index.removeStorageSync("token");
}
resolve(data2);
},
fail: (e) => {
reject(e);
},
complete: (e) => {
if (Loading) {
common_vendor.index.hideLoading();
}
}
});
});
}
function parsePageParams(options) {
const params = {};
if (!options)
return params;
if (options.scene) {
try {
const decodedScene = decodeURIComponent(options.scene);
decodedScene.split("&").forEach((item) => {
const [key, value] = item.split("=");
if (key && value) {
params[key] = value;
}
});
} catch (error) {
common_vendor.index.__f__("error", "at main.js:126", "解析scene参数失败:", error);
}
}
Object.keys(options).forEach((key) => {
if (key !== "scene" && options[key]) {
params[key] = options[key];
}
});
return params;
}
function createApp() {
const app = common_vendor.createSSRApp(_sfc_main);
app.config.globalProperties.$url = baseUrl;
app.config.globalProperties.apiUrl = apiUrl;
app.config.globalProperties.$parsePageParams = parsePageParams;
return {
app
};
}
createApp().app.mount("#app");
exports.createApp = createApp;
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("./common/vendor.js"),l={__name:"App",setup(e){const{proxy:o}=n.getCurrentInstance();n.onLaunch(()=>{try{n.index.setInnerAudioOption({obeyMuteSwitch:!1,mixWithOther:!0})}catch(r){n.index.__f__("warn","at App.vue:16","setInnerAudioOption failed:",r)}n.index.login({provider:"weixin",success:function(r){n.index.getStorageSync("token")?i(r.code):a(r.code)}})});const a=async r=>{try{const t=await o.apiUrl({url:"/api/login/mnpLogin",method:"POST",data:{code:r}},!1);t&&t.data&&t.data.token&&(n.index.setStorageSync("token",t.data.token),n.index.setStorageSync("userData",t.data))}catch{}},i=async r=>{try{const t=await o.apiUrl({url:"/api/user/info",method:"POST"},!1);if(t.code==-1){a(r);return}t&&t.data&&n.index.setStorageSync("userData",t.data)}catch{}};return()=>{}}};var d="https://admin.zhenyangtang.com.cn/";function f(e,o){const a=String(e||"").replace(/\/+$/,""),i=String(o||"").replace(/^\/+/,"");return i?`${a}/${i}`:a}function m(e,o=!0){const i={token:n.index.getStorageSync("token"),"content-type":"application/x-www-form-urlencoded",...e.header||{}},r=e.data||{},t=f(d,e.url||"");return new Promise((g,u)=>{o&&n.index.showLoading({title:"加载中...",mask:!0}),n.index.request({url:t,data:r,timeout:e.timeout!=null?e.timeout:15e3,method:e.method||"POST",header:i,sslVerify:!1,success:s=>{const c=s.data;c.code===-1&&n.index.removeStorageSync("token"),g(c)},fail:s=>{u(s)},complete:s=>{o&&n.index.hideLoading()}})})}function S(e){const o={};if(!e)return o;if(e.scene)try{decodeURIComponent(e.scene).split("&").forEach(i=>{const[r,t]=i.split("=");r&&t&&(o[r]=t)})}catch(a){n.index.__f__("error","at main.js:126","解析scene参数失败:",a)}return Object.keys(e).forEach(a=>{a!=="scene"&&e[a]&&(o[a]=e[a])}),o}function p(){const e=n.createSSRApp(l);return e.config.globalProperties.$url=d,e.config.globalProperties.apiUrl=m,e.config.globalProperties.$parsePageParams=S,{app:e}}p().app.mount("#app");exports.createApp=p;
//# sourceMappingURL=../.sourcemap/mp-weixin/app.js.map
+1 -6
View File
@@ -1,7 +1,2 @@
/* 全局基础 */
page {
background-color: #f4fbf4;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
sans-serif;
}
page{background-color:#f4fbf4;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}
page{--status-bar-height:25px;--top-window-height:0px;--window-top:0px;--window-bottom:0px;--window-left:0px;--window-right:0px;--window-magin:0px}[data-c-h="true"]{display: none !important;}
File diff suppressed because one or more lines are too long
@@ -1,40 +1,2 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const color = "#8a8a8a";
const activeColor = "#204e2b";
const _sfc_main = {
__name: "tab-bar-dock",
props: {
/** 0 血糖 1 我的 */
active: {
type: Number,
default: 0
}
},
setup(__props) {
const props = __props;
function goUser() {
if (props.active === 1)
return;
common_vendor.index.redirectTo({ url: "/pages/user/user" });
}
function goWeekly() {
if (props.active === 0)
return;
common_vendor.index.redirectTo({ url: "/tongji/pages/weekly" });
}
return (_ctx, _cache) => {
return {
a: __props.active === 0 ? 1 : "",
b: __props.active === 0 ? activeColor : color,
c: common_vendor.o(goWeekly, "40"),
d: __props.active === 1 ? "/static/user/user_no.png" : "/static/user/user.png",
e: __props.active === 1 ? activeColor : color,
f: common_vendor.o(goUser, "79")
};
};
}
};
const Component = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-c3efacf5"]]);
wx.createComponent(Component);
"use strict";const t=require("../../common/vendor.js"),r="#8a8a8a",n="#204e2b",i={__name:"tab-bar-dock",props:{active:{type:Number,default:0}},setup(e){const c=e;function o(){c.active!==1&&t.index.redirectTo({url:"/pages/user/user"})}function a(){c.active!==0&&t.index.redirectTo({url:"/tongji/pages/weekly"})}return(u,d)=>({a:e.active===0?1:"",b:e.active===0?n:r,c:t.o(a,"40"),d:e.active===1?"/static/user/user_no.png":"/static/user/user.png",e:e.active===1?n:r,f:t.o(o,"79")})}},s=t._export_sfc(i,[["__scopeId","data-v-c3efacf5"]]);wx.createComponent(s);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/components/app-tab-bar/tab-bar-dock.js.map
@@ -1,90 +1 @@
/**
* 这里是uni-app内置的常用样式变量
*
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
*
*/
/**
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
*
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
*/
/* 颜色变量 */
/* 行为相关颜色 */
/* 文字基本颜色 */
/* 背景颜色 */
/* 边框颜色 */
/* 尺寸变量 */
/* 文字尺寸 */
/* 图片尺寸 */
/* Border Radius */
/* 水平间距 */
/* 垂直间距 */
/* 透明度 */
/* 文章场景相关 */
.tab-dock.data-v-c3efacf5 {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 999;
background: #ffffff;
border-top: 1rpx solid rgba(0, 0, 0, 0.06);
box-shadow: 0 -6rpx 20rpx rgba(0, 0, 0, 0.04);
}
.tab-dock__inner.data-v-c3efacf5 {
display: flex;
align-items: flex-end;
height: 100rpx;
}
.tab-dock__item.data-v-c3efacf5 {
flex: 1;
height: 100rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.tab-dock__item--center.data-v-c3efacf5 {
justify-content: flex-end;
padding-bottom: 6rpx;
}
.tab-dock__icon.data-v-c3efacf5 {
width: 46rpx;
height: 46rpx;
}
.tab-dock__label.data-v-c3efacf5 {
margin-top: 6rpx;
font-size: 22rpx;
line-height: 1.2;
}
.tab-dock__label--center.data-v-c3efacf5 {
margin-top: 8rpx;
}
.tab-dock__fab.data-v-c3efacf5 {
width: 96rpx;
height: 96rpx;
margin-top: -48rpx;
border-radius: 50%;
background: linear-gradient(180deg, #2d7340 0%, #204e2b 100%);
border: 6rpx solid #ffffff;
box-shadow: 0 10rpx 24rpx rgba(32, 78, 43, 0.35);
display: flex;
align-items: center;
justify-content: center;
}
.tab-dock__fab--active.data-v-c3efacf5 {
transform: scale(1.04);
box-shadow: 0 12rpx 28rpx rgba(32, 78, 43, 0.42);
}
.tab-dock__fab-text.data-v-c3efacf5 {
color: #ffffff;
font-size: 38rpx;
font-weight: 700;
line-height: 1;
}
.tab-dock__safe.data-v-c3efacf5 {
height: env(safe-area-inset-bottom);
background: #ffffff;
}
.tab-dock.data-v-c3efacf5{position:fixed;left:0;right:0;bottom:0;z-index:999;background:#fff;border-top:1rpx solid rgba(0,0,0,.06);box-shadow:0 -6rpx 20rpx rgba(0,0,0,.04)}.tab-dock__inner.data-v-c3efacf5{display:flex;align-items:flex-end;height:100rpx}.tab-dock__item.data-v-c3efacf5{flex:1;height:100rpx;display:flex;flex-direction:column;align-items:center;justify-content:center}.tab-dock__item--center.data-v-c3efacf5{justify-content:flex-end;padding-bottom:6rpx}.tab-dock__icon.data-v-c3efacf5{width:46rpx;height:46rpx}.tab-dock__label.data-v-c3efacf5{margin-top:6rpx;font-size:22rpx;line-height:1.2}.tab-dock__label--center.data-v-c3efacf5{margin-top:8rpx}.tab-dock__fab.data-v-c3efacf5{width:96rpx;height:96rpx;margin-top:-48rpx;border-radius:50%;background:linear-gradient(180deg,#2d7340,#204e2b);border:6rpx solid #ffffff;box-shadow:0 10rpx 24rpx rgba(32,78,43,.35);display:flex;align-items:center;justify-content:center}.tab-dock__fab--active.data-v-c3efacf5{transform:scale(1.04);box-shadow:0 12rpx 28rpx rgba(32,78,43,.42)}.tab-dock__fab-text.data-v-c3efacf5{color:#fff;font-size:38rpx;font-weight:700;line-height:1}.tab-dock__safe.data-v-c3efacf5{height:env(safe-area-inset-bottom);background:#fff}
@@ -1,89 +1,2 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const _sfc_main = /* @__PURE__ */ common_vendor.defineComponent({
__name: "index",
props: {
bottom: { default: 200 },
inline: { type: Boolean, default: false },
tone: { default: "default" }
},
setup(__props) {
const trainingItems = [
{ url: "/training/pages/dumbbell", title: "哑铃", sub: "力量塑形", icon: "dumbbell", iconClass: "menu-icon--dumbbell" },
{ url: "/training/pages/foot-pedal", title: "脚蹬器", sub: "下肢有氧", icon: "pedal", iconClass: "menu-icon--pedal" },
{ url: "/training/pages/pilates-ring", title: "瑜伽环", sub: "核心塑形", icon: "pilates", iconClass: "menu-icon--pilates" },
{ url: "/training/pages/grip-ring", title: "握力环", sub: "握力训练", icon: "grip", iconClass: "menu-icon--grip" },
{ url: "/training/pages/metronome", title: "耗糖节拍器", sub: "健走配速", icon: "metro", iconClass: "menu-icon--metro", featured: true }
];
const props = __props;
const inlineItems = common_vendor.computed(() => {
if (props.tone !== "weekly")
return trainingItems;
const metro = trainingItems.find((i) => i.icon === "metro");
const rest = trainingItems.filter((i) => i.icon !== "metro");
return metro ? [metro, ...rest] : trainingItems;
});
const visible = common_vendor.ref(true);
const expanded = common_vendor.ref(false);
const toggle = () => {
expanded.value = !expanded.value;
};
const goto = (url) => {
expanded.value = false;
common_vendor.index.navigateTo({ url });
};
return (_ctx, _cache) => {
return common_vendor.e({
a: visible.value && props.inline
}, visible.value && props.inline ? common_vendor.e({
b: props.tone === "weekly"
}, props.tone === "weekly" ? {} : {}, {
c: common_vendor.f(inlineItems.value, (item, k0, i0) => {
return common_vendor.e({
a: item.featured
}, item.featured ? {} : {}, {
b: item.icon === "dumbbell"
}, item.icon === "dumbbell" ? {} : item.icon === "pedal" ? {} : item.icon === "pilates" ? {} : item.icon === "grip" ? {} : item.icon === "metro" ? {} : {}, {
c: item.icon === "pedal",
d: item.icon === "pilates",
e: item.icon === "grip",
f: item.icon === "metro",
g: common_vendor.n(item.iconClass),
h: common_vendor.t(item.title),
i: common_vendor.t(item.sub),
j: item.url,
k: common_vendor.o(($event) => goto(item.url), item.url)
});
}),
d: props.tone === "weekly"
}, props.tone === "weekly" ? {} : {}, {
e: props.tone === "weekly" ? 1 : ""
}) : visible.value ? common_vendor.e({
g: expanded.value
}, expanded.value ? {
h: common_vendor.o(($event) => goto("/training/pages/dumbbell"), "fd"),
i: common_vendor.o(($event) => goto("/training/pages/foot-pedal"), "a5"),
j: common_vendor.o(($event) => goto("/training/pages/pilates-ring"), "3f"),
k: common_vendor.o(($event) => goto("/training/pages/grip-ring"), "23"),
l: common_vendor.o(($event) => goto("/training/pages/metronome"), "ca"),
m: common_vendor.o(() => {
}, "26")
} : {}, {
n: !expanded.value
}, !expanded.value ? {} : {}, {
o: !expanded.value
}, !expanded.value ? {} : {}, {
p: expanded.value
}, expanded.value ? {} : {}, {
q: expanded.value ? 1 : "",
r: common_vendor.o(toggle, "98"),
s: props.bottom + "rpx"
}) : {}, {
f: visible.value
});
};
}
});
const Component = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-17534d15"]]);
wx.createComponent(Component);
"use strict";const n=require("../../common/vendor.js"),g=n.defineComponent({__name:"index",props:{bottom:{default:200},inline:{type:Boolean,default:!1},tone:{default:"default"}},setup(u){const i=[{url:"/training/pages/dumbbell",title:"哑铃",sub:"力量塑形",icon:"dumbbell",iconClass:"menu-icon--dumbbell"},{url:"/training/pages/foot-pedal",title:"脚蹬器",sub:"下肢有氧",icon:"pedal",iconClass:"menu-icon--pedal"},{url:"/training/pages/pilates-ring",title:"瑜伽环",sub:"核心塑形",icon:"pilates",iconClass:"menu-icon--pilates"},{url:"/training/pages/grip-ring",title:"握力环",sub:"握力训练",icon:"grip",iconClass:"menu-icon--grip"},{url:"/training/pages/metronome",title:"耗糖节拍器",sub:"健走配速",icon:"metro",iconClass:"menu-icon--metro",featured:!0}],t=u,c=n.computed(()=>{if(t.tone!=="weekly")return i;const a=i.find(e=>e.icon==="metro"),s=i.filter(e=>e.icon!=="metro");return a?[a,...s]:i}),r=n.ref(!0),o=n.ref(!1),p=()=>{o.value=!o.value},l=a=>{o.value=!1,n.index.navigateTo({url:a})};return(a,s)=>n.e({a:r.value&&t.inline},r.value&&t.inline?n.e({b:t.tone==="weekly"},t.tone==="weekly"?{}:{},{c:n.f(c.value,(e,v,f)=>n.e({a:e.featured},e.featured?{}:{},{b:e.icon==="dumbbell"},e.icon==="dumbbell"?{}:e.icon==="pedal"?{}:e.icon==="pilates"?{}:e.icon==="grip"?{}:e.icon==="metro"?{}:{},{c:e.icon==="pedal",d:e.icon==="pilates",e:e.icon==="grip",f:e.icon==="metro",g:n.n(e.iconClass),h:n.t(e.title),i:n.t(e.sub),j:e.url,k:n.o(b=>l(e.url),e.url)})),d:t.tone==="weekly"},t.tone==="weekly"?{}:{},{e:t.tone==="weekly"?1:""}):r.value?n.e({g:o.value},o.value?{h:n.o(e=>l("/training/pages/dumbbell"),"fd"),i:n.o(e=>l("/training/pages/foot-pedal"),"a5"),j:n.o(e=>l("/training/pages/pilates-ring"),"3f"),k:n.o(e=>l("/training/pages/grip-ring"),"23"),l:n.o(e=>l("/training/pages/metronome"),"ca"),m:n.o(()=>{},"26")}:{},{n:!o.value},o.value?{}:{},{o:!o.value},o.value?{}:{},{p:o.value},o.value?{}:{},{q:o.value?1:"",r:n.o(p,"98"),s:t.bottom+"rpx"}):{},{f:r.value})}}),d=n._export_sfc(g,[["__scopeId","data-v-17534d15"]]);wx.createComponent(d);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/components/dev-training-entry/index.js.map
File diff suppressed because one or more lines are too long
+1 -149
View File
@@ -1,150 +1,2 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
if (!Array) {
const _easycom_uni_icons2 = common_vendor.resolveComponent("uni-icons");
_easycom_uni_icons2();
}
const _easycom_uni_icons = () => "../../uni_modules/uni-icons/components/uni-icons/uni-icons.js";
if (!Math) {
_easycom_uni_icons();
}
const _sfc_main = {
__name: "Card",
setup(__props) {
const { proxy } = common_vendor.getCurrentInstance();
const cardList = common_vendor.ref([]);
const loading = common_vendor.ref(false);
common_vendor.onMounted(() => {
loadCardList();
});
common_vendor.onShow(() => {
loadCardList();
});
const loadCardList = async () => {
var _a;
loading.value = true;
try {
const token = common_vendor.index.getStorageSync("token");
if (!token) {
common_vendor.index.showToast({ title: "请先登录", icon: "none" });
return;
}
const userData = common_vendor.index.getStorageSync("userData");
const patientId = (_a = userData == null ? void 0 : userData.diagnosis) == null ? void 0 : _a.patient_id;
if (!patientId) {
return;
}
const res = await proxy.apiUrl({
url: "/api/tcm/getCardList",
method: "GET",
data: {
patient_id: patientId
}
});
if (res.code === 1) {
cardList.value = res.data || [];
} else {
common_vendor.index.showToast({ title: res.msg || "获取失败", icon: "none" });
}
} catch (err) {
common_vendor.index.__f__("error", "at pages/Card/Card.vue:139", "加载就诊卡列表失败:", err);
} finally {
loading.value = false;
}
};
const viewCardDetail = (card) => {
common_vendor.index.navigateTo({
url: `/pages/Card/edit_card?id=${card.id}`
});
};
const viewDailyRecord = (card) => {
const url = `/tongji/pages/index?diagnosis_id=${card.id}&patient_id=${card.patient_id || ""}&patient_name=${encodeURIComponent(card.patient_name || "")}&age=${card.age || ""}&gender=${card.gender || ""}`;
common_vendor.index.navigateTo({ url });
};
const createCard = () => {
const token = common_vendor.index.getStorageSync("token");
if (!token) {
common_vendor.index.showToast({ title: "请先登录", icon: "none" });
return;
}
common_vendor.index.navigateTo({
url: "/pages/Card/edit_card?add=1"
});
};
const getStatusText = (status) => {
return status === 1 ? "启用" : "禁用";
};
const getStatusClass = (status) => {
return status === 1 ? "status-confirmed" : "status-pending";
};
const diagnosisTypeMap = {
"first_visit": "初诊",
"follow_up": "复诊",
"consultation": "会诊"
};
const syndromeTypeMap = {
"qi_deficiency": "气虚",
"blood_deficiency": "血虚",
"yin_deficiency": "阴虚",
"yang_deficiency": "阳虚",
"qi_stagnation": "气滞",
"blood_stasis": "血瘀",
"phlegm_dampness": "痰湿",
"damp_heat": "湿热",
"cold_dampness": "寒湿",
"wind_cold": "风寒",
"wind_heat": "风热"
};
const getDiagnosisTypeName = (type) => {
return diagnosisTypeMap[type] || type || "-";
};
const getSyndromeTypeName = (type) => {
return syndromeTypeMap[type] || type || "-";
};
const formatDate = (timestamp) => {
if (!timestamp)
return "-";
const date = new Date(timestamp * 1e3);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
};
return (_ctx, _cache) => {
return common_vendor.e({
a: common_vendor.f(cardList.value, (card, k0, i0) => {
return {
a: common_vendor.t(card.id),
b: common_vendor.t(card.status_desc || getStatusText(card.status)),
c: common_vendor.n(getStatusClass(card.status)),
d: common_vendor.t(card.patient_name || "-"),
e: common_vendor.t(card.gender_desc || "-"),
f: common_vendor.t(card.age),
g: common_vendor.t(getDiagnosisTypeName(card.diagnosis_type)),
h: common_vendor.t(getSyndromeTypeName(card.syndrome_type)),
i: common_vendor.t(card.diagnosis_date_text || formatDate(card.diagnosis_date)),
j: common_vendor.t(card.create_time || "-"),
k: common_vendor.o(($event) => viewDailyRecord(card), card.id),
l: common_vendor.o(($event) => viewCardDetail(card), card.id),
m: card.id,
n: common_vendor.o(($event) => viewCardDetail(card), card.id)
};
}),
b: !loading.value && cardList.value.length === 0
}, !loading.value && cardList.value.length === 0 ? {
c: common_vendor.p({
type: "wallet",
size: "80",
color: "#1890ff"
}),
d: common_vendor.o(createCard, "df")
} : {}, {
e: loading.value
}, loading.value ? {} : {});
};
}
};
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-f80331d8"]]);
_sfc_main.__runtimeHooks = 2;
wx.createPage(MiniProgramPage);
"use strict";const e=require("../../common/vendor.js");Array||e.resolveComponent("uni-icons")();const C=()=>"../../uni_modules/uni-icons/components/uni-icons/uni-icons.js";Math||C();const u={__name:"Card",setup(g){const{proxy:l}=e.getCurrentInstance(),d=e.ref([]),o=e.ref(!1);e.onMounted(()=>{r()}),e.onShow(()=>{r()});const r=async()=>{var t;o.value=!0;try{if(!e.index.getStorageSync("token")){e.index.showToast({title:"请先登录",icon:"none"});return}const n=e.index.getStorageSync("userData"),a=(t=n==null?void 0:n.diagnosis)==null?void 0:t.patient_id;if(!a)return;const s=await l.apiUrl({url:"/api/tcm/getCardList",method:"GET",data:{patient_id:a}});s.code===1?d.value=s.data||[]:e.index.showToast({title:s.msg||"获取失败",icon:"none"})}catch(i){e.index.__f__("error","at pages/Card/Card.vue:139","加载就诊卡列表失败:",i)}finally{o.value=!1}},c=t=>{e.index.navigateTo({url:`/pages/Card/edit_card?id=${t.id}`})},p=t=>{const i=`/tongji/pages/index?diagnosis_id=${t.id}&patient_id=${t.patient_id||""}&patient_name=${encodeURIComponent(t.patient_name||"")}&age=${t.age||""}&gender=${t.gender||""}`;e.index.navigateTo({url:i})},m=()=>{if(!e.index.getStorageSync("token")){e.index.showToast({title:"请先登录",icon:"none"});return}e.index.navigateTo({url:"/pages/Card/edit_card?add=1"})},f=t=>t===1?"启用":"禁用",y=t=>t===1?"status-confirmed":"status-pending",v={first_visit:"初诊",follow_up:"复诊",consultation:"会诊"},x={qi_deficiency:"气虚",blood_deficiency:"血虚",yin_deficiency:"阴虚",yang_deficiency:"阳虚",qi_stagnation:"气滞",blood_stasis:"血瘀",phlegm_dampness:"痰湿",damp_heat:"湿热",cold_dampness:"寒湿",wind_cold:"风寒",wind_heat:"风热"},h=t=>v[t]||t||"-",S=t=>x[t]||t||"-",w=t=>{if(!t)return"-";const i=new Date(t*1e3),n=i.getFullYear(),a=String(i.getMonth()+1).padStart(2,"0"),s=String(i.getDate()).padStart(2,"0");return`${n}-${a}-${s}`};return(t,i)=>e.e({a:e.f(d.value,(n,a,s)=>({a:e.t(n.id),b:e.t(n.status_desc||f(n.status)),c:e.n(y(n.status)),d:e.t(n.patient_name||"-"),e:e.t(n.gender_desc||"-"),f:e.t(n.age),g:e.t(h(n.diagnosis_type)),h:e.t(S(n.syndrome_type)),i:e.t(n.diagnosis_date_text||w(n.diagnosis_date)),j:e.t(n.create_time||"-"),k:e.o(_=>p(n),n.id),l:e.o(_=>c(n),n.id),m:n.id,n:e.o(_=>c(n),n.id)})),b:!o.value&&d.value.length===0},!o.value&&d.value.length===0?{c:e.p({type:"wallet",size:"80",color:"#1890ff"}),d:e.o(m,"df")}:{},{e:o.value},o.value?{}:{})}},T=e._export_sfc(u,[["__scopeId","data-v-f80331d8"]]);u.__runtimeHooks=2;wx.createPage(T);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/Card/Card.js.map
@@ -1,189 +1 @@
/* 适老化设计:50-80岁 - 大字号、高对比、大触控区 */
.card-container.data-v-f80331d8 {
min-height: 100vh;
background: linear-gradient(180deg, #e8f0fa 0%, #ffffff 100%);
padding-bottom: 56rpx;
}
.header.data-v-f80331d8 {
background: #ffffff;
padding: 48rpx 40rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
}
.header .header-title.data-v-f80331d8 {
font-size: 48rpx;
font-weight: bold;
color: #1890ff;
}
.card-list.data-v-f80331d8 {
padding: 40rpx;
}
.card-item.data-v-f80331d8 {
background: #ffffff;
border-radius: 28rpx;
padding: 44rpx;
margin-bottom: 36rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
transition: all 0.3s;
min-height: 200rpx;
}
.card-item.data-v-f80331d8:active {
transform: scale(0.98);
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.card-header.data-v-f80331d8 {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 32rpx;
padding-bottom: 28rpx;
border-bottom: 4rpx solid #e8eaed;
}
.card-id .label.data-v-f80331d8 {
font-size: 34rpx;
color: #555;
}
.card-id .value.data-v-f80331d8 {
font-size: 40rpx;
font-weight: bold;
color: #1a1a1a;
}
.status-badge.data-v-f80331d8 {
padding: 12rpx 28rpx;
border-radius: 28rpx;
font-size: 32rpx;
font-weight: 500;
}
.status-badge.status-confirmed.data-v-f80331d8 {
background: #f6ffed;
color: #204e2b;
border: 4rpx solid #204e2b;
}
.status-badge.status-pending.data-v-f80331d8 {
background: #fff7e6;
color: #fa8c16;
border: 4rpx solid #ffd591;
}
.card-info.data-v-f80331d8 {
margin-bottom: 28rpx;
}
.info-row.data-v-f80331d8 {
display: flex;
align-items: center;
margin-bottom: 24rpx;
}
.info-row .info-label.data-v-f80331d8 {
font-size: 34rpx;
color: #555;
min-width: 200rpx;
}
.info-row .info-value.data-v-f80331d8 {
font-size: 36rpx;
color: #1a1a1a;
font-weight: 500;
}
.card-footer.data-v-f80331d8 {
padding-top: 28rpx;
border-top: 4rpx solid #e8eaed;
}
.card-actions.data-v-f80331d8 {
display: flex;
gap: 20rpx;
margin-top: 28rpx;
padding-top: 28rpx;
border-top: 4rpx solid #e8eaed;
}
.card-action-btn.data-v-f80331d8 {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
padding: 24rpx 12rpx;
background: #f6faff;
border-radius: 20rpx;
border: 2rpx solid #d6e6fb;
min-height: 80rpx;
transition: all 0.2s;
}
.card-action-btn.data-v-f80331d8:active {
transform: scale(0.98);
opacity: 0.85;
}
.card-action-btn.primary.data-v-f80331d8 {
background: linear-gradient(135deg, #1890ff, #0ea5a4);
border-color: transparent;
}
.card-action-btn.primary .card-action-icon.data-v-f80331d8,
.card-action-btn.primary .card-action-text.data-v-f80331d8 {
color: #ffffff;
}
.card-action-icon.data-v-f80331d8 {
font-size: 36rpx;
color: #1890ff;
}
.card-action-text.data-v-f80331d8 {
font-size: 30rpx;
color: #1890ff;
font-weight: 500;
}
.time-info.data-v-f80331d8 {
display: flex;
align-items: center;
margin-bottom: 20rpx;
}
.time-info .time-label.data-v-f80331d8 {
font-size: 30rpx;
color: #555;
min-width: 180rpx;
}
.time-info .time-value.data-v-f80331d8 {
font-size: 32rpx;
color: #555;
}
.view-count.data-v-f80331d8 {
display: flex;
align-items: center;
margin-top: 12rpx;
}
.view-count .count-icon.data-v-f80331d8 {
font-size: 34rpx;
margin-right: 12rpx;
}
.view-count .count-text.data-v-f80331d8 {
font-size: 30rpx;
color: #555;
}
.empty-state.data-v-f80331d8 {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 160rpx 0;
}
.empty-state .empty-icon.data-v-f80331d8 {
font-size: 140rpx;
margin-bottom: 40rpx;
}
.empty-state .empty-text.data-v-f80331d8 {
font-size: 40rpx;
color: #555;
margin-bottom: 56rpx;
}
.add-card-btn.data-v-f80331d8 {
padding: 36rpx 88rpx;
background: #1890ff;
color: #ffffff;
font-size: 40rpx;
font-weight: 500;
border-radius: 56rpx;
min-height: 100rpx;
}
.loading-state.data-v-f80331d8 {
display: flex;
justify-content: center;
padding: 120rpx 0;
}
.loading-state .loading-text.data-v-f80331d8 {
font-size: 36rpx;
color: #555;
}
.card-container.data-v-f80331d8{min-height:100vh;background:linear-gradient(180deg,#e8f0fa,#fff);padding-bottom:56rpx}.header.data-v-f80331d8{background:#fff;padding:48rpx 40rpx;box-shadow:0 2rpx 12rpx rgba(0,0,0,.06)}.header .header-title.data-v-f80331d8{font-size:48rpx;font-weight:700;color:#1890ff}.card-list.data-v-f80331d8{padding:40rpx}.card-item.data-v-f80331d8{background:#fff;border-radius:28rpx;padding:44rpx;margin-bottom:36rpx;box-shadow:0 4rpx 20rpx rgba(0,0,0,.08);transition:all .3s;min-height:200rpx}.card-item.data-v-f80331d8:active{transform:scale(.98);box-shadow:0 2rpx 12rpx rgba(0,0,0,.1)}.card-header.data-v-f80331d8{display:flex;justify-content:space-between;align-items:center;margin-bottom:32rpx;padding-bottom:28rpx;border-bottom:4rpx solid #e8eaed}.card-id .label.data-v-f80331d8{font-size:34rpx;color:#555}.card-id .value.data-v-f80331d8{font-size:40rpx;font-weight:700;color:#1a1a1a}.status-badge.data-v-f80331d8{padding:12rpx 28rpx;border-radius:28rpx;font-size:32rpx;font-weight:500}.status-badge.status-confirmed.data-v-f80331d8{background:#f6ffed;color:#204e2b;border:4rpx solid #204e2b}.status-badge.status-pending.data-v-f80331d8{background:#fff7e6;color:#fa8c16;border:4rpx solid #ffd591}.card-info.data-v-f80331d8{margin-bottom:28rpx}.info-row.data-v-f80331d8{display:flex;align-items:center;margin-bottom:24rpx}.info-row .info-label.data-v-f80331d8{font-size:34rpx;color:#555;min-width:200rpx}.info-row .info-value.data-v-f80331d8{font-size:36rpx;color:#1a1a1a;font-weight:500}.card-footer.data-v-f80331d8{padding-top:28rpx;border-top:4rpx solid #e8eaed}.card-actions.data-v-f80331d8{display:flex;gap:20rpx;margin-top:28rpx;padding-top:28rpx;border-top:4rpx solid #e8eaed}.card-action-btn.data-v-f80331d8{flex:1;display:flex;align-items:center;justify-content:center;gap:12rpx;padding:24rpx 12rpx;background:#f6faff;border-radius:20rpx;border:2rpx solid #d6e6fb;min-height:80rpx;transition:all .2s}.card-action-btn.data-v-f80331d8:active{transform:scale(.98);opacity:.85}.card-action-btn.primary.data-v-f80331d8{background:linear-gradient(135deg,#1890ff,#0ea5a4);border-color:transparent}.card-action-btn.primary .card-action-icon.data-v-f80331d8,.card-action-btn.primary .card-action-text.data-v-f80331d8{color:#fff}.card-action-icon.data-v-f80331d8{font-size:36rpx;color:#1890ff}.card-action-text.data-v-f80331d8{font-size:30rpx;color:#1890ff;font-weight:500}.time-info.data-v-f80331d8{display:flex;align-items:center;margin-bottom:20rpx}.time-info .time-label.data-v-f80331d8{font-size:30rpx;color:#555;min-width:180rpx}.time-info .time-value.data-v-f80331d8{font-size:32rpx;color:#555}.view-count.data-v-f80331d8{display:flex;align-items:center;margin-top:12rpx}.view-count .count-icon.data-v-f80331d8{font-size:34rpx;margin-right:12rpx}.view-count .count-text.data-v-f80331d8{font-size:30rpx;color:#555}.empty-state.data-v-f80331d8{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:160rpx 0}.empty-state .empty-icon.data-v-f80331d8{font-size:140rpx;margin-bottom:40rpx}.empty-state .empty-text.data-v-f80331d8{font-size:40rpx;color:#555;margin-bottom:56rpx}.add-card-btn.data-v-f80331d8{padding:36rpx 88rpx;background:#1890ff;color:#fff;font-size:40rpx;font-weight:500;border-radius:56rpx;min-height:100rpx}.loading-state.data-v-f80331d8{display:flex;justify-content:center;padding:120rpx 0}.loading-state .loading-text.data-v-f80331d8{font-size:36rpx;color:#555}
@@ -1,46 +1,2 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const _sfc_main = {
__name: "contact",
setup(__props) {
const { proxy } = common_vendor.getCurrentInstance();
const data = common_vendor.ref({});
const loading = common_vendor.ref(false);
const type = common_vendor.ref("");
common_vendor.onMounted(() => {
loadCardList();
});
const loadCardList = async () => {
loading.value = true;
try {
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
type.value = currentPage.options.type;
const res = await proxy.apiUrl({
url: "/api/index/policy",
method: "GET",
data: {
type: type.value
}
});
data.value = res.data;
common_vendor.index.setNavigationBarTitle({
title: res.data.title
});
} catch (err) {
common_vendor.index.__f__("error", "at pages/Card/contact.vue:54", "加载就诊卡列表失败:", err);
} finally {
loading.value = false;
}
};
return (_ctx, _cache) => {
return {
a: common_vendor.t(data.value.title),
b: data.value.content
};
};
}
};
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-9c9e5aa4"]]);
wx.createPage(MiniProgramPage);
"use strict";const t=require("../../common/vendor.js"),l={__name:"contact",setup(_){const{proxy:s}=t.getCurrentInstance(),a=t.ref({}),n=t.ref(!1),r=t.ref("");t.onMounted(()=>{i()});const i=async()=>{n.value=!0;try{const e=getCurrentPages(),c=e[e.length-1];r.value=c.options.type;const o=await s.apiUrl({url:"/api/index/policy",method:"GET",data:{type:r.value}});a.value=o.data,t.index.setNavigationBarTitle({title:o.data.title})}catch(e){t.index.__f__("error","at pages/Card/contact.vue:54","加载就诊卡列表失败:",e)}finally{n.value=!1}};return(e,c)=>({a:t.t(a.value.title),b:a.value.content})}},u=t._export_sfc(l,[["__scopeId","data-v-9c9e5aa4"]]);wx.createPage(u);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/Card/contact.js.map
@@ -1,136 +1 @@
.card-container.data-v-9c9e5aa4 {
min-height: 100vh;
background: linear-gradient(180deg, #f0f7ff 0%, #ffffff 100%);
padding-bottom: 40rpx;
}
.header.data-v-9c9e5aa4 {
background: #ffffff;
padding: 40rpx 30rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}
.header .header-title.data-v-9c9e5aa4 {
font-size: 40rpx;
font-weight: bold;
color: #1890ff;
}
.card-list.data-v-9c9e5aa4 {
padding: 30rpx;
}
.card-item.data-v-9c9e5aa4 {
background: #ffffff;
border-radius: 20rpx;
padding: 30rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
transition: all 0.3s;
}
.card-item.data-v-9c9e5aa4:active {
transform: scale(0.98);
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
.card-header.data-v-9c9e5aa4 {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
padding-bottom: 20rpx;
border-bottom: 2rpx solid #f0f0f0;
}
.card-id .label.data-v-9c9e5aa4 {
font-size: 28rpx;
color: #666666;
}
.card-id .value.data-v-9c9e5aa4 {
font-size: 32rpx;
font-weight: bold;
color: #333333;
}
.status-badge.data-v-9c9e5aa4 {
padding: 8rpx 20rpx;
border-radius: 20rpx;
font-size: 24rpx;
font-weight: 500;
}
.status-badge.status-confirmed.data-v-9c9e5aa4 {
background: #f6ffed;
color: #52c41a;
border: 2rpx solid #b7eb8f;
}
.status-badge.status-pending.data-v-9c9e5aa4 {
background: #fff7e6;
color: #fa8c16;
border: 2rpx solid #ffd591;
}
.card-info.data-v-9c9e5aa4 {
margin-bottom: 20rpx;
}
.info-row.data-v-9c9e5aa4 {
display: flex;
align-items: center;
margin-bottom: 16rpx;
}
.info-row .info-label.data-v-9c9e5aa4 {
font-size: 28rpx;
color: #666666;
min-width: 160rpx;
}
.info-row .info-value.data-v-9c9e5aa4 {
font-size: 30rpx;
color: #333333;
font-weight: 500;
}
.card-footer.data-v-9c9e5aa4 {
padding-top: 20rpx;
border-top: 2rpx solid #f0f0f0;
}
.time-info.data-v-9c9e5aa4 {
display: flex;
align-items: center;
margin-bottom: 12rpx;
}
.time-info .time-label.data-v-9c9e5aa4 {
font-size: 24rpx;
color: #999999;
min-width: 140rpx;
}
.time-info .time-value.data-v-9c9e5aa4 {
font-size: 26rpx;
color: #666666;
}
.view-count.data-v-9c9e5aa4 {
display: flex;
align-items: center;
margin-top: 8rpx;
}
.view-count .count-icon.data-v-9c9e5aa4 {
font-size: 28rpx;
margin-right: 8rpx;
}
.view-count .count-text.data-v-9c9e5aa4 {
font-size: 24rpx;
color: #999999;
}
.empty-state.data-v-9c9e5aa4 {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 0;
}
.empty-state .empty-icon.data-v-9c9e5aa4 {
font-size: 120rpx;
margin-bottom: 30rpx;
}
.empty-state .empty-text.data-v-9c9e5aa4 {
font-size: 32rpx;
color: #999999;
}
.loading-state.data-v-9c9e5aa4 {
display: flex;
justify-content: center;
padding: 80rpx 0;
}
.loading-state .loading-text.data-v-9c9e5aa4 {
font-size: 28rpx;
color: #999999;
}
.card-container.data-v-9c9e5aa4{min-height:100vh;background:linear-gradient(180deg,#f0f7ff,#fff);padding-bottom:40rpx}.header.data-v-9c9e5aa4{background:#fff;padding:40rpx 30rpx;box-shadow:0 2rpx 8rpx rgba(0,0,0,.05)}.header .header-title.data-v-9c9e5aa4{font-size:40rpx;font-weight:700;color:#1890ff}.card-list.data-v-9c9e5aa4{padding:30rpx}.card-item.data-v-9c9e5aa4{background:#fff;border-radius:20rpx;padding:30rpx;margin-bottom:24rpx;box-shadow:0 4rpx 12rpx rgba(0,0,0,.08);transition:all .3s}.card-item.data-v-9c9e5aa4:active{transform:scale(.98);box-shadow:0 2rpx 8rpx rgba(0,0,0,.1)}.card-header.data-v-9c9e5aa4{display:flex;justify-content:space-between;align-items:center;margin-bottom:24rpx;padding-bottom:20rpx;border-bottom:2rpx solid #f0f0f0}.card-id .label.data-v-9c9e5aa4{font-size:28rpx;color:#666}.card-id .value.data-v-9c9e5aa4{font-size:32rpx;font-weight:700;color:#333}.status-badge.data-v-9c9e5aa4{padding:8rpx 20rpx;border-radius:20rpx;font-size:24rpx;font-weight:500}.status-badge.status-confirmed.data-v-9c9e5aa4{background:#f6ffed;color:#52c41a;border:2rpx solid #b7eb8f}.status-badge.status-pending.data-v-9c9e5aa4{background:#fff7e6;color:#fa8c16;border:2rpx solid #ffd591}.card-info.data-v-9c9e5aa4{margin-bottom:20rpx}.info-row.data-v-9c9e5aa4{display:flex;align-items:center;margin-bottom:16rpx}.info-row .info-label.data-v-9c9e5aa4{font-size:28rpx;color:#666;min-width:160rpx}.info-row .info-value.data-v-9c9e5aa4{font-size:30rpx;color:#333;font-weight:500}.card-footer.data-v-9c9e5aa4{padding-top:20rpx;border-top:2rpx solid #f0f0f0}.time-info.data-v-9c9e5aa4{display:flex;align-items:center;margin-bottom:12rpx}.time-info .time-label.data-v-9c9e5aa4{font-size:24rpx;color:#999;min-width:140rpx}.time-info .time-value.data-v-9c9e5aa4{font-size:26rpx;color:#666}.view-count.data-v-9c9e5aa4{display:flex;align-items:center;margin-top:8rpx}.view-count .count-icon.data-v-9c9e5aa4{font-size:28rpx;margin-right:8rpx}.view-count .count-text.data-v-9c9e5aa4{font-size:24rpx;color:#999}.empty-state.data-v-9c9e5aa4{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:120rpx 0}.empty-state .empty-icon.data-v-9c9e5aa4{font-size:120rpx;margin-bottom:30rpx}.empty-state .empty-text.data-v-9c9e5aa4{font-size:32rpx;color:#999}.loading-state.data-v-9c9e5aa4{display:flex;justify-content:center;padding:80rpx 0}.loading-state .loading-text.data-v-9c9e5aa4{font-size:28rpx;color:#999}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -121
View File
@@ -1,122 +1,2 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const DevTrainingEntry = () => "../../components/dev-training-entry/index.js";
const TabBarDock = () => "../../components/app-tab-bar/tab-bar-dock.js";
const _sfc_main = {
name: "profileB",
components: { DevTrainingEntry, TabBarDock },
data() {
return {
userInfo: {
avatar: "",
nickname: "",
userId: "",
balance: "0",
points: "0",
coupons: "5"
},
orderTypes: [
{
name: "待付款",
image: "/static/images/my/payment.png",
path: "/pages/order/list?type=1",
count: 2
},
{
name: "待发货",
image: "/static/images/my/delivery.png",
path: "/pages/order/list?type=2",
count: 1
},
{
name: "待收货",
image: "/static/images/my/shipping.png",
path: "/pages/order/list?type=3",
count: 0
},
{
name: "待评价",
image: "/static/images/my/review.png",
path: "/pages/order/list?type=4",
count: 3
},
{
name: "退换/售后",
image: "/static/images/my/after-sale.png",
path: "/pages/order/list?type=5",
count: 0
}
],
allServices: [
{ name: "就诊卡", icon: "folder-add", path: "/pages/Card/Card", class: "bg-cyan" },
{ name: "设置", icon: "gear", path: "/pages/user/userinfo", class: "bg-gray" }
// ,
// { name: '客服中心', icon: 'headphones', path: '/pages/service/index', class: 'bg-orange', desc: '在线客服' }
]
};
},
onShow() {
this.userInfo = common_vendor.index.getStorageSync("userData");
},
methods: {
navigateTo(path) {
common_vendor.index.navigateTo({ url: path });
},
showQRCode() {
}
}
};
if (!Array) {
const _easycom_uni_icons2 = common_vendor.resolveComponent("uni-icons");
const _component_dev_training_entry = common_vendor.resolveComponent("dev-training-entry");
const _component_TabBarDock = common_vendor.resolveComponent("TabBarDock");
(_easycom_uni_icons2 + _component_dev_training_entry + _component_TabBarDock)();
}
const _easycom_uni_icons = () => "../../uni_modules/uni-icons/components/uni-icons/uni-icons.js";
if (!Math) {
_easycom_uni_icons();
}
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: $data.userInfo.avatar,
b: common_vendor.t($data.userInfo.nickname || "未登录"),
c: common_vendor.t($data.userInfo.balance || 0),
d: common_vendor.t($data.userInfo.points || 0),
e: common_vendor.t($data.userInfo.coupons || 0),
f: common_vendor.o(($event) => $options.navigateTo("/pages/user/userinfo"), "39"),
g: common_vendor.f($data.allServices, (item, index, i0) => {
return common_vendor.e({
a: "0f7520f0-0-" + i0,
b: common_vendor.p({
type: item.icon,
size: "28",
color: "#fff"
}),
c: common_vendor.n(item.class),
d: common_vendor.t(item.name),
e: item.desc
}, item.desc ? {
f: common_vendor.t(item.desc)
} : {}, {
g: "0f7520f0-1-" + i0,
h: index,
i: common_vendor.o(($event) => $options.navigateTo(item.path), index)
});
}),
h: common_vendor.p({
type: "right",
size: "20",
color: "#666"
}),
i: common_vendor.p({
bottom: 360
}),
j: common_vendor.p({
active: 1
})
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-0f7520f0"]]);
_sfc_main.__runtimeHooks = 2;
wx.createPage(MiniProgramPage);
"use strict";const e=require("../../common/vendor.js"),g=()=>"../../components/dev-training-entry/index.js",m=()=>"../../components/app-tab-bar/tab-bar-dock.js",p={name:"profileB",components:{DevTrainingEntry:g,TabBarDock:m},data(){return{userInfo:{avatar:"",nickname:"",userId:"",balance:"0",points:"0",coupons:"5"},orderTypes:[{name:"待付款",image:"/static/images/my/payment.png",path:"/pages/order/list?type=1",count:2},{name:"待发货",image:"/static/images/my/delivery.png",path:"/pages/order/list?type=2",count:1},{name:"待收货",image:"/static/images/my/shipping.png",path:"/pages/order/list?type=3",count:0},{name:"待评价",image:"/static/images/my/review.png",path:"/pages/order/list?type=4",count:3},{name:"退换/售后",image:"/static/images/my/after-sale.png",path:"/pages/order/list?type=5",count:0}],allServices:[{name:"就诊卡",icon:"folder-add",path:"/pages/Card/Card",class:"bg-cyan"},{name:"设置",icon:"gear",path:"/pages/user/userinfo",class:"bg-gray"}]}},onShow(){this.userInfo=e.index.getStorageSync("userData")},methods:{navigateTo(a){e.index.navigateTo({url:a})},showQRCode(){}}};if(!Array){const a=e.resolveComponent("uni-icons"),s=e.resolveComponent("dev-training-entry"),t=e.resolveComponent("TabBarDock");(a+s+t)()}const u=()=>"../../uni_modules/uni-icons/components/uni-icons/uni-icons.js";Math||u();function f(a,s,t,_,o,r){return{a:o.userInfo.avatar,b:e.t(o.userInfo.nickname||"未登录"),c:e.t(o.userInfo.balance||0),d:e.t(o.userInfo.points||0),e:e.t(o.userInfo.coupons||0),f:e.o(n=>r.navigateTo("/pages/user/userinfo"),"39"),g:e.f(o.allServices,(n,c,i)=>e.e({a:"0f7520f0-0-"+i,b:e.p({type:n.icon,size:"28",color:"#fff"}),c:e.n(n.class),d:e.t(n.name),e:n.desc},n.desc?{f:e.t(n.desc)}:{},{g:"0f7520f0-1-"+i,h:c,i:e.o(l=>r.navigateTo(n.path),c)})),h:e.p({type:"right",size:"20",color:"#666"}),i:e.p({bottom:360}),j:e.p({active:1})}}const d=e._export_sfc(p,[["render",f],["__scopeId","data-v-0f7520f0"]]);p.__runtimeHooks=2;wx.createPage(d);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/user/user.js.map
@@ -1,261 +1 @@
/**
* 这里是uni-app内置的常用样式变量
*
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
*
*/
/**
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
*
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
*/
/* 颜色变量 */
/* 行为相关颜色 */
/* 文字基本颜色 */
/* 背景颜色 */
/* 边框颜色 */
/* 尺寸变量 */
/* 文字尺寸 */
/* 图片尺寸 */
/* Border Radius */
/* 水平间距 */
/* 垂直间距 */
/* 透明度 */
/* 文章场景相关 */
/* 适老化设计:50-80岁老人 - 大字号、高对比、大触控区 */
.mine-page.data-v-0f7520f0 {
min-height: 100vh;
background: #e8eaed;
padding: 40rpx;
padding-bottom: calc(240rpx + env(safe-area-inset-bottom));
}
.user-card.data-v-0f7520f0 {
background: #fff;
border-radius: 28rpx;
padding: 48rpx;
margin-bottom: 40rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
}
.user-card .user-header.data-v-0f7520f0 {
display: flex;
align-items: center;
margin-bottom: 48rpx;
}
.user-card .user-header .avatar.data-v-0f7520f0 {
width: 160rpx;
height: 160rpx;
border-radius: 80rpx;
margin-right: 36rpx;
border: 4rpx solid #e0e0e0;
}
.user-card .user-header .user-info.data-v-0f7520f0 {
flex: 1;
}
.user-card .user-header .user-info .nickname.data-v-0f7520f0 {
font-size: 48rpx;
font-weight: 600;
color: #1a1a1a;
margin-bottom: 16rpx;
display: block;
letter-spacing: 1rpx;
}
.user-card .user-header .user-info .vip-tag.data-v-0f7520f0 {
background: linear-gradient(90deg, #FFD700 0%, #FFA500 100%);
color: #fff;
font-size: 28rpx;
padding: 6rpx 20rpx;
border-radius: 24rpx;
display: inline-block;
}
.user-card .user-header .qr-code.data-v-0f7520f0 {
width: 88rpx;
height: 88rpx;
border-radius: 44rpx;
background: #f5f6fa;
display: flex;
align-items: center;
justify-content: center;
}
.user-card .stat-row.data-v-0f7520f0 {
display: flex;
padding-top: 40rpx;
border-top: 4rpx solid #e8eaed;
}
.user-card .stat-row .stat-item.data-v-0f7520f0 {
flex: 1;
text-align: center;
min-height: 120rpx;
}
.user-card .stat-row .stat-item .value.data-v-0f7520f0 {
font-size: 52rpx;
font-weight: 600;
color: #1a1a1a;
margin-bottom: 12rpx;
display: block;
}
.user-card .stat-row .stat-item .label.data-v-0f7520f0 {
font-size: 32rpx;
color: #555;
font-weight: 500;
}
.order-card.data-v-0f7520f0 {
background: #fff;
border-radius: 24rpx;
padding: 40rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
}
.order-card .card-header.data-v-0f7520f0 {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 40rpx;
}
.order-card .card-header .title.data-v-0f7520f0 {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.order-card .card-header .more.data-v-0f7520f0 {
display: flex;
align-items: center;
}
.order-card .card-header .more text.data-v-0f7520f0 {
font-size: 26rpx;
color: #999;
margin-right: 8rpx;
}
.order-card .order-list.data-v-0f7520f0 {
display: flex;
justify-content: space-between;
}
.order-card .order-list .order-item.data-v-0f7520f0 {
flex: 1;
text-align: center;
}
.order-card .order-list .order-item .icon-wrapper.data-v-0f7520f0 {
width: 100rpx;
height: 100rpx;
margin: 0 auto 0;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.order-card .order-list .order-item .icon-wrapper .count-badge.data-v-0f7520f0 {
position: absolute;
top: -10rpx;
right: -10rpx;
background: #ff4d4f;
color: #fff;
font-size: 20rpx;
min-width: 32rpx;
height: 32rpx;
line-height: 32rpx;
text-align: center;
border-radius: 16rpx;
padding: 0 8rpx;
}
.order-card .order-list .order-item .icon-wrapper .order-icon.data-v-0f7520f0 {
width: 56rpx;
height: 56rpx;
}
.order-card .order-list .order-item .order-name.data-v-0f7520f0 {
font-size: 26rpx;
color: #666;
}
.quick-actions.data-v-0f7520f0 {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 30rpx;
margin-bottom: 30rpx;
}
.quick-actions .action-item.data-v-0f7520f0 {
background: #fff;
border-radius: 24rpx;
padding: 30rpx 20rpx;
text-align: center;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
}
.quick-actions .action-item .action-icon.data-v-0f7520f0 {
width: 80rpx;
height: 80rpx;
border-radius: 40rpx;
margin: 0 auto 16rpx;
display: flex;
align-items: center;
justify-content: center;
}
.quick-actions .action-item .action-name.data-v-0f7520f0 {
font-size: 26rpx;
color: #666;
}
.service-list.data-v-0f7520f0 {
background: #fff;
border-radius: 28rpx;
padding: 20rpx 40rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
}
.service-list .service-item.data-v-0f7520f0 {
display: flex;
align-items: center;
justify-content: space-between;
padding: 44rpx 0;
border-bottom: 4rpx solid #e8eaed;
min-height: 120rpx;
}
.service-list .service-item.data-v-0f7520f0:last-child {
border-bottom: none;
}
.service-list .service-item .left.data-v-0f7520f0 {
display: flex;
align-items: center;
}
.service-list .service-item .left .service-icon.data-v-0f7520f0 {
width: 88rpx;
height: 88rpx;
border-radius: 44rpx;
margin-right: 32rpx;
display: flex;
align-items: center;
justify-content: center;
}
.service-list .service-item .left .service-name.data-v-0f7520f0 {
font-size: 38rpx;
color: #1a1a1a;
font-weight: 500;
}
.service-list .service-item .right.data-v-0f7520f0 {
display: flex;
align-items: center;
}
.service-list .service-item .right .desc.data-v-0f7520f0 {
font-size: 30rpx;
color: #555;
margin-right: 20rpx;
}
.bg-red.data-v-0f7520f0 {
background: #ff4d4f;
}
.bg-blue.data-v-0f7520f0 {
background: #1890ff;
}
.bg-green.data-v-0f7520f0 {
background: #52c41a;
}
.bg-purple.data-v-0f7520f0 {
background: #722ed1;
}
.bg-orange.data-v-0f7520f0 {
background: #fa8c16;
}
.bg-cyan.data-v-0f7520f0 {
background: #13c2c2;
}
.bg-pink.data-v-0f7520f0 {
background: #eb2f96;
}
.bg-gray.data-v-0f7520f0 {
background: #666666;
}
.mine-page.data-v-0f7520f0{min-height:100vh;background:#e8eaed;padding:40rpx;padding-bottom:calc(240rpx + env(safe-area-inset-bottom))}.user-card.data-v-0f7520f0{background:#fff;border-radius:28rpx;padding:48rpx;margin-bottom:40rpx;box-shadow:0 4rpx 20rpx rgba(0,0,0,.06)}.user-card .user-header.data-v-0f7520f0{display:flex;align-items:center;margin-bottom:48rpx}.user-card .user-header .avatar.data-v-0f7520f0{width:160rpx;height:160rpx;border-radius:80rpx;margin-right:36rpx;border:4rpx solid #e0e0e0}.user-card .user-header .user-info.data-v-0f7520f0{flex:1}.user-card .user-header .user-info .nickname.data-v-0f7520f0{font-size:48rpx;font-weight:600;color:#1a1a1a;margin-bottom:16rpx;display:block;letter-spacing:1rpx}.user-card .user-header .user-info .vip-tag.data-v-0f7520f0{background:linear-gradient(90deg,gold,orange);color:#fff;font-size:28rpx;padding:6rpx 20rpx;border-radius:24rpx;display:inline-block}.user-card .user-header .qr-code.data-v-0f7520f0{width:88rpx;height:88rpx;border-radius:44rpx;background:#f5f6fa;display:flex;align-items:center;justify-content:center}.user-card .stat-row.data-v-0f7520f0{display:flex;padding-top:40rpx;border-top:4rpx solid #e8eaed}.user-card .stat-row .stat-item.data-v-0f7520f0{flex:1;text-align:center;min-height:120rpx}.user-card .stat-row .stat-item .value.data-v-0f7520f0{font-size:52rpx;font-weight:600;color:#1a1a1a;margin-bottom:12rpx;display:block}.user-card .stat-row .stat-item .label.data-v-0f7520f0{font-size:32rpx;color:#555;font-weight:500}.order-card.data-v-0f7520f0{background:#fff;border-radius:24rpx;padding:40rpx;margin-bottom:30rpx;box-shadow:0 4rpx 16rpx rgba(0,0,0,.04)}.order-card .card-header.data-v-0f7520f0{display:flex;justify-content:space-between;align-items:center;margin-bottom:40rpx}.order-card .card-header .title.data-v-0f7520f0{font-size:32rpx;font-weight:600;color:#333}.order-card .card-header .more.data-v-0f7520f0{display:flex;align-items:center}.order-card .card-header .more text.data-v-0f7520f0{font-size:26rpx;color:#999;margin-right:8rpx}.order-card .order-list.data-v-0f7520f0{display:flex;justify-content:space-between}.order-card .order-list .order-item.data-v-0f7520f0{flex:1;text-align:center}.order-card .order-list .order-item .icon-wrapper.data-v-0f7520f0{width:100rpx;height:100rpx;margin:0 auto;position:relative;display:flex;align-items:center;justify-content:center}.order-card .order-list .order-item .icon-wrapper .count-badge.data-v-0f7520f0{position:absolute;top:-10rpx;right:-10rpx;background:#ff4d4f;color:#fff;font-size:20rpx;min-width:32rpx;height:32rpx;line-height:32rpx;text-align:center;border-radius:16rpx;padding:0 8rpx}.order-card .order-list .order-item .icon-wrapper .order-icon.data-v-0f7520f0{width:56rpx;height:56rpx}.order-card .order-list .order-item .order-name.data-v-0f7520f0{font-size:26rpx;color:#666}.quick-actions.data-v-0f7520f0{display:grid;grid-template-columns:repeat(4,1fr);gap:30rpx;margin-bottom:30rpx}.quick-actions .action-item.data-v-0f7520f0{background:#fff;border-radius:24rpx;padding:30rpx 20rpx;text-align:center;box-shadow:0 4rpx 16rpx rgba(0,0,0,.04)}.quick-actions .action-item .action-icon.data-v-0f7520f0{width:80rpx;height:80rpx;border-radius:40rpx;margin:0 auto 16rpx;display:flex;align-items:center;justify-content:center}.quick-actions .action-item .action-name.data-v-0f7520f0{font-size:26rpx;color:#666}.service-list.data-v-0f7520f0{background:#fff;border-radius:28rpx;padding:20rpx 40rpx;box-shadow:0 4rpx 20rpx rgba(0,0,0,.06)}.service-list .service-item.data-v-0f7520f0{display:flex;align-items:center;justify-content:space-between;padding:44rpx 0;border-bottom:4rpx solid #e8eaed;min-height:120rpx}.service-list .service-item.data-v-0f7520f0:last-child{border-bottom:none}.service-list .service-item .left.data-v-0f7520f0{display:flex;align-items:center}.service-list .service-item .left .service-icon.data-v-0f7520f0{width:88rpx;height:88rpx;border-radius:44rpx;margin-right:32rpx;display:flex;align-items:center;justify-content:center}.service-list .service-item .left .service-name.data-v-0f7520f0{font-size:38rpx;color:#1a1a1a;font-weight:500}.service-list .service-item .right.data-v-0f7520f0{display:flex;align-items:center}.service-list .service-item .right .desc.data-v-0f7520f0{font-size:30rpx;color:#555;margin-right:20rpx}.bg-red.data-v-0f7520f0{background:#ff4d4f}.bg-blue.data-v-0f7520f0{background:#1890ff}.bg-green.data-v-0f7520f0{background:#52c41a}.bg-purple.data-v-0f7520f0{background:#722ed1}.bg-orange.data-v-0f7520f0{background:#fa8c16}.bg-cyan.data-v-0f7520f0{background:#13c2c2}.bg-pink.data-v-0f7520f0{background:#eb2f96}.bg-gray.data-v-0f7520f0{background:#666}
@@ -1,193 +1,2 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
if (!Array) {
const _easycom_uni_icons2 = common_vendor.resolveComponent("uni-icons");
_easycom_uni_icons2();
}
const _easycom_uni_icons = () => "../../uni_modules/uni-icons/components/uni-icons/uni-icons.js";
if (!Math) {
_easycom_uni_icons();
}
const _sfc_main = {
__name: "userinfo",
setup(__props) {
const { proxy } = common_vendor.getCurrentInstance();
const instance = common_vendor.getCurrentInstance();
const $url = instance.appContext.config.globalProperties.$url;
const loading = common_vendor.ref(false);
const submitting = common_vendor.ref(false);
const formData = common_vendor.ref({
avatar: "",
patient_name: "",
phone: "",
nickname: "",
sex: 1,
age: ""
});
common_vendor.onMounted(() => {
loadUserInfo();
});
const loadUserInfo = async () => {
loading.value = true;
try {
const res = await proxy.apiUrl({
url: "/api/user/info",
method: "GET",
data: {}
});
if (res.code === 1) {
const userInfo = res.data;
formData.value = {
avatar: userInfo.avatar || "",
patient_name: userInfo.patient_name || userInfo.nickname || "",
phone: userInfo.phone || "",
nickname: userInfo.nickname || "",
sex: userInfo.sex == "男" ? 1 : 2,
age: userInfo.age || ""
};
} else {
common_vendor.index.showToast({ title: res.msg || "加载失败", icon: "none" });
}
} catch (err) {
common_vendor.index.__f__("error", "at pages/user/userinfo.vue:142", "加载用户信息失败:", err);
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
} finally {
loading.value = false;
}
};
const uploadAvatar = () => {
common_vendor.index.chooseImage({
count: 1,
sizeType: ["compressed"],
sourceType: ["album", "camera"],
success: async (res) => {
const tempFilePath = res.tempFilePaths[0];
try {
common_vendor.index.showLoading({ title: "上传中..." });
const token = common_vendor.index.getStorageSync("token");
const uploadRes = await new Promise((resolve, reject) => {
common_vendor.index.uploadFile({
url: $url + "/api/upload/image",
filePath: tempFilePath,
name: "file",
header: {
token,
"content-type": "multipart/form-data"
},
success: (res2) => {
const data = JSON.parse(res2.data);
resolve(data);
},
fail: (err) => {
reject(err);
}
});
});
common_vendor.index.hideLoading();
if (uploadRes.code === 1) {
formData.value.avatar = uploadRes.data.uri || uploadRes.data;
common_vendor.index.showToast({ title: "头像上传成功", icon: "success" });
} else {
common_vendor.index.showToast({ title: uploadRes.msg || "上传失败", icon: "none" });
}
} catch (err) {
common_vendor.index.hideLoading();
common_vendor.index.__f__("error", "at pages/user/userinfo.vue:195", "上传头像失败:", err);
common_vendor.index.showToast({ title: "上传失败", icon: "none" });
}
},
fail: (err) => {
common_vendor.index.__f__("error", "at pages/user/userinfo.vue:200", "选择图片失败:", err);
common_vendor.index.showToast({ title: "选择图片失败", icon: "none" });
}
});
};
const validatePhone = (phone) => {
if (!phone)
return true;
return /^1[3-9]\d{9}$/.test(phone);
};
const submitForm = async () => {
if (!formData.value.patient_name) {
common_vendor.index.showToast({ title: "姓名不能为空", icon: "none" });
return;
}
if (formData.value.phone && !validatePhone(formData.value.phone)) {
common_vendor.index.showToast({ title: "手机号格式不正确", icon: "none" });
return;
}
submitting.value = true;
try {
const res = await proxy.apiUrl({
url: "/api/user/setInfo",
method: "POST",
data: {
avatar: formData.value.avatar,
phone: formData.value.phone,
nickname: formData.value.nickname,
sex: formData.value.sex,
age: formData.value.age,
patient_name: formData.value.patient_name
}
});
if (res.code === 1) {
const userData = common_vendor.index.getStorageSync("userData");
const updatedData = {
...userData,
avatar: formData.value.avatar,
phone: formData.value.phone,
nickname: formData.value.nickname,
sex: formData.value.sex,
age: formData.value.age,
patient_name: formData.value.patient_name,
diagnosis: {
...userData.diagnosis,
patient_name: formData.value.patient_name
}
};
common_vendor.index.setStorageSync("userData", updatedData);
common_vendor.index.showToast({ title: "保存成功", icon: "success" });
setTimeout(() => {
common_vendor.index.navigateBack();
}, 1500);
} else {
common_vendor.index.showToast({ title: res.msg || "保存失败", icon: "none" });
}
} catch (err) {
common_vendor.index.__f__("error", "at pages/user/userinfo.vue:269", "保存失败:", err);
common_vendor.index.showToast({ title: "保存失败", icon: "none" });
} finally {
submitting.value = false;
}
};
return (_ctx, _cache) => {
return common_vendor.e({
a: loading.value
}, loading.value ? {} : {
b: formData.value.avatar,
c: common_vendor.p({
type: "upload-filled",
size: "24",
color: "#FFA500"
}),
d: common_vendor.o(uploadAvatar, "bf"),
e: formData.value.nickname,
f: common_vendor.o(($event) => formData.value.nickname = $event.detail.value, "6d"),
g: formData.value.sex === 1 ? 1 : "",
h: common_vendor.o(($event) => formData.value.sex = 1, "e9"),
i: formData.value.sex === 2 ? 1 : "",
j: common_vendor.o(($event) => formData.value.sex = 2, "53"),
k: formData.value.age,
l: common_vendor.o(common_vendor.m(($event) => formData.value.age = $event.detail.value, {
number: true
}), "cf"),
m: common_vendor.t(submitting.value ? "保存中..." : "保存修改"),
n: common_vendor.o(submitForm, "84"),
o: submitting.value
});
};
}
};
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-103261b1"]]);
wx.createPage(MiniProgramPage);
"use strict";const e=require("../../common/vendor.js");Array||e.resolveComponent("uni-icons")();const h=()=>"../../uni_modules/uni-icons/components/uni-icons/uni-icons.js";Math||h();const y={__name:"userinfo",setup(l){const{proxy:c}=e.getCurrentInstance(),d=e.getCurrentInstance().appContext.config.globalProperties.$url,s=e.ref(!1),i=e.ref(!1),a=e.ref({avatar:"",patient_name:"",phone:"",nickname:"",sex:1,age:""});e.onMounted(()=>{v()});const v=async()=>{s.value=!0;try{const n=await c.apiUrl({url:"/api/user/info",method:"GET",data:{}});if(n.code===1){const t=n.data;a.value={avatar:t.avatar||"",patient_name:t.patient_name||t.nickname||"",phone:t.phone||"",nickname:t.nickname||"",sex:t.sex=="男"?1:2,age:t.age||""}}else e.index.showToast({title:n.msg||"加载失败",icon:"none"})}catch(n){e.index.__f__("error","at pages/user/userinfo.vue:142","加载用户信息失败:",n),e.index.showToast({title:"加载失败",icon:"none"})}finally{s.value=!1}},m=()=>{e.index.chooseImage({count:1,sizeType:["compressed"],sourceType:["album","camera"],success:async n=>{const t=n.tempFilePaths[0];try{e.index.showLoading({title:"上传中..."});const o=e.index.getStorageSync("token"),r=await new Promise((f,x)=>{e.index.uploadFile({url:d+"/api/upload/image",filePath:t,name:"file",header:{token:o,"content-type":"multipart/form-data"},success:u=>{const g=JSON.parse(u.data);f(g)},fail:u=>{x(u)}})});e.index.hideLoading(),r.code===1?(a.value.avatar=r.data.uri||r.data,e.index.showToast({title:"头像上传成功",icon:"success"})):e.index.showToast({title:r.msg||"上传失败",icon:"none"})}catch(o){e.index.hideLoading(),e.index.__f__("error","at pages/user/userinfo.vue:195","上传头像失败:",o),e.index.showToast({title:"上传失败",icon:"none"})}},fail:n=>{e.index.__f__("error","at pages/user/userinfo.vue:200","选择图片失败:",n),e.index.showToast({title:"选择图片失败",icon:"none"})}})},p=n=>n?/^1[3-9]\d{9}$/.test(n):!0,_=async()=>{if(!a.value.patient_name){e.index.showToast({title:"姓名不能为空",icon:"none"});return}if(a.value.phone&&!p(a.value.phone)){e.index.showToast({title:"手机号格式不正确",icon:"none"});return}i.value=!0;try{const n=await c.apiUrl({url:"/api/user/setInfo",method:"POST",data:{avatar:a.value.avatar,phone:a.value.phone,nickname:a.value.nickname,sex:a.value.sex,age:a.value.age,patient_name:a.value.patient_name}});if(n.code===1){const t=e.index.getStorageSync("userData"),o={...t,avatar:a.value.avatar,phone:a.value.phone,nickname:a.value.nickname,sex:a.value.sex,age:a.value.age,patient_name:a.value.patient_name,diagnosis:{...t.diagnosis,patient_name:a.value.patient_name}};e.index.setStorageSync("userData",o),e.index.showToast({title:"保存成功",icon:"success"}),setTimeout(()=>{e.index.navigateBack()},1500)}else e.index.showToast({title:n.msg||"保存失败",icon:"none"})}catch(n){e.index.__f__("error","at pages/user/userinfo.vue:269","保存失败:",n),e.index.showToast({title:"保存失败",icon:"none"})}finally{i.value=!1}};return(n,t)=>e.e({a:s.value},s.value?{}:{b:a.value.avatar,c:e.p({type:"upload-filled",size:"24",color:"#FFA500"}),d:e.o(m,"bf"),e:a.value.nickname,f:e.o(o=>a.value.nickname=o.detail.value,"6d"),g:a.value.sex===1?1:"",h:e.o(o=>a.value.sex=1,"e9"),i:a.value.sex===2?1:"",j:e.o(o=>a.value.sex=2,"53"),k:a.value.age,l:e.o(e.m(o=>a.value.age=o.detail.value,{number:!0}),"cf"),m:e.t(i.value?"保存中...":"保存修改"),n:e.o(_,"84"),o:i.value})}},w=e._export_sfc(y,[["__scopeId","data-v-103261b1"]]);wx.createPage(w);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/user/userinfo.js.map
@@ -1,188 +1 @@
/* 适老化设计:50-80岁 - 大字号、高对比、大触控区 */
.userinfo-container.data-v-103261b1 {
min-height: 100vh;
background: #e8eaed;
}
.header.data-v-103261b1 {
display: flex;
justify-content: space-between;
align-items: center;
background: #ffffff;
padding: 24rpx 40rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
}
.header-back.data-v-103261b1 {
font-size: 48rpx;
color: #FFA500;
width: 88rpx;
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
}
.header-title.data-v-103261b1 {
font-size: 44rpx;
font-weight: bold;
color: #1a1a1a;
flex: 1;
text-align: center;
}
.header-placeholder.data-v-103261b1 {
width: 88rpx;
}
.loading-state.data-v-103261b1 {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
font-size: 36rpx;
color: #555;
}
.form-wrapper.data-v-103261b1 {
padding: 40rpx;
}
.form-section.data-v-103261b1 {
background: #ffffff;
border-radius: 24rpx;
padding: 40rpx;
margin-bottom: 36rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
}
.section-title.data-v-103261b1 {
font-size: 40rpx;
font-weight: bold;
color: #FFA500;
margin-bottom: 32rpx;
display: block;
padding-bottom: 24rpx;
border-bottom: 4rpx solid #f0f0f0;
}
.form-group.data-v-103261b1 {
margin-bottom: 36rpx;
}
.form-group.data-v-103261b1:last-child {
margin-bottom: 0;
}
.form-label.data-v-103261b1 {
display: block;
font-size: 36rpx;
color: #1a1a1a;
font-weight: 500;
margin-bottom: 20rpx;
line-height: 1.6;
}
.form-input.data-v-103261b1 {
width: 100%;
padding: 28rpx;
border: 4rpx solid #e0e0e0;
border-radius: 16rpx;
font-size: 36rpx;
line-height: 1.6;
color: #1a1a1a;
background: #ffffff;
box-sizing: border-box;
display: block;
height: auto;
min-height: 96rpx;
}
.form-input.data-v-103261b1::-webkit-input-placeholder {
color: #999;
font-size: 34rpx;
}
.form-input.data-v-103261b1::placeholder {
color: #999;
font-size: 34rpx;
}
.avatar-container.data-v-103261b1 {
display: flex;
align-items: center;
gap: 32rpx;
}
.avatar-image.data-v-103261b1 {
width: 160rpx;
height: 160rpx;
border-radius: 80rpx;
background: #f0f0f0;
border: 4rpx solid #e0e0e0;
}
.avatar-upload.data-v-103261b1 {
flex: 1;
padding: 36rpx;
border: 4rpx dashed #FFA500;
border-radius: 20rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16rpx;
min-height: 120rpx;
}
.upload-icon.data-v-103261b1 {
font-size: 56rpx;
}
.upload-text.data-v-103261b1 {
font-size: 34rpx;
color: #FFA500;
font-weight: 500;
}
.sex-group.data-v-103261b1 {
display: flex;
gap: 28rpx;
}
.sex-btn.data-v-103261b1 {
flex: 1;
padding: 32rpx;
border: 4rpx solid #e0e0e0;
border-radius: 16rpx;
text-align: center;
font-size: 38rpx;
line-height: 1.5;
color: #555;
background: #ffffff;
transition: all 0.3s;
display: flex;
align-items: center;
justify-content: center;
min-height: 96rpx;
}
.sex-btn.active.data-v-103261b1 {
border-color: #FFA500;
background: #fff7e6;
color: #FFA500;
font-weight: bold;
}
.button-group.data-v-103261b1 {
display: flex;
gap: 28rpx;
margin-top: 56rpx;
margin-bottom: 56rpx;
}
.btn.data-v-103261b1 {
flex: 1;
padding: 36rpx;
border-radius: 20rpx;
font-size: 40rpx;
font-weight: bold;
border: none;
cursor: pointer;
transition: all 0.3s;
min-height: 100rpx;
}
.btn.data-v-103261b1:active {
transform: scale(0.98);
}
.btn.data-v-103261b1:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-cancel.data-v-103261b1 {
background: #f0f0f0;
color: #555;
}
.btn-submit.data-v-103261b1 {
background: #FFA500;
color: #ffffff;
}
.btn-submit.data-v-103261b1:active:not(:disabled) {
background: #e69500;
}
.userinfo-container.data-v-103261b1{min-height:100vh;background:#e8eaed}.header.data-v-103261b1{display:flex;justify-content:space-between;align-items:center;background:#fff;padding:24rpx 40rpx;box-shadow:0 2rpx 12rpx rgba(0,0,0,.06)}.header-back.data-v-103261b1{font-size:48rpx;color:orange;width:88rpx;height:88rpx;display:flex;align-items:center;justify-content:center}.header-title.data-v-103261b1{font-size:44rpx;font-weight:700;color:#1a1a1a;flex:1;text-align:center}.header-placeholder.data-v-103261b1{width:88rpx}.loading-state.data-v-103261b1{display:flex;justify-content:center;align-items:center;height:100vh;font-size:36rpx;color:#555}.form-wrapper.data-v-103261b1{padding:40rpx}.form-section.data-v-103261b1{background:#fff;border-radius:24rpx;padding:40rpx;margin-bottom:36rpx;box-shadow:0 4rpx 16rpx rgba(0,0,0,.06)}.section-title.data-v-103261b1{font-size:40rpx;font-weight:700;color:orange;margin-bottom:32rpx;display:block;padding-bottom:24rpx;border-bottom:4rpx solid #f0f0f0}.form-group.data-v-103261b1{margin-bottom:36rpx}.form-group.data-v-103261b1:last-child{margin-bottom:0}.form-label.data-v-103261b1{display:block;font-size:36rpx;color:#1a1a1a;font-weight:500;margin-bottom:20rpx;line-height:1.6}.form-input.data-v-103261b1{width:100%;padding:28rpx;border:4rpx solid #e0e0e0;border-radius:16rpx;font-size:36rpx;line-height:1.6;color:#1a1a1a;background:#fff;box-sizing:border-box;display:block;height:auto;min-height:96rpx}.form-input.data-v-103261b1::-webkit-input-placeholder{color:#999;font-size:34rpx}.form-input.data-v-103261b1::placeholder{color:#999;font-size:34rpx}.avatar-container.data-v-103261b1{display:flex;align-items:center;gap:32rpx}.avatar-image.data-v-103261b1{width:160rpx;height:160rpx;border-radius:80rpx;background:#f0f0f0;border:4rpx solid #e0e0e0}.avatar-upload.data-v-103261b1{flex:1;padding:36rpx;border:4rpx dashed #FFA500;border-radius:20rpx;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16rpx;min-height:120rpx}.upload-icon.data-v-103261b1{font-size:56rpx}.upload-text.data-v-103261b1{font-size:34rpx;color:orange;font-weight:500}.sex-group.data-v-103261b1{display:flex;gap:28rpx}.sex-btn.data-v-103261b1{flex:1;padding:32rpx;border:4rpx solid #e0e0e0;border-radius:16rpx;text-align:center;font-size:38rpx;line-height:1.5;color:#555;background:#fff;transition:all .3s;display:flex;align-items:center;justify-content:center;min-height:96rpx}.sex-btn.active.data-v-103261b1{border-color:orange;background:#fff7e6;color:orange;font-weight:700}.button-group.data-v-103261b1{display:flex;gap:28rpx;margin-top:56rpx;margin-bottom:56rpx}.btn.data-v-103261b1{flex:1;padding:36rpx;border-radius:20rpx;font-size:40rpx;font-weight:700;border:none;cursor:pointer;transition:all .3s;min-height:100rpx}.btn.data-v-103261b1:active{transform:scale(.98)}.btn.data-v-103261b1:disabled{opacity:.6;cursor:not-allowed}.btn-cancel.data-v-103261b1{background:#f0f0f0;color:#555}.btn-submit.data-v-103261b1{background:orange;color:#fff}.btn-submit.data-v-103261b1:active:not(:disabled){background:#e69500}
@@ -9,8 +9,7 @@
"postcss": false,
"minified": true,
"newFeature": true,
"bigPackageSizeSupport": true,
"minifyWXML": true
"bigPackageSizeSupport": true
},
"compileType": "miniprogram",
"libVersion": "",
@@ -1,86 +1,2 @@
"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);
"use strict";const t=require("../../common/vendor.js");Math||h();const h=()=>"./TongjiIcon.js",m={__name:"CelebrateBurst",props:{show:{type:Boolean,default:!1},title:{type:String,default:""},subtitle:{type:String,default:""}},setup(e){const f=e,s=t.ref([]),l=t.ref(!1),i=["#204E2B","#386641","#AFE2B3","#FBBF24","#727970","#DC2626"];function d(){const a=[];for(let n=0;n<18;n++){const o=8+Math.random()*84,c=Math.random()*.35,u=i[n%i.length],r=10+Math.floor(Math.random()*14);a.push({id:`${Date.now()}_${n}`,style:{left:`${o}%`,width:`${r}rpx`,height:`${r}rpx`,background:u,animationDelay:`${c}s`}})}return a}return t.watch(()=>f.show,a=>{a?(s.value=d(),l.value=!1,setTimeout(()=>{l.value=!0},30)):(s.value=[],l.value=!1)}),(a,n)=>t.e({a:e.show},e.show?t.e({b:t.f(s.value,(o,c,u)=>({a:o.id,b:t.s(o.style)})),c:e.title},e.title?t.e({d:t.p({name:"sparkles",size:"lg",color:"#204E2B"}),e:t.t(e.title),f:e.subtitle},e.subtitle?{g:t.t(e.subtitle)}:{},{h:l.value?1:""}):{},{i:t.o(()=>{},"49")}):{})}},b=t._export_sfc(m,[["__scopeId","data-v-313f45d6"]]);wx.createComponent(b);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/components/CelebrateBurst.js.map
@@ -1,95 +1 @@
/**
* 这里是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;
}
.celebrate-root.data-v-313f45d6{position:fixed;top:0;right:0;bottom:0;left:0;z-index:9999;pointer-events:none;overflow:hidden}.celebrate-particle.data-v-313f45d6{position:absolute;top:-20rpx;border-radius:4rpx;opacity:.9;animation:celebrate-fall-313f45d6 1.6s ease-in forwards}@keyframes celebrate-fall-313f45d6{0%{transform:translateY(0) rotate(0) scale(1);opacity:1}to{transform:translateY(110vh) rotate(540deg) scale(.4);opacity:0}}.celebrate-card.data-v-313f45d6{position:absolute;left:50%;top:38%;transform:translate(-50%,-50%) scale(.82);width:78%;max-width:560rpx;padding:36rpx 32rpx 32rpx;background:rgba(255,255,255,.96);border-radius:28rpx;box-shadow:0 20rpx 60rpx rgba(8,145,178,.22);border:2rpx solid rgba(8,145,178,.12);display:flex;flex-direction:column;align-items:center;text-align:center;opacity:0;transition:transform .35s cubic-bezier(.34,1.56,.64,1),opacity .25s ease}.celebrate-card.pop.data-v-313f45d6{opacity:1;transform:translate(-50%,-50%) scale(1)}.celebrate-card-glow.data-v-313f45d6{position:absolute;top:-20rpx;right:-20rpx;bottom:-20rpx;left:-20rpx;border-radius:36rpx;background:radial-gradient(circle,rgba(34,211,238,.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}
@@ -1,100 +1,13 @@
"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 = `
"use strict";const c=require("../../common/vendor.js"),L=require("../utils/svgDataUrl.js"),v=require("../utils/treeLevels.js"),M={__name:"SugarTreeGraphic",props:{level:{type:Number,default:0},size:{type:String,default:"md"},watering:{type:Boolean,default:!1}},setup(n){const l=v.TREE_MAX_LEVEL,o=c.ref(!0),x=n,r=c.computed(()=>Math.min(l,Math.max(0,Number(x.level)||0))),y=c.computed(()=>{const t=r.value;return t<=0?0:t<=2?1:t<=4?2:t<=6?3:t<=8?4:5}),$=c.computed(()=>(v.TREE_LEVELS[r.value]||v.TREE_LEVELS[0]).emoji);function h(t){const s=`
<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}
`,u='<ellipse cx="24" cy="44.5" rx="8" ry="2.2" fill="#386641" opacity="0.18"/>';if(t<=0)return`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 56" fill="none">
${s}${u}
<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);
</svg>`;const f=t>=6?16:t>=3?14:10,d=46-f,w=`<rect x="22.2" y="${d}" width="3.6" height="${f}" rx="1.8" fill="#78716C"/>
<rect x="22.6" y="${d+1}" width="2.8" height="${f-1}" rx="1.4" fill="#A8A29E" opacity="0.35"/>`,e=(m,g,a,b,k=1)=>`<circle cx="${m}" cy="${g}" r="${a}" fill="${b}" opacity="${k}"/>
<circle cx="${m-a*.25}" cy="${g-a*.2}" r="${a*.35}" fill="#eef6ef" opacity="0.55"/>`,p=t>=7?e(17,18,3,"#fda4af",.95)+e(31,17,2.8,"#f9a8d4",.9)+e(24,13,3.2,"#fb7185",.95)+'<circle cx="24" cy="12" r="1.3" fill="#fef3c7"/>':"",_=t>=9?e(12,22,6,"#34d399",.9)+e(36,22,6,"#34d399",.9)+e(24,10,7,"#386641",.95)+p:p;let i="";return t===1?i=e(24,38,4,"#afe2b3")+'<path d="M24 38v-5" stroke="#386641" stroke-width="1.2" stroke-linecap="round"/>':t===2?i=e(24,32,5.5,"#34d399")+e(20,34,3.5,"#6ee7b7",.9):t<=4?i=e(24,28,7,"#34d399")+e(17,30,5,"#6ee7b7",.9)+e(31,30,5,"#6ee7b7",.9):t<=6?i=e(24,24,9,"#22c55e")+e(14,26,7,"#4ade80",.92)+e(34,26,7,"#4ade80",.92)+e(24,16,6,"#386641",.88):i=e(24,20,10,"#204e2b")+e(13,24,8,"#34d399",.95)+e(35,24,8,"#34d399",.95)+_,`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 56" fill="none">${s}${u}${w}${i}</svg>`}const E=c.computed(()=>L.svgToDataUrl(h(r.value)));return(t,s)=>c.e({a:r.value>=7},r.value>=7?{}:{},{b:!o.value},o.value?{e:c.t($.value)}:{c:E.value,d:c.o(u=>o.value=!0,"20")},{f:r.value>=7},r.value>=7?{}:{},{g:r.value>=8},r.value>=8?{}:{},{h:r.value>=c.unref(l)},r.value>=c.unref(l)?{}:{},{i:c.n(`lv-${r.value}`),j:c.n(`tier-${y.value}`),k:c.n(`size-${n.size}`),l:c.n({watering:n.watering,"is-max":r.value>=c.unref(l)})})}},T=c._export_sfc(M,[["__scopeId","data-v-d91cb5e7"]]);wx.createComponent(T);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/components/SugarTreeGraphic.js.map
@@ -1,153 +1 @@
/**
* 这里是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);
}
}
.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;top:6%;right:6%;bottom:6%;left:6%;border-radius:50%;background:radial-gradient(circle,rgba(32,78,43,.22) 0%,transparent 68%);pointer-events:none}.lv-0 .stg-glow.data-v-d91cb5e7{background:radial-gradient(circle,rgba(148,163,184,.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,.3) 0%,rgba(32,78,43,.15) 55%,transparent 72%)}.stg-aura.data-v-d91cb5e7{position:absolute;top:-8%;right:-8%;bottom:-8%;left:-8%;border-radius:50%;border:2rpx solid rgba(253,224,71,.35);animation:stg-aura-pulse-d91cb5e7 2.4s ease-in-out infinite;pointer-events:none}@keyframes stg-aura-pulse-d91cb5e7{0%,to{transform:scale(.92);opacity:.5}50%{transform:scale(1.05);opacity:1}}.stg-image.data-v-d91cb5e7{position:relative;z-index:1;width:100%;height:100%;transition:transform .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 .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%,to{transform:scale(1)}35%{transform:scale(1.1) translateY(-6rpx)}60%{transform:scale(.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,.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:.4s}.stg-sparkle-c.data-v-d91cb5e7{top:22%;right:28%;width:10rpx;height:10rpx;animation-delay:.8s}@keyframes stg-sparkle-twinkle-d91cb5e7{0%,to{opacity:.4;transform:scale(.8)}50%{opacity:1;transform:scale(1.2)}}
File diff suppressed because one or more lines are too long
@@ -1,75 +1 @@
/**
* 这里是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;
}
.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%}.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}
@@ -1,28 +1,2 @@
"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);
"use strict";const t=require("../../common/vendor.js"),a={__name:"VoiceSpeakMascot",props:{active:{type:Boolean,default:!1},onDark:{type:Boolean,default:!1},size:{type:String,default:"md"}},setup(e){return(c,o)=>t.e({a:e.active},e.active?{}:{},{b:t.n(`vsm--${e.size}`),c:t.n({"vsm--active":e.active,"vsm--on-dark":e.onDark})})}},n=t._export_sfc(a,[["__scopeId","data-v-e1de3b0a"]]);wx.createComponent(n);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/components/VoiceSpeakMascot.js.map
@@ -1,230 +1 @@
/**
* 这里是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;
}
}
.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%;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,.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,.85);box-shadow:0 4rpx 12rpx rgba(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,.45);opacity:.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,.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 .72s ease-in-out infinite}.vsm--on-dark .vsm-wave.data-v-e1de3b0a{background:rgba(255,255,255,.9)}.vsm-wave--1.data-v-e1de3b0a{height:10rpx;animation-delay:0s}.vsm-wave--2.data-v-e1de3b0a{height:18rpx;animation-delay:.12s}.vsm-wave--3.data-v-e1de3b0a{height:12rpx;animation-delay:.24s}.vsm--active .vsm-body.data-v-e1de3b0a{animation:vsm-bob-e1de3b0a .9s ease-in-out infinite}.vsm--active .vsm-mouth.data-v-e1de3b0a{animation:vsm-talk-e1de3b0a .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%,to{transform:translateY(0)}50%{transform:translateY(-4rpx)}}@keyframes vsm-talk-e1de3b0a{0%{transform:scaleY(.45);border-radius:0 0 18rpx 18rpx}to{transform:scaleY(1.15);height:12rpx;border-radius:50%}}@keyframes vsm-blink-e1de3b0a{0%,42%,46%,to{transform:scaleY(1)}44%{transform:scaleY(.15)}}@keyframes vsm-wave-jump-e1de3b0a{0%,to{transform:scaleY(.45);opacity:.55}50%{transform:scaleY(1);opacity:1}}
@@ -1,307 +1,2 @@
"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;
"use strict";const r=require("../../common/vendor.js"),d=require("../utils/aiStreamRequest.js"),_="tongji_diet_ai_prefill",A=()=>({breakfast:"",drinks:"",lunch:"",dinner:"",tips:"",avoid:[],analysis:[],exercise:null,disclaimer:"",rules_summary:"",source:"",date:""});function B(a,{diagnosisId:u,showUserToast:c,formatUserMessage:k}){const n=r.ref(A()),f=r.ref(!1),v=r.ref(!1),D=r.ref(!1),o=r.ref("");let s="";const m=r.ref(""),b=r.ref(!1),l=r.ref({advice:"",level:"",level_label:"",portion:"",food:""}),R=r.computed(()=>{const e=n.value;return!!(e.breakfast||e.drinks||e.lunch||e.dinner||e.tips)});function F(e){const i=String(e||"").match(/(?:^|\n)(早餐|喝的|午餐|晚餐|提示|少碰)[:]\s*([^\n]*)$/);return i&&{早餐:"breakfast",喝的:"drinks",午餐:"lunch",晚餐:"dinner",提示:"tips",少碰:"tips"}[i[1]]||"breakfast"}function E(){const e=d.parsePartialDietPlainText(s),i={...n.value};for(const t of["breakfast","drinks","lunch","dinner","tips"])e[t]!=null&&(i[t]=e[t]);Array.isArray(e.avoid)&&(i.avoid=e.avoid),n.value=i,o.value=F(s)}function g(e){if(!e)return;const i=n.value.analysis,t=n.value.exercise;n.value={...A(),...e,avoid:Array.isArray(e.avoid)?e.avoid:[],analysis:Array.isArray(e.analysis)&&e.analysis.length?e.analysis:Array.isArray(i)?i:[],exercise:e.exercise&&typeof e.exercise=="object"?e.exercise:t||null}}function q(e){if(!e||e.source!=="rule"||!s)return e;const i=d.parsePartialDietPlainText(s);return i.breakfast?{...A(),...e,breakfast:i.breakfast,drinks:i.drinks||e.drinks||"",lunch:i.lunch||e.lunch||"",dinner:i.dinner||e.dinner||"",tips:i.tips||e.tips||"",avoid:Array.isArray(i.avoid)&&i.avoid.length?i.avoid:e.avoid||[],source:"ai",disclaimer:"AI 建议供参考,仍请结合医嘱与血糖监测。"}:e}function j(e,i){e==="analysis"?Array.isArray(i==null?void 0:i.analysis)&&(n.value={...n.value,analysis:i.analysis}):e==="exercise"?i!=null&&i.exercise&&typeof i.exercise=="object"&&(n.value={...n.value,exercise:i.exercise}):e==="delta"?(v.value=!0,i.buffer!=null?s=i.buffer:s+=i.text||"",E()):e==="done"?(g(q(i)),s="",o.value=""):e==="error"&&c(k(i==null?void 0:i.message,"饮食建议加载失败"))}async function x(e){const i=await a.apiUrl({url:"/api/tcm/dailyDietAiRecommend",method:"GET",timeout:35e3,data:{diagnosis_id:u.value,refresh:e?1:0,...e?{_t:Date.now()}:{}}},!1);(i==null?void 0:i.code)===1&&i.data?g(i.data):i!=null&&i.msg&&c(i.msg)}async function S(e=!1){if(!(!u.value||f.value)){f.value=!0,v.value=!1,D.value=!!e,s="",o.value="",e&&g(A());try{await d.requestAiStream({baseUrl:a.$url,url:"/api/tcm/dailyDietAiRecommendStream",method:"GET",data:{diagnosis_id:u.value,refresh:e?1:0,...e?{_t:Date.now()}:{}},onEvent:j,fallback:()=>x(e)})}catch{try{await x(e)}catch{c("饮食建议加载失败")}}finally{f.value=!1,v.value=!1,D.value=!1,o.value=""}}}function T(){f.value||S(!0)}async function h(e){const i=await a.apiUrl({url:"/api/tcm/dailyDietAiAsk",method:"POST",timeout:35e3,data:{diagnosis_id:u.value,question:e}},!1);(i==null?void 0:i.code)===1&&i.data?l.value={advice:i.data.advice||"",level:i.data.level||"",level_label:i.data.level_label||"",portion:i.data.portion||"",food:i.data.food||e}:c(k(i==null?void 0:i.msg,"咨询失败"))}async function w(){const e=String(m.value||"").trim();if(!e){c("请输入想咨询的食物");return}if(u.value){m.value="",b.value=!0,l.value={advice:"",level:"",level_label:"",portion:"",food:""};try{await d.requestAiStream({baseUrl:a.$url,url:"/api/tcm/dailyDietAiAskStream",method:"POST",data:{diagnosis_id:u.value,question:e},onEvent:(i,t)=>{i==="start"&&!l.value.advice?l.value={...l.value,advice:t.message||"正在想…"}:i==="delta"?l.value={...l.value,advice:t.advice||l.value.advice}:i==="done"?l.value={advice:t.advice||"",level:t.level||"",level_label:t.level_label||"",portion:t.portion||"",food:t.food||e}:i==="error"&&c(k(t==null?void 0:t.message,"咨询失败"))},fallback:()=>h(e)})}catch{try{await h(e)}catch{c("网络异常,请稍后再试")}}finally{b.value=!1}}}function L(e){var i;m.value=((i=e==null?void 0:e.detail)==null?void 0:i.value)??""}function $(){const e=n.value;if(e.breakfast)try{r.index.setStorageSync(_,{breakfast:e.breakfast,drinks:e.drinks||"",lunch:e.lunch,dinner:e.dinner,tips:e.tips||""})}catch{}}function P(e=!1){e&&$(),r.index.navigateTo({url:"/tongji/pages/more?openDiet=1"})}async function G(){n.value.breakfast||await S(!1),P(!0)}function O(){P(!1)}return{dietAiRecommend:n,dietAiLoading:f,dietAiStreaming:v,dietAiReplacing:D,dietAiStreamField:o,dietAiAskText:m,dietAiAsking:b,dietAiAskResult:l,hasDietAiMealContent:R,fetchDietAiRecommend:S,refreshDietAiRecommend:T,askDietAiFood:w,onDietAiAskInput:L,applyDietAiToForm:G,openDietForm:O}}function C(){try{const a=r.index.getStorageSync(_);if(r.index.removeStorageSync(_),a&&typeof a=="object"&&a.breakfast)return a}catch{}return null}exports.consumeDietAiPrefill=C;exports.useDietAi=B;
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/composables/useDietAi.js.map
@@ -1,291 +1,2 @@
"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;
"use strict";const s=require("../../common/vendor.js"),C=280;function w(n,u){const d=String(n||"").toLowerCase();if(/internal voice data failed/i.test(d)||/voice data failed/i.test(d)||/recordfailed|record failed|record manager/i.test(d)||/please stop after start/i.test(d)||/no recognition|not start|未开始|无识别/i.test(d))return!0;const h=Number(u);return h===-30002||h===-30003||h===-30012}function N(n){const u=String(n||"").trim();return!u||w(u)?"":/not allowed|auth|permission|权限|麦克风/i.test(u)?"请允许使用麦克风":/network|网络|timeout/i.test(u)?"网络异常,请稍后再试":"语音识别失败,请重试"}let c=null,D=!1,g=!1,i=null;function j(){if(D)return c;D=!0;try{c=requirePlugin("WechatSI").getRecordRecognitionManager()}catch(n){return s.index.__f__("warn","at tongji/composables/useSpeechToText.js:44","WechatSI record recognition not available",n),c=null,null}return c?(c.onStart=()=>{g=!0,i&&i.handleStart()},c.onRecognize=n=>{i&&i.handleRecognize(n)},c.onStop=n=>{g=!1,i&&i.handleStop(n)},c.onError=n=>{g=!1,i&&i.handleError(n)},c):null}function A({onResult:n,onSettle:u,onPartial:d,onError:h,showToast:M}={}){const l=s.ref(!1),r=s.ref(!1),y=s.ref(!1);let f=null,R=0,p=0,o=!1,x=null,a=0;function v(e){if(e){if(typeof M=="function"){M(e);return}s.index.showToast({title:e,icon:"none",duration:2e3})}}function H(e){const t=String(e||"").trim(),m=Date.now()-R;if(!(typeof u=="function"&&u(t,{heldMs:m})===!0)){if(!t){if(m<C)return;v("没听清,请再试一次");return}typeof n=="function"&&n(t)}}function T(){const e=g&&i===S;if(f&&(l.value||e))try{f.stop()}catch{}o=!1,r.value=!1,l.value=!1}function L(e){if(x===!0){e();return}s.index.authorize({scope:"scope.record",success(){x=!0,e()},fail(){x=!1,r.value=!1,s.index.showModal({title:"需要麦克风权限",content:"请在设置中允许使用麦克风,以便长按说话",confirmText:"去设置",success(t){t.confirm&&s.index.openSetting({})}})}})}function _(e){if(!(!f||e!==p||!r.value))try{o=!0,f.start({duration:6e4,lang:"zh_CN"})}catch{o=!1,g=!1,r.value=!1,v("无法开始录音")}}function W(e){if(!(!f||e!==p||!r.value)){if(g||o){a=e;try{f.stop()}catch{}return}_(e)}}function q(){if(!y.value){v("当前环境不支持语音输入");return}if(r.value)return;const e=++p;R=Date.now(),r.value=!0,i=S,L(()=>W(e))}function z(e=!1){let t=!1;if(t=g&&i===S,!(!e&&!r.value&&!l.value&&!o&&!t)){if(r.value=!1,a=0,l.value||t){T();return}if(o){o=!1,p+=1;return}p+=1}}const S={handleStart(){if(!r.value){o=!1,l.value=!1;return}o=!1,l.value=!0},handleRecognize(e){if(typeof d!="function")return;const t=String((e==null?void 0:e.result)||"").trim();t&&d(t)},handleStop(e){if(l.value=!1,o=!1,a&&a===p&&r.value){const t=a;a=0,_(t);return}a=0,H(e==null?void 0:e.result)},handleError(e){l.value=!1,o=!1;const t=e&&e.msg||"",m=e&&(e.retcode??e.errCode??e.code);if(/please stop after start/i.test(t)){try{f.stop()}catch{}a=r.value?p:0;return}a=0,r.value=!1;const I=Date.now()-R;if(w(t,m)||I<C){typeof h=="function"&&h(t||"");return}if(typeof h=="function"&&h(t||"")===!0)return;const b=N(t);b&&v(b)}};return f=j(),y.value=!!f,s.onUnmounted(()=>{r.value=!1,p+=1,a=0,T(),i===S&&(i=null)}),{sttListening:l,sttHolding:r,sttSupported:y,beginHoldSpeech:q,endHoldSpeech:z,stopSpeechToText:z}}exports.isBenignSttError=w;exports.useSpeechToText=A;
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/composables/useSpeechToText.js.map
@@ -1,64 +1,2 @@
"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;
"use strict";const o=require("../../../common/vendor.js");function g(r){let i=null;function u(){return!!String(o.index.getStorageSync("token")||"").trim()}function a(){o.index.removeStorageSync("token")}function c(){return new Promise((e,n)=>{o.index.login({provider:"weixin",success:t=>t!=null&&t.code?e(t.code):n(new Error("微信登录未返回 code")),fail:n})})}async function d(e){var t;const n=await r.apiUrl({url:"/api/login/mnpLogin",method:"POST",data:{code:e}},!1);if((n==null?void 0:n.code)!==1||!((t=n.data)!=null&&t.token))throw a(),new Error((n==null?void 0:n.msg)||"登录失败");return o.index.setStorageSync("token",n.data.token),o.index.setStorageSync("userData",n.data),!0}async function l(){if(u()){try{const n=await r.apiUrl({url:"/api/user/info",method:"POST"},!1);if((n==null?void 0:n.code)===1&&n.data)return o.index.setStorageSync("userData",n.data),!0;if((n==null?void 0:n.code)!==-1)return!0}catch{return!0}a()}const e=await c();return d(e)}async function f(){return i||(i=l().then(e=>!!e).catch(()=>!1).finally(()=>{i=null}),i)}return{ensureLoggedIn:f}}exports.useGameAuth=g;
//# sourceMappingURL=../../../../.sourcemap/mp-weixin/tongji/endless-game/composables/useGameAuth.js.map
@@ -1,240 +1,2 @@
"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;
"use strict";const l=require("../../../common/vendor.js"),N={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:!0},invite_code:""},x="tongji_endless_pending_sync_v1";function E(){const s=Math.random().toString(36).slice(2,12);return`game_${Date.now().toString(36)}_${s}`}function O(){try{const s=l.index.getStorageSync(x);return Array.isArray(s)?s.filter(u=>u&&/^[A-Za-z0-9_-]{16,64}$/.test(String(u.session_key||""))&&Number(u.learned_count)>=0).slice(-8):[]}catch{return[]}}function q(s,u){const i=l.ref(!1),v=l.ref(!1),o=l.ref("idle"),y=l.ref({...N}),w=l.ref(0);let d=E(),a=null,p=!1,t=O(),f=null;function g(){try{l.index.setStorageSync(x,t.slice(-8))}catch{}}async function h(e){if(!(s!=null&&s.apiUrl))throw new Error("平台接口未初始化");return s.apiUrl(e,!1)}function T(e,n=""){if(!e||!Array.isArray(e.players))return;n&&n!==d||(y.value={...N,...e,me:{...N.me,...e.me||{}},players:e.players}),n===d&&e.confirmed_session_learned!=null&&(w.value=Number(e.confirmed_session_learned)||0),i.value=!0,o.value="synced"}async function S(){const e=await h({url:"/api/tcm/gameWeeklyLeaderboard",method:"GET"});if((e==null?void 0:e.code)!==1||!e.data)throw new Error((e==null?void 0:e.msg)||"同行榜加载失败");return T(e.data),e.data}async function A(e){const n=String(e||"").trim().toUpperCase();if(n)try{await h({url:"/api/tcm/gameAcceptShare",method:"POST",data:{invite_code:n}})}catch{}}async function b(e=""){return f||(f=(async()=>{v.value=!0;try{if(!await u())throw new Error("登录失败");return await A(e),await S(),t.length&&c(),!0}catch{return i.value=!1,o.value="offline",!1}finally{v.value=!1,f=null}})(),f)}function M(){return a&&clearTimeout(a),d=E(),w.value=0,o.value=t.length?"pending":i.value?"synced":"offline",d}function k(e,n,r=!1){const m={session_key:d,learned_count:Math.max(0,Number(e)||0),score:Math.max(0,Number(n)||0),ended:r?1:0},P=t.findIndex(_=>_.session_key===d);if(P>=0){const _=t[P];t[P]={...m,learned_count:Math.max(_.learned_count,m.learned_count),score:Math.max(_.score,m.score),ended:Math.max(_.ended,m.ended)}}else t.push(m);g(),o.value="pending",a&&clearTimeout(a),r?c():a=setTimeout(c,1e3)}async function c(){if(a&&clearTimeout(a),a=null,p||!t.length)return;const e=t[0];p=!0,o.value="syncing";try{if(!i.value&&!await u())throw new Error("未登录");const n=await h({url:"/api/tcm/gameSubmitProgress",method:"POST",data:e});if((n==null?void 0:n.code)!==1||!n.data)throw new Error((n==null?void 0:n.msg)||"成绩保存失败");T(n.data,e.session_key),t=t.filter(r=>r.session_key!==e.session_key||Number(r.learned_count)>Number(e.learned_count)||Number(r.score)>Number(e.score)||Number(r.ended)>Number(e.ended)),g()}catch{i.value=!1,o.value="offline",g()}finally{p=!1,t.length&&i.value&&setTimeout(c,80)}}async function I(e,n){if(k(e,n,!1),await c(),!i.value)await b(),t.length&&await c();else try{await S()}catch{}}async function R(){var e;try{if(!i.value&&!await b())return;const n=await h({url:"/api/tcm/gameRecordShare",method:"POST"});(n==null?void 0:n.code)===1&&((e=n.data)!=null&&e.invite_code)&&(y.value={...y.value,invite_code:n.data.invite_code})}catch{}}function G(){a&&clearTimeout(a),a=null,g()}return{connected:i,loading:v,syncStatus:o,leaderboard:y,confirmedSessionLearned:w,connect:b,beginSession:M,queueProgress:k,flushProgress:c,syncAndRefresh:I,refreshLeaderboard:S,recordShare:R,dispose:G}}exports.useGamePlatform=q;
//# sourceMappingURL=../../../../.sourcemap/mp-weixin/tongji/endless-game/composables/useGamePlatform.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,141 +1,7 @@
"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;
"use strict";const p=require("../../common/vendor.js");function k(e,n){const r=String(e||"").replace(/\/+$/,""),t=String(n||"").replace(/^\/+/,"");return t?`${r}/${t}`:r}function v(e,n){if(!n||typeof n!="object")return e;const r=Object.keys(n);if(!r.length)return e;const t=r.map(c=>`${encodeURIComponent(c)}=${encodeURIComponent(n[c]??"")}`).join("&");return e+(e.includes("?")?"&":"?")+t}function m(e){if(typeof e=="string")return e;if(!e)return"";if(typeof TextDecoder<"u"&&e instanceof ArrayBuffer)return new TextDecoder("utf-8").decode(new Uint8Array(e));if(e instanceof ArrayBuffer){const n=new Uint8Array(e);let r="";for(let t=0;t<n.length;t++)r+=String.fromCharCode(n[t]);try{return decodeURIComponent(escape(r))}catch{return r}}return""}function h(e,n){let r=e,t=r.indexOf(`
`);for(;t!==-1;){const c=r.slice(0,t);r=r.slice(t+2);let o="message",i="";if(c.split(`
`).forEach(s=>{s.startsWith("event:")?o=s.slice(6).trim():s.startsWith("data:")&&(i+=s.slice(5).trim())}),i)try{n(o,JSON.parse(i))}catch{n(o,i)}t=r.indexOf(`
`)}return r}function w(e){const n={};if(!e)return n;const r=/^(早餐|喝的|午餐|晚餐|提示|少碰)[::]\s*(.*)$/gm;let t;for(;(t=r.exec(e))!==null;){const o={早餐:"breakfast",喝的:"drinks",午餐:"lunch",晚餐:"dinner",提示:"tips",少碰:"avoid"}[t[1]],i=(t[2]||"").trim();o==="avoid"?n.avoid=i.split(/[、,;\s]+/).map(s=>s.trim()).filter(Boolean):n[o]=i}const c=e.match(/(?:^|\n)(早餐|喝的|午餐|晚餐|提示|少碰)[::]\s*([^\n]*)$/);if(c){const o={早餐:"breakfast",喝的:"drinks",午餐:"lunch",晚餐:"dinner",提示:"tips",少碰:"avoid"}[c[1]],i=(c[2]||"").trim();o==="avoid"?i&&(n.avoid=i.split(/[、,;\s]+/).map(s=>s.trim()).filter(Boolean)):n[o]=i}return n}function C(e){const{baseUrl:n,url:r,method:t="GET",data:c={},onEvent:o,fallback:i}=e;return new Promise((s,l)=>{const y=p.index.getStorageSync("token")||"";let f="",u=k(n,r);t==="GET"&&(u=v(u,c));const d=p.wx$1.request({url:u,method:t,data:t==="POST"?c:{},enableChunked:!0,timeout:6e4,header:{token:y,"content-type":"application/x-www-form-urlencoded","Cache-Control":"no-cache"},success:a=>{a&&a.data&&(f+=m(a.data),f=h(f,o)),s(a)},fail:a=>l(a)});d&&typeof d.onChunkReceived=="function"?d.onChunkReceived(a=>{f+=m(a.data),f=h(f,o)}):typeof i=="function"?i().then(s).catch(l):l(new Error("当前环境不支持流式请求"))})}exports.parsePartialDietPlainText=w;exports.requestAiStream=C;
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/utils/aiStreamRequest.js.map
@@ -1,48 +1,2 @@
"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;
"use strict";const a=require("../../common/vendor.js");function u(e){const t=encodeURIComponent(e),r=[];for(let n=0;n<t.length;n++)t.charCodeAt(n)===37?(r.push(parseInt(t.substring(n+1,n+3),16)),n+=2):r.push(t.charCodeAt(n));return new Uint8Array(r)}function c(e){if(typeof a.index<"u"&&typeof a.index.arrayBufferToBase64=="function")return a.index.arrayBufferToBase64(e.buffer);if(typeof btoa<"u"){let n="";for(let o=0;o<e.length;o++)n+=String.fromCharCode(e[o]);return btoa(n)}const t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";let r="";for(let n=0;n<e.length;n+=3){const o=e[n],i=n+1<e.length?e[n+1]:0,f=n+2<e.length?e[n+2]:0;r+=t[o>>2],r+=t[(o&3)<<4|i>>4],r+=n+1<e.length?t[(i&15)<<2|f>>6]:"=",r+=n+2<e.length?t[f&63]:"="}return r}function s(e){const t=String(e||"").trim();return t?`data:image/svg+xml;base64,${c(u(t))}`:""}exports.svgToDataUrl=s;
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/utils/svgDataUrl.js.map
@@ -1,60 +1,2 @@
"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;
"use strict";const _=[{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:"满级大树,习惯大师"}],s={0:["种子在睡觉,记一笔就醒啦。","今天浇第一滴水,芽就要冒出来。"],1:["破土啦!再坚持几天就长高。","小芽最喜欢规律的记录了。"],2:["两片叶子为你鼓掌。","空腹餐后都记全,我会长得更快。"],3:["我已经是一棵小树啦。","家人点赞的时候,我也会发光。"],4:["枝叶越来越密,您真棒。","连续记录,我会开出更多叶子。"],5:["拔节中!习惯比完美更重要。","再浇一点水,我就更高啦。"],6:["习惯成自然,树冠成形啦。","您今天的坚持,小树都记得。"],7:["开花啦!闻到春天的味道了吗?","稳糖花只开给坚持的人。"],8:["满树花香,您已是控糖达人。","明天继续来,花儿会更艳。"],9:["满级大树陪您一路稳糖。","圆满不是终点,习惯才是。"]};function l(m){const e=Math.max(0,Number(m)||0),E=Math.min(9,Math.floor(e/50)),L=E>=9?50:e%50,c=E>=9?100:Math.round(L/50*100),o=_[E]||_[0],n=E<9?_[E+1]:null,t=E>=9?0:50-L;return{level:E,progress:c,name:o.name,emoji:o.emoji,mood:o.mood,desc:o.desc,xpInLevel:L,xpNeed:50,pointsToNext:t,nextName:(n==null?void 0:n.name)||"",isMax:E>=9}}function R(m){const e=Math.min(9,Math.max(0,Number(m)||0)),E=s[e]||s[0];return E[Math.floor(Math.random()*E.length)]}exports.TREE_LEVELS=_;exports.TREE_MAX_LEVEL=9;exports.TREE_XP_PER_LEVEL=50;exports.calcTreeFromPoints=l;exports.pickTreeWhisper=R;
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/utils/treeLevels.js.map
@@ -1,68 +1,2 @@
"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;
"use strict";const a=require("./treeLevels.js"),i=[{id:"classic",name:"稳糖灵树",tagline:"经典路线,从种子到圆满",stages:a.TREE_LEVELS.map(e=>e.emoji)},{id:"bonsai",name:"雅韵盆景",tagline:"小巧精致,书桌旁的温柔陪伴",stages:["🫘","🌱","🪴","🎋","🪴","🌳","🎍","🌸","🏵️","🏆"]},{id:"pine",name:"苍劲青松",tagline:"挺拔向上,越记越稳",stages:["🫘","🌱","🌿","🌲","🌲","🌲","⛰️","🌲","🏔️","🏆"]},{id:"sakura",name:"樱花小树",tagline:"坚持打卡,枝头渐开",stages:["🫘","🌱","🌿","🌳","🌸","🌸","🌸","🌸","🌺","🏆"]},{id:"ginkgo",name:"金秋银杏",tagline:"叶色渐变,见证每一天",stages:["🫘","🌱","🍃","🌳","🍂","🍂","🌳","🍁","🌟","🏆"]}],c=40;function E(e){return i.find(n=>n.id===e)||i[0]}function _(e,n){const t=E(e),s=Math.min(a.TREE_MAX_LEVEL,Math.max(0,Number(n)||0));return t.stages[s]||t.stages[0]||"🌱"}function r(e){const n=E(e);return a.TREE_LEVELS.map((t,s)=>{const o=s*a.TREE_XP_PER_LEVEL,m=s===0?0:Math.max(1,Math.ceil(o/c));return{level:s,name:t.name,emoji:n.stages[s]||t.emoji,desc:t.desc,xpTotal:o,daysHint:m}})}function L(){const e=a.TREE_MAX_LEVEL*a.TREE_XP_PER_LEVEL;return Math.max(1,Math.ceil(e/c))}exports.TREE_SPECIES_LIST=i;exports.estimateDaysToMaxLevel=L;exports.getTreeSpecies=E;exports.speciesEmojiAtLevel=_;exports.speciesGrowthRoadmap=r;
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/utils/treeSpecies.js.map
@@ -1,310 +1,2 @@
"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;
"use strict";const l={:0,"":0,:1,:1,:1,:2,:2,:2,:3,:3,:4,:4,:5,:5,:6,:6,:7,:7,:8,:8,:9,:9},p={:10,:10,:100,:100,:1e3,:1e3,:1e4,亿:1e8};function h(r){let i=0,t=0,e=0,n=!1,s=0,o=!1;for(const c of r)if(l[c]!==void 0)e=l[c],e===0&&(o=!0);else if(p[c]!==void 0){n=!0;const f=p[c];s=f,o=!1,f>=1e4?(t=(t+e)*f,i+=t,t=0):(e===0&&(e=1),t+=e*f),e=0}if(!n&&r.length>1){let c="";for(const f of r)l[f]!==void 0&&(c+=l[f]);if(c)return Number(c)}return e>0&&s>=100&&!o&&(e=e*(s/10)),i+t+e}function b(r){if(r.includes("点")){const i=r.split("点"),t=i[0],e=i.slice(1).join(""),n=t?h(t):0;let s="";for(const o of e)l[o]!==void 0&&(s+=l[o]);return s?+`${n}.${s}`:n}return h(r)}function S(r){for(const i of r)if(l[i]!==void 0||p[i]!==void 0)return!0;return!1}function d(r){return String(r||"").replace(/[零〇一壹幺二贰两三叁四肆五伍六陆七柒八捌九玖十拾百佰千仟万亿点]+/g,t=>{if(!S(t))return t;const e=b(t);return Number.isFinite(e)?String(e):t})}function u(r,i){for(const t of i){const e=r.indexOf(t);if(e>=0){const s=r.slice(e+t.length).match(/-?\d+(?:\.\d+)?/);if(s)return s[0]}}return""}function _(r,i,t=[]){for(const e of i){const n=r.indexOf(e);if(n>=0){let s=r.slice(n+e.length);s=s.replace(/^[是为吃了喝了吃的喝的有打了用了::,,、。\s]+/,"");let o=s.length;const c=s.match(/[,。.;!?\n]/);c&&c.index<o&&(o=c.index);for(const a of t){const g=s.indexOf(a);g>=0&&g<o&&(o=g)}const f=s.slice(0,o).trim();if(f)return f}}return""}function y(r){const i=d(r),t={},e=u(i,["空腹"]),n=u(i,["餐后","饭后"]),s=u(i,["其他","随机","睡前","凌晨","夜间","晚上"]);if(e&&(t.fasting_blood_sugar=e),n&&(t.postprandial_blood_sugar=n),s&&(t.other_blood_sugar=s),!e&&!n&&!s){const o=i.match(/\d+(?:\.\d+)?/g);o&&o.length===1&&(t.other_blood_sugar=o[0])}return t}function E(r){const i=d(r),t={};let e=u(i,["高压","收缩压","收缩"]),n=u(i,["低压","舒张压","舒张"]);if(!e||!n){const c=(i.match(/\d{2,3}/g)||[]).map(Number).filter(f=>f>=30&&f<=300);!e&&!n&&c.length>=2?(e=String(c[0]),n=String(c[1])):!e&&c.length===1&&c[0]>=90&&(e=String(c[0]))}if(e&&n&&Number(e)<Number(n)){const c=e;e=n,n=c}e&&(t.systolic_pressure=e),n&&(t.diastolic_pressure=n);const s=_(r,["胰岛素"],["高压","低压","血压","西药","备注"]);s&&(t.insulin=s);const o=_(r,["西药","降糖药","口服药"],["胰岛素","高压","低压","血压","备注"]);return o&&(t.western_medicine=o),t}const x=[{keys:["早餐","早饭","早上","早点","早晨"],field:"breakfast_foods"},{keys:["午餐","午饭","中午"],field:"lunch_foods"},{keys:["晚餐","晚饭","晚上","夜宵","夜里"],field:"dinner_foods"}];function m(r){return String(r||"").replace(/^[是为吃了吃的喝了喝的有::,,、。\s]+/,"").replace(/[。.\s]+$/,"").trim()}function N(r){const i=String(r||"").trim(),t={},e=[];if(x.forEach(s=>{for(const o of s.keys){const c=i.indexOf(o);if(c>=0){e.push({idx:c,field:s.field,klen:o.length});break}}}),!e.length)return i&&(t.note=i),t;e.sort((s,o)=>s.idx-o.idx),e.forEach((s,o)=>{const c=s.idx+s.klen,f=o+1<e.length?e[o+1].idx:i.length,a=m(i.slice(c,f));a&&!t[s.field]&&(t[s.field]=a)});const n=m(i.slice(0,e[0].idx));return n&&(t.note=n),t}const k=["今天","我","做了","做","进行了","进行","运动了","运动","锻炼了","锻炼","了","大概","左右","持续","差不多","一共","总共","的","走了","打了","跑了"];function I(r){const i=d(r),t={},e=i.match(/(\d+(?:\.\d+)?)\s*(个小时|小时|钟头|时|分钟|分)/);if(e){const s=parseFloat(e[1]),o=/个小时|小时|钟头|时/.test(e[2]);t.duration=String(Math.round(o?s*60:s))}/高强度|剧烈|很累|大汗|气喘/.test(i)?t.intensity=3:/中强度|中等强度|中等|有点累|微微出汗|微汗/.test(i)?t.intensity=2:/低强度|轻松|轻微|溜达|缓慢/.test(i)&&(t.intensity=1);let n=i;return e&&(n=n.replace(e[0],"")),n=n.replace(/高强度|中强度|中等强度|低强度|剧烈|很累|大汗|气喘|有点累|微微出汗|微汗|轻松|轻微|缓慢/g,"").replace(/\d+(?:\.\d+)?/g,"").replace(/[,。.;、\s]+/g,""),k.forEach(s=>{n=n.split(s).join("")}),n=n.trim(),n&&n.length<=20&&(t.exercise_type=n),t}const L={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:"强度"},T={1:"低强度",2:"中强度",3:"高强度"};function A(r){const i=[];return Object.keys(r).forEach(t=>{const e=L[t]||t;let n=r[t];t==="intensity"&&(n=T[n]||n),i.push(`${e}${n}`)}),i.join(" · ")}exports.normalizeNumbers=d;exports.parseBloodPressure=E;exports.parseDiet=N;exports.parseExercise=I;exports.parseGlucose=y;exports.summarizeParsed=A;
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/utils/voiceParse.js.map
@@ -1,238 +1,2 @@
"use strict";
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => {
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
const common_vendor = require("../../common/vendor.js");
class AudioPool {
constructor(src, size, volume = 1) {
__publicField(this, "list", []);
__publicField(this, "cursor", 0);
__publicField(this, "warmedUp", false);
__publicField(this, "targetVolume", 1);
this.src = src;
this.size = size;
this.targetVolume = volume;
}
create() {
for (let i = 0; i < this.size; i++) {
const ctx = common_vendor.index.createInnerAudioContext();
ctx.src = this.src;
ctx.obeyMuteSwitch = false;
ctx.autoplay = false;
ctx.volume = this.targetVolume;
this.list.push(ctx);
}
}
setVolume(volume) {
this.targetVolume = Math.max(0, Math.min(1, volume));
this.list.forEach((ctx) => {
try {
ctx.volume = this.targetVolume;
} catch (_) {
}
});
}
/**
* 预热短暂播放,让音频文件下载/解码到内存
* 真正首次 play 不会再卡冷启动
*/
warmUp() {
if (this.warmedUp)
return;
if (this.list.length === 0)
this.create();
this.list.forEach((ctx) => {
try {
ctx.volume = 0;
ctx.play();
setTimeout(() => {
try {
ctx.stop();
ctx.volume = this.targetVolume;
} catch (_) {
}
}, 60);
} catch (_) {
}
});
this.warmedUp = true;
}
play() {
if (this.list.length === 0) {
this.create();
this.warmUp();
}
const ctx = this.list[this.cursor];
this.cursor = (this.cursor + 1) % this.list.length;
try {
this.list.forEach((c) => {
try {
c.stop();
} catch (_) {
}
});
ctx.play();
} catch (_) {
try {
ctx.play();
} catch (__) {
}
}
}
destroy() {
this.list.forEach((ctx) => {
var _a;
try {
(_a = ctx.destroy) == null ? void 0 : _a.call(ctx);
} catch (_) {
}
});
this.list = [];
this.warmedUp = false;
}
}
const DEFAULT_CLICK_SRC = "https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/uploads/file/20260526/20260526105128557ef8669.mp3";
function useMetronome(options = {}) {
const {
initialBpm = 80,
bpmMin = 40,
bpmMax = 240,
clickSrc = DEFAULT_CLICK_SRC,
accentSrc,
poolSize = 4,
volume = 1,
silent = false,
onBeat
} = options;
const bpm = common_vendor.ref(initialBpm);
const isPlaying = common_vendor.ref(false);
const beatIndex = common_vendor.ref(0);
const isAccent = common_vendor.ref(false);
const accentEveryRef = common_vendor.ref(options.accentEvery ?? 4);
const intervalMs = common_vendor.computed(() => 6e4 / bpm.value);
let clickPool = null;
let accentPool = null;
let timer = null;
let nextTickAt = 0;
const ensureAudio = () => {
if (silent)
return;
if (!clickPool) {
clickPool = new AudioPool(clickSrc, poolSize, volume);
clickPool.warmUp();
}
if (accentSrc && !accentPool) {
accentPool = new AudioPool(accentSrc, Math.max(2, Math.ceil(poolSize / 2)), volume);
accentPool.warmUp();
}
};
const playSound = (accent) => {
if (accent && accentPool) {
accentPool.play();
} else if (clickPool) {
clickPool.play();
}
};
const tick = () => {
if (!isPlaying.value)
return;
const every = Math.max(1, accentEveryRef.value);
const accent = beatIndex.value % every === 0;
isAccent.value = accent;
playSound(accent);
onBeat == null ? void 0 : onBeat(beatIndex.value, accent);
beatIndex.value++;
nextTickAt += intervalMs.value;
const nextDelay = Math.max(0, nextTickAt - Date.now());
timer = setTimeout(tick, nextDelay);
};
const start = () => {
if (isPlaying.value)
return;
ensureAudio();
isPlaying.value = true;
beatIndex.value = 0;
nextTickAt = Date.now();
tick();
};
const stop = () => {
isPlaying.value = false;
if (timer) {
clearTimeout(timer);
timer = null;
}
};
const setBpm = (val) => {
bpm.value = Math.max(bpmMin, Math.min(bpmMax, Math.round(val)));
};
const setAccentEvery = (n) => {
accentEveryRef.value = Math.max(1, Math.min(16, Math.round(n)));
beatIndex.value = 0;
};
const updateAudioSrc = (newClickSrc, newAccentSrc) => {
const wasPlaying = isPlaying.value;
if (wasPlaying)
stop();
clickPool == null ? void 0 : clickPool.destroy();
accentPool == null ? void 0 : accentPool.destroy();
clickPool = null;
accentPool = null;
clickPool = new AudioPool(newClickSrc, poolSize);
if (newAccentSrc) {
accentPool = new AudioPool(newAccentSrc, Math.max(2, Math.ceil(poolSize / 2)));
}
const previewCtx = common_vendor.index.createInnerAudioContext();
previewCtx.src = newClickSrc;
previewCtx.obeyMuteSwitch = false;
previewCtx.volume = 0.4;
try {
previewCtx.play();
setTimeout(() => {
var _a;
try {
(_a = previewCtx.destroy) == null ? void 0 : _a.call(previewCtx);
} catch (_) {
}
}, 500);
} catch (_) {
}
if (wasPlaying)
start();
};
const preload = () => {
try {
common_vendor.index.setInnerAudioOption({
obeyMuteSwitch: false,
mixWithOther: true
});
} catch (_) {
}
ensureAudio();
};
common_vendor.onUnmounted(() => {
stop();
clickPool == null ? void 0 : clickPool.destroy();
accentPool == null ? void 0 : accentPool.destroy();
clickPool = null;
accentPool = null;
});
return {
bpm,
isPlaying,
beatIndex,
isAccent,
intervalMs,
accentEvery: accentEveryRef,
start,
stop,
setBpm,
setAccentEvery,
updateAudioSrc,
preload
};
}
exports.useMetronome = useMetronome;
"use strict";var R=Object.defineProperty;var B=(l,t,e)=>t in l?R(l,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):l[t]=e;var m=(l,t,e)=>(B(l,typeof t!="symbol"?t+"":t,e),e);const i=require("../../common/vendor.js");class d{constructor(t,e,o=1){m(this,"list",[]);m(this,"cursor",0);m(this,"warmedUp",!1);m(this,"targetVolume",1);this.src=t,this.size=e,this.targetVolume=o}create(){for(let t=0;t<this.size;t++){const e=i.index.createInnerAudioContext();e.src=this.src,e.obeyMuteSwitch=!1,e.autoplay=!1,e.volume=this.targetVolume,this.list.push(e)}}setVolume(t){this.targetVolume=Math.max(0,Math.min(1,t)),this.list.forEach(e=>{try{e.volume=this.targetVolume}catch{}})}warmUp(){this.warmedUp||(this.list.length===0&&this.create(),this.list.forEach(t=>{try{t.volume=0,t.play(),setTimeout(()=>{try{t.stop(),t.volume=this.targetVolume}catch{}},60)}catch{}}),this.warmedUp=!0)}play(){this.list.length===0&&(this.create(),this.warmUp());const t=this.list[this.cursor];this.cursor=(this.cursor+1)%this.list.length;try{this.list.forEach(e=>{try{e.stop()}catch{}}),t.play()}catch{try{t.play()}catch{}}}destroy(){this.list.forEach(t=>{var e;try{(e=t.destroy)==null||e.call(t)}catch{}}),this.list=[],this.warmedUp=!1}}const F="https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/uploads/file/20260526/20260526105128557ef8669.mp3";function K(l={}){const{initialBpm:t=80,bpmMin:e=40,bpmMax:o=240,clickSrc:z=F,accentSrc:U,poolSize:f=4,volume:A=1,silent:I=!1,onBeat:v}=l,M=i.ref(t),u=i.ref(!1),c=i.ref(0),E=i.ref(!1),w=i.ref(l.accentEvery??4),b=i.computed(()=>6e4/M.value);let s=null,a=null,y=null,g=0;const S=()=>{I||(s||(s=new d(z,f,A),s.warmUp()),U&&!a&&(a=new d(U,Math.max(2,Math.ceil(f/2)),A),a.warmUp()))},D=r=>{r&&a?a.play():s&&s.play()},V=()=>{if(!u.value)return;const r=Math.max(1,w.value),h=c.value%r===0;E.value=h,D(h),v==null||v(c.value,h),c.value++,g+=b.value;const p=Math.max(0,g-Date.now());y=setTimeout(V,p)},T=()=>{u.value||(S(),u.value=!0,c.value=0,g=Date.now(),V())},_=()=>{u.value=!1,y&&(clearTimeout(y),y=null)},C=r=>{M.value=Math.max(e,Math.min(o,Math.round(r)))},q=r=>{w.value=Math.max(1,Math.min(16,Math.round(r))),c.value=0},L=(r,h)=>{const p=u.value;p&&_(),s==null||s.destroy(),a==null||a.destroy(),s=null,a=null,s=new d(r,f),h&&(a=new d(h,Math.max(2,Math.ceil(f/2))));const n=i.index.createInnerAudioContext();n.src=r,n.obeyMuteSwitch=!1,n.volume=.4;try{n.play(),setTimeout(()=>{var x;try{(x=n.destroy)==null||x.call(n)}catch{}},500)}catch{}p&&T()},O=()=>{try{i.index.setInnerAudioOption({obeyMuteSwitch:!1,mixWithOther:!0})}catch{}S()};return i.onUnmounted(()=>{_(),s==null||s.destroy(),a==null||a.destroy(),s=null,a=null}),{bpm:M,isPlaying:u,beatIndex:c,isAccent:E,intervalMs:b,accentEvery:w,start:T,stop:_,setBpm:C,setAccentEvery:q,updateAudioSrc:L,preload:O}}exports.useMetronome=K;
//# sourceMappingURL=../../../.sourcemap/mp-weixin/training/hooks/useMetronome.js.map
File diff suppressed because one or more lines are too long
@@ -1,15 +1 @@
.crush-canvas-container.data-v-e11c993f {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 10;
}
.crush-canvas.data-v-e11c993f {
display: block;
width: 100%;
height: 100%;
background: transparent;
}
.crush-canvas-container.data-v-e11c993f{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:10}.crush-canvas.data-v-e11c993f{display:block;width:100%;height:100%;background:transparent}
File diff suppressed because one or more lines are too long
@@ -1,15 +1 @@
.walker-canvas-container.data-v-782dd882 {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.walker-canvas.data-v-782dd882 {
display: block;
width: 100%;
height: 100%;
background: transparent; /* Pure transparent to blend into page */
}
.walker-canvas-container.data-v-782dd882{width:100%;height:100%;position:absolute;top:0;left:0;right:0;bottom:0}.walker-canvas.data-v-782dd882{display:block;width:100%;height:100%;background:transparent}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,150 +1,2 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const training_hooks_useMetronome = require("../hooks/useMetronome.js");
if (!Math) {
WalkerCanvas();
}
const WalkerCanvas = () => "./components/walker-canvas.js";
const _sfc_main = /* @__PURE__ */ common_vendor.defineComponent({
__name: "metronome",
setup(__props) {
const LOOP_PRESETS = [
{ id: "slow", bpm: 110, label: "慢走", desc: "热身 · 恢复" },
{ id: "normal", bpm: 130, label: "健走", desc: "日常 · 通勤" },
{ id: "brisk", bpm: 150, label: "快走", desc: "提速 · 燃脂" }
];
const customExpanded = common_vendor.ref(false);
const currentLoop = common_vendor.ref("normal");
const currentSound = common_vendor.ref("crisp");
const walkerCanvasRef = common_vendor.ref(null);
const SOUND_PRESETS = {
crisp: {
normal: "https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/uploads/file/20260527/202605271539371ebe13672.mp3",
accent: "https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/uploads/file/20260527/202605271539376ff628472.mp3"
},
wood: {
normal: "https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/uploads/file/20260526/202605261051282a0a94508.mp3",
accent: "https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/uploads/file/20260526/202605261051282a0a94508.mp3"
}
};
const fg = training_hooks_useMetronome.useMetronome({
initialBpm: 130,
accentEvery: 2,
clickSrc: SOUND_PRESETS.crisp.normal,
accentSrc: SOUND_PRESETS.crisp.accent,
poolSize: 4,
// 【完美音画同步核心】
onBeat: (index, isAccent) => {
if (walkerCanvasRef.value && walkerCanvasRef.value.triggerBeat) {
walkerCanvasRef.value.triggerBeat();
}
}
});
const isPlaying = common_vendor.computed(() => fg.isPlaying.value);
const currentBpm = common_vendor.computed(() => fg.bpm.value);
const customAccent = common_vendor.computed(() => fg.accentEvery.value);
const intervalMs = common_vendor.computed(() => 6e4 / fg.bpm.value);
const onCenterTap = () => {
if (fg.isPlaying.value) {
fg.stop();
common_vendor.index.setKeepScreenOn({ keepScreenOn: false });
} else {
fg.start();
common_vendor.index.setKeepScreenOn({ keepScreenOn: true });
}
};
const onPresetTap = (id) => {
const preset = LOOP_PRESETS.find((p) => p.id === id);
if (!preset)
return;
currentLoop.value = id;
fg.setBpm(preset.bpm);
if (!fg.isPlaying.value) {
fg.start();
common_vendor.index.setKeepScreenOn({ keepScreenOn: true });
}
};
const onToggleCustom = () => {
customExpanded.value = !customExpanded.value;
if (customExpanded.value) {
fg.preload();
} else {
const matched = LOOP_PRESETS.find((p) => p.bpm === fg.bpm.value);
currentLoop.value = matched ? matched.id : null;
}
};
const onBpmDelta = (delta) => {
fg.setBpm(fg.bpm.value + delta);
currentLoop.value = null;
};
const onAccentSelect = (n) => {
fg.setAccentEvery(n);
};
const onSoundSelect = (sound) => {
currentSound.value = sound;
const preset = SOUND_PRESETS[sound];
fg.updateAudioSrc(preset.normal, preset.accent);
};
const rootStyle = common_vendor.computed(() => ({
"--beat-duration": `${intervalMs.value}ms`
}));
common_vendor.onShow(() => {
fg.preload();
});
function cleanupMetronome() {
fg.stop();
common_vendor.index.setKeepScreenOn({ keepScreenOn: false });
}
common_vendor.onHide(cleanupMetronome);
common_vendor.onUnload(cleanupMetronome);
common_vendor.onUnmounted(cleanupMetronome);
return (_ctx, _cache) => {
return common_vendor.e({
a: common_vendor.sr(walkerCanvasRef, "a75aa31b-0", {
"k": "walkerCanvasRef"
}),
b: common_vendor.o(onCenterTap, "0f"),
c: common_vendor.p({
bpm: currentBpm.value,
["is-playing"]: isPlaying.value
}),
d: currentSound.value === "crisp" ? 1 : "",
e: common_vendor.o(($event) => onSoundSelect("crisp"), "8c"),
f: currentSound.value === "wood" ? 1 : "",
g: common_vendor.o(($event) => onSoundSelect("wood"), "43"),
h: common_vendor.f(LOOP_PRESETS, (p, k0, i0) => {
return {
a: common_vendor.t(p.label),
b: common_vendor.t(p.bpm),
c: common_vendor.t(p.desc),
d: p.id,
e: currentLoop.value === p.id ? 1 : "",
f: common_vendor.o(($event) => onPresetTap(p.id), p.id)
};
}),
i: common_vendor.t(customExpanded.value ? "✕ 收起自定义" : "⚙ 自定义节奏"),
j: common_vendor.o(onToggleCustom, "29"),
k: customExpanded.value
}, customExpanded.value ? {
l: common_vendor.o(($event) => onBpmDelta(-5), "71"),
m: common_vendor.o(($event) => onBpmDelta(-1), "5b"),
n: common_vendor.t(currentBpm.value),
o: common_vendor.o(($event) => onBpmDelta(1), "90"),
p: common_vendor.o(($event) => onBpmDelta(5), "b0"),
q: common_vendor.f([2, 3, 4], (n, k0, i0) => {
return {
a: common_vendor.t(n),
b: n,
c: customAccent.value === n ? 1 : "",
d: common_vendor.o(($event) => onAccentSelect(n), n)
};
})
} : {}, {
r: common_vendor.s(rootStyle.value)
});
};
}
});
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-a75aa31b"]]);
wx.createPage(MiniProgramPage);
"use strict";const e=require("../../common/vendor.js"),x=require("../hooks/useMetronome.js");Math||z();const z=()=>"./components/walker-canvas.js",$=e.defineComponent({__name:"metronome",setup(E){const u=[{id:"slow",bpm:110,label:"慢走",desc:"热身 · 恢复"},{id:"normal",bpm:130,label:"健走",desc:"日常 · 通勤"},{id:"brisk",bpm:150,label:"快走",desc:"提速 · 燃脂"}],c=e.ref(!1),s=e.ref("normal"),p=e.ref("crisp"),l=e.ref(null),i={crisp:{normal:"https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/uploads/file/20260527/202605271539371ebe13672.mp3",accent:"https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/uploads/file/20260527/202605271539376ff628472.mp3"},wood:{normal:"https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/uploads/file/20260526/202605261051282a0a94508.mp3",accent:"https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/uploads/file/20260526/202605261051282a0a94508.mp3"}},o=x.useMetronome({initialBpm:130,accentEvery:2,clickSrc:i.crisp.normal,accentSrc:i.crisp.accent,poolSize:4,onBeat:(t,a)=>{l.value&&l.value.triggerBeat&&l.value.triggerBeat()}}),f=e.computed(()=>o.isPlaying.value),d=e.computed(()=>o.bpm.value),g=e.computed(()=>o.accentEvery.value),b=e.computed(()=>6e4/o.bpm.value),S=()=>{o.isPlaying.value?(o.stop(),e.index.setKeepScreenOn({keepScreenOn:!1})):(o.start(),e.index.setKeepScreenOn({keepScreenOn:!0}))},_=t=>{const a=u.find(n=>n.id===t);a&&(s.value=t,o.setBpm(a.bpm),o.isPlaying.value||(o.start(),e.index.setKeepScreenOn({keepScreenOn:!0})))},k=()=>{if(c.value=!c.value,c.value)o.preload();else{const t=u.find(a=>a.bpm===o.bpm.value);s.value=t?t.id:null}},r=t=>{o.setBpm(o.bpm.value+t),s.value=null},h=t=>{o.setAccentEvery(t)},v=t=>{p.value=t;const a=i[t];o.updateAudioSrc(a.normal,a.accent)},y=e.computed(()=>({"--beat-duration":`${b.value}ms`}));e.onShow(()=>{o.preload()});function m(){o.stop(),e.index.setKeepScreenOn({keepScreenOn:!1})}return e.onHide(m),e.onUnload(m),e.onUnmounted(m),(t,a)=>e.e({a:e.sr(l,"a75aa31b-0",{k:"walkerCanvasRef"}),b:e.o(S,"0f"),c:e.p({bpm:d.value,"is-playing":f.value}),d:p.value==="crisp"?1:"",e:e.o(n=>v("crisp"),"8c"),f:p.value==="wood"?1:"",g:e.o(n=>v("wood"),"43"),h:e.f(u,(n,O,P)=>({a:e.t(n.label),b:e.t(n.bpm),c:e.t(n.desc),d:n.id,e:s.value===n.id?1:"",f:e.o(w=>_(n.id),n.id)})),i:e.t(c.value?"✕ 收起自定义":"⚙ 自定义节奏"),j:e.o(k,"29"),k:c.value},c.value?{l:e.o(n=>r(-5),"71"),m:e.o(n=>r(-1),"5b"),n:e.t(d.value),o:e.o(n=>r(1),"90"),p:e.o(n=>r(5),"b0"),q:e.f([2,3,4],(n,O,P)=>({a:e.t(n),b:n,c:g.value===n?1:"",d:e.o(w=>h(n),n)}))}:{},{r:e.s(y.value)})}}),B=e._export_sfc($,[["__scopeId","data-v-a75aa31b"]]);wx.createPage(B);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/training/pages/metronome.js.map
@@ -1,311 +1 @@
/**
* 这里是uni-app内置的常用样式变量
*
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
*
*/
/**
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
*
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
*/
/* 颜色变量 */
/* 行为相关颜色 */
/* 文字基本颜色 */
/* 背景颜色 */
/* 边框颜色 */
/* 尺寸变量 */
/* 文字尺寸 */
/* 图片尺寸 */
/* Border Radius */
/* 水平间距 */
/* 垂直间距 */
/* 透明度 */
/* 文章场景相关 */
/* ============================================================
* 设计 token —— 浅色清爽
* ============================================================ */
/* ============================================================
* 页面容器
* ============================================================ */
.page.data-v-a75aa31b {
position: relative;
min-height: 100vh;
box-sizing: border-box;
padding: 30rpx 28rpx 50rpx;
background: #f8fafc;
display: flex;
flex-direction: column;
align-items: center;
gap: 20rpx;
color: #0f172a;
}
/* ============================================================
* 统一运动律动视区 (Canvas)
* ============================================================ */
.stage-canvas-wrapper.data-v-a75aa31b {
width: 100vw;
margin-left: -28rpx;
margin-right: -28rpx;
flex-shrink: 0;
/* 防止被压缩 */
height: 600rpx;
/* 固定高度,不随内容变化 */
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 24rpx;
position: relative;
}
/* ============================================================
* 音色选择器
* ============================================================ */
.sound-selector.data-v-a75aa31b {
width: 100%;
display: flex;
gap: 14rpx;
margin-bottom: 20rpx;
}
.sound-option.data-v-a75aa31b {
flex: 1;
background: #ffffff;
border: 2rpx solid rgba(15, 23, 42, 0.06);
border-radius: 24rpx;
padding: 20rpx 16rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 10rpx;
box-shadow: 0 4rpx 12rpx rgba(15, 23, 42, 0.04);
transition: all 0.18s;
}
.sound-option .sound-icon.data-v-a75aa31b {
font-size: 32rpx;
}
.sound-option .sound-label.data-v-a75aa31b {
font-size: 26rpx;
font-weight: 600;
color: #475569;
letter-spacing: 1rpx;
}
.sound-option.data-v-a75aa31b:active {
transform: scale(0.97);
}
.sound-option.active.data-v-a75aa31b {
background: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%);
border-color: #10b981;
box-shadow: 0 8rpx 20rpx rgba(16, 185, 129, 0.18), inset 0 0 0 2rpx rgba(16, 185, 129, 0.4);
}
.sound-option.active .sound-label.data-v-a75aa31b {
color: #047857;
}
/* ============================================================
* 三档位推荐
* ============================================================ */
.presets.data-v-a75aa31b {
width: 100%;
display: flex;
gap: 14rpx;
}
.preset.data-v-a75aa31b {
flex: 1;
background: #ffffff;
border: 2rpx solid rgba(15, 23, 42, 0.06);
border-radius: 24rpx;
padding: 26rpx 8rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 8rpx;
box-shadow: 0 4rpx 12rpx rgba(15, 23, 42, 0.04);
transition: all 0.18s;
}
.preset .preset-name.data-v-a75aa31b {
font-size: 28rpx;
font-weight: 700;
color: #0f172a;
letter-spacing: 2rpx;
}
.preset .preset-bpm.data-v-a75aa31b {
font-size: 22rpx;
color: #475569;
font-weight: 600;
font-variant-numeric: tabular-nums;
}
.preset .preset-desc.data-v-a75aa31b {
font-size: 18rpx;
color: #94a3b8;
letter-spacing: 1rpx;
margin-top: 2rpx;
}
.preset.data-v-a75aa31b:active {
transform: scale(0.97);
}
.preset.active.data-v-a75aa31b {
background: #d1fae5;
border-color: #10b981;
box-shadow: 0 8rpx 20rpx rgba(16, 185, 129, 0.18), inset 0 0 0 2rpx rgba(16, 185, 129, 0.4);
}
.preset.active .preset-name.data-v-a75aa31b {
color: #047857;
}
.preset.active .preset-bpm.data-v-a75aa31b {
color: #047857;
}
.preset.active .preset-desc.data-v-a75aa31b {
color: #10b981;
}
/* ============================================================
* 自定义节奏区域(标题栏 + 折叠内容)
* ============================================================ */
.custom-section.data-v-a75aa31b {
width: 100%;
margin-top: 12rpx;
}
.custom-header.data-v-a75aa31b {
width: 100%;
padding: 16rpx 6rpx;
display: flex;
align-items: center;
justify-content: center;
}
.custom-header .custom-title.data-v-a75aa31b {
font-size: 22rpx;
color: #94a3b8;
letter-spacing: 0.5rpx;
}
.custom-header .custom-title.data-v-a75aa31b:active {
color: #475569;
}
.custom-content.data-v-a75aa31b {
width: 100%;
background: #ffffff;
border: 2rpx solid rgba(245, 158, 11, 0.25);
border-radius: 24rpx;
box-shadow: 0 6rpx 18rpx rgba(245, 158, 11, 0.08);
padding: 20rpx 24rpx 22rpx;
display: flex;
flex-direction: column;
gap: 16rpx;
margin-top: 8rpx;
}
.custom-warn.data-v-a75aa31b {
display: flex;
align-items: center;
gap: 8rpx;
margin-bottom: 4rpx;
}
.custom-warn .warn-dot.data-v-a75aa31b {
font-size: 18rpx;
color: #f59e0b;
}
.custom-warn .warn-text.data-v-a75aa31b {
font-size: 22rpx;
color: #b45309;
letter-spacing: 0.5rpx;
font-weight: 600;
}
.row.data-v-a75aa31b {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12rpx;
}
.row .row-label.data-v-a75aa31b {
font-size: 24rpx;
color: #475569;
font-weight: 600;
letter-spacing: 1rpx;
flex-shrink: 0;
width: 70rpx;
}
.bpm-stepper.data-v-a75aa31b {
display: flex;
align-items: center;
gap: 6rpx;
flex: 1;
justify-content: flex-end;
}
.bpm-stepper .step-btn.data-v-a75aa31b {
min-width: 56rpx;
height: 52rpx;
line-height: 52rpx;
text-align: center;
font-size: 22rpx;
color: #475569;
background: #f1f5f9;
border-radius: 10rpx;
font-weight: 600;
padding: 0 10rpx;
font-variant-numeric: tabular-nums;
}
.bpm-stepper .step-btn.data-v-a75aa31b:active {
background: #e2e8f0;
transform: scale(0.94);
}
.bpm-stepper .step-val.data-v-a75aa31b {
min-width: 84rpx;
height: 52rpx;
line-height: 52rpx;
text-align: center;
font-size: 30rpx;
font-weight: 800;
color: #0f172a;
font-variant-numeric: tabular-nums;
background: #fef3c7;
border-radius: 10rpx;
}
.meter-tabs.data-v-a75aa31b {
display: flex;
gap: 8rpx;
}
.meter-tabs .meter-tab.data-v-a75aa31b {
height: 52rpx;
line-height: 52rpx;
padding: 0 18rpx;
font-size: 22rpx;
color: #475569;
background: #f1f5f9;
border-radius: 10rpx;
font-weight: 600;
letter-spacing: 1rpx;
}
.meter-tabs .meter-tab.active.data-v-a75aa31b {
background: #f59e0b;
color: #fff;
box-shadow: 0 4rpx 10rpx rgba(245, 158, 11, 0.3);
}
.meter-tabs .meter-tab.data-v-a75aa31b:active {
transform: scale(0.94);
}
/* ============================================================
* 底部行(已废弃,保留样式以防引用)
* ============================================================ */
.bottom-row.data-v-a75aa31b {
width: 100%;
margin-top: 6rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 6rpx;
}
.bottom-link.data-v-a75aa31b {
font-size: 22rpx;
color: #94a3b8;
letter-spacing: 0.5rpx;
padding: 8rpx 4rpx;
}
.bottom-link.data-v-a75aa31b:active {
color: #475569;
}
.bottom-hint.data-v-a75aa31b {
font-size: 22rpx;
color: #94a3b8;
letter-spacing: 0.5rpx;
}
.page.data-v-a75aa31b{position:relative;min-height:100vh;box-sizing:border-box;padding:30rpx 28rpx 50rpx;background:#f8fafc;display:flex;flex-direction:column;align-items:center;gap:20rpx;color:#0f172a}.stage-canvas-wrapper.data-v-a75aa31b{width:100vw;margin-left:-28rpx;margin-right:-28rpx;flex-shrink:0;height:600rpx;display:flex;justify-content:center;align-items:center;margin-bottom:24rpx;position:relative}.sound-selector.data-v-a75aa31b{width:100%;display:flex;gap:14rpx;margin-bottom:20rpx}.sound-option.data-v-a75aa31b{flex:1;background:#fff;border:2rpx solid rgba(15,23,42,.06);border-radius:24rpx;padding:20rpx 16rpx;display:flex;align-items:center;justify-content:center;gap:10rpx;box-shadow:0 4rpx 12rpx rgba(15,23,42,.04);transition:all .18s}.sound-option .sound-icon.data-v-a75aa31b{font-size:32rpx}.sound-option .sound-label.data-v-a75aa31b{font-size:26rpx;font-weight:600;color:#475569;letter-spacing:1rpx}.sound-option.data-v-a75aa31b:active{transform:scale(.97)}.sound-option.active.data-v-a75aa31b{background:linear-gradient(135deg,#d1fae5,#a7f3d0);border-color:#10b981;box-shadow:0 8rpx 20rpx rgba(16,185,129,.18),inset 0 0 0 2rpx rgba(16,185,129,.4)}.sound-option.active .sound-label.data-v-a75aa31b{color:#047857}.presets.data-v-a75aa31b{width:100%;display:flex;gap:14rpx}.preset.data-v-a75aa31b{flex:1;background:#fff;border:2rpx solid rgba(15,23,42,.06);border-radius:24rpx;padding:26rpx 8rpx;display:flex;flex-direction:column;align-items:center;gap:8rpx;box-shadow:0 4rpx 12rpx rgba(15,23,42,.04);transition:all .18s}.preset .preset-name.data-v-a75aa31b{font-size:28rpx;font-weight:700;color:#0f172a;letter-spacing:2rpx}.preset .preset-bpm.data-v-a75aa31b{font-size:22rpx;color:#475569;font-weight:600;font-variant-numeric:tabular-nums}.preset .preset-desc.data-v-a75aa31b{font-size:18rpx;color:#94a3b8;letter-spacing:1rpx;margin-top:2rpx}.preset.data-v-a75aa31b:active{transform:scale(.97)}.preset.active.data-v-a75aa31b{background:#d1fae5;border-color:#10b981;box-shadow:0 8rpx 20rpx rgba(16,185,129,.18),inset 0 0 0 2rpx rgba(16,185,129,.4)}.preset.active .preset-name.data-v-a75aa31b,.preset.active .preset-bpm.data-v-a75aa31b{color:#047857}.preset.active .preset-desc.data-v-a75aa31b{color:#10b981}.custom-section.data-v-a75aa31b{width:100%;margin-top:12rpx}.custom-header.data-v-a75aa31b{width:100%;padding:16rpx 6rpx;display:flex;align-items:center;justify-content:center}.custom-header .custom-title.data-v-a75aa31b{font-size:22rpx;color:#94a3b8;letter-spacing:.5rpx}.custom-header .custom-title.data-v-a75aa31b:active{color:#475569}.custom-content.data-v-a75aa31b{width:100%;background:#fff;border:2rpx solid rgba(245,158,11,.25);border-radius:24rpx;box-shadow:0 6rpx 18rpx rgba(245,158,11,.08);padding:20rpx 24rpx 22rpx;display:flex;flex-direction:column;gap:16rpx;margin-top:8rpx}.custom-warn.data-v-a75aa31b{display:flex;align-items:center;gap:8rpx;margin-bottom:4rpx}.custom-warn .warn-dot.data-v-a75aa31b{font-size:18rpx;color:#f59e0b}.custom-warn .warn-text.data-v-a75aa31b{font-size:22rpx;color:#b45309;letter-spacing:.5rpx;font-weight:600}.row.data-v-a75aa31b{display:flex;align-items:center;justify-content:space-between;gap:12rpx}.row .row-label.data-v-a75aa31b{font-size:24rpx;color:#475569;font-weight:600;letter-spacing:1rpx;flex-shrink:0;width:70rpx}.bpm-stepper.data-v-a75aa31b{display:flex;align-items:center;gap:6rpx;flex:1;justify-content:flex-end}.bpm-stepper .step-btn.data-v-a75aa31b{min-width:56rpx;height:52rpx;line-height:52rpx;text-align:center;font-size:22rpx;color:#475569;background:#f1f5f9;border-radius:10rpx;font-weight:600;padding:0 10rpx;font-variant-numeric:tabular-nums}.bpm-stepper .step-btn.data-v-a75aa31b:active{background:#e2e8f0;transform:scale(.94)}.bpm-stepper .step-val.data-v-a75aa31b{min-width:84rpx;height:52rpx;line-height:52rpx;text-align:center;font-size:30rpx;font-weight:800;color:#0f172a;font-variant-numeric:tabular-nums;background:#fef3c7;border-radius:10rpx}.meter-tabs.data-v-a75aa31b{display:flex;gap:8rpx}.meter-tabs .meter-tab.data-v-a75aa31b{height:52rpx;line-height:52rpx;padding:0 18rpx;font-size:22rpx;color:#475569;background:#f1f5f9;border-radius:10rpx;font-weight:600;letter-spacing:1rpx}.meter-tabs .meter-tab.active.data-v-a75aa31b{background:#f59e0b;color:#fff;box-shadow:0 4rpx 10rpx rgba(245,158,11,.3)}.meter-tabs .meter-tab.data-v-a75aa31b:active{transform:scale(.94)}.bottom-row.data-v-a75aa31b{width:100%;margin-top:6rpx;display:flex;align-items:center;justify-content:space-between;padding:0 6rpx}.bottom-link.data-v-a75aa31b{font-size:22rpx;color:#94a3b8;letter-spacing:.5rpx;padding:8rpx 4rpx}.bottom-link.data-v-a75aa31b:active{color:#475569}.bottom-hint.data-v-a75aa31b{font-size:22rpx;color:#94a3b8;letter-spacing:.5rpx}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,73 +1,2 @@
"use strict";
const uni_modules_uniIcons_components_uniIcons_uniicons_file_vue = require("./uniicons_file_vue.js");
const common_vendor = require("../../../../common/vendor.js");
const getVal = (val) => {
const reg = /^[0-9]*$/g;
return typeof val === "number" || reg.test(val) ? val + "px" : val;
};
const _sfc_main = {
name: "UniIcons",
emits: ["click"],
props: {
type: {
type: String,
default: ""
},
color: {
type: String,
default: "#333333"
},
size: {
type: [Number, String],
default: 16
},
customPrefix: {
type: String,
default: ""
},
fontFamily: {
type: String,
default: ""
}
},
data() {
return {
icons: uni_modules_uniIcons_components_uniIcons_uniicons_file_vue.fontData
};
},
computed: {
unicode() {
let code = this.icons.find((v) => v.font_class === this.type);
if (code) {
return code.unicode;
}
return "";
},
iconSize() {
return getVal(this.size);
},
styleObj() {
if (this.fontFamily !== "") {
return `color: ${this.color}; font-size: ${this.iconSize}; font-family: ${this.fontFamily};`;
}
return `color: ${this.color}; font-size: ${this.iconSize};`;
}
},
methods: {
_onClick() {
this.$emit("click");
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: common_vendor.s($options.styleObj),
b: common_vendor.n("uniui-" + $props.type),
c: common_vendor.n($props.customPrefix),
d: common_vendor.n($props.customPrefix ? $props.type : ""),
e: common_vendor.o((...args) => $options._onClick && $options._onClick(...args), "dd")
};
}
const Component = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createComponent(Component);
"use strict";const r=require("./uniicons_file_vue.js"),e=require("../../../../common/vendor.js"),s=t=>typeof t=="number"||/^[0-9]*$/g.test(t)?t+"px":t,u={name:"UniIcons",emits:["click"],props:{type:{type:String,default:""},color:{type:String,default:"#333333"},size:{type:[Number,String],default:16},customPrefix:{type:String,default:""},fontFamily:{type:String,default:""}},data(){return{icons:r.fontData}},computed:{unicode(){let t=this.icons.find(i=>i.font_class===this.type);return t?t.unicode:""},iconSize(){return s(this.size)},styleObj(){return this.fontFamily!==""?`color: ${this.color}; font-size: ${this.iconSize}; font-family: ${this.fontFamily};`:`color: ${this.color}; font-size: ${this.iconSize};`}},methods:{_onClick(){this.$emit("click")}}};function f(t,i,n,m,d,o){return{a:e.s(o.styleObj),b:e.n("uniui-"+n.type),c:e.n(n.customPrefix),d:e.n(n.customPrefix?n.type:""),e:e.o((...c)=>o._onClick&&o._onClick(...c),"dd")}}const l=e._export_sfc(u,[["render",f]]);wx.createComponent(l);
//# sourceMappingURL=../../../../../.sourcemap/mp-weixin/uni_modules/uni-icons/components/uni-icons/uni-icons.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -155,7 +155,7 @@ export function firstVisitConversionOverview(params: FirstVisitConversionParams)
}
export interface FirstVisitRegistrationStatsParams {
time_type: 'today' | 'week' | 'month'
time_type: 'today' | 'yesterday' | 'week' | 'month'
dept_id?: number
assistant_id?: number
}
@@ -169,7 +169,7 @@ export function firstVisitRegistrationStatsOverview(params: FirstVisitRegistrati
}
export interface FirstVisitDoctorDashboardParams {
time_type: 'today' | 'week' | 'month' | 'custom'
time_type: 'today' | 'yesterday' | 'week' | 'month' | 'custom'
start_date?: string
end_date?: string
dept_id?: number
+40
View File
@@ -780,6 +780,46 @@ export function prescriptionLibraryEditAiReport(params: {
)
}
/** 读取已保存的诊单 AI 报告;不会触发模型生成。 */
export function diagnosisAiReports(params: { id: number }) {
return request.get<PrescriptionAiReportsResponse>(
{
url: '/tcm.diagnosis/aiReports',
params,
timeout: 30000
},
{ ignoreCancelToken: true }
)
}
/** 重新生成诊单双模型 AI 报告;POST 不自动重试。 */
export function diagnosisGenerateAiReports(params: { id: number }) {
return request.post<PrescriptionAiReportsResponse>(
{
url: '/tcm.diagnosis/generateAiReports',
params,
timeout: 210000
},
{ ignoreCancelToken: true, isOpenRetry: false }
)
}
/** 编辑一份已持久化的诊单 AI 报告。 */
export function diagnosisEditAiReport(params: {
id: number
report_id: number
content: string
}) {
return request.post<PrescriptionAiReportEditResponse>(
{
url: '/tcm.diagnosis/editAiReport',
params,
timeout: 30000
},
{ ignoreCancelToken: true, isOpenRetry: false }
)
}
// ========== 诊单待办事项(T5 ==========
/** 待办列表(按 diagnosis_id */
+278 -61
View File
@@ -1,13 +1,32 @@
<template>
<div ref="paperRef" class="rx-paper">
<div class="rx-title">{{ isInternal ? '药房联' : slipTitle }}</div>
<div class="rx-notice">
<span class="rx-notice-text">
服药前请核对姓名电话医生等信息以及服法医嘱等要点
</span>
<span class="rx-notice-meta">
<span>日期{{ dateText }}</span>
<span>编号{{ serialText }}</span>
{{
isInternal
? '服药前请核对姓名、电话、医生等信息以及服法、医嘱等要点'
: '服药前请核对姓名、电话、医生等信息以及医嘱等要点'
}}
</span>
<div class="rx-notice-meta">
<div class="rx-meta-item">
<span class="rx-meta-label">日期</span>
<span class="rx-meta-value">{{ dateText }}</span>
</div>
<div class="rx-meta-item">
<span class="rx-meta-label">处方编号</span>
<span class="rx-meta-value">{{ prescriptionSnText }}</span>
</div>
<div class="rx-meta-item">
<span class="rx-meta-label">流转编号挂号</span>
<span class="rx-meta-value">{{ appointmentFlowText }}</span>
</div>
<div v-if="businessOrderNoText" class="rx-meta-item">
<span class="rx-meta-label">业务单号</span>
<span class="rx-meta-value">{{ businessOrderNoText }}</span>
</div>
</div>
</div>
<div class="rx-info">
@@ -44,65 +63,100 @@
</div>
<div class="rx-rp">
<div class="rx-watermark">药房联</div>
<div class="rx-watermark">{{ isInternal ? '药房联' : '处方联' }}</div>
<div class="rx-rp-head">
<div class="rx-rp-label">Rp.</div>
<div class="rx-rp-cols-head">
<div class="rx-rp-col-head">
<span>用药 (单剂)</span>
<span>总量</span>
<span>{{ isInternal ? '总量' : '用量' }}</span>
</div>
<div class="rx-rp-col-head">
<span>用药 (单剂)</span>
<span>总量</span>
<span>{{ isInternal ? '总量' : '用量' }}</span>
</div>
</div>
</div>
<div class="rx-herbs">
<template v-if="mainHerbs.length">
<div class="rx-herb-section-label">主方</div>
<div
v-for="(h, i) in mainHerbs"
:key="'main-' + i"
class="rx-herb-cell"
>
<span class="rx-herb-name">{{ h.name }} ({{ h.dosage }})</span>
<span class="rx-herb-total">{{ herbTotal(h.dosage) }}</span>
</div>
<template v-if="isInternal">
<template v-if="mainHerbs.length">
<div class="rx-herb-section-label">主方</div>
<div
v-for="(h, i) in mainHerbs"
:key="'main-' + i"
class="rx-herb-cell"
>
<span class="rx-herb-name">{{ h.name }} ({{ h.dosage }})</span>
<span class="rx-herb-total">{{ herbTotal(h.dosage) }}</span>
</div>
</template>
<template v-if="auxHerbs.length">
<div class="rx-herb-section-label rx-herb-section-label--aux">
辅方
<span v-if="auxLibraryName" class="rx-herb-library-name">{{ auxLibraryName }}</span>
</div>
<div
v-for="(h, i) in auxHerbs"
:key="'aux-' + i"
class="rx-herb-cell"
>
<span class="rx-herb-name">{{ h.name }} ({{ h.dosage }})</span>
<span class="rx-herb-total">{{ herbTotal(h.dosage) }}</span>
</div>
</template>
</template>
<template v-if="auxHerbs.length">
<div class="rx-herb-section-label rx-herb-section-label--aux">辅方</div>
<div
v-for="(h, i) in auxHerbs"
:key="'aux-' + i"
class="rx-herb-cell"
>
<span class="rx-herb-name">{{ h.name }} ({{ h.dosage }})</span>
<span class="rx-herb-total">{{ herbTotal(h.dosage) }}</span>
</div>
<template v-else>
<template v-if="mainHerbs.length">
<div class="rx-herb-section-label">主方</div>
<div
v-for="(h, i) in mainHerbs"
:key="'user-main-' + i"
class="rx-herb-cell"
>
<span class="rx-herb-name">{{ h.name }}</span>
<span class="rx-herb-total">{{ h.dosage }}</span>
</div>
</template>
<template v-if="auxHerbs.length">
<div class="rx-herb-section-label rx-herb-section-label--aux">辅方</div>
<div
v-for="(h, i) in auxHerbs"
:key="'user-aux-' + i"
class="rx-herb-cell"
>
<span class="rx-herb-name">{{ h.name }}</span>
<span class="rx-herb-total">{{ h.dosage }}</span>
</div>
</template>
</template>
</div>
</div>
<div class="rx-text">
<p>主方服法{{ usageText }}</p>
<p v-if="auxUsageText">辅方服法{{ auxUsageText }}</p>
<template v-if="isInternal">
<p v-if="auxUsageText">服法{{ usageText }}</p>
<p v-else>服法{{ usageText }}</p>
<p v-if="auxUsageText">辅服法{{ auxUsageText }}</p>
</template>
<p v-if="adviceText">医嘱{{ adviceText }}</p>
<p v-if="dietaryText">忌口{{ dietaryText }}</p>
<p v-if="remarkText">备注{{ remarkText }}</p>
<p v-if="isInternal && remarkText">备注{{ remarkText }}</p>
<p v-if="pharmacyRemarkText" class="rx-text-warn">
药房备注{{ pharmacyRemarkText }}
</p>
<p v-if="outPelletText" class="rx-text-warn">
<p
v-if="isInternal && outPelletText && data?.prescription_type !== '饮片'"
class="rx-text-warn"
>
出丸{{ outPelletText }}
</p>
</div>
<div class="rx-bottom">
<div class="rx-bot-row rx-bot-row-1">
<div class="rx-bot-cell rx-bot-stack rx-bot-doctor">
<div class="rx-bot-label">医师</div>
<div class="rx-bot-row rx-bot-row-1" :class="{ 'rx-bot-row-1--user': !isInternal }">
<div class="rx-bot-cell rx-bot-doctor">
<div class="rx-bot-label rx-bot-label--doctor">医师</div>
<div class="rx-bot-doctor-body">
<img
v-if="data?.doctor_signature"
@@ -110,24 +164,28 @@
alt="医师签名"
class="rx-bot-sign-img"
/>
<div v-if="!data?.doctor_signature" class="rx-bot-doctor-name">
<span v-if="!data?.doctor_signature" class="rx-bot-doctor-name">
{{ data?.doctor_name || '—' }}
</div>
</span>
</div>
</div>
<div class="rx-bot-cell rx-bot-meta">
<div v-if="isInternal" class="rx-bot-cell rx-bot-meta">
<span class="rx-bot-meta-key">类型:</span>
<span class="rx-bot-meta-val">{{ typeText }}</span>
</div>
<div class="rx-bot-cell rx-bot-meta">
<span class="rx-bot-meta-key">天数:</span>
<span class="rx-bot-meta-val">{{ data?.dose_count || '—' }}</span>
<span class="rx-bot-meta-val">{{ daysText }}</span>
</div>
<div class="rx-bot-cell rx-bot-meta">
<span class="rx-bot-meta-key">剂量:</span>
<div v-if="isInternal" class="rx-bot-cell rx-bot-meta">
<span class="rx-bot-meta-key">剂量:</span>
<span class="rx-bot-meta-val">{{ perDoseAmount }}</span>
</div>
</div>
<div class="rx-bot-row rx-hospital-row">
<div class="rx-hospital-line">成都双流甄养堂互联网医院有限公司 联系方式4001667339</div>
<div class="rx-hospital-line">地址四川省成都市双流区黄甲街道黄龙大道二段280号</div>
</div>
</div>
</div>
</template>
@@ -138,15 +196,17 @@ import {
getAuxHerbs,
getMainHerbs,
rxAdviceText,
rxAppointmentFlowText,
rxAuxUsageText,
rxBusinessOrderNoText,
rxDateText,
rxHerbTotal,
rxOutPelletText,
rxPerDoseAmount,
rxPharmacyRemarkText,
rxPrescriptionSnText,
rxRecipientText,
rxRemarkText,
rxSerialText,
rxTypeText,
rxUsageText,
slipAgeText,
@@ -156,15 +216,32 @@ import {
defineOptions({ name: 'PrescriptionSlip' })
const props = defineProps<{
/** 处方详情 / 列表行(字段与 tcm.prescription/detail 对齐) */
data: Record<string, any> | null | undefined
}>()
const props = withDefaults(
defineProps<{
/** 处方详情 / 列表行(字段与 tcm.prescription/detail 对齐) */
data: Record<string, any> | null | undefined
/** 药房联 internal / 处方联 user */
variant?: 'internal' | 'user'
/** 药房联辅方处方库名称 */
auxLibraryName?: string
/** 抬头(处方联) */
title?: string
}>(),
{
variant: 'internal',
auxLibraryName: '',
title: '成都双流甄养堂互联网医院 处方笺'
}
)
const paperRef = ref<HTMLElement | null>(null)
const isInternal = computed(() => props.variant !== 'user')
const slipTitle = computed(() => props.title || '成都双流甄养堂互联网医院 处方笺')
const dateText = computed(() => rxDateText(props.data))
const serialText = computed(() => rxSerialText(props.data))
const prescriptionSnText = computed(() => rxPrescriptionSnText(props.data))
const appointmentFlowText = computed(() => rxAppointmentFlowText(props.data))
const businessOrderNoText = computed(() => rxBusinessOrderNoText(props.data))
const recipientText = computed(() => rxRecipientText(props.data))
const genderLabel = computed(() => slipGenderLabel(props.data?.gender))
const ageText = computed(() => slipAgeText(props.data?.age))
@@ -179,6 +256,17 @@ const pharmacyRemarkText = computed(() => rxPharmacyRemarkText(props.data))
const outPelletText = computed(() => rxOutPelletText(props.data))
const typeText = computed(() => rxTypeText(props.data))
const perDoseAmount = computed(() => rxPerDoseAmount(props.data))
const daysText = computed(() => {
const md = props.data?.medication_days
if (md != null && String(md).trim() !== '') {
return `${md}`
}
const dose = props.data?.dose_count
if (dose != null && String(dose).trim() !== '') {
return `${dose}`
}
return '—'
})
function herbTotal(dosage: number | string) {
return rxHerbTotal(props.data, dosage)
@@ -194,9 +282,8 @@ defineExpose({
<style lang="scss" scoped>
/* ============================================================
* 处方笺A4 药房联样式
* 严格按 A4210mm × 297mmpadding 8mm 10mm
* 唯一视觉基准consumer/prescription/index.vue
* 处方笺A4 药房联 / 处方联样式
* Desktop order_list.vue 笺面样式保持一致
* ============================================================ */
.rx-paper {
width: 210mm;
@@ -214,29 +301,98 @@ defineExpose({
overflow: visible;
}
/* === 顶部告知(上下分块,避免左右 flex 把左侧提示挤成竖条)=== */
.rx-notice {
display: flex;
align-items: center;
justify-content: space-between;
flex-direction: column;
align-items: stretch;
gap: 10px;
background: #f3f4f6;
border: 1px solid #e5e7eb;
padding: 6px 10px;
padding: 14px 12px;
border-radius: 2px;
font-size: 12px;
color: #1f1f1f;
margin-bottom: 6px;
.rx-notice-text {
flex: 1;
flex: none;
width: 100%;
line-height: 1.55;
word-break: break-word;
}
.rx-notice-meta {
display: flex;
flex-flow: row nowrap;
align-items: center;
gap: 4px 14px;
width: 100%;
box-sizing: border-box;
margin-top: 4px;
padding: 8px 2px 2px;
border-top: 1px solid #e5e7eb;
font-size: 10px;
line-height: 2.35;
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
}
.rx-notice-meta .rx-meta-item {
display: inline-flex;
gap: 24px;
flex: 0 0 auto;
align-items: baseline;
gap: 2px;
min-width: 0;
padding: 0;
margin: 0;
background: transparent;
border: none;
border-radius: 0;
white-space: nowrap;
line-height: 2.35;
height: 20px;
}
.rx-notice-meta .rx-meta-item:not(:first-child) {
border-left: 1px solid #d1d5db;
padding-left: 14px;
margin-left: 2px;
line-height: 2.35;
}
.rx-notice-meta .rx-meta-label {
font-weight: 600;
color: #6b7280;
line-height: 1.35;
flex-shrink: 0;
}
.rx-notice-meta .rx-meta-label::after {
content: '';
font-weight: 500;
color: #9ca3af;
}
.rx-notice-meta .rx-meta-value {
color: #111827;
line-height: 1.35;
font-variant-numeric: tabular-nums;
}
}
.rx-title {
text-align: center;
font-size: 28px;
font-weight: 700;
line-height: 1.2;
margin: 10px 0 35px;
letter-spacing: 0.08em;
color: #1f1f1f;
}
/* === 患者信息表 === */
.rx-info {
border: 1px solid #c8c8c8;
border-bottom: none;
@@ -256,7 +412,7 @@ defineExpose({
display: flex;
align-items: center;
gap: 6px;
padding: 6px 10px;
padding: 16px 10px;
font-size: 13px;
border-right: 1px solid #c8c8c8;
min-height: 32px;
@@ -277,6 +433,7 @@ defineExpose({
}
}
/* === Rp 区域 + 水印 === */
.rx-rp {
position: relative;
border-left: 1px solid #c8c8c8;
@@ -289,6 +446,7 @@ defineExpose({
.rx-watermark {
position: absolute;
/* 不使用 inset 简写:html2canvas 对 inset 解析不稳定 */
top: 0;
right: 0;
bottom: 0;
@@ -318,6 +476,7 @@ defineExpose({
z-index: 1;
width: 100%;
box-sizing: border-box;
padding: 10px;
}
.rx-rp-label {
@@ -325,6 +484,7 @@ defineExpose({
font-weight: 700;
color: #1f1f1f;
line-height: 1;
padding-bottom: 15px;
}
.rx-rp-cols-head {
@@ -336,6 +496,9 @@ defineExpose({
min-width: 0;
}
/* 1fr 64px .rx-herb-cell
* 配合 .rx-herbs .rx-rp-cols-head 同样的左侧 44px 缩进
* 即可保证用药 (单剂)与药材名总量与总量数字精确对齐 */
.rx-rp-col-head {
display: grid;
grid-template-columns: 1fr 64px;
@@ -343,6 +506,7 @@ defineExpose({
font-size: 13px;
color: #1f1f1f;
min-width: 0;
padding-bottom: 15px;
& > span:last-child {
text-align: right;
@@ -350,6 +514,7 @@ defineExpose({
}
.rx-herbs {
/* 与 .rx-rp-head 中 Rp. 列(36px) + column-gap(8px) 等宽缩进 */
padding-left: 44px;
display: grid;
grid-template-columns: 1fr 1fr;
@@ -374,12 +539,17 @@ defineExpose({
margin-top: 4px;
}
.rx-herb-library-name {
font-weight: 500;
margin-left: 2px;
}
.rx-herb-cell {
display: grid;
grid-template-columns: 1fr 64px;
column-gap: 8px;
font-size: 13px;
line-height: 1.85;
line-height: 2.5;
min-width: 0;
}
@@ -398,6 +568,7 @@ defineExpose({
white-space: nowrap;
}
/* === 服法 / 备注 文本块 === */
.rx-text {
border: 1px solid #c8c8c8;
border-top: none;
@@ -415,6 +586,7 @@ defineExpose({
}
}
/* === 底部表格 === */
.rx-bottom {
border: 1px solid #c8c8c8;
border-top: none;
@@ -430,10 +602,36 @@ defineExpose({
}
.rx-bot-row-1 {
grid-template-columns: 1.4fr 0.6fr 0.6fr 0.6fr 0.6fr 1.7fr 0.95fr 1.25fr;
/* // meta
* 避免"浓缩丸-浓缩水丸"被强制折行后把整行挤错位 */
grid-template-columns: 1.6fr 0.6fr 0.6fr 0.6fr 0.6fr 1.7fr 0.95fr 1.25fr;
min-height: 70px;
}
.rx-bot-row-1--user {
grid-template-columns: 1.6fr 0.6fr;
}
.rx-hospital-row {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
gap: 2px;
padding: 10px 12px;
min-height: 52px;
text-align: left;
}
.rx-hospital-line {
font-size: 12px;
color: #374151;
line-height: 1.6;
}
/* ///
* 需要"标题置顶 + 签名留白"的格子医师/审方/配药/复核/发药请加 .rx-bot-stack
* 这样无需任何 !important 反向覆盖html2canvas/原生渲染表现一致 */
.rx-bot-cell {
border-right: 1px solid #c8c8c8;
padding: 6px 10px;
@@ -445,6 +643,7 @@ defineExpose({
justify-content: flex-start;
min-height: 32px;
box-sizing: border-box;
line-height: 2.5;
&:last-child {
border-right: none;
@@ -463,21 +662,39 @@ defineExpose({
margin-bottom: 4px;
}
.rx-bot-label--doctor {
flex-shrink: 0;
margin-bottom: 0;
margin-right: 6px;
line-height: 1.2;
align-self: center;
}
.rx-bot-doctor {
flex-direction: row;
align-items: center;
flex-wrap: nowrap;
.rx-bot-doctor-body {
flex: 1;
display: flex;
flex-direction: row;
align-items: center;
gap: 6px;
min-height: 36px;
justify-content: flex-start;
flex-wrap: nowrap;
gap: 8px;
min-height: 0;
}
.rx-bot-doctor-name {
font-size: 13px;
color: #1f1f1f;
line-height: 1.2;
white-space: nowrap;
}
.rx-bot-sign-img {
flex-shrink: 0;
max-height: 40px;
max-width: 110px;
object-fit: contain;
@@ -501,7 +718,7 @@ defineExpose({
overflow: hidden;
text-overflow: ellipsis;
min-width: 0;
line-height: 1.4;
line-height: 2.5;
}
}
</style>
@@ -173,6 +173,30 @@ export function rxSerialText(data: Record<string, any> | null | undefined): stri
)
}
/** 处方笺「处方编号」:处方 sn,缺省 visit_no */
export function rxPrescriptionSnText(data: Record<string, any> | null | undefined): string {
if (!data) return '—'
const sn = String(data.sn ?? '').trim()
if (sn) return sn
const vn = String(data.visit_no ?? '').trim()
if (vn) return vn
return '—'
}
/** 流转编号(挂号):预约/挂号主键 */
export function rxAppointmentFlowText(data: Record<string, any> | null | undefined): string {
if (!data) return '—'
const aid = Number(data.appointment_id)
if (Number.isFinite(aid) && aid > 0) return String(aid)
return '—'
}
/** 业务订单号(从打开详情的订单行合并,便于笺面核对) */
export function rxBusinessOrderNoText(data: Record<string, any> | null | undefined): string {
if (!data) return ''
return String(data.order_no ?? '').trim()
}
export function rxRecipientText(data: Record<string, any> | null | undefined): string {
if (!data) return '—'
if (data.recipient_text) return data.recipient_text
@@ -2649,7 +2649,42 @@ async function handleDownloadSlip() {
windowWidth: node.scrollWidth,
windowHeight: node.scrollHeight,
width: node.scrollWidth,
height: node.scrollHeight
height: node.scrollHeight,
/** 克隆节点上再兜一层:加高行高,避免药材/底部文字被切边 */
onclone: (_doc, cloned) => {
if (!(cloned instanceof HTMLElement)) return
cloned.querySelectorAll<HTMLElement>('.rx-notice, .rx-notice-text, .rx-notice-meta').forEach((el) => {
el.style.lineHeight = '1.95'
el.style.overflow = 'visible'
})
cloned.querySelectorAll<HTMLElement>('.rx-notice').forEach((el) => {
el.style.padding = '10px 12px'
})
cloned.querySelectorAll<HTMLElement>('.rx-info-cell, .rx-info-cell .rx-key, .rx-info-cell .rx-val').forEach((el) => {
el.style.lineHeight = '1.95'
el.style.overflow = 'visible'
})
cloned.querySelectorAll<HTMLElement>('.rx-info-cell').forEach((el) => {
el.style.padding = '10px 12px'
})
cloned.querySelectorAll<HTMLElement>('.rx-herb-cell').forEach((el) => {
el.style.lineHeight = '2.5'
})
cloned.querySelectorAll<HTMLElement>('.rx-herbs').forEach((el) => {
el.style.rowGap = '12px'
})
cloned.querySelectorAll<HTMLElement>('.rx-text').forEach((el) => {
el.style.lineHeight = '2.1'
})
cloned.querySelectorAll<HTMLElement>('.rx-bot-meta-val, .rx-hospital-line, .rx-bot-cell').forEach((el) => {
el.style.overflow = 'visible'
el.style.lineHeight = '1.85'
})
cloned.querySelectorAll<HTMLElement>('.rx-hospital-row').forEach((el) => {
el.style.paddingTop = '12px'
el.style.paddingBottom = '14px'
})
}
})
const imgData = canvas.toDataURL('image/jpeg', 0.95)
const pdf = new jsPDF({ unit: 'mm', format: 'a4', orientation: 'portrait' })
@@ -1770,6 +1770,17 @@
</div>
</template>
<div v-loading="prescriptionViewLoading" class="rx-wrap">
<!-- 药房联 / 处方联切换 -->
<el-tabs
v-if="prescriptionViewData"
v-model="prescriptionTabType"
class="mx-4 mt-2"
style="margin-bottom: 0; z-index: 1; position: relative"
>
<el-tab-pane label="药房联" name="internal" />
<el-tab-pane label="处方联" name="user" />
</el-tabs>
<!-- 状态条不进入打印/导出范围 -->
<div class="rx-statusbar" v-if="prescriptionViewData">
<el-tag
@@ -1822,6 +1833,9 @@
v-if="prescriptionViewData"
ref="rxSlipRef"
:data="prescriptionViewData"
:variant="prescriptionTabType"
:aux-library-name="slipAuxLibraryName"
:title="SLIP_TITLE"
/>
<!-- 审核痕迹不进入导出 -->
@@ -4466,6 +4480,8 @@ const prescriptionViewLoading = ref(false)
const prescriptionViewData = ref<any>(null)
/** 药房联辅方标题:处方库中该辅方模板的 prescription_name(导入或药材匹配解析) */
const slipAuxLibraryName = ref('')
/** 查看处方联次:药房联 / 处方联 */
const prescriptionTabType = ref<'internal' | 'user'>('internal')
const rxSlipRef = ref<InstanceType<typeof PrescriptionSlip> | null>(null)
const prescriptionSlipExporting = ref(false)
@@ -4864,6 +4880,7 @@ async function openPrescriptionView(row: any) {
prescriptionViewLoading.value = true
prescriptionViewData.value = null
slipAuxLibraryName.value = ''
prescriptionTabType.value = 'internal'
try {
const res: any = await prescriptionDetail({ id: row.prescription_id })
@@ -4961,7 +4978,42 @@ async function capturePrescriptionSlipCanvas(): Promise<HTMLCanvasElement> {
width: el.scrollWidth,
height: el.scrollHeight,
ignoreElements: (node: Element) =>
node instanceof HTMLElement && node.classList.contains('cf-slip-no-export')
node instanceof HTMLElement && node.classList.contains('cf-slip-no-export'),
/** 克隆节点上再兜一层:加高行高,避免药材/底部文字被切边 */
onclone: (_doc, cloned) => {
if (!(cloned instanceof HTMLElement)) return
cloned.querySelectorAll<HTMLElement>('.rx-notice, .rx-notice-text, .rx-notice-meta').forEach((node) => {
node.style.lineHeight = '1.95'
node.style.overflow = 'visible'
})
cloned.querySelectorAll<HTMLElement>('.rx-notice').forEach((node) => {
node.style.padding = '10px 12px'
})
cloned.querySelectorAll<HTMLElement>('.rx-info-cell, .rx-info-cell .rx-key, .rx-info-cell .rx-val').forEach((node) => {
node.style.lineHeight = '1.95'
node.style.overflow = 'visible'
})
cloned.querySelectorAll<HTMLElement>('.rx-info-cell').forEach((node) => {
node.style.padding = '10px 12px'
})
cloned.querySelectorAll<HTMLElement>('.rx-herb-cell').forEach((node) => {
node.style.lineHeight = '2.5'
})
cloned.querySelectorAll<HTMLElement>('.rx-herbs').forEach((node) => {
node.style.rowGap = '12px'
})
cloned.querySelectorAll<HTMLElement>('.rx-text').forEach((node) => {
node.style.lineHeight = '2.1'
})
cloned.querySelectorAll<HTMLElement>('.rx-bot-meta-val, .rx-hospital-line, .rx-bot-cell').forEach((node) => {
node.style.overflow = 'visible'
node.style.lineHeight = '1.85'
})
cloned.querySelectorAll<HTMLElement>('.rx-hospital-row').forEach((node) => {
node.style.paddingTop = '12px'
node.style.paddingBottom = '14px'
})
}
})
}
@@ -2342,6 +2342,17 @@
</div>
</template>
<div v-loading="prescriptionViewLoading" class="rx-wrap">
<!-- 药房联 / 处方联切换 -->
<el-tabs
v-if="prescriptionViewData"
v-model="prescriptionTabType"
class="mx-4 mt-2"
style="margin-bottom: 0; z-index: 1; position: relative"
>
<el-tab-pane label="药房联" name="internal" />
<el-tab-pane label="处方联" name="user" />
</el-tabs>
<!-- 状态条不进入打印/导出范围 -->
<div class="rx-statusbar" v-if="prescriptionViewData">
<el-tag
@@ -2394,6 +2405,8 @@
v-if="prescriptionViewData"
ref="rxSlipRef"
:data="prescriptionViewData"
:variant="prescriptionTabType"
:title="SLIP_TITLE"
/>
<!-- 审核痕迹不进入导出 -->
@@ -5296,6 +5309,8 @@ const SLIP_ADDRESS_LINE = '地址:四川省成都市双流区黄甲街道黄
const prescriptionViewVisible = ref(false)
const prescriptionViewLoading = ref(false)
const prescriptionViewData = ref<any>(null)
/** 查看处方联次:药房联 / 处方联 */
const prescriptionTabType = ref<'internal' | 'user'>('internal')
const rxSlipRef = ref<InstanceType<typeof PrescriptionSlip> | null>(null)
const prescriptionSlipExporting = ref(false)
@@ -5563,7 +5578,8 @@ async function openPrescriptionView(row: any) {
prescriptionViewVisible.value = true
prescriptionViewLoading.value = true
prescriptionViewData.value = null
prescriptionTabType.value = 'internal'
try {
const res: any = await prescriptionDetail({ id: row.prescription_id })
const base = res?.data ?? res ?? null
@@ -5657,7 +5673,42 @@ async function capturePrescriptionSlipCanvas(): Promise<HTMLCanvasElement> {
width: el.scrollWidth,
height: el.scrollHeight,
ignoreElements: (node: Element) =>
node instanceof HTMLElement && node.classList.contains('cf-slip-no-export')
node instanceof HTMLElement && node.classList.contains('cf-slip-no-export'),
/** 克隆节点上再兜一层:加高行高,避免药材/底部文字被切边 */
onclone: (_doc, cloned) => {
if (!(cloned instanceof HTMLElement)) return
cloned.querySelectorAll<HTMLElement>('.rx-notice, .rx-notice-text, .rx-notice-meta').forEach((node) => {
node.style.lineHeight = '1.95'
node.style.overflow = 'visible'
})
cloned.querySelectorAll<HTMLElement>('.rx-notice').forEach((node) => {
node.style.padding = '10px 12px'
})
cloned.querySelectorAll<HTMLElement>('.rx-info-cell, .rx-info-cell .rx-key, .rx-info-cell .rx-val').forEach((node) => {
node.style.lineHeight = '1.95'
node.style.overflow = 'visible'
})
cloned.querySelectorAll<HTMLElement>('.rx-info-cell').forEach((node) => {
node.style.padding = '10px 12px'
})
cloned.querySelectorAll<HTMLElement>('.rx-herb-cell').forEach((node) => {
node.style.lineHeight = '2.5'
})
cloned.querySelectorAll<HTMLElement>('.rx-herbs').forEach((node) => {
node.style.rowGap = '12px'
})
cloned.querySelectorAll<HTMLElement>('.rx-text').forEach((node) => {
node.style.lineHeight = '2.1'
})
cloned.querySelectorAll<HTMLElement>('.rx-bot-meta-val, .rx-hospital-line, .rx-bot-cell').forEach((node) => {
node.style.overflow = 'visible'
node.style.lineHeight = '1.85'
})
cloned.querySelectorAll<HTMLElement>('.rx-hospital-row').forEach((node) => {
node.style.paddingTop = '12px'
node.style.paddingBottom = '14px'
})
}
})
}
+107 -81
View File
@@ -103,40 +103,40 @@
</article>
</section>
<section class="ranking-grid">
<article class="panel ranking-panel">
<div class="panel-heading">
<div>
<h2>部门订单量占比</h2>
<p>订单创建人归属排除取消拒收及退款</p>
</div>
<span>单位</span>
</div>
<div v-if="dashboard.rankings.orders.length" class="bar-list">
<div v-for="item in dashboard.rankings.orders" :key="`order-${item.id}`" class="bar-row">
<span class="bar-name" :title="item.name">{{ item.name }}</span>
<div class="bar-track"><i class="is-teal" :style="{ width: barWidth(item.value, maxOrderValue) }" /></div>
<strong>{{ formatNumber(item.value) }} </strong>
</div>
</div>
<el-empty v-else :image-size="54" description="当前范围暂无订单数据" />
<section v-if="showRankings" class="ranking-grid">
<article class="panel ranking-panel">
<div class="panel-heading">
<div>
<h2>{{ rankingSubject }}订单量占比排名</h2>
<p>{{ rankingSubject }}归属统计排除取消拒收及退款</p>
</div>
<span>单位</span>
</div>
<div v-if="dashboard.rankings.orders.length" class="bar-list">
<div v-for="(item, index) in dashboard.rankings.orders" :key="`order-${item.id}`" class="bar-row">
<span class="bar-name" :title="item.name"><b>{{ index + 1 }}</b>{{ item.name }}</span>
<div class="bar-track"><i class="is-teal" :style="{ width: barWidth(item.value, totalOrderValue) }" /></div>
<strong><span>{{ formatNumber(item.value) }} </span><small>{{ formatShare(item.value, totalOrderValue) }}</small></strong>
</div>
</div>
<el-empty v-else :image-size="54" description="当前范围暂无订单数据" />
</article>
<article class="panel ranking-panel">
<div class="panel-heading">
<div>
<h2>部门金额占比</h2>
<p>仅统计未取消未拒收且未退款的有效金额</p>
</div>
<span>单位</span>
</div>
<div v-if="dashboard.rankings.amounts.length" class="bar-list">
<div v-for="item in dashboard.rankings.amounts" :key="`amount-${item.id}`" class="bar-row">
<span class="bar-name" :title="item.name">{{ item.name }}</span>
<div class="bar-track"><i class="is-blue" :style="{ width: barWidth(item.value, maxAmountValue) }" /></div>
<strong>{{ formatMoney(item.value) }}</strong>
</div>
</div>
<div class="panel-heading">
<div>
<h2>{{ rankingSubject }}金额占比排名</h2>
<p>{{ rankingSubject }}归属统计仅含未取消未拒收且未退款的有效金额</p>
</div>
<span>单位</span>
</div>
<div v-if="dashboard.rankings.amounts.length" class="bar-list">
<div v-for="(item, index) in dashboard.rankings.amounts" :key="`amount-${item.id}`" class="bar-row">
<span class="bar-name" :title="item.name"><b>{{ index + 1 }}</b>{{ item.name }}</span>
<div class="bar-track"><i class="is-blue" :style="{ width: barWidth(item.value, totalAmountValue) }" /></div>
<strong><span>{{ formatMoney(item.value) }}</span><small>{{ formatShare(item.value, totalAmountValue) }}</small></strong>
</div>
</div>
<el-empty v-else :image-size="54" description="当前范围暂无金额数据" />
</article>
</section>
@@ -145,7 +145,7 @@
<div class="panel-heading panel-heading--table">
<div>
<h2>明细数据列表</h2>
<p>展开部门可查看人员明细加粉=总进线=区间有效加粉按员工+客户去重须会话同意剔除已删客户与继承客户挂号=已支付且实收低于 10 元的订单预约=有效预约记录开口率=开口/加粉挂号率=挂号/加粉面诊率=面诊/挂号看挂号后流失预约率=面诊/预约看预约后未面诊面诊接诊率=接诊诊单/面诊接诊率=接诊诊单/总进线</p>
<p>展开部门可查看人员明细加粉=总进线=区间有效加粉按员工+客户去重须会话同意剔除已删客户继承客户扫一扫/搜手机号/名片分享添加及区间前已加过的重加挂号=已支付且实收低于 10 元的订单预约=有效预约记录开口率=开口/加粉挂号率=挂号/加粉面诊率=面诊/挂号看挂号后流失预约率=面诊/预约看预约后未面诊面诊接诊率=接诊诊单/面诊接诊率=接诊诊单/总进线</p>
</div>
<span>{{ dashboard.rows.length }} 个顶层节点</span>
</div>
@@ -156,7 +156,7 @@
default-expand-all
class="detail-table"
>
<el-table-column prop="name" label="部门 / 人员" min-width="280" fixed="left">
<el-table-column prop="name" label="部门 / 人员" min-width="220" fixed="left">
<template #default="{ row }">
<strong :class="{ 'is-parent': Array.isArray(row.children) && row.children.length, 'is-member': row.type === 'member' }">
{{ row.name }}
@@ -178,34 +178,34 @@
</el-tag>
</template>
</el-table-column>
<el-table-column prop="add_fans_count" label="加粉" min-width="88" align="right" />
<el-table-column prop="total_open_count" label="开口" min-width="88" align="right" />
<el-table-column prop="paid_appointment_count" label="挂号" min-width="88" align="right" />
<el-table-column prop="appointment_total_count" label="预约" min-width="88" align="right" />
<el-table-column prop="interview_count" label="面诊" min-width="88" align="right" />
<el-table-column prop="completed_order_count" label="接诊诊单" min-width="104" align="right" />
<el-table-column label="接诊金额" min-width="120" align="right">
<el-table-column prop="add_fans_count" label="加粉" min-width="72" align="right" />
<el-table-column prop="total_open_count" label="开口" min-width="72" align="right" />
<el-table-column prop="paid_appointment_count" label="挂号" min-width="72" align="right" />
<el-table-column prop="appointment_total_count" label="预约" min-width="72" align="right" />
<el-table-column prop="interview_count" label="面诊" min-width="72" align="right" />
<el-table-column prop="completed_order_count" label="接诊诊单" min-width="88" align="right" />
<el-table-column label="接诊金额" min-width="104" align="right">
<template #default="{ row }">{{ formatMoney(row.completed_order_amount) }}</template>
</el-table-column>
<el-table-column label="开口率" min-width="96" align="right">
<el-table-column label="开口率" min-width="82" align="right">
<template #default="{ row }">{{ formatPercent(row.total_open_rate) }}</template>
</el-table-column>
<el-table-column label="挂号率" min-width="96" align="right">
<el-table-column label="挂号率" min-width="82" align="right">
<template #default="{ row }">{{ formatPercent(row.paid_appointment_rate) }}</template>
</el-table-column>
<el-table-column label="面诊率" min-width="96" align="right">
<el-table-column label="面诊率" min-width="82" align="right">
<template #default="{ row }">{{ formatPercent(row.interview_paid_rate) }}</template>
</el-table-column>
<el-table-column label="预约率" min-width="96" align="right">
<el-table-column label="预约率" min-width="82" align="right">
<template #default="{ row }">{{ formatPercent(row.interview_rate) }}</template>
</el-table-column>
<el-table-column label="面诊接诊率" min-width="116" align="right">
<el-table-column label="面诊接诊率" min-width="100" align="right">
<template #default="{ row }">{{ formatPercent(row.interview_receive_rate) }}</template>
</el-table-column>
<el-table-column label="接诊率" min-width="96" align="right">
<el-table-column label="接诊率" min-width="82" align="right">
<template #default="{ row }">{{ formatPercent(row.receive_rate) }}</template>
</el-table-column>
<el-table-column label="ROI" min-width="86" align="right">
<el-table-column label="ROI" min-width="72" align="right">
<template #default="{ row }">{{ formatRatio(row.roi) }}</template>
</el-table-column>
<template #empty><el-empty description="当前权限范围内暂无转化数据" /></template>
@@ -291,9 +291,9 @@ type MediaChannelOption = {
const emptyDashboard = () => ({
meta: {
time_type: 'today', time_label: '今日', start_date: '', end_date: '', generated_at: '',
scope_value: 4, scope_label: '', selected_dept_name: '', selected_assistant_name: '',
selected_media_channel_code: '', selected_media_channel_name: '', open_count_source: ''
time_type: 'today', time_label: '今日', start_date: '', end_date: '', generated_at: '',
scope_value: 4, scope_label: '', selected_dept_name: '', selected_assistant_name: '',
selected_media_channel_code: '', selected_media_channel_name: '', open_count_source: '', ranking_kind: 'hidden'
},
filters: {
departments: [] as any[],
@@ -330,7 +330,7 @@ const timeOptions = [
{ label: '自定义', value: 'custom' }
]
const metricCards: Array<{ key: string; label: string; type: MetricType; hint: string }> = [
{ key: 'add_fans_count', label: '加粉数', type: 'count', hint: '区间有效加粉:去重,须会话同意,剔除已删客户继承客户' },
{ key: 'add_fans_count', label: '加粉数', type: 'count', hint: '区间有效加粉:去重,须会话同意,剔除已删客户继承客户、扫一扫/搜手机号/名片分享添加及区间前已加过的重加' },
{ key: 'total_open_count', label: '开口数', type: 'count', hint: '来源于个人业绩录入' },
{ key: 'interview_count', label: '面诊', type: 'count', hint: '已完成预约' },
{ key: 'completed_order_count', label: '接诊诊单', type: 'count', hint: '业务订单,按创建人归属并过滤无效单' },
@@ -364,8 +364,13 @@ const mediaChannelGroups = computed(() => {
}
return Array.from(groups.values())
})
const maxOrderValue = computed(() => Math.max(0, ...dashboard.rankings.orders.map(item => Number(item.value || 0))))
const maxAmountValue = computed(() => Math.max(0, ...dashboard.rankings.amounts.map(item => Number(item.value || 0))))
const rankingKind = computed(() => dashboard.meta.ranking_kind || (
Number(dashboard.meta.scope_value) === 4 ? 'hidden' : Number(dashboard.meta.scope_value) === 3 ? 'member' : 'group'
))
const showRankings = computed(() => rankingKind.value !== 'hidden')
const rankingSubject = computed(() => rankingKind.value === 'member' ? '组内成员' : '小组')
const totalOrderValue = computed(() => dashboard.rankings.orders.reduce((total, item) => total + Number(item.value || 0), 0))
const totalAmountValue = computed(() => dashboard.rankings.amounts.reduce((total, item) => total + Number(item.value || 0), 0))
const targetChartOption = computed(() => ({
animationDuration: 450,
color: ['#0f9185', '#2f78df'],
@@ -447,9 +452,14 @@ function formatMoney(value: any) {
return `¥${Number(value || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
}
function formatPercent(value: any) {
return `${Number(value || 0).toFixed(1)}%`
}
function formatPercent(value: any) {
return `${Number(value || 0).toFixed(1)}%`
}
function formatShare(value: any, total: number) {
if (total <= 0) return '0.0%'
return `${(Number(value || 0) / total * 100).toFixed(1)}%`
}
function nullablePercent(value: any) {
return value === null || value === undefined ? '未设置' : formatPercent(value)
@@ -464,10 +474,11 @@ function compactNumber(value: number) {
return String(Math.round(value))
}
function barWidth(value: any, maximum: number) {
if (maximum <= 0) return '0%'
return `${Math.max(4, Math.min(100, Number(value || 0) / maximum * 100))}%`
}
function barWidth(value: any, total: number) {
const numericValue = Number(value || 0)
if (total <= 0 || numericValue <= 0) return '0%'
return `${Math.max(4, Math.min(100, numericValue / total * 100))}%`
}
function progressValue(value: any) {
return Math.max(0, Math.min(100, Number(value || 0)))
@@ -477,22 +488,29 @@ onMounted(loadDashboard)
</script>
<style scoped lang="scss">
.conversion-page {
display: grid;
gap: 14px;
min-height: 640px;
padding: 16px;
color: #172033;
background: #f4f6f8;
}
.conversion-page {
display: grid;
gap: 14px;
width: 100%;
max-width: 100%;
min-width: 0;
min-height: 640px;
padding: 16px;
overflow-x: hidden;
box-sizing: border-box;
color: #172033;
background: #f4f6f8;
}
.page-heading,
.filter-strip,
.panel,
.metric-card {
border: 1px solid #dfe5ec;
background: #fff;
}
.metric-card {
min-width: 0;
box-sizing: border-box;
border: 1px solid #dfe5ec;
background: #fff;
}
.page-heading {
display: flex;
@@ -566,7 +584,7 @@ onMounted(loadDashboard)
}
.ranking-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
.panel { padding: 16px; border-radius: 10px; }
.panel { min-width: 0; max-width: 100%; padding: 16px; border-radius: 10px; box-sizing: border-box; }
.panel-heading {
display: flex;
@@ -580,18 +598,26 @@ onMounted(loadDashboard)
> span { color: #929dac; font-size: 11px; white-space: nowrap; }
}
.bar-list { display: grid; gap: 13px; }
.bar-row { display: grid; grid-template-columns: 110px minmax(80px, 1fr) 94px; align-items: center; gap: 10px; }
.bar-name { overflow: hidden; color: #66748a; font-size: 12px; text-overflow: ellipsis; white-space: nowrap; }
.bar-row > strong { text-align: right; font-size: 12px; font-variant-numeric: tabular-nums; }
.bar-list { display: grid; gap: 13px; max-height: 340px; overflow-y: auto; padding-right: 4px; }
.bar-row { display: grid; grid-template-columns: 130px minmax(80px, 1fr) 116px; align-items: center; gap: 10px; }
.bar-name { overflow: hidden; color: #66748a; font-size: 12px; text-overflow: ellipsis; white-space: nowrap; }
.bar-name b { display: inline-block; width: 20px; margin-right: 7px; color: #98a2b3; font-size: 11px; font-weight: 650; text-align: center; }
.bar-row > strong { display: flex; align-items: baseline; justify-content: flex-end; gap: 7px; text-align: right; font-size: 12px; font-variant-numeric: tabular-nums; }
.bar-row > strong small { color: #8a95a5; font-size: 10px; font-weight: 500; }
.bar-track { height: 18px; overflow: hidden; border-radius: 5px; background: #edf1f5; }
.bar-track i { display: block; height: 100%; border-radius: 5px; transition: width .35s ease; }
.bar-track i.is-teal { background: #15998d; }
.bar-track i.is-blue { background: #307bdf; }
.detail-panel { padding-bottom: 10px; }
.detail-table {
:deep(th.el-table__cell) { color: #66748a; background: #f7f9fb; font-size: 12px; }
.detail-panel { padding-bottom: 10px; overflow: hidden; }
.detail-table {
width: 100%;
max-width: 100%;
min-width: 0;
:deep(.el-table__inner-wrapper),
:deep(.el-scrollbar) { max-width: 100%; }
:deep(th.el-table__cell) { color: #66748a; background: #f7f9fb; font-size: 12px; }
:deep(td.el-table__cell) { color: #273347; font-size: 12px; }
:deep(.el-table__row--level-0 > td.el-table__cell) { background: #edf7f5; font-weight: 650; }
strong.is-parent { color: #172033; font-weight: 700; }
@@ -632,7 +658,7 @@ onMounted(loadDashboard)
.metric-grid, .ranking-grid { grid-template-columns: 1fr; }
.filter-item, .filter-item--time { width: 100%; align-items: flex-start; flex-direction: column; }
.employee-select, .dept-select, .channel-select { width: 100%; }
.bar-row { grid-template-columns: 90px minmax(70px, 1fr) 82px; }
.bar-row { grid-template-columns: 100px minmax(70px, 1fr) 96px; }
.target-summary { grid-template-columns: 1fr; }
}
</style>
@@ -321,6 +321,7 @@ const query = reactive<FirstVisitDoctorDashboardParams>({
const customDateRange = ref<string[]>([])
const timeOptions = [
{ label: '今日', value: 'today' },
{ label: '昨天', value: 'yesterday' },
{ label: '本周', value: 'week' },
{ label: '本月', value: 'month' },
{ label: '自定义', value: 'custom' }
@@ -1,92 +1,100 @@
<template>
<section class="embedded-panel" v-loading="loading">
<div class="panel-toolbar">
<div>
<h2>医生排班</h2>
<p>查看近 7 日出诊医生及可约时段与医生排班管理同口径</p>
</div>
<div class="toolbar-actions">
<el-button
:icon="Refresh"
:loading="refreshing"
:disabled="!selectedDoctorId || !form.date"
@click="handleRefreshSlots"
>
刷新时段
</el-button>
</div>
</div>
<el-form :model="form" label-width="88px" class="paiban-form">
<el-form-item label="选择医生">
<div class="doctor-list">
<el-radio-group v-model="selectedDoctorId" @change="handleDoctorChange">
<el-radio
v-for="doctor in doctorList"
:key="doctor.id"
:value="doctor.id"
class="doctor-radio"
>
{{ doctor.name }}
</el-radio>
</el-radio-group>
<el-empty
v-if="!loading && doctorList.length === 0"
description="暂无可用医生"
:image-size="64"
/>
<div class="paiban-container" v-loading="loading">
<el-card shadow="never">
<template #header>
<div class="card-header">
<span>医生排班管理</span>
<el-button
text
type="primary"
size="small"
:loading="refreshing"
:disabled="!selectedDoctorId || !form.date"
@click="handleRefreshSlots"
>
<template #icon>
<Refresh />
</template>
刷新
</el-button>
</div>
</el-form-item>
</template>
<el-form-item label="排班时间">
<el-empty v-if="!selectedDoctorId" description="请先选择医生" :image-size="72" />
<el-empty
v-else-if="selectedDoctorId && doctorRosterDates.length === 0"
description="该医生暂无排班"
:image-size="72"
/>
<div v-else class="paiban-time-container">
<div class="date-selector">
<el-button
v-for="dateOption in dateOptions"
:key="dateOption.date"
:type="form.date === dateOption.date ? 'primary' : ''"
class="date-button"
@click="selectDate(dateOption.date)"
>
{{ dateOption.label }}
</el-button>
</div>
<div v-if="form.date" class="time-slots-container">
<div class="time-slots-grid">
<div
v-for="slot in filteredTimeSlots"
:key="slot.time"
class="time-slot-item"
:class="{
available: slot.available,
unavailable: !slot.available,
selected: form.selectedTime === slot.time
}"
@click="selectTimeSlot(slot)"
<el-form :model="form" label-width="100px" class="paiban-form">
<!-- 选择医生 -->
<el-form-item label="选择医生:">
<div class="doctor-list">
<el-radio-group v-model="selectedDoctorId" @change="handleDoctorChange">
<el-radio
v-for="doctor in doctorList"
:key="doctor.id"
:value="doctor.id"
class="doctor-radio"
>
<div class="slot-time">{{ slot.time }}</div>
<div class="slot-status" :class="{ 'status-available': slot.available }">
{{ slot.available ? '可约' : slot.hasAppointment ? '已约' : '空号' }}
{{ doctor.name }}
</el-radio>
</el-radio-group>
</div>
</el-form-item>
<!-- 排班时间 -->
<el-form-item label="排班时间:">
<!-- 未选择医生提示 -->
<el-empty
v-if="!selectedDoctorId"
description="请先选择医生"
:image-size="80"
/>
<!-- 医生无排班提示 -->
<el-empty
v-else-if="selectedDoctorId && doctorRosterDates.length === 0"
description="该医生暂无排班"
:image-size="80"
/>
<!-- 有排班时显示日期和时间段 -->
<div v-else class="paiban-time-container">
<!-- 日期选择 -->
<div class="date-selector">
<el-button
v-for="dateOption in dateOptions"
:key="dateOption.date"
:type="form.date === dateOption.date ? 'primary' : ''"
class="date-button"
@click="selectDate(dateOption.date)"
>
{{ dateOption.label }}
</el-button>
</div>
<!-- 时间段区域 -->
<div v-if="form.date" class="time-slots-container">
<!-- 时间段网格 -->
<div class="time-slots-grid">
<div
v-for="slot in filteredTimeSlots"
:key="slot.time"
class="time-slot-item"
:class="{
available: slot.available,
unavailable: !slot.available,
selected: form.selectedTime === slot.time
}"
@click="selectTimeSlot(slot)"
>
<div class="slot-time">{{ slot.time }}</div>
<div class="slot-status" :class="{ 'status-available': slot.available }">
{{ slot.available ? '可约' : slot.hasAppointment ? '已约' : '空号' }}
</div>
</div>
</div>
</div>
<el-empty
v-if="filteredTimeSlots.length === 0"
description="当前日期暂无可展示时段"
:image-size="64"
/>
</div>
</div>
</el-form-item>
</el-form>
</section>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<script setup lang="ts">
@@ -164,17 +172,21 @@ const filteredTimeSlots = computed(() => {
const slotDateTime = dayjs(`${form.date} ${slot.time}`)
const isPast = slotDateTime.isBefore(now) || slotDateTime.isSame(now, 'minute')
if (isPast) {
return { ...slot, available: false }
return {
...slot,
available: false
}
}
return slot
})
})
async function loadDoctors() {
const loadDoctors = async () => {
try {
loading.value = true
const res = await getDoctors()
doctorList.value = res || []
if (doctorList.value.length > 0 && !selectedDoctorId.value) {
selectedDoctorId.value = doctorList.value[0].id
await handleDoctorChange()
@@ -187,25 +199,28 @@ async function loadDoctors() {
}
}
async function handleDoctorChange() {
const handleDoctorChange = async () => {
form.selectedTime = ''
form.date = ''
timeSlots.value = []
doctorRosterDates.value = []
if (selectedDoctorId.value) {
await loadDoctorRoster()
}
}
async function loadDoctorRoster() {
const loadDoctorRoster = async () => {
if (!selectedDoctorId.value) {
return
}
try {
loading.value = true
const startDate = dayjs().format('YYYY-MM-DD')
const endDate = dayjs().add(6, 'day').format('YYYY-MM-DD')
const res = await rosterLists({
doctor_id: selectedDoctorId.value,
start_date: startDate,
@@ -216,6 +231,7 @@ async function loadDoctorRoster() {
if (res?.lists && res.lists.length > 0) {
doctorRosterDates.value = [...new Set(res.lists.map((item: any) => item.date))] as string[]
doctorRosterDates.value.sort()
await nextTick()
if (doctorRosterDates.value.length > 0) {
const today = dayjs().format('YYYY-MM-DD')
@@ -237,7 +253,7 @@ async function loadDoctorRoster() {
}
}
function selectDate(date: string) {
const selectDate = (date: string) => {
form.date = date
form.selectedTime = ''
if (selectedDoctorId.value) {
@@ -245,16 +261,19 @@ function selectDate(date: string) {
}
}
async function loadTimeSlots(silent = false) {
const loadTimeSlots = async (silent = false) => {
if (!selectedDoctorId.value || !form.date) {
return
}
if (isLoadingSlots) {
console.log('正在加载中,跳过本次请求')
return
}
try {
isLoadingSlots = true
if (!silent) {
loading.value = true
}
@@ -285,7 +304,7 @@ async function loadTimeSlots(silent = false) {
}
}
function selectTimeSlot(slot: TimeSlot) {
const selectTimeSlot = (slot: TimeSlot) => {
if (!slot.available) {
feedback.msgWarning('该时间段不可预约')
return
@@ -293,10 +312,11 @@ function selectTimeSlot(slot: TimeSlot) {
form.selectedTime = slot.time
}
async function handleRefreshSlots() {
const handleRefreshSlots = async () => {
if (!selectedDoctorId.value || !form.date) {
return
}
if (isLoadingSlots) {
feedback.msgWarning('正在刷新中,请稍候')
return
@@ -306,13 +326,16 @@ async function handleRefreshSlots() {
refreshing.value = true
const previousSelection = form.selectedTime
form.selectedTime = ''
await loadTimeSlots()
if (previousSelection) {
const slot = timeSlots.value.find((s) => s.time === previousSelection)
if (slot?.available) {
if (slot && slot.available) {
form.selectedTime = previousSelection
}
}
feedback.msgSuccess('刷新成功')
} catch (error) {
console.error('刷新失败:', error)
@@ -322,8 +345,9 @@ async function handleRefreshSlots() {
}
}
function startAutoRefresh() {
const startAutoRefresh = () => {
stopAutoRefresh()
autoRefreshTimer = window.setInterval(() => {
if (selectedDoctorId.value && form.date) {
loadTimeSlots(true)
@@ -331,7 +355,7 @@ function startAutoRefresh() {
}, 5000)
}
function stopAutoRefresh() {
const stopAutoRefresh = () => {
if (autoRefreshTimer) {
clearInterval(autoRefreshTimer)
autoRefreshTimer = null
@@ -362,36 +386,16 @@ onUnmounted(() => {
</script>
<style scoped lang="scss">
.embedded-panel {
padding-top: 4px;
.paiban-container {
padding: 0;
}
.panel-toolbar {
.card-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
margin-bottom: 18px;
h2 {
margin: 0;
font-size: 18px;
font-weight: 700;
color: #1f2a37;
}
p {
margin: 6px 0 0;
color: #667085;
font-size: 13px;
}
}
.toolbar-actions {
display: flex;
align-items: center;
gap: 10px;
flex-shrink: 0;
font-size: 16px;
font-weight: 600;
}
.paiban-form {
@@ -404,10 +408,14 @@ onUnmounted(() => {
display: flex;
flex-wrap: wrap;
gap: 12px;
width: 100%;
.doctor-radio {
margin-right: 0;
:deep(.el-radio__label) {
display: flex;
align-items: center;
}
}
}
@@ -419,7 +427,7 @@ onUnmounted(() => {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 16px;
margin-bottom: 20px;
.date-button {
min-width: 130px;
@@ -441,6 +449,19 @@ onUnmounted(() => {
padding: 16px;
}
.time-slots-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
.header-title {
font-size: 15px;
font-weight: 600;
color: #303133;
}
}
.time-slots-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
@@ -467,7 +488,7 @@ onUnmounted(() => {
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0 8px;
padding: 0px 8px;
border: 2px solid #e4e7ed;
border-radius: 8px;
cursor: pointer;
@@ -485,7 +506,7 @@ onUnmounted(() => {
.slot-status {
font-size: 12px;
color: #909399;
padding: 0 8px;
padding: 0px 8px;
border-radius: 4px;
background-color: #f4f4f5;
@@ -350,7 +350,7 @@ const ProgressPanel = defineAsyncComponent(() => import('./components/ProgressPa
const PaibanPanel = defineAsyncComponent(() => import('./components/PaibanPanel.vue'))
type StatusFilter = '' | 'unbooked' | 'pending_interview' | 'completed' | 'missed'
type DateType = 'all' | 'today' | 'tomorrow' | 'day_after' | 'last7' | 'last30' | 'custom'
type DateType = 'all' | 'yesterday' | 'today' | 'tomorrow' | 'day_after' | 'last7' | 'last30' | 'custom'
const router = useRouter()
const userStore = useUserStore()
@@ -401,6 +401,7 @@ const statusOptions: Array<{ label: string; value: StatusFilter }> = [
const dateOptions: Array<{ label: string; value: DateType }> = [
{ label: '全部时间', value: 'all' },
{ label: '昨天', value: 'yesterday' },
{ label: '今日预约', value: 'today' },
{ label: '明日', value: 'tomorrow' },
{ label: '后天', value: 'day_after' },
@@ -466,6 +467,9 @@ function selectDateType(type: DateType) {
if (type === 'all') {
formData.start_date = ''
formData.end_date = ''
} else if (type === 'yesterday') {
formData.start_date = today.subtract(1, 'day').format('YYYY-MM-DD')
formData.end_date = formData.start_date
} else if (type === 'today') {
formData.start_date = today.format('YYYY-MM-DD')
formData.end_date = formData.start_date
@@ -325,6 +325,7 @@ const dashboard = reactive(emptyDashboard())
const query = reactive<FirstVisitRegistrationStatsParams>({ time_type: 'today' })
const timeOptions = [
{ label: '今日', value: 'today' },
{ label: '昨天', value: 'yesterday' },
{ label: '本周', value: 'week' },
{ label: '本月', value: 'month' }
]
@@ -98,7 +98,7 @@
</div>
</article>
<article class="metric-card">
<article v-if="dashboard.scope.kind !== 'assistant'" class="metric-card">
<div class="metric-label-row">
<span>本人本月业绩</span>
<el-tooltip :content="dashboard.meta.commission_note" placement="top">
@@ -151,11 +151,11 @@
<div class="panel-header">
<div>
<h2>{{ dashboard.rankings.appointments.title }}</h2>
<p>{{ appointmentRankingSubtitle }}</p>
<p v-if="appointmentRankingSubtitle">{{ appointmentRankingSubtitle }}</p>
</div>
<div class="panel-header-actions">
<el-tree-select
v-if="dashboard.rankings.appointments.kind !== 'doctor'"
v-if="showRankingDeptSelect"
v-model="rankingDeptId"
:data="dashboard.filters.ranking_departments"
:props="departmentTreeProps"
@@ -164,7 +164,7 @@
clearable
filterable
default-expand-all
placeholder="全部可见部门"
placeholder="全部部门"
class="ranking-dept-select"
@change="handleRankingDeptChange"
/>
@@ -176,12 +176,16 @@
v-for="(item, index) in dashboard.rankings.appointments.items"
:key="`appointment-${item.id}-${index}`"
class="ranking-row"
:class="{ 'is-self': item.is_self }"
>
<span class="ranking-index" :class="{ 'is-top': index < 2 }">{{ index + 1 }}</span>
<span class="ranking-index" :class="{ 'is-top': index < 2, 'is-self': item.is_self }">{{ index + 1 }}</span>
<div class="ranking-main">
<div class="ranking-copy">
<strong>{{ item.name || '未命名成员' }}</strong>
<span>{{ formatNumber(item.count) }} 挂号</span>
<strong>
{{ item.name || '未命名成员' }}
<i v-if="item.is_self" class="ranking-self-tag"></i>
</strong>
<span>{{ formatNumber(item.count) }} {{ rankingCountUnit }}</span>
</div>
<div class="ranking-meter" aria-hidden="true">
<span :style="{ width: rankingWidth(item.count, appointmentRankingMax) }" />
@@ -190,14 +194,16 @@
<b class="ranking-value">{{ formatNumber(item.count) }}</b>
</div>
</div>
<el-empty v-else :image-size="72" description="当前数据范围内暂无挂号排行" />
<el-empty v-else :image-size="72" :description="`当前数据范围内暂无${rankingCountUnit}排行`" />
</article>
<article class="dashboard-panel ranking-panel">
<div class="panel-header">
<div>
<h2>{{ dashboard.rankings.performance.title }}</h2>
<p>{{ dashboard.rankings.performance.scope_label }}内今日计入业绩的业务订单</p>
<p>
{{ performanceRankingSubtitle }}
</p>
</div>
<span class="panel-meta">今日</span>
</div>
@@ -206,11 +212,15 @@
v-for="(item, index) in dashboard.rankings.performance.items"
:key="`performance-${item.id}-${index}`"
class="ranking-row"
:class="{ 'is-self': item.is_self }"
>
<span class="ranking-index" :class="{ 'is-top': index < 2 }">{{ index + 1 }}</span>
<span class="ranking-index" :class="{ 'is-top': index < 2, 'is-self': item.is_self }">{{ index + 1 }}</span>
<div class="ranking-main">
<div class="ranking-copy">
<strong>{{ item.name || '未命名部门' }}</strong>
<strong>
{{ item.name || (dashboard.rankings.performance.kind === 'person' ? '未命名成员' : '未命名部门') }}
<i v-if="item.is_self" class="ranking-self-tag"></i>
</strong>
<span>{{ formatNumber(item.count) }} 个诊单</span>
</div>
<div class="ranking-meter" aria-hidden="true">
@@ -335,6 +345,7 @@ interface RankingItem {
name: string
count: number
amount: number
is_self?: boolean
}
type ComparisonDirection = 'up' | 'down' | 'flat'
@@ -355,10 +366,12 @@ const emptyComparison = (): MetricComparison => ({
const createInitialDashboard = () => ({
scope: {
key: '',
kind: '',
scope_value: 4,
label: '',
is_limited: true,
viewer_name: '',
viewer_id: 0,
role_ids: [] as number[],
role_names: [] as string[],
department_names: [] as string[],
@@ -401,11 +414,13 @@ const createInitialDashboard = () => ({
appointments: {
title: '实时挂号排行',
kind: 'assistant',
metric: 'registration',
scope_label: '',
items: [] as RankingItem[],
},
performance: {
title: '今日部门业绩排行',
kind: 'dept',
scope_label: '',
items: [] as RankingItem[],
},
@@ -413,6 +428,7 @@ const createInitialDashboard = () => ({
filters: {
ranking_departments: [] as any[],
ranking_dept_id: 0,
ranking_selectable: true,
},
trend: {
date_range: [] as string[],
@@ -533,9 +549,34 @@ const currentDate = computed(() => new Intl.DateTimeFormat('zh-CN', {
weekday: 'short',
}).format(now.value))
const showRankingDeptSelect = computed(() => (
dashboard.filters.ranking_selectable !== false
&& dashboard.scope.kind !== 'group_leader'
&& dashboard.rankings.appointments.kind !== 'doctor'
&& dashboard.filters.ranking_departments.length > 0
))
const rankingCountUnit = computed(() => (
dashboard.rankings.appointments.metric === 'appointment' ? '预约' : '挂号'
))
const appointmentRankingSubtitle = computed(() => {
if (dashboard.rankings.appointments.metric === 'appointment') {
return ''
}
const actor = dashboard.rankings.appointments.kind === 'doctor' ? '医生' : '成员'
return `${dashboard.rankings.appointments.scope_label}${actor}已支付且实收低于 10 元的订单数`
const scopeLabel = dashboard.rankings.appointments.scope_label || '当前部门'
return `${scopeLabel}${actor}已支付且实收低于 10 元的订单数`
})
const performanceRankingSubtitle = computed(() => {
const scopeLabel = dashboard.rankings.performance.scope_label || '当前部门'
if (dashboard.rankings.performance.kind === 'person') {
return dashboard.scope.kind === 'group_leader'
? '本小组内今日计入业绩的个人订单'
: `${scopeLabel}内今日计入业绩的个人订单`
}
return `${scopeLabel}内今日计入业绩的业务订单`
})
const appointmentRankingMax = computed(() => Math.max(
@@ -656,7 +697,7 @@ const loadDashboard = async () => {
errorMessage.value = ''
try {
const res: any = await performanceDashboardOverview({
ranking_dept_id: rankingDeptId.value,
ranking_dept_id: loaded.value ? (rankingDeptId.value || 0) : undefined,
// GET
_t: Date.now(),
})
@@ -998,6 +1039,11 @@ onBeforeUnmount(() => {
padding: 7px 9px;
border-radius: 11px;
background: var(--dash-surface-muted);
&.is-self {
background: color-mix(in srgb, var(--dash-accent) 16%, var(--dash-surface));
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--dash-accent) 42%, transparent);
}
}
.ranking-index {
@@ -1017,6 +1063,24 @@ onBeforeUnmount(() => {
color: #fff;
background: var(--dash-accent);
}
&.is-self:not(.is-top) {
color: var(--dash-accent);
background: color-mix(in srgb, var(--dash-accent) 18%, var(--el-fill-color-light));
font-weight: 750;
}
}
.ranking-self-tag {
flex-shrink: 0;
padding: 0 5px;
border-radius: 6px;
color: #fff;
font-size: 10px;
font-style: normal;
font-weight: 700;
line-height: 18px;
background: var(--dash-accent);
}
.ranking-main {
@@ -1032,6 +1096,9 @@ onBeforeUnmount(() => {
margin-bottom: 5px;
strong {
display: inline-flex;
align-items: center;
gap: 6px;
overflow: hidden;
font-size: 12px;
text-overflow: ellipsis;
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Some files were not shown because too many files have changed in this diff Show More