持续优化

This commit is contained in:
eafonyang
2026-06-30 10:41:03 +08:00
parent d5bb46e738
commit cac5bef5a7
11 changed files with 841 additions and 203 deletions
+6 -28
View File
@@ -64,34 +64,6 @@ const Ota = sequelize.define('Ota', {
defaultValue: 0,
comment: '是否灰度,1-是,0-否',
},
pawVerCode: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
field: 'pawVerCode',
comment: '配对版本号',
},
pawVerName: {
type: DataTypes.STRING(20),
allowNull: false,
defaultValue: '',
field: 'pawVerName',
comment: '配对版本名称',
},
pawUrl: {
type: DataTypes.STRING(255),
allowNull: false,
defaultValue: '',
field: 'pawUrl',
comment: '配对版本 URL',
},
pawMd5: {
type: DataTypes.STRING(32),
allowNull: false,
defaultValue: '',
field: 'pawMd5',
comment: '配对版本 MD5',
},
startTime: {
type: DataTypes.DATE,
allowNull: true,
@@ -110,6 +82,12 @@ const Ota = sequelize.define('Ota', {
defaultValue: 1,
comment: '是否可用',
},
create_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
comment: '创建时间',
},
}, {
tableName: 'ota',
comment: 'OTA 升级版本',
+65
View File
@@ -0,0 +1,65 @@
/**
* Dashboard 路由 — 首页统计数据
*/
const router = require('express').Router();
const { Op } = require('sequelize');
const Model = require('../models/Model');
const Ota = require('../models/Ota');
const logger = require('../config/logger');
const { ApiResponse } = require('../utils/response');
const { authMiddleware } = require('../middleware/auth');
// 所有 dashboard 接口需登录
router.use(authMiddleware);
/**
* GET /api/dashboard/today
* 返回今日新增的耳机型号和 OTA 数据
*/
router.get('/api/dashboard/today', async (req, res) => {
try {
// 今日 00:00:00 (UTC+8)
const now = new Date();
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
// 今日新增型号
const newModels = await Model.findAll({
where: {
create_at: { [Op.gte]: todayStart },
},
order: [['id', 'DESC']],
attributes: ['id', 'brand_name', 'name', 'create_at'],
});
// 今日新增 OTA
const newOtas = await Ota.findAll({
where: {
create_at: { [Op.gte]: todayStart },
},
order: [['id', 'DESC']],
attributes: ['id', 'verName', 'model', 'create_at'],
});
return res.json(
ApiResponse.success({
models: newModels.map((m) => ({
id: m.id,
brand_name: m.brand_name,
name: m.name,
create_at: m.create_at ? m.create_at.toISOString() : null,
})),
otas: newOtas.map((o) => ({
id: o.id,
verName: o.verName,
model: o.model,
create_at: o.create_at ? o.create_at.toISOString() : null,
})),
})
);
} catch (e) {
logger.error(`Error getting dashboard today stats: ${e.message}`);
return res.json(ApiResponse.error(`获取统计数据失败:${e.message}`));
}
});
module.exports = router;
+2 -1
View File
@@ -7,5 +7,6 @@ const modelsRouter = require('./models');
const otaRouter = require('./ota');
const shareCodeLogsRouter = require('./shareCodeLogs');
const usersRouter = require('./users');
const dashboardRouter = require('./dashboard');
module.exports = [authRouter, brandsRouter, modelsRouter, otaRouter, shareCodeLogsRouter, usersRouter];
module.exports = [authRouter, brandsRouter, modelsRouter, otaRouter, shareCodeLogsRouter, usersRouter, dashboardRouter];
+1 -4
View File
@@ -281,13 +281,10 @@ function otaToDict(ota) {
hw: ota.hw,
target: ota.target,
beta: ota.beta,
pawVerCode: ota.pawVerCode,
pawVerName: ota.pawVerName,
pawUrl: ota.pawUrl,
pawMd5: ota.pawMd5,
startTime: ota.startTime ? ota.startTime.toISOString() : null,
endTime: ota.endTime ? ota.endTime.toISOString() : null,
status: ota.status,
create_at: ota.create_at ? ota.create_at.toISOString() : null,
};
}
-8
View File
@@ -11,10 +11,6 @@ const OtaCreateSchema = z.object({
hw: z.number().int().optional().default(0),
target: z.number().int().min(0).max(1).optional().default(0),
beta: z.number().int().min(0).max(1).optional().default(0),
pawVerCode: z.number().int().optional().default(0),
pawVerName: z.string().max(20).optional().default(''),
pawUrl: z.string().max(255).optional().default(''),
pawMd5: z.string().length(32).optional().default(''),
startTime: z.string().optional().nullable(),
endTime: z.string().optional().nullable(),
status: z.number().int().min(0).max(1).optional().default(1),
@@ -31,10 +27,6 @@ const OtaUpdateSchema = z.object({
hw: z.number().int().optional().nullable(),
target: z.number().int().min(0).max(1).optional().nullable(),
beta: z.number().int().min(0).max(1).optional().nullable(),
pawVerCode: z.number().int().optional().nullable(),
pawVerName: z.string().max(20).optional().nullable(),
pawUrl: z.string().max(255).optional().nullable(),
pawMd5: z.string().length(32).optional().nullable(),
startTime: z.string().optional().nullable(),
endTime: z.string().optional().nullable(),
status: z.number().int().min(0).max(1).optional().nullable(),
+2 -2
View File
@@ -16,8 +16,8 @@
"vue-router": "^4.2.4"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.3.4",
"vite": "^4.4.9"
"@vitejs/plugin-vue": "^5.2.4",
"vite": "^6.4.3"
},
"packageManager": "pnpm@11.5.2+sha512.71c631e382066efc25625d5cf029075de07b61b37f6e27350fbd84b1bda5864c8c1967adc280776b45c30a715c0359a3be08fef42d5bb09e2b99029979692916"
}
+454 -136
View File
@@ -28,11 +28,11 @@ importers:
version: 4.6.4(vue@3.5.35)
devDependencies:
'@vitejs/plugin-vue':
specifier: ^4.3.4
version: 4.3.4(vite@4.4.9)(vue@3.5.35)
specifier: ^5.2.4
version: 5.2.4(vite@6.4.3)(vue@3.5.35)
vite:
specifier: ^4.4.9
version: 4.4.9
specifier: ^6.4.3
version: 6.4.3
packages:
@@ -62,135 +62,153 @@ packages:
peerDependencies:
vue: ^3.2.0
'@esbuild/android-arm64@0.18.10':
resolution: {integrity: sha512-ynm4naLbNbK0ajf9LUWtQB+6Vfg1Z/AplArqr4tGebC00Z6m9Y91OVIcjDa461wGcZwcaHYaZAab4yJxfhisTQ==}
engines: {node: '>=12'}
'@esbuild/aix-ppc64@0.25.0':
resolution: {integrity: sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
'@esbuild/android-arm64@0.25.0':
resolution: {integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
'@esbuild/android-arm@0.18.10':
resolution: {integrity: sha512-3KClmVNd+Fku82uZJz5C4Rx8m1PPmWUFz5Zkw8jkpZPOmsq+EG1TTOtw1OXkHuX3WczOFQigrtf60B1ijKwNsg==}
engines: {node: '>=12'}
'@esbuild/android-arm@0.25.0':
resolution: {integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
'@esbuild/android-x64@0.18.10':
resolution: {integrity: sha512-vFfXj8P9Yfjh54yqUDEHKzqzYuEfPyAOl3z7R9hjkwt+NCvbn9VMxX+IILnAfdImRBfYVItgSUsqGKhJFnBwZw==}
engines: {node: '>=12'}
'@esbuild/android-x64@0.25.0':
resolution: {integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
'@esbuild/darwin-arm64@0.18.10':
resolution: {integrity: sha512-k2OJQ7ZxE6sVc91+MQeZH9gFeDAH2uIYALPAwTjTCvcPy9Dzrf7V7gFUQPYkn09zloWhQ+nvxWHia2x2ZLR0sQ==}
engines: {node: '>=12'}
'@esbuild/darwin-arm64@0.25.0':
resolution: {integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-x64@0.18.10':
resolution: {integrity: sha512-tnz/mdZk1L1Z3WpGjin/L2bKTe8/AKZpI8fcCLtH+gq8WXWsCNJSxlesAObV4qbtTl6pG5vmqFXfWUQ5hV8PAQ==}
engines: {node: '>=12'}
'@esbuild/darwin-x64@0.25.0':
resolution: {integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
'@esbuild/freebsd-arm64@0.18.10':
resolution: {integrity: sha512-QJluV0LwBrbHnYYwSKC+K8RGz0g/EyhpQH1IxdoFT0nM7PfgjE+aS8wxq/KFEsU0JkL7U/EEKd3O8xVBxXb2aA==}
engines: {node: '>=12'}
'@esbuild/freebsd-arm64@0.25.0':
resolution: {integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-x64@0.18.10':
resolution: {integrity: sha512-Hi/ycUkS6KTw+U9G5PK5NoK7CZboicaKUSVs0FSiPNtuCTzK6HNM4DIgniH7hFaeuszDS9T4dhAHWiLSt/Y5Ng==}
engines: {node: '>=12'}
'@esbuild/freebsd-x64@0.25.0':
resolution: {integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
'@esbuild/linux-arm64@0.18.10':
resolution: {integrity: sha512-Nz6XcfRBOO7jSrVpKAyEyFOPGhySPNlgumSDhWAspdQQ11ub/7/NZDMhWDFReE9QH/SsCOCLQbdj0atAk/HMOQ==}
engines: {node: '>=12'}
'@esbuild/linux-arm64@0.25.0':
resolution: {integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm@0.18.10':
resolution: {integrity: sha512-HfFoxY172tVHPIvJy+FHxzB4l8xU7e5cxmNS11cQ2jt4JWAukn/7LXaPdZid41UyTweqa4P/1zs201gRGCTwHw==}
engines: {node: '>=12'}
'@esbuild/linux-arm@0.25.0':
resolution: {integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
'@esbuild/linux-ia32@0.18.10':
resolution: {integrity: sha512-otMdmSmkMe+pmiP/bZBjfphyAsTsngyT9RCYwoFzqrveAbux9nYitDTpdgToG0Z0U55+PnH654gCH2GQ1aB6Yw==}
engines: {node: '>=12'}
'@esbuild/linux-ia32@0.25.0':
resolution: {integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
'@esbuild/linux-loong64@0.18.10':
resolution: {integrity: sha512-t8tjFuON1koxskzQ4VFoh0T5UDUMiLYjwf9Wktd0tx8AoK6xgU+5ubKOpWpcnhEQ2tESS5u0v6QuN8PX/ftwcQ==}
engines: {node: '>=12'}
'@esbuild/linux-loong64@0.25.0':
resolution: {integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
'@esbuild/linux-mips64el@0.18.10':
resolution: {integrity: sha512-+dUkcVzcfEJHz3HEnVpIJu8z8Wdn2n/nWMWdl6FVPFGJAVySO4g3+XPzNKFytVFwf8hPVDwYXzVcu8GMFqsqZw==}
engines: {node: '>=12'}
'@esbuild/linux-mips64el@0.25.0':
resolution: {integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
'@esbuild/linux-ppc64@0.18.10':
resolution: {integrity: sha512-sO3PjjxEGy+PY2qkGe2gwJbXdZN9wAYpVBZWFD0AwAoKuXRkWK0/zaMQ5ekUFJDRDCRm8x5U0Axaub7ynH/wVg==}
engines: {node: '>=12'}
'@esbuild/linux-ppc64@0.25.0':
resolution: {integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
'@esbuild/linux-riscv64@0.18.10':
resolution: {integrity: sha512-JDtdbJg3yjDeXLv4lZYE1kiTnxv73/8cbPHY9T/dUKi8rYOM/k5b3W4UJLMUksuQ6nTm5c89W1nADsql6FW75A==}
engines: {node: '>=12'}
'@esbuild/linux-riscv64@0.25.0':
resolution: {integrity: sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
'@esbuild/linux-s390x@0.18.10':
resolution: {integrity: sha512-NLuSKcp8WckjD2a7z5kzLiCywFwBTMlIxDNuud1AUGVuwBBJSkuubp6cNjJ0p5c6CZaA3QqUGwjHJBiG1SoOFw==}
engines: {node: '>=12'}
'@esbuild/linux-s390x@0.25.0':
resolution: {integrity: sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
'@esbuild/linux-x64@0.18.10':
resolution: {integrity: sha512-wj2KRsCsFusli+6yFgNO/zmmLslislAWryJnodteRmGej7ZzinIbMdsyp13rVGde88zxJd5vercNYK9kuvlZaQ==}
engines: {node: '>=12'}
'@esbuild/linux-x64@0.25.0':
resolution: {integrity: sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
'@esbuild/netbsd-x64@0.18.10':
resolution: {integrity: sha512-pQ9QqxEPI3cVRZyUtCoZxhZK3If+7RzR8L2yz2+TDzdygofIPOJFaAPkEJ5rYIbUO101RaiYxfdOBahYexLk5A==}
engines: {node: '>=12'}
'@esbuild/netbsd-arm64@0.25.0':
resolution: {integrity: sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [netbsd]
'@esbuild/netbsd-x64@0.25.0':
resolution: {integrity: sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
'@esbuild/openbsd-x64@0.18.10':
resolution: {integrity: sha512-k8GTIIW9I8pEEfoOUm32TpPMgSg06JhL5DO+ql66aLTkOQUs0TxCA67Wi7pv6z8iF8STCGcNbm3UWFHLuci+ag==}
engines: {node: '>=12'}
'@esbuild/openbsd-arm64@0.25.0':
resolution: {integrity: sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
'@esbuild/openbsd-x64@0.25.0':
resolution: {integrity: sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
'@esbuild/sunos-x64@0.18.10':
resolution: {integrity: sha512-vIGYJIdEI6d4JBucAx8py792G8J0GP40qSH+EvSt80A4zvGd6jph+5t1g+eEXcS2aRpgZw6CrssNCFZxTdEsxw==}
engines: {node: '>=12'}
'@esbuild/sunos-x64@0.25.0':
resolution: {integrity: sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
'@esbuild/win32-arm64@0.18.10':
resolution: {integrity: sha512-kRhNcMZFGMW+ZHCarAM1ypr8OZs0k688ViUCetVCef9p3enFxzWeBg9h/575Y0nsFu0ZItluCVF5gMR2pwOEpA==}
engines: {node: '>=12'}
'@esbuild/win32-arm64@0.25.0':
resolution: {integrity: sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
'@esbuild/win32-ia32@0.18.10':
resolution: {integrity: sha512-AR9PX1whYaYh9p0EOaKna0h48F/A101Mt/ag72+kMkkBZXPQ7cjbz2syXI/HI3OlBdUytSdHneljfjvUoqwqiQ==}
engines: {node: '>=12'}
'@esbuild/win32-ia32@0.25.0':
resolution: {integrity: sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
'@esbuild/win32-x64@0.18.10':
resolution: {integrity: sha512-5sTkYhAGHNRr6bVf4RM0PsscqVr6/DBYdrlMh168oph3usid3lKHcHEEHmr34iZ9GHeeg2juFOxtpl6XyC3tpw==}
engines: {node: '>=12'}
'@esbuild/win32-x64@0.25.0':
resolution: {integrity: sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
@@ -206,9 +224,150 @@ packages:
'@jridgewell/sourcemap-codec@1.5.5':
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
'@rollup/rollup-android-arm-eabi@4.62.2':
resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==}
cpu: [arm]
os: [android]
'@rollup/rollup-android-arm64@4.62.2':
resolution: {integrity: sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==}
cpu: [arm64]
os: [android]
'@rollup/rollup-darwin-arm64@4.62.2':
resolution: {integrity: sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==}
cpu: [arm64]
os: [darwin]
'@rollup/rollup-darwin-x64@4.62.2':
resolution: {integrity: sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==}
cpu: [x64]
os: [darwin]
'@rollup/rollup-freebsd-arm64@4.62.2':
resolution: {integrity: sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==}
cpu: [arm64]
os: [freebsd]
'@rollup/rollup-freebsd-x64@4.62.2':
resolution: {integrity: sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==}
cpu: [x64]
os: [freebsd]
'@rollup/rollup-linux-arm-gnueabihf@4.62.2':
resolution: {integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.62.2':
resolution: {integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==}
cpu: [arm]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.62.2':
resolution: {integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.62.2':
resolution: {integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-loong64-gnu@4.62.2':
resolution: {integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==}
cpu: [loong64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-loong64-musl@4.62.2':
resolution: {integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==}
cpu: [loong64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-ppc64-gnu@4.62.2':
resolution: {integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-ppc64-musl@4.62.2':
resolution: {integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==}
cpu: [ppc64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-riscv64-gnu@4.62.2':
resolution: {integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.62.2':
resolution: {integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==}
cpu: [riscv64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.62.2':
resolution: {integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.62.2':
resolution: {integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.62.2':
resolution: {integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==}
cpu: [x64]
os: [linux]
libc: [musl]
'@rollup/rollup-openbsd-x64@4.62.2':
resolution: {integrity: sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==}
cpu: [x64]
os: [openbsd]
'@rollup/rollup-openharmony-arm64@4.62.2':
resolution: {integrity: sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==}
cpu: [arm64]
os: [openharmony]
'@rollup/rollup-win32-arm64-msvc@4.62.2':
resolution: {integrity: sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==}
cpu: [arm64]
os: [win32]
'@rollup/rollup-win32-ia32-msvc@4.62.2':
resolution: {integrity: sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==}
cpu: [ia32]
os: [win32]
'@rollup/rollup-win32-x64-gnu@4.62.2':
resolution: {integrity: sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==}
cpu: [x64]
os: [win32]
'@rollup/rollup-win32-x64-msvc@4.62.2':
resolution: {integrity: sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==}
cpu: [x64]
os: [win32]
'@sxzz/popperjs-es@2.11.8':
resolution: {integrity: sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==}
'@types/estree@1.0.9':
resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
'@types/lodash-es@4.17.12':
resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
@@ -218,11 +377,11 @@ packages:
'@types/web-bluetooth@0.0.21':
resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
'@vitejs/plugin-vue@4.3.4':
resolution: {integrity: sha512-ciXNIHKPriERBisHFBvnTbfKa6r9SAesOYXeGDzgegcvy9Q4xdScSHAmKbNT0M3O0S9LKhIf5/G+UYG4NnnzYw==}
engines: {node: ^14.18.0 || >=16.0.0}
'@vitejs/plugin-vue@5.2.4':
resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==}
engines: {node: ^18.0.0 || >=20.0.0}
peerDependencies:
vite: ^4.0.0
vite: ^5.0.0 || ^6.0.0
vue: ^3.2.25
'@vue/compiler-core@3.5.35':
@@ -339,14 +498,23 @@ packages:
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
engines: {node: '>= 0.4'}
esbuild@0.18.10:
resolution: {integrity: sha512-33WKo67auOXzZHBY/9DTJRo7kIvfU12S+D4sp2wIz39N88MDIaCGyCwbW01RR70pK6Iya0I74lHEpyLfFqOHPA==}
engines: {node: '>=12'}
esbuild@0.25.0:
resolution: {integrity: sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==}
engines: {node: '>=18'}
hasBin: true
estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
fdir@6.5.0:
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
engines: {node: '>=12.0.0'}
peerDependencies:
picomatch: ^3 || ^4
peerDependenciesMeta:
picomatch:
optional: true
follow-redirects@1.16.0:
resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==}
engines: {node: '>=4.0'}
@@ -441,6 +609,10 @@ packages:
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
picomatch@4.0.4:
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
engines: {node: '>=12'}
postcss@8.5.15:
resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==}
engines: {node: ^10 || ^12 || >=14}
@@ -449,42 +621,58 @@ packages:
resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==}
engines: {node: '>=10'}
rollup@3.30.0:
resolution: {integrity: sha512-kQvGasUgN+AlWGliFn2POSajRQEsULVYFGTvOZmK06d7vCD+YhZztt70kGk3qaeAXeWYL5eO7zx+rAubBc55eA==}
engines: {node: '>=14.18.0', npm: '>=8.0.0'}
rollup@4.62.2:
resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
vite@4.4.9:
resolution: {integrity: sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==}
engines: {node: ^14.18.0 || >=16.0.0}
tinyglobby@0.2.17:
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
engines: {node: '>=12.0.0'}
vite@6.4.3:
resolution: {integrity: sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
peerDependencies:
'@types/node': '>= 14'
'@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
jiti: '>=1.21.0'
less: '*'
lightningcss: ^1.21.0
sass: '*'
sass-embedded: '*'
stylus: '*'
sugarss: '*'
terser: ^5.4.0
terser: ^5.16.0
tsx: ^4.8.1
yaml: ^2.4.2
peerDependenciesMeta:
'@types/node':
optional: true
jiti:
optional: true
less:
optional: true
lightningcss:
optional: true
sass:
optional: true
sass-embedded:
optional: true
stylus:
optional: true
sugarss:
optional: true
terser:
optional: true
tsx:
optional: true
yaml:
optional: true
vue-component-type-helpers@3.3.3:
resolution: {integrity: sha512-x4nsFpy5Pe8fqPzp/5vkTPeTTDBpAx4WVtV47Ejt0+2FQrq4pRRsJs7JmYRqMFzTu/LW+pCWEjQ3YVCkPV7f9g==}
@@ -529,70 +717,79 @@ snapshots:
dependencies:
vue: 3.5.35
'@esbuild/android-arm64@0.18.10':
'@esbuild/aix-ppc64@0.25.0':
optional: true
'@esbuild/android-arm@0.18.10':
'@esbuild/android-arm64@0.25.0':
optional: true
'@esbuild/android-x64@0.18.10':
'@esbuild/android-arm@0.25.0':
optional: true
'@esbuild/darwin-arm64@0.18.10':
'@esbuild/android-x64@0.25.0':
optional: true
'@esbuild/darwin-x64@0.18.10':
'@esbuild/darwin-arm64@0.25.0':
optional: true
'@esbuild/freebsd-arm64@0.18.10':
'@esbuild/darwin-x64@0.25.0':
optional: true
'@esbuild/freebsd-x64@0.18.10':
'@esbuild/freebsd-arm64@0.25.0':
optional: true
'@esbuild/linux-arm64@0.18.10':
'@esbuild/freebsd-x64@0.25.0':
optional: true
'@esbuild/linux-arm@0.18.10':
'@esbuild/linux-arm64@0.25.0':
optional: true
'@esbuild/linux-ia32@0.18.10':
'@esbuild/linux-arm@0.25.0':
optional: true
'@esbuild/linux-loong64@0.18.10':
'@esbuild/linux-ia32@0.25.0':
optional: true
'@esbuild/linux-mips64el@0.18.10':
'@esbuild/linux-loong64@0.25.0':
optional: true
'@esbuild/linux-ppc64@0.18.10':
'@esbuild/linux-mips64el@0.25.0':
optional: true
'@esbuild/linux-riscv64@0.18.10':
'@esbuild/linux-ppc64@0.25.0':
optional: true
'@esbuild/linux-s390x@0.18.10':
'@esbuild/linux-riscv64@0.25.0':
optional: true
'@esbuild/linux-x64@0.18.10':
'@esbuild/linux-s390x@0.25.0':
optional: true
'@esbuild/netbsd-x64@0.18.10':
'@esbuild/linux-x64@0.25.0':
optional: true
'@esbuild/openbsd-x64@0.18.10':
'@esbuild/netbsd-arm64@0.25.0':
optional: true
'@esbuild/sunos-x64@0.18.10':
'@esbuild/netbsd-x64@0.25.0':
optional: true
'@esbuild/win32-arm64@0.18.10':
'@esbuild/openbsd-arm64@0.25.0':
optional: true
'@esbuild/win32-ia32@0.18.10':
'@esbuild/openbsd-x64@0.25.0':
optional: true
'@esbuild/win32-x64@0.18.10':
'@esbuild/sunos-x64@0.25.0':
optional: true
'@esbuild/win32-arm64@0.25.0':
optional: true
'@esbuild/win32-ia32@0.25.0':
optional: true
'@esbuild/win32-x64@0.25.0':
optional: true
'@floating-ui/core@1.7.5':
@@ -608,8 +805,85 @@ snapshots:
'@jridgewell/sourcemap-codec@1.5.5': {}
'@rollup/rollup-android-arm-eabi@4.62.2':
optional: true
'@rollup/rollup-android-arm64@4.62.2':
optional: true
'@rollup/rollup-darwin-arm64@4.62.2':
optional: true
'@rollup/rollup-darwin-x64@4.62.2':
optional: true
'@rollup/rollup-freebsd-arm64@4.62.2':
optional: true
'@rollup/rollup-freebsd-x64@4.62.2':
optional: true
'@rollup/rollup-linux-arm-gnueabihf@4.62.2':
optional: true
'@rollup/rollup-linux-arm-musleabihf@4.62.2':
optional: true
'@rollup/rollup-linux-arm64-gnu@4.62.2':
optional: true
'@rollup/rollup-linux-arm64-musl@4.62.2':
optional: true
'@rollup/rollup-linux-loong64-gnu@4.62.2':
optional: true
'@rollup/rollup-linux-loong64-musl@4.62.2':
optional: true
'@rollup/rollup-linux-ppc64-gnu@4.62.2':
optional: true
'@rollup/rollup-linux-ppc64-musl@4.62.2':
optional: true
'@rollup/rollup-linux-riscv64-gnu@4.62.2':
optional: true
'@rollup/rollup-linux-riscv64-musl@4.62.2':
optional: true
'@rollup/rollup-linux-s390x-gnu@4.62.2':
optional: true
'@rollup/rollup-linux-x64-gnu@4.62.2':
optional: true
'@rollup/rollup-linux-x64-musl@4.62.2':
optional: true
'@rollup/rollup-openbsd-x64@4.62.2':
optional: true
'@rollup/rollup-openharmony-arm64@4.62.2':
optional: true
'@rollup/rollup-win32-arm64-msvc@4.62.2':
optional: true
'@rollup/rollup-win32-ia32-msvc@4.62.2':
optional: true
'@rollup/rollup-win32-x64-gnu@4.62.2':
optional: true
'@rollup/rollup-win32-x64-msvc@4.62.2':
optional: true
'@sxzz/popperjs-es@2.11.8': {}
'@types/estree@1.0.9': {}
'@types/lodash-es@4.17.12':
dependencies:
'@types/lodash': 4.17.24
@@ -618,9 +892,9 @@ snapshots:
'@types/web-bluetooth@0.0.21': {}
'@vitejs/plugin-vue@4.3.4(vite@4.4.9)(vue@3.5.35)':
'@vitejs/plugin-vue@5.2.4(vite@6.4.3)(vue@3.5.35)':
dependencies:
vite: 4.4.9
vite: 6.4.3
vue: 3.5.35
'@vue/compiler-core@3.5.35':
@@ -773,33 +1047,40 @@ snapshots:
has-tostringtag: 1.0.2
hasown: 2.0.4
esbuild@0.18.10:
esbuild@0.25.0:
optionalDependencies:
'@esbuild/android-arm': 0.18.10
'@esbuild/android-arm64': 0.18.10
'@esbuild/android-x64': 0.18.10
'@esbuild/darwin-arm64': 0.18.10
'@esbuild/darwin-x64': 0.18.10
'@esbuild/freebsd-arm64': 0.18.10
'@esbuild/freebsd-x64': 0.18.10
'@esbuild/linux-arm': 0.18.10
'@esbuild/linux-arm64': 0.18.10
'@esbuild/linux-ia32': 0.18.10
'@esbuild/linux-loong64': 0.18.10
'@esbuild/linux-mips64el': 0.18.10
'@esbuild/linux-ppc64': 0.18.10
'@esbuild/linux-riscv64': 0.18.10
'@esbuild/linux-s390x': 0.18.10
'@esbuild/linux-x64': 0.18.10
'@esbuild/netbsd-x64': 0.18.10
'@esbuild/openbsd-x64': 0.18.10
'@esbuild/sunos-x64': 0.18.10
'@esbuild/win32-arm64': 0.18.10
'@esbuild/win32-ia32': 0.18.10
'@esbuild/win32-x64': 0.18.10
'@esbuild/aix-ppc64': 0.25.0
'@esbuild/android-arm': 0.25.0
'@esbuild/android-arm64': 0.25.0
'@esbuild/android-x64': 0.25.0
'@esbuild/darwin-arm64': 0.25.0
'@esbuild/darwin-x64': 0.25.0
'@esbuild/freebsd-arm64': 0.25.0
'@esbuild/freebsd-x64': 0.25.0
'@esbuild/linux-arm': 0.25.0
'@esbuild/linux-arm64': 0.25.0
'@esbuild/linux-ia32': 0.25.0
'@esbuild/linux-loong64': 0.25.0
'@esbuild/linux-mips64el': 0.25.0
'@esbuild/linux-ppc64': 0.25.0
'@esbuild/linux-riscv64': 0.25.0
'@esbuild/linux-s390x': 0.25.0
'@esbuild/linux-x64': 0.25.0
'@esbuild/netbsd-arm64': 0.25.0
'@esbuild/netbsd-x64': 0.25.0
'@esbuild/openbsd-arm64': 0.25.0
'@esbuild/openbsd-x64': 0.25.0
'@esbuild/sunos-x64': 0.25.0
'@esbuild/win32-arm64': 0.25.0
'@esbuild/win32-ia32': 0.25.0
'@esbuild/win32-x64': 0.25.0
estree-walker@2.0.2: {}
fdir@6.5.0(picomatch@4.0.4):
optionalDependencies:
picomatch: 4.0.4
follow-redirects@1.16.0: {}
form-data@4.0.5:
@@ -884,6 +1165,8 @@ snapshots:
picocolors@1.1.1: {}
picomatch@4.0.4: {}
postcss@8.5.15:
dependencies:
nanoid: 3.3.12
@@ -892,17 +1175,52 @@ snapshots:
proxy-from-env@2.1.0: {}
rollup@3.30.0:
rollup@4.62.2:
dependencies:
'@types/estree': 1.0.9
optionalDependencies:
'@rollup/rollup-android-arm-eabi': 4.62.2
'@rollup/rollup-android-arm64': 4.62.2
'@rollup/rollup-darwin-arm64': 4.62.2
'@rollup/rollup-darwin-x64': 4.62.2
'@rollup/rollup-freebsd-arm64': 4.62.2
'@rollup/rollup-freebsd-x64': 4.62.2
'@rollup/rollup-linux-arm-gnueabihf': 4.62.2
'@rollup/rollup-linux-arm-musleabihf': 4.62.2
'@rollup/rollup-linux-arm64-gnu': 4.62.2
'@rollup/rollup-linux-arm64-musl': 4.62.2
'@rollup/rollup-linux-loong64-gnu': 4.62.2
'@rollup/rollup-linux-loong64-musl': 4.62.2
'@rollup/rollup-linux-ppc64-gnu': 4.62.2
'@rollup/rollup-linux-ppc64-musl': 4.62.2
'@rollup/rollup-linux-riscv64-gnu': 4.62.2
'@rollup/rollup-linux-riscv64-musl': 4.62.2
'@rollup/rollup-linux-s390x-gnu': 4.62.2
'@rollup/rollup-linux-x64-gnu': 4.62.2
'@rollup/rollup-linux-x64-musl': 4.62.2
'@rollup/rollup-openbsd-x64': 4.62.2
'@rollup/rollup-openharmony-arm64': 4.62.2
'@rollup/rollup-win32-arm64-msvc': 4.62.2
'@rollup/rollup-win32-ia32-msvc': 4.62.2
'@rollup/rollup-win32-x64-gnu': 4.62.2
'@rollup/rollup-win32-x64-msvc': 4.62.2
fsevents: 2.3.3
source-map-js@1.2.1: {}
vite@4.4.9:
tinyglobby@0.2.17:
dependencies:
esbuild: 0.18.10
fdir: 6.5.0(picomatch@4.0.4)
picomatch: 4.0.4
vite@6.4.3:
dependencies:
esbuild: 0.25.0
fdir: 6.5.0(picomatch@4.0.4)
picomatch: 4.0.4
postcss: 8.5.15
rollup: 3.30.0
rollup: 4.62.2
tinyglobby: 0.2.17
optionalDependencies:
fsevents: 2.3.3
+11
View File
@@ -0,0 +1,11 @@
import request from '@/utils/request'
/**
* 获取今日新增数据(型号 + OTA)
*/
export function getTodayStats() {
return request({
url: '/dashboard/today',
method: 'get'
})
}
+188 -2
View File
@@ -12,6 +12,7 @@
</div>
</el-card>
<!-- 快捷入口 -->
<div class="home-links">
<el-card
v-for="item in quickLinks"
@@ -29,20 +30,86 @@
</div>
</el-card>
</div>
<!-- 今日新增数据 -->
<el-card class="home-today-card-wrapper">
<template #header>
<h2 class="home-today-title">今日新增</h2>
</template>
<div class="home-today-grid">
<!-- 今日新增型号 -->
<el-card class="home-today-card">
<template #header>
<div class="home-card-header">
<span class="home-card-title">
<el-icon><List /></el-icon>
新增型号
</span>
<el-tag size="small" type="primary">{{ todayModels.length }}</el-tag>
</div>
</template>
<div v-if="todayLoading" class="home-today-loading">
<el-icon class="is-loading"><Loading /></el-icon>
</div>
<div v-else-if="todayModels.length === 0" class="home-today-empty">
暂无新增
</div>
<ul v-else class="home-today-list">
<li v-for="item in todayModels" :key="item.id" class="home-today-item">
<span class="home-today-item-text">{{ item.brand_name }} {{ item.name }}</span>
<span class="home-today-item-time">{{ formatTime(item.create_at) }}</span>
</li>
</ul>
</el-card>
<!-- 今日新增 OTA -->
<el-card class="home-today-card">
<template #header>
<div class="home-card-header">
<span class="home-card-title">
<el-icon><Upload /></el-icon>
新增 OTA
</span>
<el-tag size="small" type="success">{{ todayOtas.length }}</el-tag>
</div>
</template>
<div v-if="todayLoading" class="home-today-loading">
<el-icon class="is-loading"><Loading /></el-icon>
</div>
<div v-else-if="todayOtas.length === 0" class="home-today-empty">
暂无新增
</div>
<ul v-else class="home-today-list">
<li v-for="item in todayOtas" :key="item.id" class="home-today-item">
<span class="home-today-item-text">{{ item.verName }} · {{ item.model }}</span>
<span class="home-today-item-time">{{ formatTime(item.create_at) }}</span>
</li>
</ul>
</el-card>
</div>
</el-card>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { computed, ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { House, Collection, List, Document, Share } from '@element-plus/icons-vue'
import { House, Collection, List, Document, Share, Upload, Loading } from '@element-plus/icons-vue'
import { getUser } from '@/utils/auth'
import { getTodayStats } from '@/api/dashboard'
defineOptions({ name: 'Home' })
const router = useRouter()
const username = computed(() => getUser()?.username || '')
const todayLoading = ref(false)
const todayModels = ref([])
const todayOtas = ref([])
const quickLinks = [
{ path: '/brand', title: '耳机品牌管理', desc: '维护耳机品牌信息', icon: Collection },
{ path: '/model', title: '耳机型号管理', desc: '维护耳机型号与配置', icon: List },
@@ -50,9 +117,35 @@ const quickLinks = [
{ path: '/share-code/log', title: '分享日志', desc: '查看分享码使用记录', icon: Share }
]
function formatTime(isoStr) {
if (!isoStr) return ''
const d = new Date(isoStr)
const pad = (n) => String(n).padStart(2, '0')
return `${pad(d.getHours())}:${pad(d.getMinutes())}`
}
async function loadTodayStats() {
todayLoading.value = true
try {
const res = await getTodayStats()
if (res.code === 1 && res.data) {
todayModels.value = res.data.models || []
todayOtas.value = res.data.otas || []
}
} catch (e) {
console.error('获取今日数据失败:', e)
} finally {
todayLoading.value = false
}
}
function go(path) {
router.push(path)
}
onMounted(() => {
loadTodayStats()
})
</script>
<style scoped>
@@ -98,6 +191,99 @@ function go(path) {
color: #94a3b8;
}
/* 今日新增 */
.home-today-title {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #e2e8f0;
}
.home-today-card-wrapper {
max-width: 760px;
}
.home-today-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
}
.home-card-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.home-card-title {
display: flex;
align-items: center;
gap: 6px;
font-size: 15px;
font-weight: 600;
color: #e2e8f0;
}
.home-card-title .el-icon {
color: #7dd3fc;
}
.home-today-loading {
display: flex;
align-items: center;
justify-content: center;
padding: 32px 0;
color: #94a3b8;
}
.home-today-loading .el-icon {
font-size: 24px;
}
.home-today-empty {
text-align: center;
padding: 24px 0;
color: #64748b;
font-size: 14px;
}
.home-today-list {
margin: 0;
padding: 0;
list-style: none;
max-height: 280px;
overflow-y: auto;
}
.home-today-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px;
border-radius: 8px;
transition: background-color 0.15s;
}
.home-today-item:hover {
background: rgba(148, 163, 184, 0.08);
}
.home-today-item-text {
font-size: 14px;
color: #cbd5e1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.home-today-item-time {
flex-shrink: 0;
font-size: 12px;
color: #64748b;
margin-left: 12px;
}
/* 快捷入口 */
.home-links {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
+112 -4
View File
@@ -49,12 +49,20 @@
@click="handlePushToSearch"
>
<el-icon><Upload /></el-icon>
推送搜索 ({{ selectedIds.length }})
推送搜索 {{ selectedIds.length }}
</el-button>
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>
新型号
</el-button>
<el-button
type="success"
:disabled="selectedIds.length === 0"
@click="handleCopySelectedNames"
>
<el-icon><CopyDocument /></el-icon>
复制 {{ selectedIds.length }}
</el-button>
</div>
</div>
@@ -71,8 +79,34 @@
>
<el-table-column type="selection" width="55" />
<el-table-column prop="id" label="ID" width="80" sortable="custom" />
<el-table-column prop="brand_name" label="品牌名称" width="150" />
<el-table-column prop="name" label="型号名称" />
<el-table-column prop="brand_name" label="品牌名称" width="150">
<template #default="scope">
<div class="brand-name-cell">
<span>{{ scope.row.brand_name }}</span>
<el-icon
class="brand-name-copy-icon"
title="复制品牌名称"
@click.stop="handleCopyBrand(scope.row)"
>
<CopyDocument />
</el-icon>
</div>
</template>
</el-table-column>
<el-table-column prop="name" label="型号名称">
<template #default="scope">
<div class="model-name-cell">
<span>{{ scope.row.name }}</span>
<el-icon
class="model-name-copy-icon"
title="复制品牌+型号"
@click.stop="handleCopyName(scope.row)"
>
<CopyDocument />
</el-icon>
</div>
</template>
</el-table-column>
<el-table-column prop="form" label="佩戴方式" width="120">
<template #default="scope">
<el-tag v-if="scope.row.form === 'in-ear'" type="success">入耳式</el-tag>
@@ -435,7 +469,8 @@ import {
Loading,
CircleCheck,
CircleClose,
ArrowDown
ArrowDown,
CopyDocument
} from '@element-plus/icons-vue'
import {
getModels,
@@ -738,6 +773,33 @@ function formatFormLabel(form) {
return form || '-'
}
const handleCopyName = (row) => {
const text = `${row.brand_name} ${row.name}`
navigator.clipboard.writeText(text).then(
() => ElMessage.success(`已复制: ${text}`),
() => ElMessage.error('复制失败')
)
}
const handleCopyBrand = (row) => {
const text = row.brand_name
navigator.clipboard.writeText(text).then(
() => ElMessage.success(`已复制: ${text}`),
() => ElMessage.error('复制失败')
)
}
const handleCopySelectedNames = () => {
if (selectedRows.value.length === 0) return
const text = selectedRows.value
.map((row) => `${row.brand_name} ${row.name}`)
.join('\n')
navigator.clipboard.writeText(text).then(
() => ElMessage.success(`已复制 ${selectedRows.value.length}`),
() => ElMessage.error('复制失败')
)
}
function resetPushViewDialog() {
pushViewLoading.value = false
pushViewPushed.value = false
@@ -1459,6 +1521,52 @@ onMounted(() => {
white-space: nowrap;
}
.model-name-cell {
display: flex;
align-items: center;
gap: 6px;
}
.model-name-copy-icon {
flex-shrink: 0;
font-size: 14px;
color: #94a3b8;
cursor: pointer;
opacity: 0;
transition: opacity 0.2s, color 0.2s;
}
.model-name-copy-icon:hover {
color: var(--el-color-primary);
}
.model-name-cell:hover .model-name-copy-icon {
opacity: 1;
}
.brand-name-cell {
display: flex;
align-items: center;
gap: 6px;
}
.brand-name-copy-icon {
flex-shrink: 0;
font-size: 14px;
color: #94a3b8;
cursor: pointer;
opacity: 0;
transition: opacity 0.2s, color 0.2s;
}
.brand-name-copy-icon:hover {
color: var(--el-color-primary);
}
.brand-name-cell:hover .brand-name-copy-icon {
opacity: 1;
}
.row-actions-more {
flex-shrink: 0;
margin-left: 4px;
-18
View File
@@ -252,8 +252,6 @@ import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Refresh, Plus } from '@element-plus/icons-vue'
import { getOtaList, createOta, updateOta, deleteOta, uploadOtaPackage } from '@/api/ota'
const PAW_MD5_PLACEHOLDER = '0'.repeat(32)
const PACKAGE_UPLOAD_MODELS = ['Luxsin-X8', 'Luxsin-X9']
const loading = ref(false)
@@ -294,10 +292,6 @@ function emptyForm() {
hw: 6,
target: 0,
beta: 0,
pawVerCode: 0,
pawVerName: '',
pawUrl: '',
pawMd5: '',
status: 1
}
}
@@ -344,10 +338,6 @@ const formRules = {
}
function buildPayload() {
let pawMd5 = (formData.pawMd5 || '').trim()
if (pawMd5.length !== 32) {
pawMd5 = PAW_MD5_PLACEHOLDER
}
const model = (formData.model == null || formData.model === '') ? '' : String(formData.model).trim()
return {
verCode: Number(formData.verCode),
@@ -360,10 +350,6 @@ function buildPayload() {
hw: Number(formData.hw ?? 0),
target: Number(formData.target ?? 0),
beta: Number(formData.beta ?? 0),
pawVerCode: Number(formData.pawVerCode ?? 0),
pawVerName: (formData.pawVerName || '').trim(),
pawUrl: (formData.pawUrl || '').trim(),
pawMd5,
startTime: null,
endTime: null,
status: Number(formData.status ?? 1)
@@ -497,10 +483,6 @@ function applyOtaRowToForm(row, isCopy) {
hw: row.hw ?? 0,
target: row.target ?? 0,
beta: row.beta ?? 0,
pawVerCode: row.pawVerCode ?? 0,
pawVerName: row.pawVerName ?? '',
pawUrl: row.pawUrl ?? '',
pawMd5: row.pawMd5 || '',
status: row.status ?? 1
})
}