You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
267 lines
17 KiB
267 lines
17 KiB
import { defineComponent, computed, reactive, ref, useSSRContext } from 'vue';
|
|
import { ssrRenderAttrs, ssrIncludeBooleanAttr, ssrLooseContain, ssrLooseEqual, ssrRenderList, ssrRenderAttr, ssrInterpolate, ssrRenderStyle, ssrRenderClass } from 'vue/server-renderer';
|
|
import { u as useAuthStore } from './auth-QFkCoU1C.mjs';
|
|
import { u as useApi } from './useApi-B7CB0Cnn.mjs';
|
|
import { _ as _export_sfc } from './server.mjs';
|
|
import 'pinia';
|
|
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 'perfect-debounce';
|
|
|
|
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
__name: "dashboard",
|
|
__ssrInlineRender: true,
|
|
setup(__props) {
|
|
const auth = useAuthStore();
|
|
useApi();
|
|
const REGIONS = [
|
|
["cn", "中国"],
|
|
["us", "美国"],
|
|
["ea", "东亚"],
|
|
["sea", "东南亚"],
|
|
["sa", "南亚"],
|
|
["me", "中东"],
|
|
["eu", "欧洲"],
|
|
["ru", "俄罗斯"],
|
|
["af", "非洲"],
|
|
["sam", "南美"],
|
|
["nam", "北美"],
|
|
["oc", "大洋洲"],
|
|
["br", "巴西"],
|
|
["in", "印度"]
|
|
];
|
|
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);
|
|
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: "" });
|
|
const appOptions = ref([]);
|
|
const productOptions = ref([]);
|
|
const regionOptions = computed(() => {
|
|
if (IS_AGENT.value && BOUND_REGIONS.value.length) {
|
|
return REGIONS.filter((r) => BOUND_REGIONS.value.includes(r[0])).map((r) => ({ value: r[0], label: r[1] }));
|
|
}
|
|
return REGIONS.map((r) => ({ value: r[0], label: r[1] }));
|
|
});
|
|
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) => {
|
|
_push(`<div${ssrRenderAttrs(_attrs)} data-v-d169bdaa><div class="panel p-4 mb-5" data-v-d169bdaa><div class="flex flex-wrap items-end gap-3" data-v-d169bdaa><div class="form-control" data-v-d169bdaa><label class="label py-1" data-v-d169bdaa><span class="label-text text-xs text-slate-500" data-v-d169bdaa>应用</span></label><select class="select select-bordered select-sm w-40" data-v-d169bdaa><option value="" data-v-d169bdaa${ssrIncludeBooleanAttr(Array.isArray(filters.app) ? ssrLooseContain(filters.app, "") : ssrLooseEqual(filters.app, "")) ? " selected" : ""}>全部应用</option><!--[-->`);
|
|
ssrRenderList(appOptions.value, (a) => {
|
|
_push(`<option${ssrRenderAttr("value", a.value)} data-v-d169bdaa${ssrIncludeBooleanAttr(Array.isArray(filters.app) ? ssrLooseContain(filters.app, a.value) : ssrLooseEqual(filters.app, a.value)) ? " selected" : ""}>${ssrInterpolate(a.label)}</option>`);
|
|
});
|
|
_push(`<!--]--></select></div><div class="form-control" data-v-d169bdaa><label class="label py-1" data-v-d169bdaa><span class="label-text text-xs text-slate-500" data-v-d169bdaa>区域</span></label><select class="select select-bordered select-sm w-32" data-v-d169bdaa><option value="" data-v-d169bdaa${ssrIncludeBooleanAttr(Array.isArray(filters.region) ? ssrLooseContain(filters.region, "") : ssrLooseEqual(filters.region, "")) ? " selected" : ""}>全部区域</option><!--[-->`);
|
|
ssrRenderList(regionOptions.value, (r) => {
|
|
_push(`<option${ssrRenderAttr("value", r.value)} data-v-d169bdaa${ssrIncludeBooleanAttr(Array.isArray(filters.region) ? ssrLooseContain(filters.region, r.value) : ssrLooseEqual(filters.region, r.value)) ? " selected" : ""}>${ssrInterpolate(r.label)}</option>`);
|
|
});
|
|
_push(`<!--]--></select></div><div class="form-control" data-v-d169bdaa><label class="label py-1" data-v-d169bdaa><span class="label-text text-xs text-slate-500" data-v-d169bdaa>产品</span></label><select class="select select-bordered select-sm w-48" data-v-d169bdaa><option value="" data-v-d169bdaa${ssrIncludeBooleanAttr(Array.isArray(filters.product) ? ssrLooseContain(filters.product, "") : ssrLooseEqual(filters.product, "")) ? " selected" : ""}>全部产品</option><!--[-->`);
|
|
ssrRenderList(productOptions.value, (p) => {
|
|
_push(`<option${ssrRenderAttr("value", p.value)} data-v-d169bdaa${ssrIncludeBooleanAttr(Array.isArray(filters.product) ? ssrLooseContain(filters.product, p.value) : ssrLooseEqual(filters.product, p.value)) ? " selected" : ""}>${ssrInterpolate(p.label)}</option>`);
|
|
});
|
|
_push(`<!--]--></select></div><button class="btn btn-primary btn-sm text-white" data-v-d169bdaa>查询</button><span class="text-xs text-slate-400 ml-auto self-center" data-v-d169bdaa>顶部为全量总览;各版面右上角可单独选时间周期</span></div></div><div class="sec-title muted" data-v-d169bdaa><span class="bar" style="${ssrRenderStyle({ "background": "#cbd5e1" })}" data-v-d169bdaa></span><h2 data-v-d169bdaa>累计数据</h2><span class="sub" data-v-d169bdaa>全量累计 · 随筛选条件变化</span></div><div class="grid grid-cols-2 md:grid-cols-4 gap-3 mb-8" data-v-d169bdaa><!--[-->`);
|
|
ssrRenderList(globalCards.value, (c) => {
|
|
_push(`<div class="cum-card" data-v-d169bdaa><span class="ico" style="${ssrRenderStyle({ background: cardStyle(c[0])[1] })}" data-v-d169bdaa>${ssrInterpolate(cardStyle(c[0])[0])}</span><div class="body" data-v-d169bdaa><div class="lbl" data-v-d169bdaa>${ssrInterpolate(c[1])}</div><div class="val" data-v-d169bdaa>${ssrInterpolate(globalVal(c))}</div></div></div>`);
|
|
});
|
|
_push(`<!--]--></div><div class="today-wrap mb-8" data-v-d169bdaa><div class="sec-title hero" style="${ssrRenderStyle({ "margin-bottom": "16px" })}" data-v-d169bdaa><span class="bar" style="${ssrRenderStyle({ "background": "linear-gradient(180deg,#0EA5E9,#22D3EE)" })}" data-v-d169bdaa></span><h2 data-v-d169bdaa>今日数据</h2><span class="tag" style="${ssrRenderStyle({ "color": "#0369a1", "background": "#dbeafe" })}" data-v-d169bdaa>${ssrInterpolate(todayTag.value)}</span><span class="sub" data-v-d169bdaa>今日 0 点至现在 · 含较昨日同比(设备类无日期维度,仅见累计)</span></div><div class="grid grid-cols-2 md:grid-cols-4 gap-4" data-v-d169bdaa><!--[-->`);
|
|
ssrRenderList(todayCards.value, (c) => {
|
|
_push(`<div class="today-card" data-v-d169bdaa><span class="accent" style="${ssrRenderStyle({ background: cardStyle(c[0])[2] })}" data-v-d169bdaa></span><div class="today-card-top" data-v-d169bdaa><span class="lbl" data-v-d169bdaa>${ssrInterpolate(c[1])}</span><span class="ico" style="${ssrRenderStyle({ background: cardStyle(c[0])[1] })}" data-v-d169bdaa>${ssrInterpolate(cardStyle(c[0])[0])}</span></div><div class="val" data-v-d169bdaa>${ssrInterpolate(todayVal(c))}</div><div class="foot" data-v-d169bdaa>`);
|
|
if (todayDelta(c).type === "new") {
|
|
_push(`<span class="delta up" data-v-d169bdaa>新</span>`);
|
|
} else if (todayDelta(c).type !== "none") {
|
|
_push(`<span class="${ssrRenderClass([todayDelta(c).type, "delta"])}" data-v-d169bdaa>${ssrInterpolate(todayDelta(c).arrow)} ${ssrInterpolate(todayDelta(c).abs)}%</span>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`<span data-v-d169bdaa>较昨日</span></div></div>`);
|
|
});
|
|
_push(`<!--]--></div></div><div class="sec-title" data-v-d169bdaa><span class="bar" style="${ssrRenderStyle({ "background": "linear-gradient(180deg,#10B981,#22D3EE)" })}" data-v-d169bdaa></span><h2 data-v-d169bdaa>趋势分析</h2><span class="sub" data-v-d169bdaa>各版面右上角可独立选择时间周期</span></div><div class="grid grid-cols-1 xl:grid-cols-2 gap-6" data-v-d169bdaa><!--[-->`);
|
|
ssrRenderList(panels.value, (p) => {
|
|
_push(`<div class="panel" data-v-d169bdaa><div class="panel-head" data-v-d169bdaa><span class="panel-dot" style="${ssrRenderStyle({ background: p.tint })}" data-v-d169bdaa><svg width="17" height="17" fill="none" viewBox="0 0 24 24"${ssrRenderAttr("stroke", p.color)} data-v-d169bdaa><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"${ssrRenderAttr("d", p.icon)} data-v-d169bdaa></path></svg></span><span class="font-semibold text-slate-700 text-sm" data-v-d169bdaa>${ssrInterpolate(p.title)}</span>`);
|
|
if (p.key === "device") {
|
|
_push(`<span class="ml-auto text-xs text-slate-400" data-v-d169bdaa>当前累计 · 无日期维度</span>`);
|
|
} else {
|
|
_push(`<div class="ml-auto flex items-center gap-1" data-v-d169bdaa><!--[-->`);
|
|
ssrRenderList(rangeBtns, (d) => {
|
|
_push(`<button class="${ssrRenderClass([{ "tbtn-on": d[0] === panelDays[p.key] }, "tbtn"])}" data-v-d169bdaa>${ssrInterpolate(d[1])}</button>`);
|
|
});
|
|
_push(`<!--]--></div>`);
|
|
}
|
|
_push(`</div><div class="px-5 py-4" data-v-d169bdaa><div class="grid grid-cols-2 sm:grid-cols-3 gap-2.5" data-v-d169bdaa><!--[-->`);
|
|
ssrRenderList(p.stats, (s) => {
|
|
_push(`<div class="stat-box" data-v-d169bdaa><div class="stat-label" data-v-d169bdaa>${ssrInterpolate(s[1])}</div><div class="stat-val" data-v-d169bdaa>${ssrInterpolate(panelStatVal(p, s))}</div></div>`);
|
|
});
|
|
_push(`<!--]--></div>`);
|
|
if (p.key !== "device") {
|
|
_push(`<div style="${ssrRenderStyle({ "height": "170px", "margin-top": "1rem" })}" data-v-d169bdaa><canvas data-v-d169bdaa></canvas></div>`);
|
|
} else {
|
|
_push(`<!---->`);
|
|
}
|
|
_push(`</div></div>`);
|
|
});
|
|
_push(`<!--]--></div></div>`);
|
|
};
|
|
}
|
|
});
|
|
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-d169bdaa"]]);
|
|
|
|
export { dashboard as default };
|
|
//# sourceMappingURL=dashboard-ClL32CVK.mjs.map
|
|
|