diff --git a/examples/basic-admin/index.ts b/examples/basic-admin/index.ts index 714ae49..b5a7a4a 100644 --- a/examples/basic-admin/index.ts +++ b/examples/basic-admin/index.ts @@ -4,12 +4,18 @@ * 用 MemoryShrimpCrud 跑一个零依赖的 admin server,展示: * - Shrimp 核心(模块注册 / RPC dispatch / 权限树) * - ShrimpTableModule + MemoryShrimpCrud 的 CRUD 自动事件 - * - 通过 Express 暴露成 HTTP RPC + * - 通过 Express 暴露成完整的 hand-admin 协议: + * GET /admin-api/config + * POST /admin-api/login + * GET /admin-api/me + * POST /admin-api/[/...]/ * * 不使用 ShrimpServer/Mongoose/JWT,刻意暴露 ShrimpServer 把太多东西耦合在 * Express+Mongo+Auth 一起的事实,作为后续重构的对照样本。 + * + * 单一 mock super-admin operator,前端登录直接放行,token 是固定字符串。 */ -import express from "express" +import express, { type Request, type Response } from "express" import { Shrimp, ShrimpTableModule, @@ -18,7 +24,25 @@ import { ShrimpError, ShrimpCode, } from "shrimp-server" -import type { IFieldConfig, WithId } from "shrimp-server" +import type { + IFieldConfig, + WithId, + IShrimpOperator, + IModuleInfo, + IMenuItem, + IMeResult, +} from "shrimp-server" + +class DemoShrimp extends Shrimp { + /** 默认 getMenuTree 用 i18n key,demo 没翻译这些键,直接用模块自带的 label */ + getMenuTree(): IMenuItem[] { + const items: IMenuItem[] = [] + this.modules.forEach(m => { + items.push({ name: m.moduleKey, label: m.label, moduleKey: m.moduleKey }) + }) + return items + } +} interface ITag { _id: string; name: string } interface IProduct { _id: string; code: string; name: string; price: number; tagIds: string[] } @@ -37,13 +61,27 @@ const productFields: IFieldConfig[] = [ }, ] -async function bootstrap() { - const shrimp = new Shrimp() +const FAKE_TOKEN = "demo-super-admin-token" +const superAdmin: IShrimpOperator = { + userId: "demo-admin", + roleId: ["demo-super"], + isSuperAdmin: true, +} + +async function bootstrap() { + const shrimp = new DemoShrimp() + + // TODO(70→90): ShrimpTableModule 当前只在 dataSource.model 存在时自动登记 crudRegistry, + // 这里需要手动 wire 让 MemoryShrimpCrud 的 populate 也能查到关联表 const tagCrud = new MemoryShrimpCrud>() + tagCrud.getRelatedCrud = (k) => shrimp.crudRegistry.get(k) + shrimp.crudRegistry.set("tag", tagCrud) new ShrimpTableModule(shrimp, "tag", tagCrud, tagFields, "标签管理").initCrud() const productCrud = new MemoryShrimpCrud>() + productCrud.getRelatedCrud = (k) => shrimp.crudRegistry.get(k) + shrimp.crudRegistry.set("product", productCrud) new ShrimpTableModule(shrimp, "product", productCrud, productFields, "商品管理").initCrud() // 种子数据 @@ -52,34 +90,92 @@ async function bootstrap() { await productCrud.add({ code: "P001", name: "苹果", price: 5, tagIds: [t1._id] } as any) await productCrud.add({ code: "P002", name: "香蕉", price: 3, tagIds: [t1._id, t2._id] } as any) + // super admin 看得到所有模块 + const allKeys = (await shrimp.allPower()).map(p => p.key) + const superPowers = new Set(allKeys) + const app = express() app.use(express.json()) - // 简化的 RPC dispatch: POST /api/<...path> body 是参数数组 - app.post(/^\/api\/.+/, async (req, res) => { - const path = req.path.replace(/^\/api\//, "").split("/").filter(Boolean) - const args = Array.isArray(req.body) ? req.body : [] - try { - const data = await shrimp.call(path, ...args) - res.json({ code: ShrimpCode.OK, data }) - } catch (e: any) { - if (e instanceof ShrimpError) { - res.json({ code: e.code, tip: e.tip }) - } else { - console.error(e) - res.json({ code: ShrimpCode.SERVER_ERROR, log: String(e?.message || e) }) - } + // 简单认证: 仅检查 Authorization,通过即注入 super-admin operator + function authedRunner(req: Request, res: Response, run: () => Promise) { + const auth = req.headers.authorization || "" + const token = auth.replace(/^Bearer\s+/i, "") + if (token !== FAKE_TOKEN) { + res.json({ code: ShrimpCode.UNAUTHORIZED, tip: { t: "shrimp.relogin" } }) + return } + Shrimp.asyncLocalStorage.run(superAdmin, async () => { + try { + const data = await run() + res.json({ code: ShrimpCode.OK, data }) + } catch (e: any) { + if (e instanceof ShrimpError) { + res.json({ code: e.code, tip: e.tip }) + } else { + console.error(e) + res.json({ code: ShrimpCode.SERVER_ERROR, log: String(e?.message || e) }) + } + } + }) + } + + // --- 公开接口 --- + app.get("/admin-api/config", (_req, res) => { + res.json({ code: ShrimpCode.OK, data: { title: "Shrimp Basic Admin Demo" } }) + }) + + app.post("/admin-api/login", (req, res) => { + const { username, password } = req.body || {} + // demo 模式不校验密码,任意账号都给同一个 token + if (!username || !password) { + res.json({ code: ShrimpCode.UNAUTHORIZED, tip: { t: "shrimp.loginFailed" } }) + return + } + res.json({ code: ShrimpCode.OK, data: { token: FAKE_TOKEN } }) + }) + + // --- 需要登录的接口 --- + app.get("/admin-api/me", (req, res) => { + authedRunner(req, res, async () => { + const moduleInfo: IModuleInfo[] = shrimp.getModuleInfo(superPowers) + const fullMenu = shrimp.getMenuTree() + const menuTree: IMenuItem[] = shrimp.filterMenuTree(fullMenu, allKeys) + const result: IMeResult = { + nickname: "Demo Admin", + moduleInfo, + menuTree, + options: {}, + } + return result + }) + }) + + // RPC dispatch: POST /admin-api/<...path> body 是参数数组 + app.post(/^\/admin-api\/.+/, (req, res) => { + const rawPath = req.path.replace(/^\/admin-api\//, "") + // 不要把 login/config 也接进来 + if (rawPath === "login" || rawPath === "config" || rawPath === "me") { + res.status(404).end() + return + } + authedRunner(req, res, async () => { + const path = rawPath.split("/").filter(Boolean) + const args = Array.isArray(req.body) ? req.body : [] + return shrimp.call(path, ...args) + }) }) const port = 3100 app.listen(port, () => { console.log(`Shrimp basic-admin demo: http://localhost:${port}`) console.log("") - console.log("# 试试:") - console.log(` curl -s -X POST http://localhost:${port}/api/product/list -H 'content-type: application/json' -d '[{}]' | jq`) - console.log(` curl -s -X POST http://localhost:${port}/api/tag/list -H 'content-type: application/json' -d '[{}]' | jq`) - console.log(` curl -s -X POST http://localhost:${port}/api/product/add -H 'content-type: application/json' -d '[{"code":"P003","name":"橙子","price":4,"tagIds":[]}]' | jq`) + console.log("# 配合前端: cd web && npm install && npm run dev,然后访问 http://localhost:5173") + console.log("# 登录: 任意账号密码") + console.log("") + console.log("# 也可以直接 curl:") + console.log(` TOKEN=${FAKE_TOKEN}`) + console.log(` curl -s -X POST http://localhost:${port}/admin-api/product/list -H 'content-type: application/json' -H "Authorization: Bearer $TOKEN" -d '[{}]' | jq`) }) } diff --git a/examples/basic-admin/web/index.html b/examples/basic-admin/web/index.html new file mode 100644 index 0000000..766f104 --- /dev/null +++ b/examples/basic-admin/web/index.html @@ -0,0 +1,13 @@ + + + + + + + + +
+ + + + diff --git a/examples/basic-admin/web/package-lock.json b/examples/basic-admin/web/package-lock.json new file mode 100644 index 0000000..ae7dce4 --- /dev/null +++ b/examples/basic-admin/web/package-lock.json @@ -0,0 +1,1993 @@ +{ + "name": "shrimp-basic-admin-web", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "shrimp-basic-admin-web", + "version": "0.0.0", + "dependencies": { + "@element-plus/icons-vue": "^2.1.0", + "axios": "1.3.4", + "element-plus": "^2.10.4", + "vue": "^3.3.4", + "vue-i18n": "^9.1.9", + "vue-router": "4.1.6" + }, + "devDependencies": { + "@types/node": "^20.8.3", + "@vitejs/plugin-vue": "^4.2.3", + "sass": "^1.89.2", + "typescript": "^5.0.2", + "vite": "^4.4.5", + "vue-tsc": "^1.8.5" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz", + "integrity": "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@element-plus/icons-vue": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz", + "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "license": "MIT" + }, + "node_modules/@intlify/core-base": { + "version": "9.14.5", + "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.14.5.tgz", + "integrity": "sha512-5ah5FqZG4pOoHjkvs8mjtv+gPKYU0zCISaYNjBNNqYiaITxW8ZtVih3GS/oTOqN8d9/mDLyrjD46GBApNxmlsA==", + "license": "MIT", + "dependencies": { + "@intlify/message-compiler": "9.14.5", + "@intlify/shared": "9.14.5" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/message-compiler": { + "version": "9.14.5", + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.14.5.tgz", + "integrity": "sha512-IHzgEu61/YIpQV5Pc3aRWScDcnFKWvQA9kigcINcCBXN8mbW+vk9SK+lDxA6STzKQsVJxUPg9ACC52pKKo3SVQ==", + "license": "MIT", + "dependencies": { + "@intlify/shared": "9.14.5", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/shared": { + "version": "9.14.5", + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.14.5.tgz", + "integrity": "sha512-9gB+E53BYuAEMhbCAxVgG38EZrk59sxBtv3jSizNL2hEWlgjBjAw1AwpLHtNaeda12pe6W20OGEa0TwuMSRbyQ==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@popperjs/core": { + "name": "@sxzz/popperjs-es", + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.8.tgz", + "integrity": "sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@types/lodash": { + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", + "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/node": { + "version": "20.19.41", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz", + "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.6.2.tgz", + "integrity": "sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.0.0 || ^5.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.11.1.tgz", + "integrity": "sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "1.11.1" + } + }, + "node_modules/@volar/source-map": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.11.1.tgz", + "integrity": "sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "muggle-string": "^0.3.1" + } + }, + "node_modules/@volar/typescript": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.11.1.tgz", + "integrity": "sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "1.11.1", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.34.tgz", + "integrity": "sha512-s9cLyK5mLcvZ4Agva5QgRsQyLKvts9WbU9DB6NqiZkkGEdwmcEiylj5Jbwkp680drF/NNCV8OlAJSe+yMLxaJw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@vue/shared": "3.5.34", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.34.tgz", + "integrity": "sha512-EbF/T++k0e2MMZlJsBhzK8Sgwt0HcIPOhzn1CTB/lv6sQcyk+OWf8YeiLxZp3ro7MbbLcAfAJ6sEvjFWuNgUCw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.34", + "@vue/shared": "3.5.34" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.34.tgz", + "integrity": "sha512-D/ihr6uZeIt6r+pVZf46RWT1fAsLFMbUP7k8G1VkiiWexriED9GrX3echHd4Abbt17zjlfiFJ8z7a3BxZOPNjg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@vue/compiler-core": "3.5.34", + "@vue/compiler-dom": "3.5.34", + "@vue/compiler-ssr": "3.5.34", + "@vue/shared": "3.5.34", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.14", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.34.tgz", + "integrity": "sha512-cDtTHKibkThKGHH1SP+WdccquNRYQDFH6rRjQCqT9G2ltFAfoR5pUftpab/z+aM5mW9HLLVQW7hfKKQe/1GBeQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.34", + "@vue/shared": "3.5.34" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@vue/language-core": { + "version": "1.8.27", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.27.tgz", + "integrity": "sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "~1.11.1", + "@volar/source-map": "~1.11.1", + "@vue/compiler-dom": "^3.3.0", + "@vue/shared": "^3.3.0", + "computeds": "^0.0.1", + "minimatch": "^9.0.3", + "muggle-string": "^0.3.1", + "path-browserify": "^1.0.1", + "vue-template-compiler": "^2.7.14" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.34.tgz", + "integrity": "sha512-y9XDjCEuBp+98k+UL5dbYkh57AHU4o6cxZedOPXw3bmrZZYLQsVHguGurq7hVrPCSrQtrnz1f9dssyFr+dMXfQ==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.34" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.34.tgz", + "integrity": "sha512-mKeBYvu8tcMSLhypAHBmriUFfWXKTCF/23Z4jiCoYK3UtWepkliViNLuR90V9XOyD62mUxs9p1jsrpK3CCGIzw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.34", + "@vue/shared": "3.5.34" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.34.tgz", + "integrity": "sha512-e8kZzERmCwUnBRVsgSQlAfrfU2rGoy0FFKPBXSlfEjc/O3KfA7QP0t1/2ZylrbchjmIKB4dPTd07A6WPr0eOrg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.34", + "@vue/runtime-core": "3.5.34", + "@vue/shared": "3.5.34", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.34.tgz", + "integrity": "sha512-nHxmJoTrKsmrkbILRhkC9gY1G3moZbJTqCzDd7DOOzG5KH9oeJ0Unqrff5f9v0pW//jES05ZkJcNtfE8JjOIew==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.34", + "@vue/shared": "3.5.34" + }, + "peerDependencies": { + "vue": "3.5.34" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.34.tgz", + "integrity": "sha512-24uqU4OIiX29ryC3MeWid/Xf2fa2EFRUVLb77nRhk+UrTVrh/XiGtFAFmJBAtBRbjwNdsPRP+jj/OL27Eg1NDA==", + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.3.0.tgz", + "integrity": "sha512-aHfz47g0ZhMtTVHmIzMVpJy8ePhhOy68GY5bv110+5DVtZ+W7BsOx+m61UNQqfrWyPztIHIanWa3E2tib3NFIw==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "14.3.0", + "@vueuse/shared": "14.3.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/@vueuse/metadata": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.3.0.tgz", + "integrity": "sha512-BwxmbAzwAVF50+MW57GXOUEV61nFBGnlBvrTqj49PqWJu3uw7hdu72ztXeZ33RdZtDY6kO+bfCAE1PCn88Tktw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.3.0.tgz", + "integrity": "sha512-bZpge9eSXwa4ToSiqJ7j6KRwhAsneMFoSz3LMWKQDkqimm3D/tbFlrklrs/IOqC8tEcYmXQZJ6N0UrjhBirVCg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", + "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/computeds": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/computeds/-/computeds-0.0.1.tgz", + "integrity": "sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz", + "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", + "license": "MIT" + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/element-plus": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.14.0.tgz", + "integrity": "sha512-POgH+TtoreaEKWqYYAVQyE6i8rQMEFqAEublyF29dBA5yASWPLKY6EzfeqBTr2Uv26mPss4vSrMrNPyaK7LX5w==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^4.2.0", + "@element-plus/icons-vue": "^2.3.2", + "@floating-ui/dom": "^1.0.1", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.8", + "@types/lodash": "^4.17.24", + "@types/lodash-es": "^4.17.12", + "@vueuse/core": "14.3.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.20", + "lodash": "^4.18.1", + "lodash-es": "^4.18.1", + "lodash-unified": "^1.0.3", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0", + "vue-component-type-helpers": "^3.2.8" + }, + "peerDependencies": { + "vue": "^3.3.7" + } + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/immutable": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz", + "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", + "license": "MIT" + }, + "node_modules/lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "license": "MIT", + "peerDependencies": { + "@types/lodash-es": "*", + "lodash": "*", + "lodash-es": "*" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/muggle-string": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.3.1.tgz", + "integrity": "sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", + "license": "BSD-3-Clause" + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/rollup": { + "version": "3.30.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.30.0.tgz", + "integrity": "sha512-kQvGasUgN+AlWGliFn2POSajRQEsULVYFGTvOZmK06d7vCD+YhZztt70kGk3qaeAXeWYL5eO7zx+rAubBc55eA==", + "dev": true, + "license": "MIT", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/sass": { + "version": "1.100.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.100.0.tgz", + "integrity": "sha512-B5j0rYMlinhhOo9tjQebMVVn0TfyXAF+wB3b2ggZUuJ/is/Y+7+JGjirAMxHZ9Z3hIP98NPfamlAkBHa1lAaXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "immutable": "^5.1.5", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=20.19.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "4.5.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.14.tgz", + "integrity": "sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.34.tgz", + "integrity": "sha512-WdLBG9gm02OgJIG9axd5Hpx0TFLdzVgfG2evFFu8Rur5O/IoGc5cMjnjh3tPL6GnRGsYvUhBSKVPYVcxRKpMCA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.34", + "@vue/compiler-sfc": "3.5.34", + "@vue/runtime-dom": "3.5.34", + "@vue/server-renderer": "3.5.34", + "@vue/shared": "3.5.34" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-component-type-helpers": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-3.3.1.tgz", + "integrity": "sha512-pu58kqxmVyEH6VfNYW1UyEfR3XAnJ27ZXT3yzXxxpjLxVzAbyC35Zk/nm/RMs7ijWnJNSd9fWkeex2OhUsx3MA==", + "license": "MIT" + }, + "node_modules/vue-i18n": { + "version": "9.14.5", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.5.tgz", + "integrity": "sha512-0jQ9Em3ymWngyiIkj0+c/k7WgaPO+TNzjKSNq9BvBQaKJECqn9cd9fL4tkDhB5G1QBskGl9YxxbDAhgbFtpe2g==", + "deprecated": "v9 and v10 no longer supported. please migrate to v11. about maintenance status, see https://vue-i18n.intlify.dev/guide/maintenance.html", + "license": "MIT", + "dependencies": { + "@intlify/core-base": "9.14.5", + "@intlify/shared": "9.14.5", + "@vue/devtools-api": "^6.5.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/vue-router": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.1.6.tgz", + "integrity": "sha512-DYWYwsG6xNPmLq/FmZn8Ip+qrhFEzA14EI12MsMgVxvHFDYvlr4NXpVF5hrRH1wVcDP8fGi5F4rxuJSl8/r+EQ==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.4.5" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/vue-template-compiler": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz", + "integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/vue-tsc": { + "version": "1.8.27", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.8.27.tgz", + "integrity": "sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "~1.11.1", + "@vue/language-core": "1.8.27", + "semver": "^7.5.4" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": "*" + } + } + } +} diff --git a/examples/basic-admin/web/package.json b/examples/basic-admin/web/package.json new file mode 100644 index 0000000..5775ced --- /dev/null +++ b/examples/basic-admin/web/package.json @@ -0,0 +1,27 @@ +{ + "name": "shrimp-basic-admin-web", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@element-plus/icons-vue": "^2.1.0", + "axios": "1.3.4", + "element-plus": "^2.10.4", + "vue": "^3.3.4", + "vue-i18n": "^9.1.9", + "vue-router": "4.1.6" + }, + "devDependencies": { + "@types/node": "^20.8.3", + "@vitejs/plugin-vue": "^4.2.3", + "sass": "^1.89.2", + "typescript": "^5.0.2", + "vite": "^4.4.5", + "vue-tsc": "^1.8.5" + } +} diff --git a/examples/basic-admin/web/public/img/cashPos.png b/examples/basic-admin/web/public/img/cashPos.png new file mode 100644 index 0000000..065b586 Binary files /dev/null and b/examples/basic-admin/web/public/img/cashPos.png differ diff --git a/examples/basic-admin/web/public/img/point.png b/examples/basic-admin/web/public/img/point.png new file mode 100644 index 0000000..7d5c2b0 Binary files /dev/null and b/examples/basic-admin/web/public/img/point.png differ diff --git a/examples/basic-admin/web/src/ShrimpClient.ts b/examples/basic-admin/web/src/ShrimpClient.ts new file mode 100644 index 0000000..4cb4c6a --- /dev/null +++ b/examples/basic-admin/web/src/ShrimpClient.ts @@ -0,0 +1,372 @@ +/** + * ShrimpClient — Shrimp 框架前端基类 + * 框架无关层,包含协议通信、模块管理、菜单树操作 + */ +import { ShrimpCode, ShrimpRequestFn, IShrimpResponse, IModuleInfo, IMenuItem, ILoginResult, IMeResult, IShrimpClient, ILabel } from '@shrimp/IShrimpType' + +/** tip 展示接口(由框架层实现,如 Element Plus)。msg 是 ILabel,实现层负责翻译后展示 */ +export interface IShrimpTipper { + success(msg: ILabel): void + error(msg: ILabel): void + alert(msg: ILabel): Promise +} + +/** 路由注册信息,子类 addRoute 的参数 */ +export interface IRouteInfo { + moduleKey: string + name: string + path?: string + label: ILabel + events: string[] + /** 组件相对 module 目录的路径(不含 .vue),不传则默认 moduleKey。用于子模块嵌套目录定位 */ + componentPath?: string + /** 子模块跳转按钮图标(element-plus icon 名,仅 sub 模块有意义) */ + icon?: string +} + +class ShrimpResponseError extends Error { + constructor(public data: IShrimpResponse) { + super(data.code.toString()) + this.name = 'ShrimpResponseError' + } +} +/** + * 处理 Shrimp 响应中的 tip 和 log + * @returns 处理后的响应数据,或抛出错误 + */ +export function handleShrimpResponse( + body: IShrimpResponse, + tipper: IShrimpTipper, + onUnauthorized?: () => void, +): any { + if (body.log) { + console.warn('[Shrimp]', body.log) + } + + if (body.code === ShrimpCode.OK) { + if (body.tip) tipper.success(body.tip) + return body.data + } + + // 业务错误:根据 code 决定展示方式 + if (body.tip) { + if (body.code === ShrimpCode.UNAUTHORIZED) { + tipper.alert(body.tip).then(() => onUnauthorized?.()) + } else { + tipper.error(body.tip) + } + } else { + tipper.alert("").then(() => onUnauthorized?.()) + // tipper.error(t('base.fail')) + } + + const err = new ShrimpResponseError(body) as any + err.code = body.code + err.handled = true + throw err +} + +/** + * 根据协议类型生成类型安全的 API 调用器 + * 用法: + * const userApi = getCaller(request, "user") + * const subApi = getCaller(request, ["parent", "sub"]) + */ +export function getCaller

>(request: ShrimpRequestFn, moduleKey: string | string[]): P { + const segments = Array.isArray(moduleKey) ? moduleKey : [moduleKey] + return new Proxy({} as any, { + get: (target, prop, receiver) => { + if (typeof prop === 'symbol' || prop === 'valueOf' || prop === 'toString') { + return Reflect.get(target, prop, receiver) + } + return (...args: any[]) => request([...segments, prop as string], ...args) + } + }) +} + +/** + * ShrimpClient 基类 + * 封装与 Shrimp 协议相关的公共方法,框架无关 + * 业务层(如 HandAdmin)继承此类并实现底层请求 + */ +export abstract class ShrimpClient implements IShrimpClient { + /** 当前用户的模块权限列表 */ + moduleInfo: IModuleInfo[] = [] + + /** 当前用户的菜单树(含层级结构) */ + menuTree: IMenuItem[] = [] + + /** 全局选项(/me 返回的 options) */ + globalOptions: Record = {} + + private callers: Map = new Map() + + /** 子类必须实现:底层 HTTP 请求 */ + abstract rawRequest(method: string, url: string, data?: any): Promise + + /** 子类必须实现:路由注册(框架特定,如 Vue Router) */ + abstract registerRoute(info: IRouteInfo): void + + // ---- Shrimp RPC ---- + + /** Shrimp RPC 请求(POST /moduleKey/eventName) */ + async doRequest(path: string[], data?: any[]): Promise { + return this.rawRequest('POST', '/' + path.join('/'), data) + } + + /** 标准 Shrimp 请求函数(过滤 undefined 参数),供 getCaller 使用 */ + async shrimpRequest(path: string[], ...args: any[]): Promise { + const filteredArgs = args.filter(arg => arg !== undefined) + return await this.doRequest(path, filteredArgs.length > 0 ? filteredArgs : undefined) + } + + // ---- REST 接口 ---- + + /** 获取当前用户信息(权限 + 菜单) */ + async fetchMe(): Promise { + return this.rawRequest('GET', '/me') + } + + /** 用户登录 */ + async fetchLogin(data: { username: string; password: string }): Promise { + return this.rawRequest('POST', '/login', data) + } + + /** 获取后台配置 */ + async fetchConfig(): Promise> { + return this.rawRequest>('GET', '/config') + } + + // ---- 用户信息 ---- + + /** 获取当前用户信息(moduleInfo + menuTree)并注册路由 */ + async loadUserInfo(): Promise { + const data = await this.fetchMe() + this.moduleInfo = data.moduleInfo + this.menuTree = data.menuTree + this.globalOptions = data.options || {} + if (this.menuTree.length > 0) { + this.registerRoutesFromMenuTree(this.menuTree) + } else { + this.registerRoutesFromModuleInfo(this.moduleInfo) + } + // 子模块永远走 moduleInfo 递归注册(不进 menu) + this.registerSubModuleRoutes(this.moduleInfo) + return data + } + + // ---- 路由注册 ---- + + /** + * 根据 moduleInfo 扁平注册路由 + * 适用于无层级菜单的场景:moduleKey 即路由名,path 为 /moduleKey + * 子模块(带 parent)不会作为顶层路由,由 registerSubModuleRoutes 处理 + */ + registerRoutesFromModuleInfo(moduleInfo: IModuleInfo[]) { + moduleInfo.forEach(info => { + if (info.parent) return + this.registerRoute({ + moduleKey: info.moduleKey, + name: info.moduleKey, + label: info.label, + path: '/' + info.moduleKey, + events: info.events, + }) + }) + } + + /** + * 根据 menuTree 层级注册路由 + * 遍历菜单树,对每个带 moduleKey 的项调用 addRoute + */ + registerRoutesFromMenuTree(menuTree: IMenuItem[]) { + const walk = (items: IMenuItem[]) => { + items.forEach(item => { + if (item.moduleKey) { + this.registerRoute({ + moduleKey: item.moduleKey, + name: item.name, + path: item.path, + label: item.label, + events: this.getEvents(item.moduleKey), + }) + } + if (item.children) { + walk(item.children) + } + }) + } + walk(menuTree) + } + + /** + * 注册子模块路由(扁平 moduleInfo,通过 parent 链接) + * 子模块路径 = //,组件文件 = @/module//.vue + */ + registerSubModuleRoutes(moduleInfo: IModuleInfo[]) { + const byKey = new Map(moduleInfo.map(m => [m.moduleKey, m])) + const buildSegments = (info: IModuleInfo): string[] => { + const segs = [info.moduleKey] + let p = info.parent + while (p) { + segs.unshift(p) + p = byKey.get(p)?.parent + } + return segs + } + moduleInfo.forEach(info => { + if (!info.parent) return + const segments = buildSegments(info) + this.registerRoute({ + moduleKey: info.moduleKey, + name: segments.join('.'), + path: '/' + segments.join('/'), + label: info.label, + events: info.events, + componentPath: segments.join('/'), + icon: info.icon, + }) + }) + } + + // ---- 菜单树操作 ---- + + /** 获取第一个菜单路径(用于默认跳转) */ + getFirstMenuPath(): string { + const findFirst = (items: IMenuItem[]): string | null => { + for (const item of items) { + if (item.path) return item.path + if (item.moduleKey) return '/' + item.moduleKey + if (item.children) { + const found = findFirst(item.children) + if (found) return found + } + } + return null + } + const path = findFirst(this.menuTree) + if (path) return path + const top = this.moduleInfo.find(m => !m.parent) + return '/' + (top?.moduleKey || '') + } + + /** 根据 path 获取所在顶级菜单的索引 */ + getMenuIndex(path: string): number { + const findIndex = (items: IMenuItem[], targetPath: string): number => { + for (let i = 0; i < items.length; i++) { + const item = items[i] + if (item.path === targetPath || '/' + item.name === targetPath) return i + if (item.children) { + const childIndex = findIndex(item.children, targetPath) + if (childIndex !== -1) return i + } + } + return -1 + } + return findIndex(this.menuTree, path) + } + + /** 判断是否为分组菜单(有 children) */ + isGroupMenu(item?: IMenuItem): boolean { + return item != null && item.children != null && item.children.length > 0 + } + + /** 根据 path 生成面包屑路径(支持子模块: 走 moduleInfo.parent 链) */ + getBreadcrumb(path: string): { key: string; label: ILabel; path?: string }[] { + const result: { key: string; label: ILabel; path?: string }[] = [] + const findInMenu = (items: IMenuItem[], targetPath: string, out: typeof result): boolean => { + for (const item of items) { + const itemPath = item.path || '/' + item.name + if (itemPath === targetPath) { + out.unshift({ key: item.name, label: item.label, path: itemPath }) + return true + } + if (item.children) { + if (findInMenu(item.children, targetPath, out)) { + out.unshift({ key: item.name, label: item.label, path: item.path }) + return true + } + } + } + return false + } + if (findInMenu(this.menuTree, path, result)) return result + + // 子模块 fallback: 沿 moduleInfo.parent 链构建,带累积路径 + const segments = path.split('/').filter(Boolean) + if (segments.length === 0) return result + const byKey = new Map(this.moduleInfo.map(m => [m.moduleKey, m])) + const chain: IModuleInfo[] = [] + let info = byKey.get(segments[segments.length - 1]) + while (info) { + chain.unshift(info) + info = info.parent ? byKey.get(info.parent) : undefined + } + if (chain.length === 0) return result + + // 顶层模块如果在 menu 分组下, 先把分组上下文放进来 + findInMenu(this.menuTree, '/' + chain[0].moduleKey, result) + const lastKey = result.length > 0 ? result[result.length - 1].key : '' + let acc = '' + chain.forEach(m => { + acc += '/' + m.moduleKey + if (m.moduleKey === lastKey) return // 已被 menu 添加 + result.push({ key: m.moduleKey, label: m.label, path: acc }) + }) + return result + } + + // ---- 模块权限 ---- + + /** 获取模块的权限事件列表 */ + getEvents(moduleKey: string): string[] { + const mod = this.moduleInfo.find(m => m.moduleKey === moduleKey) + return mod?.events || [] + } + + /** 判断是否有某个事件的权限 */ + hasEvent(moduleKey: string, eventName: string): boolean { + return this.getEvents(moduleKey).includes(eventName) + } + + /** 获取绑定到指定模块的权限检查器:const can = client.can('shopAdmin'); can('apply') */ + can(moduleKey: string): (event: string) => boolean { + return (event: string) => this.hasEvent(moduleKey, event) + } + + // ---- 模块 API ---- + + /** 获取类型安全的模块 API(带缓存,自动沿 parent 链解析嵌套模块路径) */ + getModule

>(moduleKey: string): P { + let caller = this.callers.get(moduleKey) + if (!caller) { + const segments = this.resolveModulePath(moduleKey) + caller = getCaller

(this.shrimpRequest.bind(this), segments) + this.callers.set(moduleKey, caller) + } + return caller + } + + /** 从 moduleInfo 沿 parent 链回溯出完整路径(根→自身);找不到时退化为单段 */ + private resolveModulePath(moduleKey: string): string[] { + const byKey = new Map(this.moduleInfo.map(m => [m.moduleKey, m])) + const info = byKey.get(moduleKey) + if (!info) return [moduleKey] + const segs = [info.moduleKey] + let p = info.parent + while (p) { + segs.unshift(p) + p = byKey.get(p)?.parent + } + return segs + } + + /** 清除模块 API 缓存 */ + clearModuleCache(moduleKey?: string) { + if (moduleKey) { + this.callers.delete(moduleKey) + } else { + this.callers.clear() + } + } +} diff --git a/examples/basic-admin/web/src/VueAdmin.ts b/examples/basic-admin/web/src/VueAdmin.ts new file mode 100644 index 0000000..a58e738 --- /dev/null +++ b/examples/basic-admin/web/src/VueAdmin.ts @@ -0,0 +1,425 @@ +import * as elIcons from '@element-plus/icons-vue' +import { type App, type Component, defineAsyncComponent, reactive } from 'vue' +import axios from 'axios' +import { ElMessage, ElMessageBox } from 'element-plus' +import ElementPlus from 'element-plus' +import { createRouter, createWebHashHistory, Router } from 'vue-router' +import zh_cn from '@/locales/zh-cn.json' +import en from '@/locales/en.json' +import { createI18n, I18n } from 'vue-i18n' +import config from '@/config' +import { IShrimpResponse, ILabel } from '@shrimp/IShrimpType' + +import { ShrimpClient, handleShrimpResponse, type IShrimpTipper, type IRouteInfo } from './ShrimpClient' +import FieldString from '@/components/fields/FieldString.vue' + +// ============== 字段组件注册表 ============== + +const fieldRegistry = new Map() +const fieldOverrides = new Map() + +// 系统自带字段组件(手动注册) +fieldRegistry.set('string', FieldString) +fieldRegistry.set('boolean', defineAsyncComponent(() => import('@/components/fields/FieldBoolean.vue'))) +fieldRegistry.set('date', defineAsyncComponent(() => import('@/components/fields/FieldDate.vue'))) +fieldRegistry.set('number', defineAsyncComponent(() => import('@/components/fields/FieldNumber.vue'))) +fieldRegistry.set('password', defineAsyncComponent(() => import('@/components/fields/FieldPassword.vue'))) +fieldRegistry.set('relation', defineAsyncComponent(() => import('@/components/fields/FieldRelation.vue'))) +fieldRegistry.set('select', defineAsyncComponent(() => import('@/components/fields/FieldSelect.vue'))) +fieldRegistry.set('textarea', defineAsyncComponent(() => import('@/components/fields/FieldTextarea.vue'))) +fieldRegistry.set('json', defineAsyncComponent(() => import('@/components/fields/FieldJson.vue'))) +fieldRegistry.set('tab', defineAsyncComponent(() => import('@/components/fields/FieldTab.vue'))) + +// 业务字段组件(自动加载 module/fields 目录) +const fieldModules = import.meta.glob('@/module/fields/Field*.vue') + +for (const path in fieldModules) { + const match = path.match(/\/Field(.+)\.vue$/) + if (match) { + const name = 'field' + match[1] // FieldGoodsList → fieldGoodsList + fieldRegistry.set(name, defineAsyncComponent(fieldModules[path])) + } +} + +// 模块页面组件(支持嵌套,顶层 module/{key}.vue,子模块 module/{parent}/{sub}.vue) +const moduleComponents = import.meta.glob('@/module/**/*.vue') + +function resolveModuleComponent(componentPath: string): (() => Promise) | null { + const suffix = `/module/${componentPath}.vue` + for (const key in moduleComponents) { + if (key.endsWith(suffix)) return moduleComponents[key] + } + console.warn('[VueAdmin] available module keys:', Object.keys(moduleComponents)) + return null +} + +/** 注册自定义字段组件 */ +export function registerField(type: string, comp: Component) { + fieldRegistry.set(type, comp) +} + +/** 为指定模块的指定字段覆盖组件 */ +export function overrideField(moduleKey: string, fieldKey: string, comp: Component) { + const registryKey = `__override_${moduleKey}_${fieldKey}` + fieldRegistry.set(registryKey, comp) + fieldOverrides.set(`${moduleKey}.${fieldKey}`, registryKey) +} + +/** 根据模块+字段获取对应组件,优先 override,再按 type,最后回退 FieldString */ +export function getFieldComponent(moduleKey: string, type: string, fieldKey?: string): Component { + if (fieldKey) { + const override = fieldOverrides.get(`${moduleKey}.${fieldKey}`) + if (override && fieldRegistry.has(override)) return fieldRegistry.get(override)! + } + return fieldRegistry.get(type) || FieldString +} + +// ============== LocalData(localStorage 封装) ============== + +class LocalData { + set(key: string, data: any) { + localStorage.setItem(key, JSON.stringify(data)) + } + get(key: string) { + try { + const value = localStorage.getItem(key) + return value ? JSON.parse(value) : null + } catch { + return null + } + } + remove(key: string) { + localStorage.removeItem(key) + } + clear() { + localStorage.clear() + } +} + +// ============== VueAdmin(Vue 整合层) ============== + +export class VueAdmin extends ShrimpClient { + router!: Router + localData = new LocalData() + i18n!: I18n + adminConfig: Record = reactive({}) + userName = '' + + // ---- 字段组件注册 ---- + + /** 注册自定义字段组件 */ + registerField(type: string, comp: Component) { + fieldRegistry.set(type, comp) + } + + /** 为指定模块的指定字段覆盖组件 */ + overrideField(moduleKey: string, fieldKey: string, comp: Component) { + const registryKey = `__override_${moduleKey}_${fieldKey}` + fieldRegistry.set(registryKey, comp) + fieldOverrides.set(`${moduleKey}.${fieldKey}`, registryKey) + } + + /** 根据模块+字段获取对应组件 */ + getFieldComponent(moduleKey: string, type: string, fieldKey?: string): Component { + if (fieldKey) { + const override = fieldOverrides.get(`${moduleKey}.${fieldKey}`) + if (override && fieldRegistry.has(override)) return fieldRegistry.get(override)! + } + return fieldRegistry.get(type) || FieldString + } + + state = reactive({ + isMobile: false, + }) + + // ---- token(跨刷新持久化) ---- + + private _token: string | null = null + get token() { return this._token } + set token(value: string | null) { + this.localData.set("token", value) + this._token = value + } + + langList = [ + { lang: 'zh-cn', name: '简体中文' }, + // { lang: 'en', name: 'English' }, + ] + + /** 可用语言数量,<=1 时不需要显示切换按钮 */ + get langCount() { return this.langList.length } + + // ---- locale(跨刷新持久化) ---- + + get locale(): string { + return (this.i18n.global.locale as any).value + } + set locale(value: string) { + (this.i18n.global.locale as any).value = value + this.localData.set("lang", value) + } + + /** 切换到下一个语言 */ + toggleLang() { + if (this.langCount <= 1) return + const idx = this.langList.findIndex(l => l.lang === this.locale) + const next = this.langList[(idx + 1) % this.langCount] + this.locale = next.lang + } + + /** 获取当前语言的显示名 */ + get currentLangName() { + return this.langList.find(l => l.lang === this.locale)?.name || this.locale + } + + + // ---- HTTP ---- + + request = axios.create({ + baseURL: config.API_URL, + timeout: config.TIMEOUT + }) + + constructor() { + super() + this.initHttp() + this.initI18n() + this.initRouter() + this.initMobileCheck() + this._token = this.localData.get("token") + } + + // ---- i18n ---- + + initI18n() { + this.i18n = createI18n({ + locale: this.localData.get("lang") || 'zh-cn', + globalInjection: true, + legacy: false, + messages: { 'zh-cn': zh_cn, 'en': en } + }) + } + + /** + * 通用翻译方法,处理 ILabel union + * - string → 字面量,原样返回 + * - { t, params } → vue-i18n t(),params 递归翻译 + * - { tc, count } → vue-i18n tc(),复数 + * - { d, format } → vue-i18n d(),日期格式化 + * - { n, format } → vue-i18n n(),数字格式化 + * - { zh, en, ... }→ 内联多语言,按当前 locale 取 + */ + t(value?: ILabel | null): string { + if (value == null) return '' + if (typeof value === 'string') return value + const v = value as any + if ('t' in v) return (this.i18n.global.t as any)(v.t, this.recurseParams(v.params)) + if ('tc' in v) return ((this.i18n.global as any).t)(v.tc, this.recurseParams(v.params), v.count) + if ('d' in v) return (this.i18n.global.d as any)(v.d, v.format) + if ('n' in v) return (this.i18n.global.n as any)(v.n, v.format) + return v[this.locale] || '' + } + + private recurseParams(params?: Record): Record { + if (!params) return {} + const out: Record = {} + for (const [k, val] of Object.entries(params)) { + out[k] = (val !== null && typeof val === 'object') ? this.t(val as ILabel) : val + } + return out + } + + // ---- ShrimpClient 抽象方法实现 ---- + + /** 覆盖:保存用户名和全局选项 */ + async loadUserInfo() { + const data = await this.fetchMe() + this.userName = (data as any).nickname || '' + this.moduleInfo = data.moduleInfo + this.menuTree = data.menuTree + this.globalOptions = data.options || {} + if (this.menuTree.length > 0) { + this.registerRoutesFromMenuTree(this.menuTree) + } else { + this.registerRoutesFromModuleInfo(this.moduleInfo) + } + this.registerSubModuleRoutes(this.moduleInfo) + return data + } + + /** 底层 HTTP 传输:所有请求都走这里 */ + async rawRequest(method: string, url: string, data?: any): Promise { + const res = await this.request({ method, url, data }) + return res.data as T + } + + /** 注册 Vue 路由 */ + registerRoute(info: IRouteInfo) { + const componentPath = info.componentPath || info.moduleKey + const importer = resolveModuleComponent(componentPath) + if (!importer) { + console.warn(`[VueAdmin] module component not found: @/module/${componentPath}.vue`) + return + } + this.router.addRoute("layout", { + path: info.path || "/" + info.name, + name: info.name, + component: importer, + props: { moduleKey: info.moduleKey, events: info.events }, + meta: { title: info.label } + }) + } + + // ---- Router ---- + + initRouter() { + this.router = createRouter({ + history: createWebHashHistory(), + routes: [ + { + name: "layout", + path: "/", + component: () => import('@/views/layout.vue'), + children: [ + ] + }, + { + path: "/login", + component: () => import('@/views/login.vue'), + meta: { title: "t:login.signIn" } + }, + { + path: "/:pathMatch(.*)", + component: () => import('@/views/404.vue'), + }, + ], + }) + + this.router.afterEach((to) => { + if (to.meta?.title) { + document.title = this.t(to.meta.title as string) + } else { + document.title = this.adminConfig.title || 'Admin' + } + }) + + this.router.beforeEach(async (to, _from, next) => { + if (to.path === "/login") { + next() + } else if (!this.token) { + next({ path: '/login' }) + } else if (this.moduleInfo.length === 0) { + try { + await this.loadUserInfo() + const firstPath = this.getFirstMenuPath() + next(to.path === "/" + ? { path: firstPath, replace: true } + : { ...to, replace: true }) + } catch { + // /me 失败(token 过期等),跳登录 + this.token = null + next({ path: '/login' }) + } + } else { + next() + } + }) + } + + // ---- Tipper(Shrimp 协议提示接口) ---- + + tipper: IShrimpTipper = { + success: (msg) => ElMessage.success(this.t(msg)), + error: (msg) => ElMessage.error(this.t(msg)), + alert: (msg) => ElMessageBox.alert(this.t(msg), { + confirmButtonText: (this.i18n.global.t as any)('base.confirm'), + showClose: true, + }), + } + + // ---- HTTP 拦截器 ---- + + initHttp() { + this.request.interceptors.request.use((reqConfig) => { + if (this.token) { + reqConfig.headers.Authorization = `Bearer ${this.token}` + } + return reqConfig + }) + + this.request.interceptors.response.use( + (res) => { + const body = res.data as IShrimpResponse + + // 兼容非 Shrimp 标准格式的接口 + if (body.code === undefined) { + return res + } + + try { + if (body.tip) { + body.tip = this.t(body.tip) + } + res.data = handleShrimpResponse(body, this.tipper, () => { + this.token = null + this.router.push('/login') + }) + return res + } catch (err) { + return Promise.reject(err) + } + }, + (error) => { + ElMessage.error(this.t('t:base.netError')) + throw error + } + ) + } + + // ---- 移动端检测 ---- + + initMobileCheck() { + const check = () => { this.state.isMobile = document.body.clientWidth <= 992 } + window.addEventListener('resize', check) + check() + } + + // ---- 全屏切换 ---- + + fullscreen() { + const doc = document as any + const isFull = !!(doc.webkitIsFullScreen || doc.mozFullScreen || doc.msFullscreenElement || doc.fullscreenElement) + if (isFull) { + doc.exitFullscreen?.() || doc.msExitFullscreen?.() || doc.mozCancelFullScreen?.() || doc.webkitExitFullscreen?.() + } else { + const el = doc.documentElement + el.requestFullscreen?.() || el.msRequestFullscreen?.() || el.mozRequestFullScreen?.() || el.webkitRequestFullscreen?.() + } + } + + // ---- 工具方法 ---- + + clearCache() { + this.localData.clear() + } + + reset() { + this.token = null + location.reload() + } +} + +export let vueAdmin = new VueAdmin() + +export default { + install(app: App) { + app.use(ElementPlus) + app.use(vueAdmin.i18n) + app.use(vueAdmin.router) + for (const key in elIcons) { + app.component(`ElIcon${key}`, (elIcons as any)[key]) + + // app.component(`el-icon-${key}`, (elIcons as any)[key]) + } + }, +} diff --git a/examples/basic-admin/web/src/assets/icons/BugFill.vue b/examples/basic-admin/web/src/assets/icons/BugFill.vue new file mode 100644 index 0000000..35a6edc --- /dev/null +++ b/examples/basic-admin/web/src/assets/icons/BugFill.vue @@ -0,0 +1,3 @@ + diff --git a/examples/basic-admin/web/src/assets/icons/BugLine.vue b/examples/basic-admin/web/src/assets/icons/BugLine.vue new file mode 100644 index 0000000..b1cb9ad --- /dev/null +++ b/examples/basic-admin/web/src/assets/icons/BugLine.vue @@ -0,0 +1,3 @@ + diff --git a/examples/basic-admin/web/src/assets/icons/Code.vue b/examples/basic-admin/web/src/assets/icons/Code.vue new file mode 100644 index 0000000..9d1581c --- /dev/null +++ b/examples/basic-admin/web/src/assets/icons/Code.vue @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/examples/basic-admin/web/src/assets/icons/Download.vue b/examples/basic-admin/web/src/assets/icons/Download.vue new file mode 100644 index 0000000..3b139da --- /dev/null +++ b/examples/basic-admin/web/src/assets/icons/Download.vue @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/examples/basic-admin/web/src/assets/icons/FileExcel.vue b/examples/basic-admin/web/src/assets/icons/FileExcel.vue new file mode 100644 index 0000000..c6d5fdf --- /dev/null +++ b/examples/basic-admin/web/src/assets/icons/FileExcel.vue @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/examples/basic-admin/web/src/assets/icons/FilePpt.vue b/examples/basic-admin/web/src/assets/icons/FilePpt.vue new file mode 100644 index 0000000..9fcf2a2 --- /dev/null +++ b/examples/basic-admin/web/src/assets/icons/FilePpt.vue @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/examples/basic-admin/web/src/assets/icons/FileWord.vue b/examples/basic-admin/web/src/assets/icons/FileWord.vue new file mode 100644 index 0000000..cfd035a --- /dev/null +++ b/examples/basic-admin/web/src/assets/icons/FileWord.vue @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/examples/basic-admin/web/src/assets/icons/Lang.vue b/examples/basic-admin/web/src/assets/icons/Lang.vue new file mode 100644 index 0000000..30a0b87 --- /dev/null +++ b/examples/basic-admin/web/src/assets/icons/Lang.vue @@ -0,0 +1,21 @@ + diff --git a/examples/basic-admin/web/src/assets/icons/Organization.vue b/examples/basic-admin/web/src/assets/icons/Organization.vue new file mode 100644 index 0000000..a666d76 --- /dev/null +++ b/examples/basic-admin/web/src/assets/icons/Organization.vue @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/examples/basic-admin/web/src/assets/icons/Upload.vue b/examples/basic-admin/web/src/assets/icons/Upload.vue new file mode 100644 index 0000000..c0bf5e6 --- /dev/null +++ b/examples/basic-admin/web/src/assets/icons/Upload.vue @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/examples/basic-admin/web/src/assets/icons/Vue.vue b/examples/basic-admin/web/src/assets/icons/Vue.vue new file mode 100644 index 0000000..5253d61 --- /dev/null +++ b/examples/basic-admin/web/src/assets/icons/Vue.vue @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/examples/basic-admin/web/src/assets/icons/Wechat.vue b/examples/basic-admin/web/src/assets/icons/Wechat.vue new file mode 100644 index 0000000..a572f7b --- /dev/null +++ b/examples/basic-admin/web/src/assets/icons/Wechat.vue @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/examples/basic-admin/web/src/assets/icons/index.ts b/examples/basic-admin/web/src/assets/icons/index.ts new file mode 100644 index 0000000..754ea93 --- /dev/null +++ b/examples/basic-admin/web/src/assets/icons/index.ts @@ -0,0 +1,12 @@ +export { default as Vue } from './Vue.vue' +export { default as Code } from './Code.vue' +export { default as Wechat } from './Wechat.vue' +export { default as BugFill } from './BugFill.vue' +export { default as BugLine } from './BugLine.vue' +export { default as FileWord } from './FileWord.vue' +export { default as FileExcel } from './FileExcel.vue' +export { default as FilePpt } from './FilePpt.vue' +export { default as Organization } from './Organization.vue' +export { default as Upload } from './Upload.vue' +export { default as Download } from './Download.vue' +export { default as Lang } from './Lang.vue' \ No newline at end of file diff --git a/examples/basic-admin/web/src/components/LangSwitch.vue b/examples/basic-admin/web/src/components/LangSwitch.vue new file mode 100644 index 0000000..1529493 --- /dev/null +++ b/examples/basic-admin/web/src/components/LangSwitch.vue @@ -0,0 +1,47 @@ + + + + + + + diff --git a/examples/basic-admin/web/src/components/PowerTree.vue b/examples/basic-admin/web/src/components/PowerTree.vue new file mode 100644 index 0000000..55b7c35 --- /dev/null +++ b/examples/basic-admin/web/src/components/PowerTree.vue @@ -0,0 +1,122 @@ + + + + + diff --git a/examples/basic-admin/web/src/components/ShrimpEditDrawer.vue b/examples/basic-admin/web/src/components/ShrimpEditDrawer.vue new file mode 100644 index 0000000..b849e04 --- /dev/null +++ b/examples/basic-admin/web/src/components/ShrimpEditDrawer.vue @@ -0,0 +1,126 @@ + + + + + diff --git a/examples/basic-admin/web/src/components/ShrimpFilterDrawer.vue b/examples/basic-admin/web/src/components/ShrimpFilterDrawer.vue new file mode 100644 index 0000000..398d510 --- /dev/null +++ b/examples/basic-admin/web/src/components/ShrimpFilterDrawer.vue @@ -0,0 +1,104 @@ + + + + + diff --git a/examples/basic-admin/web/src/components/ShrimpFilterTrigger.vue b/examples/basic-admin/web/src/components/ShrimpFilterTrigger.vue new file mode 100644 index 0000000..623464e --- /dev/null +++ b/examples/basic-admin/web/src/components/ShrimpFilterTrigger.vue @@ -0,0 +1,22 @@ + + + diff --git a/examples/basic-admin/web/src/components/ShrimpSearchInput.vue b/examples/basic-admin/web/src/components/ShrimpSearchInput.vue new file mode 100644 index 0000000..6f1ba6b --- /dev/null +++ b/examples/basic-admin/web/src/components/ShrimpSearchInput.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/examples/basic-admin/web/src/components/ShrimpTable.vue b/examples/basic-admin/web/src/components/ShrimpTable.vue new file mode 100644 index 0000000..093bf11 --- /dev/null +++ b/examples/basic-admin/web/src/components/ShrimpTable.vue @@ -0,0 +1,583 @@ + + + + + diff --git a/examples/basic-admin/web/src/components/fields/FieldBoolean.vue b/examples/basic-admin/web/src/components/fields/FieldBoolean.vue new file mode 100644 index 0000000..136cb85 --- /dev/null +++ b/examples/basic-admin/web/src/components/fields/FieldBoolean.vue @@ -0,0 +1,36 @@ + + + diff --git a/examples/basic-admin/web/src/components/fields/FieldDate.vue b/examples/basic-admin/web/src/components/fields/FieldDate.vue new file mode 100644 index 0000000..bc01fff --- /dev/null +++ b/examples/basic-admin/web/src/components/fields/FieldDate.vue @@ -0,0 +1,137 @@ + + + + + diff --git a/examples/basic-admin/web/src/components/fields/FieldJson.vue b/examples/basic-admin/web/src/components/fields/FieldJson.vue new file mode 100644 index 0000000..cc686a1 --- /dev/null +++ b/examples/basic-admin/web/src/components/fields/FieldJson.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/examples/basic-admin/web/src/components/fields/FieldNumber.vue b/examples/basic-admin/web/src/components/fields/FieldNumber.vue new file mode 100644 index 0000000..fc865eb --- /dev/null +++ b/examples/basic-admin/web/src/components/fields/FieldNumber.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/examples/basic-admin/web/src/components/fields/FieldPassword.vue b/examples/basic-admin/web/src/components/fields/FieldPassword.vue new file mode 100644 index 0000000..439e324 --- /dev/null +++ b/examples/basic-admin/web/src/components/fields/FieldPassword.vue @@ -0,0 +1,20 @@ + + + diff --git a/examples/basic-admin/web/src/components/fields/FieldRelation.vue b/examples/basic-admin/web/src/components/fields/FieldRelation.vue new file mode 100644 index 0000000..8c168fd --- /dev/null +++ b/examples/basic-admin/web/src/components/fields/FieldRelation.vue @@ -0,0 +1,94 @@ + + + diff --git a/examples/basic-admin/web/src/components/fields/FieldSelect.vue b/examples/basic-admin/web/src/components/fields/FieldSelect.vue new file mode 100644 index 0000000..32bc4aa --- /dev/null +++ b/examples/basic-admin/web/src/components/fields/FieldSelect.vue @@ -0,0 +1,65 @@ + + + diff --git a/examples/basic-admin/web/src/components/fields/FieldString.vue b/examples/basic-admin/web/src/components/fields/FieldString.vue new file mode 100644 index 0000000..0f56c78 --- /dev/null +++ b/examples/basic-admin/web/src/components/fields/FieldString.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/examples/basic-admin/web/src/components/fields/FieldTab.vue b/examples/basic-admin/web/src/components/fields/FieldTab.vue new file mode 100644 index 0000000..ff3cfdb --- /dev/null +++ b/examples/basic-admin/web/src/components/fields/FieldTab.vue @@ -0,0 +1,120 @@ + + + + + diff --git a/examples/basic-admin/web/src/components/fields/FieldTextarea.vue b/examples/basic-admin/web/src/components/fields/FieldTextarea.vue new file mode 100644 index 0000000..206e5a0 --- /dev/null +++ b/examples/basic-admin/web/src/components/fields/FieldTextarea.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/examples/basic-admin/web/src/composables/useTableUrlSync.ts b/examples/basic-admin/web/src/composables/useTableUrlSync.ts new file mode 100644 index 0000000..8c1460a --- /dev/null +++ b/examples/basic-admin/web/src/composables/useTableUrlSync.ts @@ -0,0 +1,98 @@ +import { ref, watch } from 'vue' +import { useRoute, useRouter } from 'vue-router' + +export interface UseTableUrlSyncOptions { + pageSize: number +} + +export function useTableUrlSync(options: UseTableUrlSyncOptions) { + const route = useRoute() + const router = useRouter() + + const currentPage = ref(1) + const currentPageSize = ref(options.pageSize) + const keyword = ref('') + const searchKeyword = ref('') + const currentSortBy = ref('') + const currentSortOrder = ref<'asc' | 'desc'>('asc') + const filterData = ref>({}) + + function restoreFromQuery() { + const q = route.query + if (q.page) currentPage.value = Number(q.page) || 1 + if (q.pageSize) currentPageSize.value = Number(q.pageSize) || options.pageSize + if (q.keyword) { + keyword.value = String(q.keyword) + searchKeyword.value = String(q.keyword) + } + if (q.sortBy) currentSortBy.value = String(q.sortBy) + if (q.sortOrder && (q.sortOrder === 'asc' || q.sortOrder === 'desc')) { + currentSortOrder.value = q.sortOrder + } + if (q.filter) { + try { + const parsed = JSON.parse(String(q.filter)) + if (typeof parsed === 'object' && parsed !== null) { + filterData.value = parsed + } + } catch { /* ignore invalid JSON */ } + } + } + + // 表格自身管理的 query key,sync 时只覆盖这几个,其它保留(如 ?id= 子表父记录、自定义参数) + const TABLE_QUERY_KEYS = ['page', 'pageSize', 'keyword', 'sortBy', 'sortOrder', 'filter'] + + function syncToQuery() { + const preserved: Record = {} + for (const [k, v] of Object.entries(route.query)) { + if (!TABLE_QUERY_KEYS.includes(k)) preserved[k] = v + } + const query: Record = { ...preserved } + if (currentPage.value > 1) query.page = String(currentPage.value) + if (currentPageSize.value !== options.pageSize) query.pageSize = String(currentPageSize.value) + if (searchKeyword.value) query.keyword = searchKeyword.value + if (currentSortBy.value) { + query.sortBy = currentSortBy.value + query.sortOrder = currentSortOrder.value + } + const cleanFilter: Record = {} + for (const [key, val] of Object.entries(filterData.value)) { + if (val === '' || val === null || val === undefined) continue + if (Array.isArray(val) && val.length === 0) continue + cleanFilter[key] = val + } + if (Object.keys(cleanFilter).length > 0) { + query.filter = JSON.stringify(cleanFilter) + } + router.replace({ query }) + } + + let isRestoring = false + + watch( + [currentPage, currentPageSize, searchKeyword, currentSortBy, currentSortOrder, filterData], + () => { + if (!isRestoring) { + syncToQuery() + } + }, + { deep: true } + ) + + function restore() { + isRestoring = true + restoreFromQuery() + isRestoring = false + } + + return { + currentPage, + currentPageSize, + keyword, + searchKeyword, + currentSortBy, + currentSortOrder, + filterData, + restore, + } +} diff --git a/examples/basic-admin/web/src/config/index.ts b/examples/basic-admin/web/src/config/index.ts new file mode 100644 index 0000000..287901a --- /dev/null +++ b/examples/basic-admin/web/src/config/index.ts @@ -0,0 +1,42 @@ + +// 配置可能有分以下几种 每种可能数据来源方式有所不同 +// 程序配置 +// 项目配置 +// 客户配置 +// 客户定义 +// 临时选择 + + +const DEFAULT_CONFIG = { + + //追加其他头 + HEADERS: {}, + + + //版本号 + APP_VER: "0.0.1", + + //接口地址 + API_URL: "/admin-api", + + //请求超时 + TIMEOUT: 10000, + + //请求是否开启缓存 + REQUEST_CACHE: false, + + + + //是否加密localStorage, 为空不加密,可填写AES(模式ECB,移位Pkcs7)加密 + LS_ENCRYPTION: '', + + //localStorageAES加密秘钥,位数建议填写8的倍数 + LS_ENCRYPTION_key: '2XNN4K8LC0ELVWN4', + +} + + + +const CONFIG = DEFAULT_CONFIG + +export default CONFIG diff --git a/examples/basic-admin/web/src/locales/en.json b/examples/basic-admin/web/src/locales/en.json new file mode 100644 index 0000000..52e0d8c --- /dev/null +++ b/examples/basic-admin/web/src/locales/en.json @@ -0,0 +1,131 @@ +{ + "base": { + "name": "Name", + "add": "Add", + "edit": "Edit", + "delete": "Delete", + "search": "Search", + "filter": "Filter", + "reset": "Reset", + "confirm": "Confirm", + "cancel": "Cancel", + "close": "Close", + "save": "Save", + "submit": "Submit", + "operation": "Operation", + "exportExcel": "Export Excel", + "importExcel": "Import Excel", + "success": "Success", + "fail": "Failed", + "noNavMenu": "No submenu", + "clearCache": "Clear Cache", + "logout": "Logout", + "netError": "Network error", + "disabled": "Disabled", + "enabled": "Enabled", + "pressEnter": "Press Enter to add", + "expand": "Expand", + "collapse": "Collapse" + }, + "user": { + "username": "Username", + "password": "Password", + "nickname": "Nickname", + "roleId": "Role", + "active": "Active", + "createdAt": "Created At", + "updatedAt": "Updated At", + "menuManage": "User", + "operationLog": "Operation Log" + }, + "operationLog": { + "ip": "IP", + "method": "Action", + "args": "Args", + "success": "Success", + "errorMsg": "Error", + "createdAt": "Time" + }, + "role": { + "name": "Role Name", + "description": "Description", + "isSuperAdmin": "Super Admin", + "powers": "Permissions", + "createdAt": "Created At", + "updatedAt": "Updated At", + "menuManage": "Role", + "menuList": "Role List", + "empty": "No roles", + "addRole": "Add Role", + "confirmDelete": "Are you sure you want to delete this role?", + "selectOrCreate": "Please select or create a role", + "selectAll": "Select All", + "newRole": "New Role" + }, + "table": { + "addSuccess": "Added successfully", + "editSuccess": "Updated successfully", + "confirmDelete": "Are you sure you want to delete this record?", + "confirmClear": "Are you sure you want to clear all data? This cannot be undone!", + "yes": "Yes", + "no": "No", + "unlimited": "Any", + "startTime": "Start Time", + "endTime": "End Time", + "min": "Min", + "max": "Max", + "justNow": "Just now", + "minutesAgo": "{n} min ago", + "hoursAgo": "{n} hr ago", + "daysAgo": "{n} days ago", + "textareaMultipleNotSupported": "Textarea does not support multiple selection", + "passwordMultipleNotSupported": "Password does not support multiple selection", + "pressEnterForNumber": "Type number and press Enter" + }, + "login": { + "userPlaceholder": "Username / Phone / Email", + "PWPlaceholder": "Please enter password", + "signIn": "Sign In", + "userError": "Please enter username", + "PWError": "Please enter password" + }, + "power": { + "user": "User", + "role": "Role", + "add": "Add", + "edit": "Edit", + "delete": "Delete", + "list": "List", + "get": "Detail", + "importExcel": "Import Excel", + "exportExcel": "Export Excel", + "view": "View", + "clear": "Clear", + "export": "Export", + "import": "Import" + }, + "shrimp": { + "loginFailed": "Incorrect username or password", + "userNotFound": "User not found", + "relogin": "Please log in again", + "userDisabled": "User has been disabled", + "emptyPath": "Request path cannot be empty", + "forbidden": "Insufficient permissions", + "serverError": "Server error", + "cannotDisableSelf": "Cannot disable yourself", + "cannotDeleteSelf": "Cannot delete yourself", + "cannotChangeOwnRole": "Cannot change your own role", + "cannotEditOwnRole": "Cannot edit your own role", + "cannotDeleteOwnRole": "Cannot delete your own role", + "cannotClearAllUsers": "Cannot clear all users", + "cannotClearAllRoles": "Cannot clear all roles", + "fieldRequired": "Field \"{field}\" is required", + "fieldTypeError": "Field \"{field}\" has invalid type", + "fieldInvalid": "Field \"{field}\" has invalid value", + "fieldMin": "Field \"{field}\" minimum value is {min}", + "fieldMax": "Field \"{field}\" maximum value is {max}", + "fieldMinLength": "Field \"{field}\" minimum length is {min}", + "fieldMaxLength": "Field \"{field}\" maximum length is {max}", + "invalidJson": "Invalid JSON format" + } +} \ No newline at end of file diff --git a/examples/basic-admin/web/src/locales/zh-cn.json b/examples/basic-admin/web/src/locales/zh-cn.json new file mode 100644 index 0000000..96e3bb4 --- /dev/null +++ b/examples/basic-admin/web/src/locales/zh-cn.json @@ -0,0 +1,130 @@ +{ + "base": { + "name": "名称", + "add": "新增", + "edit": "编辑", + "delete": "删除", + "search": "搜索", + "filter": "筛选", + "reset": "重置", + "confirm": "确定", + "cancel": "取消", + "close": "关闭", + "save": "保存", + "submit": "提交", + "operation": "操作", + "exportExcel": "导出Excel", + "importExcel": "导入Excel", + "success": "成功", + "fail": "失败", + "noNavMenu": "没有子菜单", + "clearCache": "清除缓存", + "logout": "退出登录", + "netError": "网络出错", + "disabled": "禁用", + "enabled": "启用", + "pressEnter": "按回车添加", + "back": "返回", + "refresh": "刷新", + "notFound": "未找到数据", + "expand": "展开", + "collapse": "收起" + }, + "user": { + "username": "用户名", + "password": "密码", + "nickname": "昵称", + "roleId": "角色", + "active": "启用状态", + "createdAt": "创建时间", + "updatedAt": "更新时间", + "operationLog": "操作日志" + }, + "operationLog": { + "ip": "IP", + "method": "操作", + "args": "参数", + "success": "成功", + "errorMsg": "错误信息", + "createdAt": "时间" + }, + "role": { + "name": "角色名", + "description": "描述", + "isSuperAdmin": "超级管理员", + "powers": "权限", + "createdAt": "创建时间", + "updatedAt": "更新时间", + "menuList": "角色列表", + "empty": "暂无角色", + "addRole": "新增角色", + "confirmDelete": "确定删除此角色吗?", + "selectOrCreate": "请选择或新建角色", + "selectAll": "全选", + "newRole": "新角色" + }, + "table": { + "addSuccess": "新增成功", + "editSuccess": "编辑成功", + "confirmDelete": "确定删除此记录吗?", + "confirmClear": "确定清空数据吗?此操作不可恢复!", + "yes": "是", + "no": "否", + "unlimited": "不限", + "startTime": "开始时间", + "endTime": "结束时间", + "min": "最小值", + "max": "最大值", + "justNow": "刚刚", + "minutesAgo": "{n} 分钟前", + "hoursAgo": "{n} 小时前", + "daysAgo": "{n} 天前", + "textareaMultipleNotSupported": "文本域暂不支持多选", + "passwordMultipleNotSupported": "密码暂不支持多选", + "pressEnterForNumber": "输入数字后按回车" + }, + "login": { + "userPlaceholder": "用户名 / 手机 / 邮箱", + "PWPlaceholder": "请输入密码", + "signIn": "登录", + "userError": "请输入用户名", + "PWError": "请输入密码" + }, + "power": { + "add": "新增", + "edit": "编辑", + "delete": "删除", + "list": "列表", + "get": "详情", + "importExcel": "导入Excel", + "exportExcel": "导出Excel", + "view": "查看", + "clear": "清除", + "export": "导出", + "import": "导入" + }, + "shrimp": { + "loginFailed": "用户名或密码错误", + "userNotFound": "用户不存在", + "relogin": "请重新登录", + "userDisabled": "用户已被禁用", + "emptyPath": "请求路径不能为空", + "forbidden": "权限不足", + "serverError": "服务器错误", + "cannotDisableSelf": "不能禁用自己", + "cannotDeleteSelf": "不能删除自己", + "cannotChangeOwnRole": "不能修改自己的角色", + "cannotEditOwnRole": "不能编辑自己的角色", + "cannotDeleteOwnRole": "不能删除自己的角色", + "cannotClearAllUsers": "不能清空所有用户", + "cannotClearAllRoles": "不能清空所有角色", + "fieldRequired": "字段\"{field}\"不能为空", + "fieldTypeError": "字段\"{field}\"类型错误", + "fieldInvalid": "字段\"{field}\"值不合法", + "fieldMin": "字段\"{field}\"最小值为 {min}", + "fieldMax": "字段\"{field}\"最大值为 {max}", + "fieldMinLength": "字段\"{field}\"最小长度为 {min}", + "fieldMaxLength": "字段\"{field}\"最大长度为 {max}", + "invalidJson": "JSON格式错误" + } +} \ No newline at end of file diff --git a/examples/basic-admin/web/src/main.ts b/examples/basic-admin/web/src/main.ts new file mode 100644 index 0000000..b7a56df --- /dev/null +++ b/examples/basic-admin/web/src/main.ts @@ -0,0 +1,10 @@ +import { createApp } from 'vue' +import 'element-plus/dist/index.css' +import 'element-plus/theme-chalk/display.css' + +import App from './views/App.vue' +import vueAdmin from './VueAdmin' + +const app = createApp(App) +app.use(vueAdmin) +app.mount('#app') diff --git a/examples/basic-admin/web/src/module/FieldRelativeTime.vue b/examples/basic-admin/web/src/module/FieldRelativeTime.vue new file mode 100644 index 0000000..4ba30e0 --- /dev/null +++ b/examples/basic-admin/web/src/module/FieldRelativeTime.vue @@ -0,0 +1,42 @@ + + + diff --git a/examples/basic-admin/web/src/module/role.vue b/examples/basic-admin/web/src/module/role.vue new file mode 100644 index 0000000..04a5365 --- /dev/null +++ b/examples/basic-admin/web/src/module/role.vue @@ -0,0 +1,238 @@ + + + + + diff --git a/examples/basic-admin/web/src/module/user.vue b/examples/basic-admin/web/src/module/user.vue new file mode 100644 index 0000000..fbb6aa7 --- /dev/null +++ b/examples/basic-admin/web/src/module/user.vue @@ -0,0 +1,14 @@ + + + + + diff --git a/examples/basic-admin/web/src/module/user/operationLog.vue b/examples/basic-admin/web/src/module/user/operationLog.vue new file mode 100644 index 0000000..cd7beeb --- /dev/null +++ b/examples/basic-admin/web/src/module/user/operationLog.vue @@ -0,0 +1,67 @@ + + + + + diff --git a/examples/basic-admin/web/src/style/app.scss b/examples/basic-admin/web/src/style/app.scss new file mode 100644 index 0000000..8bdf1b5 --- /dev/null +++ b/examples/basic-admin/web/src/style/app.scss @@ -0,0 +1,521 @@ +/* 全局 */ +#app, +body, +html { + width: 100%; + height: 100%; + background-color: #f6f8f9; + font-size: 12px; + font-family: "sans-serif"; +} + +a { + color: #333; + text-decoration: none; +} + +a:hover, +a:focus { + color: #000; + text-decoration: none; +} + +a:link { + text-decoration: none; +} + +a:-webkit-any-link { + text-decoration: none; +} + +a, +button, +input, +textarea { + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + box-sizing: border-box; + outline: none !important; + -webkit-appearance: none; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; + outline: none; +} + +/* 大布局样式 */ +.aminui { + display: flex; + flex-flow: column; +} + +.aminui-wrapper { + display: flex; + flex: 1; + overflow: auto; +} + +/* 全局滚动条样式 */ +.scrollabel { + -webkit-overflow-scrolling: touch; +} + +::-webkit-scrollbar { + width: 5px; + height: 5px; +} + +::-webkit-scrollbar-thumb { + background-color: rgba(50, 50, 50, 0.3); +} + +::-webkit-scrollbar-thumb:hover { + background-color: rgba(50, 50, 50, 0.6); +} + +::-webkit-scrollbar-track { + background-color: rgba(50, 50, 50, 0.1); +} + +::-webkit-scrollbar-track:hover { + background-color: rgba(50, 50, 50, 0.2); +} + +/*布局设置*/ +.layout-setting { + position: fixed; + width: 40px; + height: 40px; + border-radius: 3px 0 0 3px; + bottom: 100px; + right: 0px; + z-index: 100; + background: #409EFF; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + cursor: pointer; +} + +.layout-setting i { + font-size: 18px; + color: #fff; +} + +/* 头部 */ +.adminui-header { + height: 58px; + background: #222b45; + color: #fff; + display: flex; + justify-content: space-between; +} + +.adminui-header-left { + display: flex; + align-items: center; + padding-left: 20px; +} + +.adminui-header-right { + display: flex; + align-items: center; +} + +.adminui-header .logo-bar { + font-size: 20px; + font-weight: bold; + display: flex; + align-items: center; +} + +.adminui-header .logo-bar .logo { + margin-right: 10px; + width: 35px; + height: 35px; +} + +.adminui-header .nav { + display: flex; + height: 100%; + margin-left: 40px; +} + +.adminui-header .nav li { + padding: 0 10px; + margin: 0 10px 0 0; + font-size: 14px; + color: rgba(255, 255, 255, 0.6); + list-style: none; + height: 100%; + display: flex; + align-items: center; + cursor: pointer; +} + +.adminui-header .nav li i { + margin-right: 5px; +} + +.adminui-header .nav li:hover { + color: #fff; +} + +.adminui-header .nav li.active { + background: rgba(255, 255, 255, 0.1); + color: #fff; +} + +.adminui-header .user-bar .panel-item:hover { + background: rgba(255, 255, 255, 0.1) !important; +} + +.adminui-header .user-bar .user label { + color: #fff; +} + +/* 左侧菜单 */ +.aminui-side-split { + width: 65px; + flex-shrink: 0; + background: #222b45; + display: flex; + flex-flow: column; +} + +.aminui-side-split-top { + height: 49px; +} + +.aminui-side-split-top a { + display: inline-block; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} + +.aminui-side-split-top .logo { + height: 30px; + vertical-align: bottom; +} + +.adminui-side-split-scroll { + display: flex; + flex-direction: column; + overflow: hidden; + height: 100%; + flex: 1; +} + +.aminui-side-split li { + cursor: pointer; + width: 65px; + height: 65px; + color: #fff; + text-align: center; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.aminui-side-split li i { + font-size: 18px; +} + +.aminui-side-split li p { + margin-top: 5px; + padding: 0 4px; + font-size: 12px; + line-height: 1.2; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 100%; +} + +.aminui-side-split li:hover { + background: rgba(255, 255, 255, 0.1); +} + +.aminui-side-split li.active { + background: #409EFF; +} + +.adminui-side-split-scroll::-webkit-scrollbar-thumb { + background-color: rgba(255, 255, 255, 0.4); + border-radius: 5px; +} + +.adminui-side-split-scroll::-webkit-scrollbar-thumb:hover { + background-color: rgba(255, 255, 255, 0.5); +} + +.adminui-side-split-scroll::-webkit-scrollbar-track { + background-color: rgba(255, 255, 255, 0); +} + +.adminui-side-split-scroll::-webkit-scrollbar-track:hover { + background-color: rgba(255, 255, 255, 0); +} + +.aminui-side { + display: flex; + flex-flow: column; + flex-shrink: 0; + width: 210px; + background: #fff; + box-shadow: 2px 0 8px 0 rgba(29, 35, 41, .05); + border-right: 1px solid #e6e6e6; + transition: width 0.3s; +} + +.adminui-side-top { + border-bottom: 1px solid #ebeef5; + height: 50px; + line-height: 50px; +} + +.adminui-side-top h2 { + padding: 0 20px; + font-size: 17px; + color: #3c4a54; +} + +.adminui-side-scroll { + overflow: auto; + overflow-x: hidden; + flex: 1; +} + +.adminui-side-bottom { + border-top: 1px solid #ebeef5; + height: 51px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; +} + +.adminui-side-bottom i { + font-size: 16px; +} + +.adminui-side-bottom:hover { + color: var(--el-color-primary); +} + +.aminui-side.isCollapse { + width: 65px; +} + +.el-menu .menu-tag { + position: absolute; + height: 18px; + line-height: 18px; + background: var(--el-color-danger); + font-size: 12px; + color: #fff; + right: 20px; + border-radius: 18px; + padding: 0 6px; +} + +.el-menu .el-sub-menu__title .menu-tag { + right: 40px; +} + +.el-menu--horizontal>li .menu-tag { + display: none; +} + +/* 右侧内容 */ +.aminui-body { + flex: 1; + display: flex; + flex-flow: column; +} + +.adminui-topbar { + height: 50px; + border-bottom: 1px solid #ebeef5; + background: #fff; + box-shadow: 0 1px 4px rgba(0, 21, 41, .08); + display: flex; + justify-content: space-between; +} + +.adminui-topbar .left-panel { + display: flex; + align-items: center; +} + +.adminui-topbar .right-panel { + display: flex; + align-items: center; +} + +.right-panel-search { + display: flex; + align-items: center; +} + +.right-panel-search>*+* { + margin-left: 10px; +} + +.adminui-tags { + height: 35px; + background: #fff; + border-bottom: 1px solid #e6e6e6; +} + +.adminui-tags ul { + display: flex; + overflow: hidden; +} + +.adminui-tags li { + cursor: pointer; + display: inline-block; + float: left; + height: 34px; + line-height: 34px; + position: relative; + flex-shrink: 0; +} + +.adminui-tags li::after { + content: " "; + width: 1px; + height: 100%; + position: absolute; + right: 0px; + background-image: linear-gradient(#fff, #e6e6e6); +} + +.adminui-tags li a { + display: inline-block; + padding: 0 10px; + width: 100%; + height: 100%; + color: #999; + text-decoration: none; + display: flex; + align-items: center; +} + +.adminui-tags li i { + margin-left: 10px; + border-radius: 3px; + width: 18px; + height: 18px; + display: flex; + align-items: center; + justify-content: center; +} + +.adminui-tags li i:hover { + background: rgba(0, 0, 0, .2); + color: #fff; +} + +.adminui-tags li:hover { + background: #ecf5ff; +} + +.adminui-tags li.active { + background: #409EFF; +} + +.adminui-tags li.active a { + color: #fff; +} + +.adminui-tags li.sortable-ghost { + opacity: 0; +} + +.adminui-main { + overflow: hidden; + background-color: #f6f8f9; + flex: 1; + min-height: 0; + display: flex; + flex-direction: column; +} + +/* 让路由内容铺满 .adminui-main,使内部的滚动容器(如 ShrimpTable 的 .el-main)能拿到正确高度 */ +.adminui-main > * { + flex: 1; + min-height: 0; +} + +/*页面最大化*/ +.aminui.main-maximize { + .main-maximize-exit { + display: block; + } + + .aminui-side-split, + .aminui-side, + .adminui-header, + .adminui-topbar, + .adminui-tags { + display: none; + } +} + +.main-maximize-exit { + display: none; + position: fixed; + z-index: 3000; + top: -20px; + left: 50%; + margin-left: -20px; + border-radius: 50%; + width: 40px; + height: 40px; + cursor: pointer; + background: rgba(0, 0, 0, 0.2); + text-align: center; +} + +.main-maximize-exit i { + font-size: 14px; + margin-top: 22px; + color: #fff; +} + +.main-maximize-exit:hover { + background: rgba(0, 0, 0, 0.4); +} + +/*定宽页面*/ +.sc-page { + width: 1230px; + margin: 0 auto; +} + +.el-drawer .el-form-item { + display: block; +} + +// .el-drawer.rtl {width: min(600px, 100%)!important;} +//新增全局字体样式 +.el-tag+.el-tag { + margin-left: 5px; +} + +.el-button+.el-button { + margin-left: 5px; +} + +.el-table .el-table__cell { + padding: 5px; +} \ No newline at end of file diff --git a/examples/basic-admin/web/src/style/dark.scss b/examples/basic-admin/web/src/style/dark.scss new file mode 100644 index 0000000..2f551f5 --- /dev/null +++ b/examples/basic-admin/web/src/style/dark.scss @@ -0,0 +1,42 @@ +@use 'element-plus/theme-chalk/src/dark/css-vars.scss'; + +html.dark { + //变量 + --el-text-color-primary: #d0d0d0; + --el-color-primary-dark-2: var(--el-color-primary-light-2) !important; + --el-color-primary-light-9: var(--el-color-primary-dark-8) !important; + --el-color-primary-light-8: var(--el-color-primary-dark-7) !important; + --el-color-primary-light-7: var(--el-color-primary-dark-6) !important; + --el-color-primary-light-5: var(--el-color-primary-dark-4) !important; + --el-color-primary-light-3: var(--el-color-primary-dark-3) !important; + + //背景 + #app {background: var(--el-bg-color);} + + //登录背景 + .login_bg {background: var(--el-bg-color);} + + //框架 + .adminui-header {background: var(--el-bg-color-overlay);border-bottom: 1px solid var(--el-border-color-light);height:59px;} + .aminui-side-split {background: var(--el-bg-color);} + .aminui-side-split li {color: var(--el-text-color-primary);} + .aminui-side {background: var(--el-bg-color-overlay);border-color: var(--el-border-color-light);} + .adminui-side-top, .adminui-side-bottom {border-color: var(--el-border-color-light);} + .adminui-side-top h2 {color: var(--el-text-color-primary);} + .adminui-topbar, .adminui-tags {background: var(--el-bg-color-overlay);border-color: var(--el-border-color-light);} + .adminui-main {background: var(--el-bg-color);} + .drawerBG {background: var(--el-bg-color);} + .adminui-header-menu .el-menu {--el-menu-bg-color:var(--el-bg-color-overlay) !important;--el-menu-hover-bg-color: #171819 !important;} + .adminui-header-menu .el-menu .el-sub-menu__title {background-color:transparent !important;} + + //全局滚动条样式 + ::-webkit-scrollbar-thumb {background-color: rgba(163, 166, 173, 0.3);} + ::-webkit-scrollbar-thumb:hover {background-color: rgba(163, 166, 173, 0.5);} + + //组件 + .el-header, .el-main.nopadding, .el-footer {background: var(--el-bg-color-overlay);border-color: var(--el-border-color-light);} + .el-main {background: var(--el-bg-color);} + .el-aside {background: var(--el-bg-color-overlay);border-color: var(--el-border-color-light);} + .el-table .el-table__body-wrapper {background: var(--el-bg-color);} + .el-table th.is-sortable:hover {background: #111;} +} diff --git a/examples/basic-admin/web/src/style/fix.scss b/examples/basic-admin/web/src/style/fix.scss new file mode 100644 index 0000000..4b2f08c --- /dev/null +++ b/examples/basic-admin/web/src/style/fix.scss @@ -0,0 +1,91 @@ +/* 覆盖element-plus样式 */ + +:root { + --el-color-primary: #409EFF; + --el-color-primary-light-1: #53a7ff; + --el-color-primary-light-2: #66b1ff; + --el-color-primary-light-3: #79bbff; + --el-color-primary-light-4: #8cc4ff; + --el-color-primary-light-5: #9fceff; + --el-color-primary-light-6: #b2d8ff; + --el-color-primary-light-7: #c5e1ff; + --el-color-primary-light-8: #d8ebff; + --el-color-primary-light-9: #ebf5ff; + --el-color-primary-dark-1: #398ee5; + --el-color-primary-dark-2: #337ecc; + --el-color-primary-dark-3: #2c6eb2; + --el-color-primary-dark-4: #265e99; + --el-color-primary-dark-5: #204f7f; + --el-color-primary-dark-6: #193f66; + --el-color-primary-dark-7: #132f4c; + --el-color-primary-dark-8: #0c1f32; + --el-color-primary-dark-9: #060f19; +} + +.el-menu {border: none!important;} +.el-menu .el-menu-item a {color: inherit;text-decoration: none;display: block;width:100%;height:100%;position: absolute;top:0px;left:0px;} +.el-form-item-msg {font-size: 12px;color: #999;clear: both;width: 100%;} +.el-container {height: 100%;} +.el-aside {border-right: 1px solid var(--el-border-color-light);} +.el-container + .el-aside {border-right: 0;border-left: 1px solid var(--el-border-color-light);} +.el-header {background: #fff;border-bottom: 1px solid var(--el-border-color-light);padding:13px 15px;display: flex;align-items: center;} +.el-header .left-panel {display: flex;align-items: center;} +.el-header .right-panel {display: flex;align-items: center;} +.el-header .right-panel > * + * {margin-left:10px;} +.el-footer {background: #fff;border-top: 1px solid var(--el-border-color-light);padding:13px 15px;} +.el-main {padding:15px;} +.el-main.nopadding {padding:0;background: #fff;} +.el-drawer__body {overflow: auto;padding:0;} +.el-popconfirm__main {margin: 14px 0;} +// .el-card__header {border-bottom: 0;font-size: 17px;font-weight: bold;padding:15px 20px 0px 20px;} +// .el-card__header {padding:5px 20px 5px 20px;} +.el-dialog__title {font-size: 17px;font-weight: bold;} +.el-drawer__header>:first-child {font-size: 17px;font-weight: bold;} +.el-tree.menu .el-tree-node__content {height:36px;} +.el-tree.menu .el-tree-node__content .el-tree-node__label .icon {margin-right: 5px;} +.el-progress__text {font-size: 12px!important;} +.el-progress__text i {font-size: 14.4px!important;} +.el-step.is-horizontal .el-step__line {height:1px;} +.el-step__title {font-size: 14px;} +.drawerBG {background: #f6f8f9;} +.el-button+.el-dropdown {margin-left: 10px;} +.el-button-group+.el-dropdown {margin-left: 10px;} +.el-button-group+.el-button-group {margin-left: 10px;} +.el-tabs__nav-wrap::after {height: 1px;} +.el-table th.is-sortable {transition: .1s;} +.el-table th.is-sortable:hover {background: #eee;} +.el-table .el-table__body-wrapper {background: #f6f8f9;} +.el-col .el-card {margin-bottom: 15px;} +.el-main {flex-basis: 100%;} +.el-main > .scTable .el-table--border::before {display: none;} +.el-main > .scTable .el-table--border::after {display: none;} +.el-main > .scTable .el-table--border .el-table__inner-wrapper::after {display: none;} +.el-main > .scTable .el-table__border-left-patch {display: none;} +.el-main > .scTable .el-table--border .el-table__inner-wrapper tr:first-child td:first-child {border-left: 0;} +.el-main > .scTable .el-table--border .el-table__inner-wrapper tr:first-child th:first-child {border-left: 0;} +.el-table.el-table--large {font-size: 14px;} +.el-table.el-table--small {font-size: 12px;} +.el-table {font-size: 12px;} +.el-radio-button__inner {font-size: 12px;} +.el-checkbox-button__inner {font-size: 12px;} +.el-sub-menu .el-icon {font-size: 17px;} +.el-sub-menu .el-sub-menu__icon-arrow {font-size: 12px;} + +.aminui-side-split li.active {background-color: var(--el-color-primary);} +.adminui-tags li:hover {background-color: var(--el-color-primary-light-9);} +.adminui-tags li.active {background-color: var(--el-color-primary)!important;} +.contextmenu li:hover {background-color: var(--el-color-primary-light-9)!important;color: var(--el-color-primary-light-2)!important;} +.data-box .item-background {background-color: var(--el-color-primary)!important;} +.layout-setting,.diy-grid-setting {background-color: var(--el-color-primary)!important;} + +/* 全部禁用el-tag动画 */ +.el-tag {transition: all 0s !important;} + +/* 覆盖tinymce样式 */ +.sceditor .tox-tinymce {border: 1px solid #DCDFE6;border-radius: 0;} +body .tox-tinymce-aux {z-index: 5700;} + +//新增 +.el-table .cell{ + word-break: break-word; +} \ No newline at end of file diff --git a/examples/basic-admin/web/src/style/media.scss b/examples/basic-admin/web/src/style/media.scss new file mode 100644 index 0000000..559198c --- /dev/null +++ b/examples/basic-admin/web/src/style/media.scss @@ -0,0 +1,174 @@ +@media (max-width: 992px) { + + // 移动端样式覆盖 + .el-form-item { + display: block; + } + + .el-form-item__label { + margin: 5px !important; + } + + .el-dialog { + width: 90% !important; + } + + .el-dialog.is-fullscreen { + width: 100% !important; + } + + .el-drawer.rtl { + width: min(400px, 100%) !important; + } + + .el-form-item__content { + margin: 5px !important; + } + // .arrayInput{ + // width: 100% !important; + // } + + .adminui-main { + >.el-container { + display: block; + height: auto; + } + + >.el-container>.el-aside { + width: 100% !important; + border: 0 + } + } + + .scTable { + + .el-table, + .el-table__body-wrapper { + display: block !important; + height: auto !important; + } + + .el-scrollbar__wrap { + height: auto !important; + } + + .scTable-page { + padding: 0 5px !important; + } + + .el-pagination__total, + .el-pagination__jump, + .el-pagination__sizes { + display: none !important; + } + } + + .headerPublic { + height: auto !important; + display: block; + + .left-panel { + overflow: auto; + } + + .left-panel::-webkit-scrollbar { + display: none; + } + + .right-panel { + display: block; + border-top: 1px solid var(--el-border-color-light); + margin-top: 15px; + } + + .right-panel .right-panel-search { + display: block; + } + + .right-panel .right-panel-search>* { + width: 100%; + margin: 0; + margin-top: 15px; + } + } + + .adminui-main>.el-container>*:first-child:not(.el-aside):not(.el-header) { + border: 0; + margin-top: 0; + } + + .adminui-main>.el-container>*:first-child:not(.el-aside):not(.el-header)+.el-aside { + margin-top: 0; + } + + .adminui-main>.el-container>.el-aside { + border-bottom: 1px solid var(--el-border-color-light) !important; + } + + .adminui-main>.el-container>.el-container { + border-top: 1px solid var(--el-border-color-light); + border-bottom: 1px solid var(--el-border-color-light); + margin-top: 15px; + } + + .adminui-main>.el-container>.el-container+.el-aside { + border-top: 1px solid var(--el-border-color-light); + margin-top: 15px; + } + + .adminui-main>.el-container>.el-header { + @extend .headerPublic; + } + + .adminui-main>.el-container>.el-main.nopadding { + border-top: 1px solid var(--el-border-color-light); + border-bottom: 1px solid var(--el-border-color-light); + margin-top: 15px; + } + + .adminui-main>.el-container>.el-main+.el-aside { + border-left: 0 !important; + border-top: 1px solid var(--el-border-color-light); + margin-top: 15px; + } + + .adminui-main>.el-container>.el-footer { + margin-top: 15px; + border-bottom: 1px solid var(--el-border-color-light); + } + + .adminui-main>.el-container>.el-container>.el-header { + @extend .headerPublic + } + + .adminui-main>.el-container>.el-container>.el-header .left-panel { + display: block; + } + + .adminui-main>.el-container>.el-container>.el-header .right-panel { + display: block; + margin-top: 15px; + } + + .sc-page { + width: 100%; + margin: 0; + } + + .common-main .el-form { + width: 100% !important; + } + + .common-header-logo label { + display: none; + } + + .common-header-title { + display: none; + } + + //手机端没有左右边距 新增 + .el-main { + padding: 15px 0px; + } +} \ No newline at end of file diff --git a/examples/basic-admin/web/src/style/pages.scss b/examples/basic-admin/web/src/style/pages.scss new file mode 100644 index 0000000..564c8ca --- /dev/null +++ b/examples/basic-admin/web/src/style/pages.scss @@ -0,0 +1,44 @@ +/* USERCENTER */ +.page-user { + .user-info-top {text-align: center;} + .user-info-top h2 {font-size: 18px;margin-top: 5px;} + .user-info-top p {margin: 8px 0 10px 0;} + .menu {background: none;} + .menu .el-menu-item {font-size: 12px;--el-menu-item-height:50px;} + .menu .el-menu-item-group {border-top: 1px solid var(--el-border-color-light);} + .menu .el-menu-item-group:first-child {border: 0;} +} + +/*static-table*/ +.static-table {border-collapse: collapse;width: 100%;font-size: 14px;margin-bottom: 45px;line-height: 1.5em;} +.static-table th {text-align: left;white-space: nowrap;color: #909399;font-weight: 400;border-bottom: 1px solid #dcdfe6;padding: 15px;max-width: 250px;} +.static-table td {border-bottom: 1px solid #dcdfe6;padding: 15px;max-width: 250px;color: #606266;} + +/*header-tabs*/ +.header-tabs {padding:10px 0 0 0;display:block;border:0!important;height:50px;background: none;} +.header-tabs .el-tabs__header {padding-left:10px;margin: 0;} +.header-tabs .el-tabs__content {display: none;} +.header-tabs .el-tabs__nav {border-radius: 0 !important;} +.header-tabs .el-tabs__item {font-size: 13px;} +.header-tabs .el-tabs__item.is-active {background-color: var(--el-bg-color-overlay);} + +/*common-page*/ +.common-page {} +.common-header-left {display: flex;align-items: center;} +.common-header-logo {display: flex;align-items: center;} +.common-header-logo img {height:30px;margin-right: 10px;vertical-align: bottom;} +.common-header-logo label {font-size: 20px;} +.common-header-title {font-size: 16px;border-left: 1px solid var(--el-border-color-light);margin-left: 15px;padding-left: 15px;} +.common-header-right {display: flex;align-items: center;} +.common-header-right a {font-size: 14px;color: var(--el-color-primary);cursor: pointer;} +.common-header-right a:hover {color: var(--el-color-primary-light-3);} +.common-container {max-width: 1240px;margin:30px auto 30px auto;} +.common-main {padding:20px;} +.common-title {font-size: 26px;margin-bottom: 20px;font-weight: normal;} +.common-main .el-form {width: 500px;margin:30px auto;} +.common-main .el-steps .el-step__title {font-size: 14px;} +.common-main .el-steps .el-step__icon {border: 1px solid;} +.common-main .yzm {display: flex;width: 100%;} +.common-main .yzm .el-button {margin-left: 10px;} +.common-main .link {color: var(--el-color-primary);cursor: pointer;} +.common-main .link:hover {color: var(--el-color-primary-light-3);} diff --git a/examples/basic-admin/web/src/style/style.scss b/examples/basic-admin/web/src/style/style.scss new file mode 100644 index 0000000..467ac17 --- /dev/null +++ b/examples/basic-admin/web/src/style/style.scss @@ -0,0 +1,6 @@ +@use 'app.scss'; +@use 'fix.scss'; +@use 'pages.scss'; +@use 'media.scss'; +@use 'dark.scss'; +@use 'zd.scss'; diff --git a/examples/basic-admin/web/src/style/zd.scss b/examples/basic-admin/web/src/style/zd.scss new file mode 100644 index 0000000..6f20cee --- /dev/null +++ b/examples/basic-admin/web/src/style/zd.scss @@ -0,0 +1,48 @@ +.zd-flex-center { + display: flex; + justify-content: center; + align-items: center; +} + +.zd-flex-row { + display: flex; + flex-direction: row; + flex-wrap: wrap; +} + +.zd-flex-column { + display: flex; + flex-direction: column; +} + +.zd-padding-0 { + padding: 0px; +} + +.zd-padding-1 { + padding: 1px; +} + +.zd-padding-2 { + padding: 2px; +} + +.zd-padding-3 { + padding: 3px; +} + +.zd-padding-5 { + padding: 5px; +} + +.zd-padding-8 { + padding: 8px; +} + +.zd-padding-10 { + padding: 10px; +} + +.zd-aside+.zd-aside { + margin-left: 10px; +} \ No newline at end of file diff --git a/examples/basic-admin/web/src/types/game.ts b/examples/basic-admin/web/src/types/game.ts new file mode 100644 index 0000000..54da33b --- /dev/null +++ b/examples/basic-admin/web/src/types/game.ts @@ -0,0 +1,110 @@ +export enum GameState { + READYING, + PLAYING, + ENDING, +} + +export enum RoomState { + WAITING, + READYING, + PLAYING, + ENDING, +} + +export enum PlayerState { + ALIVE, + DEAD, + SUCCESS, +} + +export interface IPlayer { + name: string + uid: string + sid: string + playerId: string + vip: number + face: string + score: number + exCount: { + bag: number + save: number + } +} + +export interface IThingVo { + thingId: string + configId: string +} + +export interface IBagVO { + things: IThingVo[] + count: number + addCount: number +} + +export interface ITeamInfo { + playerInfo: IPlayer + team: { + teamId: number + birthplace: number + exitPlane: number + roomId: number + sitId: number + isOnline: boolean + state: PlayerState + } + bag: IBagVO + saveBag: IBagVO +} + +export interface IRoomInfo { + birthplaceList: { b: number; e: number }[] + index: number + state: RoomState + lockInfo: { [key: string]: boolean } +} + +export interface IRoomsInfo { + rooms: IRoomInfo[] + roomIndex: number + endTime: number +} + +export interface IBaseInfo { + playerId?: string + bag?: string + clothingId?: number +} + +export interface IBaseSC { + baseInfo(value: IBaseInfo): void + upLockSit(roomId: number, index: number, value: boolean): void + upRoom(value: IRoomsInfo): void + addUser(value: ITeamInfo): void + removeUser(value: ITeamInfo): void + playerInfo(value: ITeamInfo): void + upPlayer(value: ITeamInfo): void + kick(): void + eliminate(): void + addThing(value: IThingVo[]): void + addSaveThing(value: IThingVo[]): void + removeThing(value: number): void +} + +export interface IBossRouter { + iamboss(): Promise + changeSit(uid: string, sitId: number, teamId: number, roomIndex?: number): Promise + lockSit(roomId: number, index: number, lock: boolean): Promise + kill(uid: string): Promise + ready(): Promise + start(): Promise + end(): Promise +} + +export interface LoginVo { + userId: string +} + +export interface IWebCS { + getToken(vo: LoginVo): string +} diff --git a/examples/basic-admin/web/src/views/404.vue b/examples/basic-admin/web/src/views/404.vue new file mode 100644 index 0000000..e6836a5 --- /dev/null +++ b/examples/basic-admin/web/src/views/404.vue @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/examples/basic-admin/web/src/views/App.vue b/examples/basic-admin/web/src/views/App.vue new file mode 100644 index 0000000..10e19ec --- /dev/null +++ b/examples/basic-admin/web/src/views/App.vue @@ -0,0 +1,24 @@ + + + + diff --git a/examples/basic-admin/web/src/views/layout.vue b/examples/basic-admin/web/src/views/layout.vue new file mode 100644 index 0000000..f203003 --- /dev/null +++ b/examples/basic-admin/web/src/views/layout.vue @@ -0,0 +1,110 @@ + + + + diff --git a/examples/basic-admin/web/src/views/login.vue b/examples/basic-admin/web/src/views/login.vue new file mode 100644 index 0000000..dc47d45 --- /dev/null +++ b/examples/basic-admin/web/src/views/login.vue @@ -0,0 +1,130 @@ + + + + + diff --git a/examples/basic-admin/web/src/views/logo.vue b/examples/basic-admin/web/src/views/logo.vue new file mode 100644 index 0000000..79264bc --- /dev/null +++ b/examples/basic-admin/web/src/views/logo.vue @@ -0,0 +1,17 @@ + + + + + diff --git a/examples/basic-admin/web/src/views/navMenu.vue b/examples/basic-admin/web/src/views/navMenu.vue new file mode 100644 index 0000000..3f96990 --- /dev/null +++ b/examples/basic-admin/web/src/views/navMenu.vue @@ -0,0 +1,37 @@ + + + diff --git a/examples/basic-admin/web/src/views/sideM.vue b/examples/basic-admin/web/src/views/sideM.vue new file mode 100644 index 0000000..bdc26a9 --- /dev/null +++ b/examples/basic-admin/web/src/views/sideM.vue @@ -0,0 +1,83 @@ + + + + + diff --git a/examples/basic-admin/web/src/views/topbar.vue b/examples/basic-admin/web/src/views/topbar.vue new file mode 100644 index 0000000..d42e31b --- /dev/null +++ b/examples/basic-admin/web/src/views/topbar.vue @@ -0,0 +1,102 @@ + + + + + diff --git a/examples/basic-admin/web/src/views/userbar.vue b/examples/basic-admin/web/src/views/userbar.vue new file mode 100644 index 0000000..01f3ffe --- /dev/null +++ b/examples/basic-admin/web/src/views/userbar.vue @@ -0,0 +1,90 @@ + + + + + + diff --git a/examples/basic-admin/web/src/vite-env.d.ts b/examples/basic-admin/web/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/examples/basic-admin/web/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/basic-admin/web/tsconfig.json b/examples/basic-admin/web/tsconfig.json new file mode 100644 index 0000000..7826b53 --- /dev/null +++ b/examples/basic-admin/web/tsconfig.json @@ -0,0 +1,41 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "moduleResolution": "node", + "strict": true, + "noLib": false, + "forceConsistentCasingInFileNames": true, + "allowSyntheticDefaultImports": true, + "strictFunctionTypes": false, + "jsx": "preserve", + "baseUrl": ".", + "allowJs": true, + "sourceMap": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "experimentalDecorators": true, + "lib": ["dom", "esnext"], + "noImplicitAny": false, + "skipLibCheck": true, + "types": ["vite/client"], + "removeComments": true, + "paths": { + "@/*": ["src/*"], + "@shrimp/*": ["../../../src/*"], + "#/*": ["types/*"] + } + }, + "include": [ + "src/**/*.ts", + "src/**/*.d.ts", + "src/**/*.tsx", + "src/**/*.vue", + "types/**/*.d.ts", + "types/**/*.ts", + "vite.config.ts" + ], + "exclude": ["node_modules", "dist", "**/*.js"] +} diff --git a/examples/basic-admin/web/tsconfig.node.json b/examples/basic-admin/web/tsconfig.node.json new file mode 100644 index 0000000..42872c5 --- /dev/null +++ b/examples/basic-admin/web/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/examples/basic-admin/web/vite.config.ts b/examples/basic-admin/web/vite.config.ts new file mode 100644 index 0000000..f6ccd51 --- /dev/null +++ b/examples/basic-admin/web/vite.config.ts @@ -0,0 +1,36 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import * as path from "path"; + +// shrimp-server/examples/basic-admin/web/vite.config.ts +// 这是 examples 临时演示前端,@shrimp 直接指向同仓 src/ +export default defineConfig({ + base: '/', + resolve: { + alias: + [ + { + find: '@', + replacement: path.resolve(__dirname, './src') + }, + { + find: '@shrimp', + replacement: path.resolve(__dirname, '../../../src') + }, + ] + }, + plugins: [vue({ reactivityTransform: true })], + server: { + port: 5173, + proxy: { + "/admin-api": { + target: "http://localhost:3100", + changeOrigin: true, + }, + "/api": { + target: "http://localhost:3100", + changeOrigin: true, + }, + } + } +})