import { _ as __nuxt_component_0$1 } from './nuxt-link-BsKoUOa0.mjs';
import { defineComponent, computed, reactive, ref, unref, mergeProps, withCtx, createTextVNode, useSSRContext } from 'vue';
import { ssrRenderAttrs, ssrRenderComponent, ssrIncludeBooleanAttr, ssrLooseContain, ssrLooseEqual, ssrRenderList, ssrRenderAttr, ssrInterpolate, ssrRenderStyle, ssrRenderClass } from 'vue/server-renderer';
import { u as useApi } from './useApi-BNSkSrat.mjs';
import { h as healthLastRunText, R as REGION_LABEL, a as healthReason } from './svchealth-8zW8U1Ef.mjs';
import { _ as _export_sfc, a as useAuthStore } from './server.mjs';
import { n as normalizeRegion, R as REGION_OPTIONS } from './regions-D1dSykUW.mjs';
import '../nitro/nitro.mjs';
import 'node:http';
import 'node:https';
import 'node:events';
import 'node:buffer';
import 'node:fs';
import 'node:path';
import 'node:crypto';
import 'node:url';
import '../routes/renderer.mjs';
import 'vue-bundle-renderer/runtime';
import 'unhead/server';
import 'devalue';
import 'unhead/utils';
import 'pinia';
import 'perfect-debounce';
const LIMIT = 12;
const _sfc_main$1 = /* @__PURE__ */ defineComponent({
__name: "SvcAlertBanner",
__ssrInlineRender: true,
setup(__props, { expose: __expose }) {
const { webApi } = useApi();
const alerts = ref([]);
const errorCount = ref(0);
const warnCount = ref(0);
const lastRun = ref(0);
const expanded = ref(false);
const truncated = ref(false);
const lastRunText = computed(() => healthLastRunText(lastRun.value));
function scopeText(a) {
const app = a.app_name ? a.app_name : "全局";
return a.region ? `${app} · ${REGION_LABEL[a.region] || "区域" + a.region}` : app;
}
function reasonOf(a) {
return healthReason(a);
}
async function load() {
try {
const res = await webApi("getsvcalerts", { limit: LIMIT });
alerts.value = res.alerts || [];
errorCount.value = res.error_count || 0;
warnCount.value = res.warn_count || 0;
lastRun.value = res.last_run || 0;
truncated.value = errorCount.value + warnCount.value > alerts.value.length;
expanded.value = errorCount.value > 0;
} catch {
alerts.value = [];
errorCount.value = 0;
warnCount.value = 0;
}
}
__expose({ load });
return (_ctx, _push, _parent, _attrs) => {
const _component_NuxtLink = __nuxt_component_0$1;
if (errorCount.value + warnCount.value > 0) {
_push(`
0 ? "lv-error" : "lv-warn"]
}, _attrs))} data-v-3dd2fb7c>
${ssrInterpolate(errorCount.value > 0 ? "🚨" : "⚠️")}${ssrInterpolate(errorCount.value > 0 ? `${errorCount.value} 项第三方服务不可用` : `${warnCount.value} 项第三方服务存在隐患`)}`);
if (errorCount.value > 0 && warnCount.value > 0) {
_push(`,另有 ${ssrInterpolate(warnCount.value)} 项隐患`);
} else {
_push(``);
}
_push(` · 最近巡检 ${ssrInterpolate(unref(lastRunText))}
`);
_push(ssrRenderComponent(_component_NuxtLink, {
to: "/svcinspect",
class: "ab-btn ab-btn-primary"
}, {
default: withCtx((_, _push2, _parent2, _scopeId) => {
if (_push2) {
_push2(`前往巡检`);
} else {
return [
createTextVNode("前往巡检")
];
}
}),
_: 1
}, _parent));
_push(`
`);
if (expanded.value) {
_push(`
`);
ssrRenderList(alerts.value, (a) => {
_push(`
${ssrInterpolate(a.name || a.svc_id)}`);
if (a.provider) {
_push(`${ssrInterpolate(a.provider)}`);
} else {
_push(``);
}
_push(`${ssrInterpolate(scopeText(a))}${ssrInterpolate(reasonOf(a))}`);
if (a.fail_streak > 1) {
_push(`连续失败 ${ssrInterpolate(a.fail_streak)} 次`);
} else {
_push(``);
}
_push(`
`);
});
_push(``);
if (truncated.value) {
_push(`
仅显示前 ${ssrInterpolate(alerts.value.length)} 条,其余请到`);
_push(ssrRenderComponent(_component_NuxtLink, {
to: "/svcinspect",
class: "ab-link"
}, {
default: withCtx((_, _push2, _parent2, _scopeId) => {
if (_push2) {
_push2(`服务巡检`);
} else {
return [
createTextVNode("服务巡检")
];
}
}),
_: 1
}, _parent));
_push(`查看
`);
} else {
_push(``);
}
_push(`
`);
} else {
_push(``);
}
_push(`
`);
} else {
_push(``);
}
};
}
});
const _sfc_setup$1 = _sfc_main$1.setup;
_sfc_main$1.setup = (props, ctx) => {
const ssrContext = useSSRContext();
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("components/SvcAlertBanner.vue");
return _sfc_setup$1 ? _sfc_setup$1(props, ctx) : void 0;
};
const __nuxt_component_0 = /* @__PURE__ */ Object.assign(_export_sfc(_sfc_main$1, [["__scopeId", "data-v-3dd2fb7c"]]), { __name: "SvcAlertBanner" });
const _sfc_main = /* @__PURE__ */ defineComponent({
__name: "dashboard",
__ssrInlineRender: true,
setup(__props) {
const auth = useAuthStore();
useApi();
const CARD_STYLE = {
new_user_count: ["👥", "#EEF2FF", "#6366F1"],
active_user_count: ["🟢", "#ECFDF5", "#10B981"],
bind_device_count: ["📱", "#E0F2FE", "#0EA5E9"],
qr_active_count: ["🔳", "#FCE7F3", "#EC4899"],
order_paid_count: ["🧾", "#FEF3C7", "#F59E0B"],
order_paid_amount: ["💰", "#FEE2E2", "#EF4444"],
pay_user_count: ["⭐", "#FFF7ED", "#F97316"],
meet_second: ["🎙️", "#CFFAFE", "#22D3EE"],
trade_second: ["🌐", "#F3E8FF", "#8B5CF6"]
};
const GLOBAL_CARDS_ALL = [
["new_user_count", "累计用户", false],
["bind_device_count", "累计绑定设备", false],
["qr_active_count", "累计二维码激活", false],
["order_paid_count", "累计支付订单", false],
["order_paid_amount", "累计支付金额", true],
["meet_second", "累计会议时长", false],
["trade_second", "累计翻译时长", false]
];
const TODAY_CARDS_ALL = [
["new_user_count", "新增用户", false],
["active_user_count", "活跃用户", false],
["qr_active_count", "二维码激活", false],
["order_paid_count", "支付订单", false],
["order_paid_amount", "支付金额", true],
["pay_user_count", "付费用户", false],
["meet_second", "会议时长", false, true],
["trade_second", "翻译时长", false, true]
];
const PANELS_ALL = [
{
key: "user",
title: "用户增长",
color: "#6366F1",
tint: "#EEF2FF",
icon: "M17 20h5v-2a4 4 0 00-3-3.87M9 20H4v-2a4 4 0 013-3.87m6-1.13a4 4 0 10-4-4 4 4 0 004 4z",
stats: [["new_user_count", "新增用户"], ["active_user_count", "活跃用户"], ["login_user_count", "登录用户"], ["login_count", "登录次数"]],
lines: [["new_user_count", "新增用户", "#6366F1"], ["active_user_count", "活跃用户", "#34D399"]]
},
{
key: "device",
title: "设备激活绑定",
color: "#0EA5E9",
tint: "#E0F2FE",
icon: "M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z",
stats: [["active_device_count", "已激活"], ["bind_device_count", "已绑定"]],
lines: [["active_device_count", "已激活", "#0EA5E9"], ["bind_device_count", "已绑定", "#818CF8"]]
},
{
key: "qrcode",
title: "二维码激活",
color: "#EC4899",
tint: "#FCE7F3",
icon: "M12 4v1m6 11h2m-6 0h-1v4m-6-4H5m14 0h-2m-6-7H5m0 0V5m0 4v6m14-6V5h-4M5 5h4M5 19h4m6 0h4v-4M4 4h6v6H4V4zm10 0h6v6h-6V4zM4 14h6v6H4v-6z",
stats: [["qr_active_count", "二维码激活次数"]],
lines: [["qr_active_count", "二维码激活", "#EC4899"]]
},
{
key: "order",
title: "订单支付",
color: "#F59E0B",
tint: "#FEF3C7",
icon: "M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z",
stats: [["order_create_count", "创建订单"], ["order_paid_count", "支付订单"], ["order_failed_count", "失败订单"], ["pay_user_count", "付费用户"], ["order_paid_amount", "支付金额", true], ["order_create_amount", "创建金额", true]],
lines: [["order_paid_count", "支付订单", "#F59E0B"], ["order_create_count", "创建订单", "#FCD34D"]]
},
{
key: "meeting",
title: "会议",
color: "#22D3EE",
tint: "#CFFAFE",
icon: "M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z",
stats: [["meet_count", "会议次数"], ["meet_second", "会议时长", false, true], ["grant_meet_second", "发放会议时长", false, true]],
lines: [["meet_count", "会议次数", "#22D3EE"], ["meet_second", "会议时长(秒)", "#0EA5E9"]]
},
{
key: "translate",
title: "翻译",
color: "#8B5CF6",
tint: "#F3E8FF",
icon: "M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3 18.129",
stats: [["trade_count", "翻译次数"], ["trade_second", "翻译时长", false, true], ["trade_words", "翻译字数"], ["grant_trade_second", "发放翻译时长", false, true]],
lines: [["trade_count", "翻译次数", "#8B5CF6"], ["trade_second", "翻译时长(秒)", "#A78BFA"]]
}
];
const IS_AGENT = computed(() => auth.identity === 3 && !auth.brandid);
const IS_SCOPED = computed(() => auth.isBrandAccount || auth.isDealer);
computed(() => auth.apps);
const BOUND_REGIONS = computed(() => auth.regions);
computed(() => auth.products.map((p) => parseInt(p)).filter((n) => !isNaN(n)));
const HIDE_FOR_AGENT = ["new_user_count", "active_user_count", "qr_active_count"];
const globalCards = computed(() => IS_AGENT.value ? GLOBAL_CARDS_ALL.filter((c) => !HIDE_FOR_AGENT.includes(c[0])) : GLOBAL_CARDS_ALL);
const todayCards = computed(() => IS_AGENT.value ? TODAY_CARDS_ALL.filter((c) => !HIDE_FOR_AGENT.includes(c[0])) : TODAY_CARDS_ALL);
const panels = computed(() => IS_AGENT.value ? PANELS_ALL.filter((p) => p.key !== "user" && p.key !== "qrcode") : PANELS_ALL);
const rangeBtns = [[7, "7天"], [30, "30天"], [90, "90天"], [0, "全部"]];
const filters = reactive({ app: "", region: "", product: "", channel: "" });
const appOptions = ref([]);
const channelOptions = ref([]);
const productOptions = ref([]);
const regionOptions = computed(() => {
if (IS_AGENT.value && BOUND_REGIONS.value.length) {
const bound = new Set(BOUND_REGIONS.value.map((r) => normalizeRegion(r)));
return REGION_OPTIONS.filter((r) => bound.has(r.value));
}
return REGION_OPTIONS;
});
const globalData = ref({});
const deviceData = ref({});
const todayData = ref({});
const yestData = ref({});
const todayTag = ref("实时");
const panelStats = ref({});
const panelDays = reactive({});
PANELS_ALL.forEach((p) => {
panelDays[p.key] = 30;
});
function fmt(n, isAmount) {
n = Number(n) || 0;
return isAmount ? "¥" + (n / 100).toLocaleString(void 0, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : n.toLocaleString();
}
function fmtSecs(sec) {
sec = Math.max(0, Math.floor(Number(sec) || 0));
const h = Math.floor(sec / 3600), m = Math.floor(sec % 3600 / 60), s = sec % 60;
if (h > 0) return h + "时" + m + "分";
if (m > 0) return m + "分" + s + "秒";
return s + "秒";
}
function fmtStat(v, isAmount, isSecs) {
if (isSecs) return fmtSecs(v);
return fmt(v, isAmount);
}
function pctDelta(cur, prev) {
cur = Number(cur) || 0;
prev = Number(prev) || 0;
if (prev === 0) return cur > 0 ? null : 0;
return (cur - prev) / prev * 100;
}
function cardStyle(field) {
return CARD_STYLE[field] || ["📊", "#f1f5f9", "#94a3b8"];
}
function globalVal(c) {
const src = c[0] === "bind_device_count" || c[0] === "active_device_count" ? deviceData.value : globalData.value;
const isSecs = c[0] === "meet_second" || c[0] === "trade_second";
return fmtStat(src[c[0]], c[2], isSecs);
}
function todayVal(c) {
return fmtStat(todayData.value[c[0]], c[2], !!c[3]);
}
function todayDelta(c) {
const pct = pctDelta(todayData.value[c[0]], yestData.value[c[0]]);
if (pct === null) return { type: "new", arrow: "", abs: "" };
const type = pct > 0 ? "up" : pct < 0 ? "down" : "flat";
const arrow = pct > 0 ? "▲" : pct < 0 ? "▼" : "—";
return { type, arrow, abs: Math.abs(pct).toFixed(1) };
}
function panelStatVal(p, s) {
return fmtStat((panelStats.value[p.key] || {})[s[0]], s[2], s[3]);
}
return (_ctx, _push, _parent, _attrs) => {
const _component_SvcAlertBanner = __nuxt_component_0;
_push(``);
_push(ssrRenderComponent(_component_SvcAlertBanner, null, null, _parent));
_push(`
`);
if (!IS_SCOPED.value || appOptions.value.length) {
_push(`
`);
} else {
_push(``);
}
if (!IS_SCOPED.value) {
_push(`
`);
} else {
_push(``);
}
_push(`
`);
if (!unref(auth).isDealer) {
_push(`
`);
} else {
_push(``);
}
_push(`
顶部为全量总览;各版面右上角可单独选时间周期
累计数据
全量累计 · 随筛选条件变化`);
ssrRenderList(globalCards.value, (c) => {
_push(`
${ssrInterpolate(cardStyle(c[0])[0])}${ssrInterpolate(c[1])}
${ssrInterpolate(globalVal(c))}
`);
});
_push(`
今日数据
${ssrInterpolate(todayTag.value)}今日 0 点至现在 · 含较昨日同比(设备类无日期维度,仅见累计)`);
ssrRenderList(todayCards.value, (c) => {
_push(`
${ssrInterpolate(c[1])}${ssrInterpolate(cardStyle(c[0])[0])}
${ssrInterpolate(todayVal(c))}
`);
});
_push(`
趋势分析
各版面右上角可独立选择时间周期`);
ssrRenderList(panels.value, (p) => {
_push(`
${ssrInterpolate(p.title)}`);
if (p.key === "device") {
_push(`
当前累计 · 无日期维度`);
} else {
_push(`
`);
ssrRenderList(rangeBtns, (d) => {
_push(``);
});
_push(`
`);
}
_push(`
`);
ssrRenderList(p.stats, (s) => {
_push(`
${ssrInterpolate(s[1])}
${ssrInterpolate(panelStatVal(p, s))}
`);
});
_push(`
`);
if (p.key !== "device") {
_push(`
`);
} else {
_push(``);
}
_push(`
`);
});
_push(`
`);
};
}
});
const _sfc_setup = _sfc_main.setup;
_sfc_main.setup = (props, ctx) => {
const ssrContext = useSSRContext();
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("pages/dashboard.vue");
return _sfc_setup ? _sfc_setup(props, ctx) : void 0;
};
const dashboard = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-449e8f97"]]);
export { dashboard as default };
//# sourceMappingURL=dashboard-Y8255DFm.mjs.map