commit db15eb3ee0f62b74fbfbaea1b8f4f3c142f6a092 Author: Vicky <10212746+haifeng-jiang@user.noreply.gitee.com> Date: Mon Oct 12 17:35:07 2026 +0800 first commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..192c070 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b3c85c8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +dist/ +.DS_Store +.env +.env.* +!.env.example diff --git a/README.md b/README.md new file mode 100644 index 0000000..7139d18 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +# ReelShort Studio Console + +Frontend-only short-drama content management console built with Vue 3, Vite, Vue Router, and Element Plus. + +## Development + +```sh +npm install +npm run dev +``` + +## Production build + +```sh +npm run build +npm run preview +``` + +## Demo access + +- Username: `admin` +- Password: `admin123` + +Data and RBAC changes are local demo state. No backend service is connected. + +## Source layout + +- `src/views/common/` contains shared route pages such as login and dashboard. +- `src/views/contentManagement/` contains short-drama and episode pages. +- `src/views/permission/` contains role, menu, and role-category pages plus their composables. +- `src/views/userManagement/` contains administrator pages and their page data/composables. +- `src/views/systemManagement/` contains system settings pages. +- `src/views/components/` contains reusable page-level UI components. +- `src/data/` contains shared domain records used across views. +- `src/layouts/` contains the authenticated application shell. +- `src/router/` contains routes and access guards. +- `src/stores/` contains auth, RBAC, and theme state. +- `src/permissions/` contains permission definitions and role defaults. diff --git a/index.html b/index.html new file mode 100644 index 0000000..6d42b7f --- /dev/null +++ b/index.html @@ -0,0 +1,5 @@ + + + ReelShort · 内容管理 +
+ diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9aa7861 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1565 @@ +{ + "name": "reelshort-admin", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "reelshort-admin", + "version": "1.0.0", + "dependencies": { + "@element-plus/icons-vue": "^2.3.1", + "element-plus": "^2.9.1", + "vue": "^3.5.13", + "vue-router": "^4.5.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.1", + "vite": "^6.0.7" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.2.1.tgz", + "integrity": "sha512-Vh5uy9Y1JBMId6J1f68TMXnZrTCvNdo9Fu2k0d1YnJngR+sM5kWTKl441c/3TFs+cG0NWOjRWgQ/iaWhbYx+7A==", + "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/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", + "license": "MIT" + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "license": "MIT" + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "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/@rollup/rollup-android-arm-eabi": { + "version": "4.64.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.64.5.tgz", + "integrity": "sha512-MrfgRQSpD5hkkRg6XI16B6EDLb+6+xp8xje6bpdSwkYw+qxrBKbUVrRAQu9h82UT+QumSIVQ8HXsXjPd2dlhqw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.64.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.64.5.tgz", + "integrity": "sha512-+uu2p/R9Tzn8GpoQyX1pAo5dmKT1DhXevfaCzqzmPFhnLjHLWw7uWkaVGlxmhwYoRiYYhsGfRcD5cTBpL7rgSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.64.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.64.5.tgz", + "integrity": "sha512-17dys+YEmzwzzFVHQAVDONg5FALXY9kd99HFEpfk4gJVHYxoh5bGea3D4t1fXXnwkTcOe25wPyd6nWS0I8IdLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.64.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.64.5.tgz", + "integrity": "sha512-wg0zQEKkEvFZf1UaaX1UELOoPs//OSKUG7D+yITuqOsAvza7LvHTv5Wu4nmMfzNG7EYI2jp2PJC7Id6OETmsYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.64.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.64.5.tgz", + "integrity": "sha512-CoJmWcpEMuwin+TZ/ev/H9uDyF/uxUKQSWXdmIPn/UBx/uGV9+4ebMp0FwnlhWR9PE1AdzYsKf3bZevhif38+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.64.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.64.5.tgz", + "integrity": "sha512-PxZnLAaTUAL5xG0W2BXLeYPKrK2b3LIASV2/e/Kf7PZvSLI1iAQIm9B9aJD+YyFIT7hc6qaMV3+OOO35R28LfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.64.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.64.5.tgz", + "integrity": "sha512-uPo3IvnONjhbFXwWgroBeFwOicuhT7ZwQrnzkzmOtBJzeQbkUJUE1SJDfDI31ijQ5+ZUIA6sPrdUMxTSnxUWAg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.64.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.64.5.tgz", + "integrity": "sha512-ywI8Jyuc4LyHYTjC0mw4yA1ulvZBkHusdpWnoiEBsmBuHvSnt90btTBGCZ+s/2y+YNNfVAEKy33nPo5UI9zTmg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.64.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.64.5.tgz", + "integrity": "sha512-tmuXJoQ/3ovSr637aykZMdQteU/CB2APAz2CrvsbibuoHZi1BS1Db13cVViBdEARGwXCLn1DSJ8wsjoThLJPGA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.64.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.64.5.tgz", + "integrity": "sha512-5FMjSYNcUOHx7oW8Qu9KB6jGPpvF/8sG7+BDHwU/Ihh1/6FMJ9ySmsFG4tJ1qbNRuP8OLzCBDso4sF0mQaLuFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.64.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.64.5.tgz", + "integrity": "sha512-K8ZJ+neLDbUkIrL8dqF/5LtuVCrKwo27kREgOlxjBEjlOXwHvDnHLU8Z/UKv/B49qi7e8hsSDOyoJwhwqhvX4w==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.64.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.64.5.tgz", + "integrity": "sha512-NlVxsq7PFPv0DBKGuNWRDKixQYQCjM53zl04mxEcKMBmJbCBIUm1JdPfIWDRHyOOFzzYPmLQzfeEwh4716ayQg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.64.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.64.5.tgz", + "integrity": "sha512-ARhRHJYs5eCFet5azfrHD97vOkD8OzNW2RSMlvo5CcBqXaTOLfp0OHDtUQh7/K5AL5fUYoFp2fgC9KDg4vVYMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.64.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.64.5.tgz", + "integrity": "sha512-atSgreM5C7jpxd9aEXN+6vJ5wTd05iYw+S3FiQpw7VsoEl8B90llbq35ARON+GOHj5QlVQEq2vOKWCHTob/BSQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.64.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.64.5.tgz", + "integrity": "sha512-vmlmwBFGnjVWxSRoVabNkyYuscSmg52Pd0fM8jEbNT3Jmka4A6HcB84x4uAmuumznWfBVaxKOqU6jbEgomWKCA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.64.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.64.5.tgz", + "integrity": "sha512-hhJrP6S9WkbsbcHgUkvCBS5Cn8IdsG8XjlLZyxjnv9+NdoYfbbZE4TWJYr8apdaj1M5t5Y1id2StnEslxpNFyA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.64.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.64.5.tgz", + "integrity": "sha512-NNVCyN9pGrwFmQa4bf+Cnf5aVH3g6ZpnlOg1Cs/H9swIHT9bRixijfzUqyEIWm1WtiAuR1LJ+rJH6zpBm8kgQA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.64.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.64.5.tgz", + "integrity": "sha512-dR9nd6OKnvSS60Ifz/L7Mj5zzYyoqyRn2NwTuaOONKaa3xsVGXyp8YusMbvFyEOR94DxnwgcbgsFqkHd29ZSzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.64.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.64.5.tgz", + "integrity": "sha512-1tzsMjaqhsh3bAO6SJiJqh8hIr9sSPRd+i2vPPxJHod8/XYnf9TEnK0z/u3r1z1AkI347X0KbRSg9dWcxeGahw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.64.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.64.5.tgz", + "integrity": "sha512-01mWMQblKoXXsmk28KXtpbA96w7g/aOWUoV3AC16fdHdqkykGalqyyM+5u8M7uP5/MEMiiPv69Hv7iv/hEH/dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.64.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.64.5.tgz", + "integrity": "sha512-trdnMPb/DNm4V/CgsaoGgr90HfQQ071wfK0KoIs4YpLvjBgVQGXhGabg2Z6nK4EcYNilrb9ZZPcsP6+zpJIdxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.64.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.64.5.tgz", + "integrity": "sha512-x2LxZlfdfsw99T6/qb9EqE/Qi84JWzibjyOJyhJsgFJy6CafaTiqIXCCnslCGrV5XWNh7fwrS3FbRENcuKnooA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.64.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.64.5.tgz", + "integrity": "sha512-/Qd/9aDQIo0+jmItgBmPIs8195YVbDxRRApn47GyufgbutCDOh7eP5vSKKlI4QNFeaI+wKQAkSMsPnF3EuMpaA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.64.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.64.5.tgz", + "integrity": "sha512-ngUJ/XvOWrqbd2IMsfXfh2xXFLTP3sY7sCKvxbGvYdBZ1KmXFd3ewDZ9n3/GNzAUgzRuhLT/UvJRivbNoAQRBw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.64.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.64.5.tgz", + "integrity": "sha512-IhV6ew/DuzUWwHaYmlh7MrPCV3lKFUq/amTEwysi4r/mwNPAEfAMh5dYWeTSPTeMy2pFHGMEWYtPkHzfX3tG5A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.26", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.26.tgz", + "integrity": "sha512-48NWfiY6oNcKOh47Z4h+sIvwcQR91h6KPhaYKgeMIrkT2jV82Gy90SErj60vW8jQbeMSOSbhIv3bZ7JoqPPVhA==", + "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/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": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.43.tgz", + "integrity": "sha512-zdiLhnbe1QQqgDT8xZMpNmyqZ3qlI+/Q/FHQco57Kwl/b05HhCzN6eVGN9QU9rbga4CrS0H5SYY8VGHZCt/1Hg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@vue/shared": "3.5.43", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.43.tgz", + "integrity": "sha512-PEZoAk3NQmsn/ejMzSOCyTYqwGqczrWm70PuhBKjjv1+TCoQAaO/zOqNwjV+honlNstT5ILxtc+8r8UUfj+iEQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.43", + "@vue/shared": "3.5.43" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.43.tgz", + "integrity": "sha512-FCbrG3XNCRl+js3huuKx4IVHBLTvMkJhVepjbxSPu1gn4yWLaYtGNQdjJGZaMytXB6qb76qQDDmDSLy/vkmleQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@vue/compiler-core": "3.5.43", + "@vue/compiler-dom": "3.5.43", + "@vue/compiler-ssr": "3.5.43", + "@vue/shared": "3.5.43", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.28", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.43.tgz", + "integrity": "sha512-GF62orf7KiJX9RqrHNGrYBudsQGD0OhJ5nDs90O8UiDuS40+YMYomiXu6w6EuvtXuRDc3MSNis3EaaSKAVSWpg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.43", + "@vue/shared": "3.5.43" + } + }, + "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/reactivity": { + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.43.tgz", + "integrity": "sha512-G/c9GyOZNI2jVaaS6OX1EF1SSFSv7H0ERqNTl4+DTFMlZmB5eVAB53aLQNam/7NL2NPtaDD7RdVrzf8uJzMuOA==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.43" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.43.tgz", + "integrity": "sha512-hU6U6VnVhBGQDpvlnnDlIB8ZGJBiOcgk2lh/0InltHiz3D8oSkluvuvY+do1G2H3+udeKFsmaBlgVYP7gXQzEw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.43", + "@vue/shared": "3.5.43" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.43.tgz", + "integrity": "sha512-Bb2Jc0YjjJdMt1SJmb9b2L/IWd3I8lIT9x9eS/xvvP9CiVgna0ffua74xKRmt4/uSJ+0r4iN8ex1jrqkhQGWQw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.43", + "@vue/runtime-core": "3.5.43", + "@vue/shared": "3.5.43", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.43.tgz", + "integrity": "sha512-l2Ygjv9NehV94PSBxNWsAHC0j/eIIKbn92mBuWXAPGLnn6HfJ6MH5ubsd+Nk0YoZ5FRuxWI1P2VSoh+dbPQhCQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.43", + "@vue/runtime-dom": "3.5.43", + "@vue/shared": "3.5.43" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.43.tgz", + "integrity": "sha512-uksS7YGMR5NZyr4JNq0Rp+QyLns0ueaz20KwzIPW9R0LH1Vnt4E+XUM29PNseEbf1www2gOuhuDi5AKOIXag9Q==", + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-15.0.0.tgz", + "integrity": "sha512-Zv67h5mrk7YHDJsRObx+DXaCBz6RKBUpxqLesJ+3xZNPG6yuQs7ja7kg3veFrrouYLWAqydQ7CwGEbLVxB43Ng==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "15.0.0", + "@vueuse/shared": "15.0.0" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/@vueuse/metadata": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-15.0.0.tgz", + "integrity": "sha512-sfWE5LADAKJ0KnjtGliMdSN8Ij/L7uP6rXIZlgPloAI4zvvefbw6H5Lt7KJA6RqQOYgVqWF5Guj50HHMIzygBg==", + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-15.0.0.tgz", + "integrity": "sha512-vbNXhGrbV53xPOMSyOzMHQRvhMgZf7oNDw2RChUetVxwTIEkInQ1joZ9JbT0G3z2n2AJNjvmCWeM9ySYrnvogA==", + "license": "MIT", + "engines": { + "node": ">=22" + }, + "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/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.23", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.23.tgz", + "integrity": "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==", + "license": "MIT" + }, + "node_modules/element-plus": { + "version": "2.14.7", + "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.14.7.tgz", + "integrity": "sha512-lrG7pZyCS+mpCNVpzxxyu5r35+T6V0hzkAeIwn7HA6to772XSTHKuYCZG51OfzlqaxGbNegLxs650zAF1j43gA==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^4.2.1", + "@element-plus/icons-vue": "^2.3.2", + "@floating-ui/dom": "^1.8.0", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.8", + "@types/lodash": "^4.17.24", + "@types/lodash-es": "^4.17.12", + "@vueuse/core": "15.0.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.3.11" + }, + "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/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "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/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "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/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/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/nanoid": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "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/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/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.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.29", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.29.tgz", + "integrity": "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==", + "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.19", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.64.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.64.5.tgz", + "integrity": "sha512-EAP5ZHcz2ijbgCWwPkpI7zh6bfsri2Ez5FtCy2dCp1k62duXzfG7DLGGh7RU2QQXWwavT+u4qpvDF/TWOiiGTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.64.5", + "@rollup/rollup-android-arm64": "4.64.5", + "@rollup/rollup-darwin-arm64": "4.64.5", + "@rollup/rollup-darwin-x64": "4.64.5", + "@rollup/rollup-freebsd-arm64": "4.64.5", + "@rollup/rollup-freebsd-x64": "4.64.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.64.5", + "@rollup/rollup-linux-arm-musleabihf": "4.64.5", + "@rollup/rollup-linux-arm64-gnu": "4.64.5", + "@rollup/rollup-linux-arm64-musl": "4.64.5", + "@rollup/rollup-linux-loong64-gnu": "4.64.5", + "@rollup/rollup-linux-loong64-musl": "4.64.5", + "@rollup/rollup-linux-ppc64-gnu": "4.64.5", + "@rollup/rollup-linux-ppc64-musl": "4.64.5", + "@rollup/rollup-linux-riscv64-gnu": "4.64.5", + "@rollup/rollup-linux-riscv64-musl": "4.64.5", + "@rollup/rollup-linux-s390x-gnu": "4.64.5", + "@rollup/rollup-linux-x64-gnu": "4.64.5", + "@rollup/rollup-linux-x64-musl": "4.64.5", + "@rollup/rollup-openbsd-x64": "4.64.5", + "@rollup/rollup-openharmony-arm64": "4.64.5", + "@rollup/rollup-win32-arm64-msvc": "4.64.5", + "@rollup/rollup-win32-ia32-msvc": "4.64.5", + "@rollup/rollup-win32-x64-gnu": "4.64.5", + "@rollup/rollup-win32-x64-msvc": "4.64.5", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/vite": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.4.tgz", + "integrity": "sha512-xGjU/SyjeNyb0E4KxXBvYz/1C+3JXuujvf2hXNOtnlcE+58o5ajd9N2/n2VhkiJvpCP2qQasnUpx5PXNeI+p1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.43.tgz", + "integrity": "sha512-o5qZoksdnjIKvW1srZ3ab7pcDNYAerBjRe54D0LBLfRdCYFrSgBHVXokMas35czQc0//lmx4/tuY4ZNQ+Rf2Ng==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.43", + "@vue/compiler-sfc": "3.5.43", + "@vue/runtime-dom": "3.5.43", + "@vue/server-renderer": "3.5.43", + "@vue/shared": "3.5.43" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-component-type-helpers": { + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-3.3.13.tgz", + "integrity": "sha512-02KCxSmKDhIITjKyJ4QT5csP6L0WoRborLqY/34a40+S9uvMvdNgMV0yVjO3p2Gkz3ZoPmFz/0JsGHacwJYTxw==", + "license": "MIT" + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..af58beb --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "reelshort-admin", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { "@element-plus/icons-vue": "^2.3.1", "element-plus": "^2.9.1", "vue": "^3.5.13", "vue-router": "^4.5.0" }, + "devDependencies": { "@vitejs/plugin-vue": "^5.2.1", "vite": "^6.0.7" } +} diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..f8ccd2f --- /dev/null +++ b/src/App.vue @@ -0,0 +1,5 @@ + + + diff --git a/src/dark-theme.css b/src/dark-theme.css new file mode 100644 index 0000000..df9e807 --- /dev/null +++ b/src/dark-theme.css @@ -0,0 +1,85 @@ +html.dark { + color-scheme:dark; + --el-bg-color:#242830; + --el-bg-color-page:#1b1e24; + --el-bg-color-overlay:#2a2f38; + --el-text-color-primary:#e5e8ed; + --el-text-color-regular:#c0c6cf; + --el-text-color-secondary:#9ba3ae; + --el-border-color:#414751; + --el-border-color-light:#373d47; + --el-border-color-lighter:#303640; + --el-fill-color-blank:#242830; + --el-fill-color-light:#303640; + --el-fill-color-lighter:#2a2f38; + --el-fill-color:#343a44; + --el-color-primary:#82a9dc; + --el-color-success:#66b587; + --el-color-warning:#d5a348; + --el-color-danger:#d27676} +html.dark body,html.dark .app-shell,html.dark .login-screen { + background:#1b1e24; + color:#e5e8ed} +html.dark .sidebar,html.dark .topbar,html.dark .page-tabs,html.dark .panel,html.dark .stat-card,html.dark .cover-card,html.dark .add-cover,html.dark .login-card { + background:#242830; + border-color:#3b414b} +html.dark .brand { + border-color:#3b414b} +html.dark .brand b,html.dark .page-heading h1,html.dark .panel-heading h2,html.dark .stat-value,html.dark .drama-cell b,html.dark .episode-title b,html.dark .role-copy b,html.dark .login-heading h1 { + color:#e5e8ed} +html.dark .brand small,html.dark .page-heading p,html.dark .panel-heading p,html.dark .stat-top,html.dark .breadcrumb,html.dark .drama-table td,html.dark .role-copy small,html.dark .role-copy span,html.dark .episode-title small,html.dark .login-heading p { + color:#a6aeb9} +html.dark .side-menu,html.dark .side-menu>.el-menu-item,html.dark .side-menu .el-sub-menu__title { + background:transparent; + color:#c0c6cf} +html.dark .side-menu .el-menu-item:hover,html.dark .side-menu .el-sub-menu__title:hover { + background:#2e3540; + color:#e5e8ed} +html.dark .side-menu .el-menu-item.is-active { + background:#303d50; + color:#a8c7f0} +html.dark .drama-table th { + background:#2b3038; + color:#b1b8c2} +html.dark .drama-table td,html.dark .episode-row,html.dark .role-card,html.dark .page-tabs { + border-color:#3b414b} +html.dark .episode-row,html.dark .role-card { + background:#242830} +html.dark .role-active { + background:#2d3540; + border-color:#607998} +html.dark .episode-thumb,html.dark .drama-cover,html.dark .cover-placeholder { + background:#353b45!important; + color:#b9c1cb} +html.dark .permission-note,html.dark .library-toolbar { + background:#2b3038; + border-color:#3b414b} +html.dark .page-tabs .el-tabs__item { + color:#b8c0ca} +html.dark .page-tabs .el-tabs__item.is-active { + background:#2b3340; + color:#a8c7f0; + border-color:#3b414b!important} +html.dark .cover-card-info b,html.dark .cover-card-info small,html.dark .selected-file { + color:#d3d8e0} +html.dark .login-card { + box-shadow:0 8px 30px #0003} +html.dark .demo-credentials { + border-color:#3b414b; + color:#aab2bc} +html.dark .el-dialog { + background:#242830} +html.dark .el-dialog__title,html.dark .el-form-item__label { + color:#e1e5eb} +html.dark .el-upload-dragger { + background:#2b3038; + border-color:#4a515c} +html.dark .fullscreen-button,html.dark .theme-button,html.dark .logout-button,html.dark .role-switch { + color:#c5ccd5!important} +.theme-button { + font-size:18px!important; + color:#56616e!important; + padding:7px!important} +.theme-button:hover { + color:#355f97!important; + background:#f1f4f7!important} diff --git a/src/data/customMenus.js b/src/data/customMenus.js new file mode 100644 index 0000000..874bbce --- /dev/null +++ b/src/data/customMenus.js @@ -0,0 +1,12 @@ +import { reactive } from 'vue' + +const storageKey = 'reelshort-custom-menus' +let savedMenus = [] +try { savedMenus = JSON.parse(localStorage.getItem(storageKey) || '[]') } catch { savedMenus = [] } + +export const customMenus = reactive(Array.isArray(savedMenus) ? savedMenus : []) +export const menuIconNames = ['Document', 'DataAnalysis', 'Picture', 'User', 'Setting', 'VideoCamera'] + +export function saveCustomMenus() { + localStorage.setItem(storageKey, JSON.stringify(customMenus)) +} diff --git a/src/data/dramas.js b/src/data/dramas.js new file mode 100644 index 0000000..f5114ca --- /dev/null +++ b/src/data/dramas.js @@ -0,0 +1,9 @@ +import { reactive } from 'vue' + +export const dramas = reactive([ + { id: 1, title: '替嫁新娘:总裁的掌心宠', genre: '都市情感', status: '已上线', episodes: 68, views: '2.4M', updatedAt: '2025-06-12', description: '一场意外替嫁,让她成为冷面总裁的契约新娘。', cover: '' }, + { id: 2, title: '重生之我在豪门当千金', genre: '重生逆袭', status: '审核中', episodes: 42, views: '—', updatedAt: '2025-06-18', description: '重活一世,她要拿回属于自己的一切。', cover: '' }, + { id: 3, title: '心动信号:请和我恋爱', genre: '甜宠爱情', status: '已上线', episodes: 56, views: '1.8M', updatedAt: '2025-05-29', description: '恋爱观察室里,心动正在悄悄发生。', cover: '' }, + { id: 4, title: '她从深渊中走来', genre: '悬疑复仇', status: '草稿', episodes: 24, views: '—', updatedAt: '2025-06-20', description: '真相只有一个,而她亲手揭开迷雾。', cover: '' }, + { id: 5, title: '闪婚后,傅先生每天都在撒糖', genre: '都市情感', status: '已上线', episodes: 72, views: '3.1M', updatedAt: '2025-05-21', description: '一纸婚约,开启了甜蜜又心动的同居生活。', cover: '' }, +]) diff --git a/src/data/episodes.js b/src/data/episodes.js new file mode 100644 index 0000000..11ffcb9 --- /dev/null +++ b/src/data/episodes.js @@ -0,0 +1,8 @@ +import { reactive } from 'vue' + +export const episodeData = reactive([ + { id: 1, dramaId: 1, number: 1, title: '命运的相遇', duration: '02:18', status: '已上线', views: '48,201' }, + { id: 2, dramaId: 1, number: 2, title: '意外的婚约', duration: '01:56', status: '已上线', views: '42,817' }, + { id: 3, dramaId: 1, number: 3, title: '心动的开始', duration: '02:04', status: '已上线', views: '39,506' }, + { id: 4, dramaId: 1, number: 4, title: '他的秘密', duration: '02:31', status: '待发布', views: '—' }, +]) diff --git a/src/data/roles.js b/src/data/roles.js new file mode 100644 index 0000000..f52f136 --- /dev/null +++ b/src/data/roles.js @@ -0,0 +1,8 @@ +import { reactive } from 'vue' + +export const roleData = reactive([ + { id: 1, name: '超级管理员', description: '拥有系统全部管理权限', memberCount: 2, category: '系统管理员', level: 1, status: '启用' }, + { id: 2, name: '内容运营', description: '管理短剧、剧集及封面素材', memberCount: 8, category: '内容管理', level: 2, status: '启用' }, + { id: 3, name: '审核专员', description: '审核短剧内容与发布状态', memberCount: 5, category: '内容管理', level: 3, status: '启用' }, + { id: 4, name: '数据分析师', description: '查看内容数据与运营报表', memberCount: 3, category: '数据分析', level: 4, status: '启用' }, +]) diff --git a/src/drama-management.css b/src/drama-management.css new file mode 100644 index 0000000..6c84856 --- /dev/null +++ b/src/drama-management.css @@ -0,0 +1,252 @@ +.library-toolbar { + justify-content:flex-start; + gap:16px; + min-height:64px; + padding:12px 0!important} +.drama-filters { + display:flex; + align-items:center; + gap:10px; + flex:0 0 auto} +.drama-filters>.el-input { + width:300px!important; + flex:0 0 300px!important} +.drama-filters>.el-select { + width:160px!important; + flex:0 0 160px!important} +.table-result-count { + margin-left:auto; + color:#858b95; + font-size:13px; + white-space:nowrap} +.table-actions { + display:flex; + align-items:center; + gap:8px; + white-space:nowrap} +.table-actions .el-button { + font-size:13px!important; + font-weight:400!important; + padding:5px 4px!important} +.table-actions .danger-action { + color:#a66a6a!important} +.table-actions .danger-action:hover { + color:#b54848!important} +.drama-cell .drama-cover,.drama-cell .drama-cover-image { + width:44px; + height:56px; + flex:0 0 44px; + border-radius:4px; + object-fit:cover} +.drama-cell .drama-cover { + display:grid; + place-content:center; + text-align:center; + font-size:10px; + line-height:1.3; + color:#7e8791; + background:#f0f2f4!important} +.dark .table-result-count { + color:#a6aeb9} +.dark .drama-cell .drama-cover { + background:#353b45!important; + color:#b9c1cb} +@media(max-width:800px) { + .drama-filters>.el-input { + width:min(300px,45vw)!important; + flex-basis:min(300px,45vw)!important} +.drama-filters>.el-select { + width:150px!important; + flex-basis:150px!important} +.table-result-count { + display:none} +} +.drama-el-table,.dashboard-el-table { + width:100%; + font-size:13px; + --el-table-header-bg-color:#f7f8fa; + --el-table-border-color:#eaecf0; + --el-table-row-hover-bg-color:#f8fafc} +.drama-el-table .el-table__cell,.dashboard-el-table .el-table__cell { + padding:14px 0} +.drama-el-table th.el-table__cell,.dashboard-el-table th.el-table__cell { + font-size:12px; + font-weight:600; + color:#737b85} +.drama-el-table td.el-table__cell,.dashboard-el-table td.el-table__cell { + font-size:13px; + color:#505862} +.drama-el-table .cell,.dashboard-el-table .cell { + line-height:1.5} +.drama-el-table .el-table__inner-wrapper:before,.dashboard-el-table .el-table__inner-wrapper:before { + height:0} +.dark .drama-el-table,.dark .dashboard-el-table { + --el-table-header-bg-color:#2b3038; + --el-table-tr-bg-color:#242830; + --el-table-bg-color:#242830; + --el-table-border-color:#3b414b; + --el-table-row-hover-bg-color:#2e3540; + --el-table-text-color:#c0c6cf; + --el-table-header-text-color:#b1b8c2} +.dark .drama-el-table td.el-table__cell,.dark .dashboard-el-table td.el-table__cell { + color:#c0c6cf} +.table-actions .el-button { + font-size:13px!important; + font-weight:600!important; + padding:7px 11px!important} +.table-actions .el-button--primary { + --el-button-bg-color:#456fae; + --el-button-border-color:#456fae; + --el-button-hover-bg-color:#385f97; + --el-button-hover-border-color:#385f97} +.table-actions .el-button--success { + --el-button-bg-color:#4d866a; + --el-button-border-color:#4d866a; + --el-button-hover-bg-color:#3c7358; + --el-button-hover-border-color:#3c7358} +.table-actions .el-button--danger { + --el-button-bg-color:#bd5b5b; + --el-button-border-color:#bd5b5b; + --el-button-hover-bg-color:#a94b4b; + --el-button-hover-border-color:#a94b4b} +.pagination { + display:flex; + justify-content:flex-end; + align-items:center; + padding:18px 0 4px!important; + overflow-x:auto} +.pagination .el-pagination { + font-size:13px!important; + --el-pagination-font-size:13px; + --el-pagination-button-bg-color:#f1f3f6; + --el-pagination-hover-color:#456fae; + --el-pagination-button-color:#4c5561; + gap:5px} +.pagination .el-pagination__sizes { + margin-right:8px} +.pagination .el-select { + width:112px} +.pagination .el-pagination__jump { + margin-left:8px; + white-space:nowrap} +.dark .pagination .el-pagination { + --el-pagination-button-bg-color:#353b45; + --el-pagination-button-color:#c3cad3} +.drama-el-table td.el-table__cell,.drama-el-table th.el-table__cell { + font-size:14px} +.drama-cell b { + font-size:15px} +.drama-cell small { + font-size:13px} +.table-result-count { + font-size:14px} +.table-actions .el-button { + font-size:14px!important} +.library-toolbar .el-input__inner,.library-toolbar .el-select__placeholder { + font-size:14px} +.pagination .el-pagination { + font-size:14px!important; + --el-pagination-font-size:14px} +.el-message-box__title,.el-message-box__message { + font-size:15px} +.el-message-box__btns .el-button { + font-size:14px} +.episode-toolbar { + margin-bottom:16px} +.episode-el-table { + margin-top:4px} +.episode-table-title b,.episode-table-title small { + display:block} +.episode-table-title b { + font-size:14px; + color:#343a43} +.episode-table-title small { + margin-top:4px; + font-size:13px; + color:#858b95} +.dark .episode-table-title b { + color:#e5e8ed} +.dark .episode-table-title small { + color:#a6aeb9} +.menu-management-panel { + margin-top:20px} +.menu-management-panel .panel-heading { + display:flex; + align-items:center; + justify-content:space-between; + gap:16px} +.menu-management-panel .el-alert { + margin-top:4px} +.menu-management-panel .el-select { + width:100%} +.admin-user-search { + width:min(440px,65vw)} +.muted-text { + font-size:13px; + color:#9298a1} +.role-permission-layout { + display:grid; + grid-template-columns:minmax(520px,1.25fr) minmax(340px,.9fr); + gap:18px; + align-items:start} +.role-table-panel,.permission-tree-panel { + min-width:0} +.role-config-table .el-table__row { + cursor:pointer} +.permission-tree-panel .el-tree { + max-height:68vh; + overflow:auto; + padding:5px 8px} +.permission-tree-panel .el-tree-node__label { + font-size:14px} +.permission-warning { + color:#d66b4f!important} +.admin-users-panel { + overflow:hidden} +.admin-user-toolbar { + display:flex; + align-items:center; + gap:10px; + flex-wrap:wrap; + padding:0 0 16px} +.admin-user-toolbar>.el-input { + width:230px} +.admin-user-toolbar>.el-select { + width:190px} +.admin-users-table { + min-width:1250px} +.menu-editor-layout { + display:grid; + grid-template-columns:minmax(270px,.7fr) minmax(520px,1.5fr); + gap:18px; + align-items:start} +.menu-tree-panel,.menu-form-panel { + min-width:0} +.menu-tree-panel .el-tree { + max-height:70vh; + overflow:auto} +.menu-form-panel .panel-heading { + display:flex; + justify-content:space-between; + align-items:center} +.menu-editor-form { + max-width:680px; + padding:16px 12px 0} +.menu-editor-form .el-input,.menu-editor-form .el-select { + max-width:360px; + width:100%} +.menu-editor-form .el-textarea { + max-width:360px} +.menu-editor-form .el-input-number { + width:180px} +.menu-form-footer { + padding:12px 0 4px 122px; + border-top:1px solid #eceef0} +.dark .menu-form-footer { + border-color:#3b414b} +@media(max-width:1150px) { + .role-permission-layout,.menu-editor-layout { + grid-template-columns:1fr} +.permission-tree-panel .el-tree,.menu-tree-panel .el-tree { + max-height:45vh} +} diff --git a/src/layouts/AdminLayout.vue b/src/layouts/AdminLayout.vue new file mode 100644 index 0000000..d9e0df4 --- /dev/null +++ b/src/layouts/AdminLayout.vue @@ -0,0 +1,94 @@ + + + diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..0817f26 --- /dev/null +++ b/src/main.js @@ -0,0 +1,16 @@ +import { createApp } from 'vue' +import ElementPlus from 'element-plus' +import zhCn from 'element-plus/dist/locale/zh-cn.mjs' +import 'element-plus/dist/index.css' +import 'element-plus/theme-chalk/dark/css-vars.css' +import './style.css' +import './theme.css' +import './typography.css' +import './navigation.css' +import './dark-theme.css' +import './drama-management.css' +import './stores/theme.js' +import App from './App.vue' +import router from './router' + +createApp(App).use(ElementPlus, { locale: zhCn }).use(router).mount('#app') diff --git a/src/navigation.css b/src/navigation.css new file mode 100644 index 0000000..d822c71 --- /dev/null +++ b/src/navigation.css @@ -0,0 +1,138 @@ +.sidebar { + padding:22px 12px; + background:#fff} +.brand { + padding:0 10px 22px; + margin-bottom:8px; + border-bottom:1px solid #eceef0} +.side-menu { + border-right:0!important} +.side-menu>.el-menu-item,.side-menu>.el-sub-menu>.el-sub-menu__title { + height:46px; + line-height:46px; + font-size:14px; + border-radius:4px; + margin:3px 0} +.side-menu .el-sub-menu .el-menu-item { + min-width:0; + height:42px; + line-height:42px; + font-size:13px; + padding-left:48px!important} +.side-menu .el-sub-menu .el-menu-item { + padding-left:48px!important} +.main-area { + margin-left:250px} +.topbar { + height:62px; + padding:0 28px} +.top-actions { + gap:12px} +.current-user { + font-size:13px; + color:#535b66} +.role-switch,.logout-button { + font-size:13px!important; + color:#56616e!important} +.logout-button { + border-left:1px solid #e7e9ec; + border-radius:0!important; + padding-left:16px!important} +.logout-button .el-icon { + margin-right:5px} +.page-tabs { + height:43px; + background:#fff; + border-bottom:1px solid #e7e9ec; + padding:0 24px} +.page-tabs .el-tabs__header { + margin:0; + border-bottom:0; + height:43px} +.page-tabs .el-tabs__nav { + border:0!important; + height:43px} +.page-tabs .el-tabs__item { + height:43px; + line-height:43px; + padding:0 17px!important; + font-size:13px; + color:#68717c; + border:0!important; + background:transparent} +.page-tabs .el-tabs__item.is-active { + color:#365f96; + border-bottom:2px solid #456fae!important; + background:#f8f9fb} +.page-tabs .el-tabs__item .is-icon-close { + font-size:13px} +.page-tabs .el-tabs__content { + display:none} +.page-content { + padding-top:26px} +.brand-mark { + width:30px; + height:30px} +.sidebar>.side-menu { + margin-top:4px} +.el-sub-menu__title:hover,.side-menu .el-menu-item:hover { + background:#f4f6f8} +.side-menu .el-menu-item.is-active { + background:#edf2f8; + color:#355f97} +.page-heading { + margin-bottom:20px} +@media(max-width:1200px) { + .topbar { + padding:0 18px} +.page-tabs { + padding:0 14px} +.page-content { + padding:24px 24px 40px} +} +.side-menu>.el-menu-item,.side-menu>.el-sub-menu>.el-sub-menu__title { + box-sizing:border-box; + padding-left:20px!important; + display:flex; + align-items:center} +.side-menu>.el-menu-item>.el-icon,.side-menu>.el-sub-menu>.el-sub-menu__title>.el-icon { + width:18px; + min-width:18px; + margin-right:10px} +.side-menu .el-sub-menu .el-sub-menu__title .el-icon { + margin-right:10px} +.topbar { + position:sticky; + top:0; + z-index:20} +.page-tabs { + position:sticky; + top:62px; + z-index:19} +.fullscreen-button { + font-size:18px!important; + color:#56616e!important; + padding:7px!important} +.fullscreen-button:hover { + color:#355f97!important; + background:#f1f4f7!important} +.status-badge { + border:0!important; + font-weight:500} +.status-badge.is-online { + background:#eaf5ee!important; + color:#32724b!important} +.status-badge.is-reviewing { + background:#fff3df!important; + color:#946000!important} +.status-badge.is-draft { + background:#eef0f2!important; + color:#616a74!important} +.status-badge.is-pending { + background:#eaf2fb!important; + color:#35689d!important} +.status-badge.is-offline { + background:#fbeaea!important; + color:#a74444!important} +.side-menu>.el-menu-item>.el-icon,.side-menu>.el-sub-menu>.el-sub-menu__title>.el-icon { + font-size:16px} diff --git a/src/permissions/permissions.js b/src/permissions/permissions.js new file mode 100644 index 0000000..1f2279b --- /dev/null +++ b/src/permissions/permissions.js @@ -0,0 +1,15 @@ +export const permissionGroups = [ + { name: '短剧管理', items: ['查看短剧列表', '创建短剧', '编辑短剧信息', '删除短剧'] }, + { name: '剧集管理', items: ['查看剧集列表', '上传剧集视频', '管理剧集排序'] }, + { name: '封面管理', items: ['上传封面素材'] }, + { name: '审核与系统', items: ['审核并发布', '查看数据报表'] }, + { name: '后台管理', items: ['角色与权限配置', '管理员用户列表', '菜单管理', '角色分类管理'] }, +] + +export const allPermissions = permissionGroups.flatMap(group => group.items) +export const defaultRolePermissions = { + 超级管理员: [...allPermissions], + 内容运营: ['查看短剧列表', '创建短剧', '编辑短剧信息', '删除短剧', '查看剧集列表', '上传剧集视频', '管理剧集排序', '上传封面素材'], + 审核专员: ['查看短剧列表', '查看剧集列表', '审核并发布', '查看数据报表'], + 数据分析师: ['查看短剧列表', '查看数据报表'], +} diff --git a/src/router/index.js b/src/router/index.js new file mode 100644 index 0000000..8cb7aa1 --- /dev/null +++ b/src/router/index.js @@ -0,0 +1,47 @@ +import { createRouter, createWebHistory } from 'vue-router' +import { useAuth } from '../stores/auth' +import { useRbac } from '../stores/rbac' +import { customMenus } from '../data/customMenus' + +const router = createRouter({ + history: createWebHistory(), + routes: [ + { path: '/login', name: 'login', component: () => import('@/views/common/LoginView.vue'), meta: { public: true } }, + { path: '/', name: 'admin', component: () => import('@/layouts/AdminLayout.vue'), children: [ + { path: '', name: 'dashboard', component: () => import('@/views/common/DashboardView.vue'), meta: { title: '工作台', permission: '查看短剧列表' } }, + { path: 'dramas', name: 'dramas', component: () => import('@/views/contentManagement/DramaManagementView.vue'), meta: { title: '短剧管理', permission: '查看短剧列表' } }, + { path: 'episodes', name: 'episodes', component: () => import('@/views/contentManagement/EpisodeManagementView.vue'), meta: { title: '剧集管理', permission: '查看剧集列表' } }, + { path: 'access', name: 'access', component: () => import('@/views/permission/RoleManagementView.vue'), meta: { title: '角色权限配置', permission: '角色与权限配置' } }, + { path: 'admin-users', name: 'admin-users', component: () => import('@/views/userManagement/AdminUsersView.vue'), meta: { title: '管理员用户列表', permission: '管理员用户列表' } }, + { path: 'menus', name: 'menus', component: () => import('@/views/permission/MenuManagementView.vue'), meta: { title: '菜单管理', permission: '菜单管理' } }, + { path: 'role-categories', name: 'role-categories', component: () => import('@/views/permission/RoleCategoriesView.vue'), meta: { title: '角色分类', permission: '角色分类管理' } }, + { path: 'settings', name: 'settings', component: () => import('@/views/systemManagement/SettingsView.vue'), meta: { title: '系统设置', permission: '角色与权限配置' } }, + ] }, + { path: '/:pathMatch(.*)*', redirect: '/' }, + ], +}) + +export function registerCustomMenu(menu) { + const name = `custom-menu-${menu.key}` + if (router.hasRoute(name)) return + if (menu.type === '按钮') return + const path = String(menu.route || `custom/${menu.key}`).replace(/^\/+/, '') + router.addRoute('admin', { + path, + name, + component: () => import('@/views/permission/CustomMenuView.vue'), + meta: { title: menu.title, permission: menu.permission, menuKey: menu.key }, + }) +} + +customMenus.forEach(registerCustomMenu) + +router.beforeEach(to => { + const { isAuthenticated } = useAuth() + const { can } = useRbac() + if (!to.meta.public && !isAuthenticated.value) return { name: 'login' } + if (to.name === 'login' && isAuthenticated.value) return { name: 'dashboard' } + if (to.meta.permission && !can(to.meta.permission)) return { name: 'dashboard' } +}) + +export default router diff --git a/src/stores/auth.js b/src/stores/auth.js new file mode 100644 index 0000000..9dcfd53 --- /dev/null +++ b/src/stores/auth.js @@ -0,0 +1,22 @@ +import { computed, ref } from 'vue' + +const authenticated = ref(localStorage.getItem('reelshort-auth') === '1') +const currentRole = ref('超级管理员') + +export function useAuth() { + const isAuthenticated = computed(() => authenticated.value) + function login(username, password) { + if (username !== 'admin' || password !== 'admin123') return false + localStorage.setItem('reelshort-auth', '1') + currentRole.value = '超级管理员' + authenticated.value = true + return true + } + function logout() { + localStorage.removeItem('reelshort-auth') + currentRole.value = '超级管理员' + authenticated.value = false + } + function setRole(role) { currentRole.value = role } + return { isAuthenticated, currentRole, login, logout, setRole } +} diff --git a/src/stores/rbac.js b/src/stores/rbac.js new file mode 100644 index 0000000..7a6c6d1 --- /dev/null +++ b/src/stores/rbac.js @@ -0,0 +1,25 @@ +import { reactive, computed } from 'vue' +import { defaultRolePermissions } from '../permissions/permissions' +import { useAuth } from './auth' +import { customMenus } from '../data/customMenus' + +const rolePermissions = reactive(Object.fromEntries(Object.entries(defaultRolePermissions).map(([role, list]) => [role, [...list]]))) +rolePermissions['超级管理员'].push(...customMenus.map(menu => menu.permission)) + +export function useRbac() { + const { currentRole } = useAuth() + const permissions = computed(() => rolePermissions[currentRole.value] || []) + const can = permission => permissions.value.includes(permission) + function updateRolePermission(role, permission, enabled) { + const current = rolePermissions[role] || (rolePermissions[role] = []) + rolePermissions[role] = enabled ? [...new Set([...current, permission])] : current.filter(item => item !== permission) + } + function createRole(name) { if (!rolePermissions[name]) rolePermissions[name] = [] } + function createPermission(permission) { + rolePermissions['超级管理员'] = [...new Set([...(rolePermissions['超级管理员'] || []), permission])] + } + function removePermission(permission) { + for (const role of Object.keys(rolePermissions)) rolePermissions[role] = rolePermissions[role].filter(item => item !== permission) + } + return { permissions, can, rolePermissions, updateRolePermission, createRole, createPermission, removePermission } +} diff --git a/src/stores/theme.js b/src/stores/theme.js new file mode 100644 index 0000000..81ffe7c --- /dev/null +++ b/src/stores/theme.js @@ -0,0 +1,25 @@ +import { ref } from 'vue' + +const storageKey = 'reelshort-theme' +const systemTheme = window.matchMedia('(prefers-color-scheme: dark)') +const storedTheme = localStorage.getItem(storageKey) +const isDark = ref(storedTheme ? storedTheme === 'dark' : systemTheme.matches) + +function applyTheme(value) { + isDark.value = value + document.documentElement.classList.toggle('dark', value) +} + +applyTheme(isDark.value) +systemTheme.addEventListener?.('change', event => { + if (!localStorage.getItem(storageKey)) applyTheme(event.matches) +}) + +export function useTheme() { + function toggleTheme() { + const next = !isDark.value + localStorage.setItem(storageKey, next ? 'dark' : 'light') + applyTheme(next) + } + return { isDark, toggleTheme } +} diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..25ff7dd --- /dev/null +++ b/src/style.css @@ -0,0 +1,1062 @@ +@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=Manrope:wght@400;500;600;700;800&family=Noto+Sans+SC:wght@400;500;600;700&display=swap'); + :root { + font-family:'DM Sans','Noto Sans SC',sans-serif; + color:#27253b; + background:#f7f8fc; + font-synthesis:none; + text-rendering:optimizeLegibility; + font-optical-sizing:auto; + font-weight:400; + --purple:#7257e8; + --muted:#9695a8; + --line:#eeedf3} +* { + box-sizing:border-box} +body { + margin:0; + min-width:980px; + min-height:100vh} +.app-shell { + min-height:100vh} +.sidebar { + width:250px; + position:fixed; + inset:0 auto 0 0; + background:#fff; + border-right:1px solid #eeedf4; + padding:27px 17px 16px; + display:flex; + flex-direction:column; + z-index:4} +.brand { + display:flex; + align-items:center; + gap:11px; + padding:0 9px 28px} +.brand-mark { + width:32px; + height:32px; + border-radius:10px; + background:#7359e8; + display:flex; + align-items:center; + justify-content:center; + gap:2px; + transform:rotate(-8deg)} +.brand-mark span { + width:4px; + background:white; + border-radius:4px; + transform:skew(-15deg)} +.brand-mark span:nth-child(1) { + height:11px; + opacity:.72} +.brand-mark span:nth-child(2) { + height:18px} +.brand-mark span:nth-child(3) { + height:13px; + opacity:.85} +.brand b { + display:block; + font:800 18px Manrope,sans-serif; + letter-spacing:-.8px; + color:#242139} +.brand small { + font-size:8px; + letter-spacing:1.5px; + color:#a3a1b1; + display:block; + margin-top:1px} +.workspace { + height:58px; + border:1px solid #efedf4; + border-radius:10px; + display:flex; + align-items:center; + padding:0 10px; + margin-bottom:27px; + gap:10px} +.workspace-avatar,.profile-avatar,.top-avatar { + display:grid; + place-items:center; + background:#efeaff; + color:#6c55da; + font-weight:700} +.workspace-avatar { + width:32px; + height:32px; + border-radius:8px} +.workspace-copy { + flex:1} +.workspace-copy b,.workspace-copy small { + display:block} +.workspace-copy b { + font-size:11px} +.workspace-copy small { + color:#a09eae; + font-size:10px; + margin-top:3px} +.workspace>.el-icon { + color:#aaa8b7; + font-size:13px} +.nav-label { + padding:0 11px 10px; + color:#aaa8b7; + font-size:10px; + letter-spacing:.5px} +.side-menu { + border:0!important} +.side-menu .el-menu-item { + height:43px; + line-height:43px; + border-radius:8px; + margin:2px 0; + color:#777589; + font-size:12px; + padding:0 11px!important} +.side-menu .el-menu-item .el-icon { + font-size:16px; + margin-right:12px} +.side-menu .el-menu-item:hover { + background:#f8f6ff; + color:#6850df} +.side-menu .el-menu-item.is-active { + background:#f1edff; + color:#684fdf; + font-weight:600} +.menu-count { + margin-left:auto; + border-radius:10px; + background:#f0edfa; + color:#8b79df; + padding:2px 7px; + font-size:10px} +.access-label { + margin-top:27px} +.sidebar-bottom { + margin-top:auto} +.help-card { + background:#f8f6ff; + border:1px solid #f0edfa; + border-radius:10px; + padding:13px 13px 14px; + margin:0 2px 17px} +.help-icon { + width:23px; + height:23px; + display:grid; + place-items:center; + background:#ebe5ff; + border-radius:7px; + color:#755de6; + margin-bottom:9px; + font-size:13px} +.help-card b { + font-size:11px} +.help-card p { + font-size:10px; + line-height:1.6; + color:#9290a2; + margin:5px 0 9px} +.help-card a { + font-size:10px; + color:#7258e5; + font-weight:600} +.help-card a span { + margin-left:4px} +.user-profile { + border-top:1px solid #f0eff4; + padding:15px 4px 0; + display:flex; + align-items:center; + gap:9px} +.profile-avatar { + width:33px; + height:33px; + border-radius:50%; + font-size:12px} +.profile-info { + flex:1} +.profile-info b,.profile-info small { + display:block} +.profile-info b { + font-size:11px} +.profile-info small { + font-size:10px; + color:#a09eae; + margin-top:3px} +.user-profile>.el-icon { + color:#aaa8b7; + transform:rotate(90deg)} +.main-area { + margin-left:250px; + min-height:100vh} +.topbar { + height:64px; + background:rgba(255,255,255,.92); + border-bottom:1px solid #eeedf4; + display:flex; + align-items:center; + justify-content:space-between; + padding:0 39px; + position:sticky; + top:0; + z-index:3} +.breadcrumb { + font-size:11px; + color:#aaa8b6} +.breadcrumb .slash { + padding:0 10px; + color:#d4d2dd} +.breadcrumb b { + font-weight:600; + color:#555367} +.top-actions { + display:flex; + align-items:center; + gap:18px} +.environment { + border:1px solid #eeedf2; + border-radius:15px; + padding:6px 10px; + font-size:10px; + color:#777589} +.environment i { + display:inline-block; + width:6px; + height:6px; + border-radius:50%; + background:#62c491; + margin-right:6px} +.icon-button { + position:relative; + color:#777589} +.notification-dot { + width:5px; + height:5px; + border-radius:50%; + background:#ef7d85; + position:absolute; + right:6px; + top:5px; + border:1px solid white} +.top-avatar { + width:30px; + height:30px; + border-radius:50%; + font-size:11px} +.page-content { + max-width:1440px; + margin:0 auto; + padding:35px 40px 46px} +.page-heading { + display:flex; + justify-content:space-between; + align-items:center; + margin-bottom:28px} +.eyebrow { + font-size:9px; + letter-spacing:1.25px; + color:#a5a2b2; + font-weight:600} +.eyebrow span { + color:#a990f0; + margin-left:5px} +.page-heading h1 { + font:700 26px Manrope,'Noto Sans SC',sans-serif; + letter-spacing:-.65px; + margin:8px 0 7px; + color:#29263d} +.page-heading p { + font-size:11px; + color:#9694a7; + margin:0} +.wave { + font-size:20px} +.primary-button { + background:#7157e7!important; + border-color:#7157e7!important; + border-radius:7px!important; + font-size:11px!important; + font-weight:600!important; + height:37px!important; + padding:0 15px!important; + box-shadow:0 4px 10px #7257e822} +.primary-button:hover { + background:#6147d5!important} +.primary-button .el-icon { + margin-right:6px} +.stats-grid { + display:grid; + grid-template-columns:repeat(4,minmax(0,1fr)); + gap:15px; + margin-bottom:18px} +.stat-card,.panel { + background:#fff; + border:1px solid #efedf3; + border-radius:10px} +.stat-card { + padding:16px 17px 13px; + min-height:130px} +.stat-top { + display:flex; + align-items:center; + justify-content:space-between; + color:#858396; + font-size:10px} +.stat-icon { + width:27px; + height:27px; + display:grid; + place-items:center; + border-radius:8px; + font-size:13px} +.lilac { + background:#f0edff; + color:#795fe6} +.blush { + background:#fff0f2; + color:#e98191} +.amber { + background:#fff5e7; + color:#e9aa4b} +.mint { + background:#eaf8f1; + color:#55b987} +.stat-value { + font:700 25px Manrope,sans-serif; + letter-spacing:-.8px; + margin-top:12px; + color:#2c2940} +.stat-value small { + font:500 11px 'Noto Sans SC',sans-serif; + color:#9c99aa; + letter-spacing:0; + margin-left:3px} +.stat-foot { + display:flex; + align-items:center; + gap:6px; + color:#aaa8b6; + font-size:9px; + margin-top:9px} +.positive { + font-size:9px; + color:#49b786; + background:#eef9f4; + padding:3px 5px; + border-radius:4px} +.neutral { + color:#aaa8b6} +.sparkline { + height:24px; + display:flex; + align-items:flex-end; + gap:3px; + margin-left:auto} +.sparkline i { + width:3px; + border-radius:2px 2px 0 0; + opacity:.7} +.purple-line i { + background:#a693f5} +.pink-line i { + background:#ed9bab} +.orange-line i { + background:#f2c17a} +.green-line i { + background:#87d4b0} +.dashboard-grid { + display:grid; + grid-template-columns:1.62fr 1fr; + gap:16px; + margin-bottom:17px} +.panel { + padding:19px 20px} +.panel-heading { + display:flex; + align-items:flex-start; + justify-content:space-between} +.panel-heading h2,.episode-toolbar h2 { + font-size:13px; + margin:0; + color:#302d44; + font-weight:650} +.panel-heading p,.episode-toolbar p { + font-size:10px; + color:#a09eae; + margin:5px 0 0} +.period-select { + width:88px} +.period-select .el-input__wrapper { + box-shadow:0 0 0 1px #efedf3 inset!important} +.period-select .el-input__inner { + font-size:10px} +.chart-summary { + display:flex; + align-items:baseline; + gap:8px; + margin-top:15px} +.chart-summary b { + font:700 20px Manrope,sans-serif; + letter-spacing:-.5px} +.chart-summary>span:not(.positive) { + color:#a09eae; + font-size:10px} +.chart { + display:flex; + margin-top:13px; + height:164px} +.chart-y { + width:39px; + display:flex; + flex-direction:column; + justify-content:space-between; + color:#b2b0bd; + font-size:8px; + padding-bottom:18px} +.chart-main { + position:relative; + flex:1} +.chart-grid { + position:absolute; + inset:0 0 20px; + display:flex; + flex-direction:column; + justify-content:space-between} +.chart-grid i { + border-top:1px dashed #eeedf3} +.chart-svg { + position:absolute; + inset:0 0 20px; + width:100%; + height:calc(100% - 20px); + overflow:visible} +.chart-x { + position:absolute; + bottom:0; + left:0; + right:0; + display:flex; + justify-content:space-between; + color:#b2b0bd; + font-size:8px} +.review-count,.muted-count { + display:inline-grid; + place-items:center; + border-radius:10px; + background:#f2efff; + color:#755bdd; + font:600 9px 'DM Sans',sans-serif; + min-width:18px; + height:18px; + margin-left:4px; + padding:0 5px} +.view-all { + font-size:10px!important; + color:#7963df!important} +.view-all span { + margin-left:5px} +.review-list { + margin-top:17px} +.review-row { + display:flex; + align-items:center; + gap:9px; + padding:11px 0; + border-bottom:1px solid #f3f2f6} +.review-row:last-child { + border-bottom:0} +.review-art { + width:34px; + height:37px; + border-radius:6px; + display:grid; + place-items:center; + font-size:17px} +.art-one { + background:#fff0ed} +.art-two { + background:#f2efff} +.art-three { + background:#eaf6f7} +.review-copy { + flex:1; + min-width:0} +.review-copy b,.review-copy small { + display:block} +.review-copy b { + font-size:10px; + white-space:nowrap; + overflow:hidden; + text-overflow:ellipsis} +.review-copy small { + font-size:8px; + color:#aaa8b6; + margin-top:5px} +.review-row .el-tag,.drama-table .el-tag { + border:0; + font-size:9px; + height:21px} +.recent-panel { + padding-bottom:6px} +.recent-panel .panel-heading { + margin-bottom:15px} +.table-wrap { + overflow-x:auto} +.drama-table { + width:100%; + border-collapse:collapse; + text-align:left; + white-space:nowrap} +.drama-table th { + font-size:9px; + font-weight:500; + color:#aaa8b6; + background:#fafafd; + padding:10px 11px} +.drama-table th:first-child { + border-radius:5px 0 0 5px; + padding-left:12px} +.drama-table th:last-child { + border-radius:0 5px 5px 0} +.drama-table td { + font-size:10px; + color:#767487; + padding:10px 11px; + border-bottom:1px solid #f3f2f6} +.drama-table tr:last-child td { + border-bottom:0} +.drama-cell { + display:flex; + align-items:center; + gap:9px} +.drama-cell b { + font-size:10px; + font-weight:600; + color:#4a475e} +.drama-cover { + width:31px; + height:38px; + border-radius:4px; + display:grid; + place-items:center; + font-size:16px; + flex:none; + box-shadow:inset 0 0 0 1px #00000008} +.rose { + background:linear-gradient(145deg,#f8d6d7,#ffece4)} +.purple { + background:linear-gradient(145deg,#dfd4ff,#f4e4f4)} +.peach { + background:linear-gradient(145deg,#ffd6bd,#fff0d7)} +.blue { + background:linear-gradient(145deg,#d6e7ff,#e4f5fa)} +.gold { + background:linear-gradient(145deg,#f5e3b3,#fff0dc)} +.row-action { + font-size:9px!important; + color:#765ed8!important} +.inner-heading { + margin-bottom:24px} +.library-panel { + padding:0 20px 12px} +.library-toolbar { + display:flex; + align-items:center; + justify-content:space-between; + border-bottom:1px solid #f0eff4; + margin-bottom:0} +.library-toolbar .el-tabs__header,.cover-toolbar .el-tabs__header { + margin:0} +.library-toolbar .el-tabs__nav-wrap:after,.cover-toolbar .el-tabs__nav-wrap:after { + display:none} +.library-toolbar .el-tabs__item,.cover-toolbar .el-tabs__item { + font-size:11px; + color:#9997a7; + height:46px} +.library-toolbar .el-tabs__item.is-active,.cover-toolbar .el-tabs__item.is-active { + color:#7258e2} +.library-toolbar .el-tabs__active-bar,.cover-toolbar .el-tabs__active-bar { + background:#7459e6} +.toolbar-filters { + display:flex; + gap:9px} +.toolbar-filters .el-input { + width:190px} +.toolbar-filters .el-select { + width:120px} +.toolbar-filters .el-input__wrapper,.toolbar-filters .el-select__wrapper { + box-shadow:0 0 0 1px #efedf3 inset!important} +.toolbar-filters .el-input__inner,.toolbar-filters .el-select__placeholder { + font-size:10px} +.full-table th { + padding-top:13px; + padding-bottom:13px} +.full-table td { + padding-top:13px; + padding-bottom:13px} +.full-table .drama-cell small { + display:block; + font-size:9px; + color:#aaa8b6; + margin-top:4px; + max-width:235px; + overflow:hidden; + text-overflow:ellipsis} +.pagination { + display:flex; + align-items:center; + justify-content:space-between; + color:#aaa8b6; + font-size:10px; + padding:15px 2px 3px} +.pagination .el-pagination { + --el-pagination-button-bg-color:#fff; + --el-pagination-hover-color:#7357e8} +.pagination .el-pager li { + font-size:10px} +.empty-cell { + text-align:center!important; + padding:45px!important; + color:#aaa!important} +.episode-panel { + padding:0 23px} +.episode-context { + display:flex; + align-items:center; + padding:20px 0; + border-bottom:1px solid #f0eff4} +.large-cover { + width:48px; + height:58px; + font-size:22px; + border-radius:6px} +.episode-context-copy { + margin-left:12px; + display:flex; + flex-direction:column; + gap:4px} +.episode-context-copy>span { + font-size:9px; + color:#a7a5b3} +.episode-context-copy b { + font-size:13px} +.episode-context-copy small { + font-size:9px; + color:#aaa8b6} +.drama-select { + margin-left:auto; + width:215px} +.drama-select .el-input__wrapper { + box-shadow:0 0 0 1px #efedf3 inset!important} +.drama-select .el-input__inner { + font-size:10px} +.episode-toolbar { + display:flex; + align-items:center; + justify-content:space-between; + padding:20px 0 14px} +.secondary-button { + border-radius:6px!important; + border-color:#eae8f0!important; + color:#6e6c80!important; + font-size:10px!important; + height:31px!important} +.secondary-button .el-icon { + margin-right:5px} +.episode-row { + height:69px; + border:1px solid #efedf4; + border-radius:7px; + display:flex; + align-items:center; + gap:12px; + padding:8px 12px; + margin-bottom:8px} +.drag-handle { + color:#c2c0cb; + font-size:17px; + cursor:grab} +.episode-thumb { + width:62px; + height:45px; + border-radius:5px; + background:linear-gradient(135deg,#29273b,#55516e); + display:grid; + place-items:center; + color:#fff; + font-size:15px} +.episode-title { + flex:1} +.episode-title b,.episode-title small { + display:block} +.episode-title b { + font-size:10px; + color:#49465a} +.episode-title small { + font-size:9px; + color:#aaa8b6; + margin-top:6px} +.episode-title small .el-icon { + vertical-align:middle} +.episode-row .el-tag { + font-size:9px; + border:0} +.episode-actions { + display:flex; + margin-left:8px} +.episode-actions .el-button { + padding:5px; + color:#9997a7} +.episode-actions .danger-action:hover { + color:#e77680} +.episode-footer { + display:flex; + justify-content:space-between; + align-items:center; + color:#aaa8b6; + font-size:9px; + padding:12px 1px} +.cover-toolbar { + display:flex; + align-items:center; + justify-content:space-between; + margin-bottom:17px; + border-bottom:1px solid #eeedf3} +.cover-grid { + display:grid; + grid-template-columns:repeat(5,minmax(130px,1fr)); + gap:15px} +.cover-card,.add-cover { + background:white; + border:1px solid #eeedf3; + border-radius:9px; + overflow:hidden} +.poster-art { + height:235px; + position:relative; + display:flex; + flex-direction:column; + justify-content:flex-end; + padding:17px; + overflow:hidden} +.poster-art.rose { + background:linear-gradient(155deg,#e8bfc5,#8b455e 65%,#36273c)} +.poster-art.purple { + background:linear-gradient(155deg,#beb3e8,#584879 65%,#28263c)} +.poster-art.peach { + background:linear-gradient(155deg,#f4c9a8,#cb7181 65%,#553b56)} +.poster-art.blue { + background:linear-gradient(155deg,#9bc2d7,#426b85 65%,#292c4e)} +.poster-art.gold { + background:linear-gradient(155deg,#efd8a6,#b8754d 65%,#48334b)} +.poster-glow { + position:absolute; + width:150px; + height:150px; + border-radius:50%; + background:#ffffff30; + filter:blur(25px); + top:30px; + left:30px} +.poster-emoji { + position:absolute; + top:45px; + left:50%; + transform:translateX(-50%); + font-size:68px; + filter:drop-shadow(0 6px 6px #0004)} +.poster-copy { + position:relative; + color:white; + text-shadow:0 1px 5px #0008; + z-index:1} +.poster-copy small { + display:block; + font:600 7px 'DM Sans'; + letter-spacing:1.6px; + opacity:.8; + margin-bottom:8px} +.poster-copy b { + display:block; + font-size:15px; + line-height:1.5} +.poster-copy span { + display:block; + font-size:8px; + margin-top:5px; + opacity:.8} +.poster-stamp { + position:absolute; + right:10px; + top:12px; + color:white; + border:1px solid #ffffff8a; + padding:4px 6px; + font-size:8px; + border-radius:3px} +.cover-card-info { + padding:10px; + display:flex; + align-items:center; + justify-content:space-between} +.cover-card-info b,.cover-card-info small { + display:block; + max-width:140px; + overflow:hidden; + text-overflow:ellipsis; + white-space:nowrap} +.cover-card-info b { + font-size:9px} +.cover-card-info small { + font-size:8px; + color:#aaa8b6; + margin-top:4px} +.add-cover { + min-height:276px; + border-style:dashed; + display:flex; + flex-direction:column; + align-items:center; + justify-content:center; + color:#858397; + cursor:pointer} +.add-cover>span { + width:36px; + height:36px; + border-radius:50%; + display:grid; + place-items:center; + background:#f3f0ff; + color:#755ce4; + font-size:17px; + margin-bottom:11px} +.add-cover b { + font-size:10px; + font-weight:600} +.add-cover small { + font-size:8px; + color:#aaa8b6; + margin-top:6px} +.access-layout { + display:grid; + grid-template-columns:1fr 1.48fr; + gap:17px} +.roles-panel,.permissions-panel { + padding:19px} +.role-list { + margin-top:14px; + display:flex; + flex-direction:column; + gap:8px} +.role-card { + width:100%; + border:1px solid #efedf4; + background:#fff; + border-radius:8px; + display:flex; + align-items:center; + text-align:left; + padding:11px; + gap:10px; + cursor:pointer; + font:inherit} +.role-card:hover { + border-color:#d9d1fa} +.role-active { + border-color:#bcb0f2; + background:#faf9ff} +.role-icon { + width:34px; + height:34px; + display:grid; + place-items:center; + border-radius:8px; + font-size:15px} +.role-icon.red { + background:#fff0ef; + color:#e47b79} +.role-icon.purple { + background:#f1edff; + color:#785ee5} +.role-icon.blue { + background:#edf5ff; + color:#6195dc} +.role-icon.green { + background:#eaf8f0; + color:#50ae80} +.role-copy { + flex:1} +.role-copy b,.role-copy small,.role-copy span { + display:block} +.role-copy b { + font-size:10px; + color:#47445a} +.role-copy small { + font-size:8px; + color:#a09eae; + margin-top:4px} +.role-copy span { + font-size:8px; + color:#aaa8b6; + margin-top:6px} +.role-chevron { + font-size:10px; + color:#aaa8b6; + transform:rotate(-90deg)} +.role-badge { + font-size:8px; + color:#7b63df; + background:#f1edff; + border-radius:10px; + padding:4px 7px; + vertical-align:2px; + margin-left:5px; + font-weight:500} +.permission-groups { + display:grid; + grid-template-columns:1fr 1fr; + gap:15px; + margin:23px 0 18px} +.permission-group { + display:flex; + flex-direction:column; + gap:14px} +.permission-group h3 { + font-size:10px; + margin:0 0 2px; + color:#5b586d} +.permission-group h3 .el-icon { + font-size:12px; + vertical-align:middle; + margin-right:5px; + color:#816be2} +.permission-group .el-checkbox { + height:auto; + margin:0} +.permission-group .el-checkbox__label { + font-size:9px; + color:#858397} +.permission-group .el-checkbox__input.is-checked .el-checkbox__inner { + background:#765ce5; + border-color:#765ce5} +.permission-note { + padding:10px; + background:#f8f7fc; + border-radius:6px; + font-size:8px; + color:#9997a8; + display:flex; + align-items:center; + gap:7px} +.permission-note .el-icon { + color:#8a78dc} +.placeholder-panel { + text-align:center; + padding:70px 20px} +.placeholder-icon { + display:grid; + place-items:center; + width:45px; + height:45px; + border-radius:12px; + margin:auto; + background:#f1edff; + color:#765ce5; + font-size:20px} +.placeholder-panel h2 { + font-size:14px; + margin:14px 0 5px} +.placeholder-panel p { + font-size:10px; + color:#a09eae} +.el-dialog { + border-radius:11px!important} +.el-dialog__header { + padding:20px 22px 12px!important; + margin-right:0!important; + border-bottom:1px solid #f0eff4} +.el-dialog__title { + font-size:14px; + font-weight:650} +.el-dialog__body { + padding:15px 22px 5px!important} +.el-dialog__footer { + padding:13px 22px 19px!important; + border-top:1px solid #f2f1f5} +.dialog-subtitle { + font-size:10px; + color:#9997a8; + margin-bottom:16px} +.drama-form .form-row,.app-dialog .form-row { + display:grid; + grid-template-columns:1fr 1fr; + gap:14px} +.app-dialog .el-form-item { + margin-bottom:14px} +.app-dialog .el-form-item__label { + font-size:10px; + color:#666377; + padding-bottom:5px} +.app-dialog .el-input__inner,.app-dialog .el-textarea__inner,.app-dialog .el-select__placeholder { + font-size:10px} +.upload-area { + width:100%} +.upload-area .el-upload,.upload-area .el-upload-dragger { + width:100%} +.upload-area .el-upload-dragger { + height:112px; + padding:14px; + border-radius:7px; + border-color:#e8e5ef; + background:#fcfbfe} +.upload-icon { + font-size:20px; + color:#8a75e5; + margin-bottom:6px} +.upload-area .el-upload__text { + font-size:10px; + color:#777589} +.upload-area .el-upload__text em { + font-style:normal; + color:#7358e2} +.upload-hint { + font-size:8px; + color:#aaa8b6; + margin-top:5px} +.app-dialog .el-input-number { + width:100%} +.app-dialog .el-input-number .el-input__wrapper { + width:100%} +@media(min-width:1500px) { + .page-content { + padding-left:48px; + padding-right:48px} +.stats-grid { + gap:18px} +.stat-card { + min-height:141px; + padding:18px 19px} +.cover-grid { + grid-template-columns:repeat(6,minmax(130px,1fr))} +} +@media(max-width:1200px) { + .sidebar { + width:220px} +.main-area { + margin-left:220px} +.page-content { + padding:30px 26px} +.topbar { + padding:0 26px} +.stats-grid { + gap:10px} +.stat-card { + padding:14px} +.dashboard-grid { + grid-template-columns:1.35fr 1fr; + gap:12px} +.cover-grid { + grid-template-columns:repeat(4,minmax(130px,1fr))} +} diff --git a/src/theme.css b/src/theme.css new file mode 100644 index 0000000..1edb604 --- /dev/null +++ b/src/theme.css @@ -0,0 +1,231 @@ +:root { + font-family:Arial,'Noto Sans SC',sans-serif; + color:#30343b; + background:#f5f6f8; + --purple:#4b76b8; + --muted:#858b95; + --line:#e8eaed} +body { + background:#f5f6f8} +.sidebar { + background:#fff; + border-right-color:#e7e9ec; + padding-top:22px} +.brand { + padding-bottom:23px} +.brand-mark { + background:#456fae; + border-radius:7px; + transform:none} +.brand b { + font:700 16px Arial,sans-serif; + letter-spacing:-.4px; + color:#29313b} +.brand small { + letter-spacing:.9px} +.workspace { + border-radius:5px; + border-color:#e5e7eb; + margin-bottom:22px} +.workspace-avatar,.profile-avatar,.top-avatar { + background:#edf1f6; + color:#4b6689; + border-radius:5px} +.nav-label { + color:#9298a1; + font-size:10px} +.side-menu .el-menu-item { + border-radius:5px; + color:#616873} +.side-menu .el-menu-item:hover { + background:#f5f7f9; + color:#355f97} +.side-menu .el-menu-item.is-active { + background:#edf2f8; + color:#355f97} +.menu-count { + background:#eef1f4; + color:#747d88} +.help-card { + background:#f7f8fa; + border-color:#eceef0; + border-radius:5px} +.help-icon { + background:#e9edf2; + color:#617b9c; + border-radius:5px} +.help-card a { + color:#416a9f} +.topbar { + background:#fff} +.environment { + border-radius:4px} +.page-content { + padding-top:30px} +.eyebrow { + letter-spacing:.7px; + color:#9298a1} +.page-heading h1 { + font:600 23px Arial,'Noto Sans SC',sans-serif; + letter-spacing:0; + color:#2f343c} +.page-heading p { + color:#818791} +.primary-button { + background:#456fae!important; + border-color:#456fae!important; + border-radius:5px!important; + box-shadow:none!important} +.primary-button:hover { + background:#385f97!important} +.stats-grid { + gap:12px} +.stat-card,.panel { + border-color:#e6e8eb; + border-radius:6px; + box-shadow:none} +.stat-card { + min-height:116px; + padding:14px 16px} +.stat-top { + color:#727983} +.stat-icon { + border-radius:5px; + background:#f0f2f4!important; + color:#697584!important} +.stat-value { + font-family:Arial,sans-serif; + color:#303640} +.stat-foot .positive { + color:#4d866a; + background:#eff5f1} +.sparkline { + opacity:.42} +.positive { + color:#4d866a; + background:#eff5f1} +.review-count,.muted-count { + background:#eef1f4; + color:#61738a} +.chart-svg path:first-of-type { + fill:#dce5f0} +.chart-svg path:last-of-type { + stroke:#587eae} +.view-all,.row-action { + color:#456fae!important} +.review-art { + background:#f1f3f5!important; + border-radius:5px; + font-size:0} +.review-art:after { + content:'—'; + font-size:14px; + color:#818a95} +.review-row .el-tag,.drama-table .el-tag,.episode-row .el-tag { + border-radius:4px; + background:#f0f2f4!important; + color:#606b78!important} +.drama-cover { + border-radius:4px; + box-shadow:none; + font-size:14px; + background:#eceff2!important} +.drama-cover.rose,.drama-cover.purple,.drama-cover.peach,.drama-cover.blue,.drama-cover.gold { + background:#eceff2!important} +.drama-table th { + background:#f7f8fa; + color:#7d848e} +.drama-table td { + color:#656c76} +.drama-cell b { + color:#343a43} +.library-panel { + padding-left:18px; + padding-right:18px} +.library-toolbar .el-tabs__item,.cover-toolbar .el-tabs__item { + font-size:11px} +.library-toolbar .el-tabs__item.is-active,.cover-toolbar .el-tabs__item.is-active { + color:#456fae} +.library-toolbar .el-tabs__active-bar,.cover-toolbar .el-tabs__active-bar { + background:#456fae} +.toolbar-filters .el-input__wrapper,.toolbar-filters .el-select__wrapper,.drama-select .el-input__wrapper { + border-radius:5px} +.episode-panel { + padding-left:20px; + padding-right:20px} +.episode-row { + border-color:#e7e9ec; + border-radius:5px} +.episode-thumb { + border-radius:4px; + background:#e9ecef; + color:#7d8792} +.episode-thumb .el-icon { + color:#606a75} +.episode-actions .el-button { + color:#767e88} +.cover-grid { + grid-template-columns:repeat(auto-fill,minmax(145px,1fr))} +.cover-card,.add-cover { + border-color:#e6e8eb; + border-radius:5px} +.poster-art { + background:#e9ecef!important; + filter:grayscale(.72)} +.poster-art.rose,.poster-art.purple,.poster-art.peach,.poster-art.blue,.poster-art.gold { + background:#e9ecef!important} +.poster-glow,.poster-stamp { + display:none} +.poster-emoji { + font-size:48px; + filter:grayscale(1); + opacity:.55} +.poster-copy { + text-shadow:none; + color:#303640} +.poster-copy small,.poster-copy span { + color:#606873; + opacity:1} +.cover-card-info b { + color:#383e46} +.add-cover { + min-height:276px; + background:#fafbfc; + color:#656d77} +.add-cover>span { + border-radius:5px; + background:#edf0f3; + color:#68798d} +.role-card { + border-radius:5px; + border-color:#e7e9ec} +.role-active { + border-color:#aebed1; + background:#f7f9fb} +.role-icon { + border-radius:5px; + background:#eef1f4!important; + color:#65758a!important} +.role-badge { + border-radius:4px; + background:#eef1f4; + color:#65758a} +.permission-group h3 .el-icon { + color:#617b9b} +.permission-group .el-checkbox__input.is-checked .el-checkbox__inner { + background:#456fae; + border-color:#456fae} +.permission-note { + background:#f6f7f8; + border-radius:4px} +.el-dialog { + border-radius:6px!important} +.el-dialog__header,.el-dialog__footer { + border-color:#e8eaed} +.upload-area .el-upload-dragger { + border-radius:5px; + background:#fafbfc} +.upload-icon,.upload-area .el-upload__text em { + color:#456fae} +.el-tabs__item,.el-input__inner,.el-select__placeholder { + font-family:Arial,'Noto Sans SC',sans-serif} diff --git a/src/typography.css b/src/typography.css new file mode 100644 index 0000000..14c5f96 --- /dev/null +++ b/src/typography.css @@ -0,0 +1,9 @@ +.stat-top>.stat-icon { + width:30px; + height:30px; + flex:0 0 30px; + display:grid; + place-items:center; + border-radius:5px} +.stat-top>.stat-icon .el-icon { + font-size:16px} diff --git a/src/utils/status.js b/src/utils/status.js new file mode 100644 index 0000000..4c7e018 --- /dev/null +++ b/src/utils/status.js @@ -0,0 +1,11 @@ +const statusClasses = { + 已上线: 'is-online', + 审核中: 'is-reviewing', + 草稿: 'is-draft', + 待发布: 'is-pending', + 已下线: 'is-offline', +} + +export function statusClass(status) { + return statusClasses[status] || 'is-draft' +} diff --git a/src/views/common/DashboardView.vue b/src/views/common/DashboardView.vue new file mode 100644 index 0000000..2c7bfc0 --- /dev/null +++ b/src/views/common/DashboardView.vue @@ -0,0 +1,23 @@ + + + diff --git a/src/views/common/LoginView.vue b/src/views/common/LoginView.vue new file mode 100644 index 0000000..76a39b3 --- /dev/null +++ b/src/views/common/LoginView.vue @@ -0,0 +1,73 @@ + + + + + diff --git a/src/views/components/PageHeading.vue b/src/views/components/PageHeading.vue new file mode 100644 index 0000000..f3ded1a --- /dev/null +++ b/src/views/components/PageHeading.vue @@ -0,0 +1,18 @@ + + + diff --git a/src/views/contentManagement/DramaManagementView.vue b/src/views/contentManagement/DramaManagementView.vue new file mode 100644 index 0000000..c8d3ff3 --- /dev/null +++ b/src/views/contentManagement/DramaManagementView.vue @@ -0,0 +1,80 @@ + + + diff --git a/src/views/contentManagement/EpisodeManagementView.vue b/src/views/contentManagement/EpisodeManagementView.vue new file mode 100644 index 0000000..b8b0f44 --- /dev/null +++ b/src/views/contentManagement/EpisodeManagementView.vue @@ -0,0 +1,44 @@ + + + diff --git a/src/views/permission/CustomMenuView.vue b/src/views/permission/CustomMenuView.vue new file mode 100644 index 0000000..fe9bad6 --- /dev/null +++ b/src/views/permission/CustomMenuView.vue @@ -0,0 +1,13 @@ + + + diff --git a/src/views/permission/MenuManagementView.vue b/src/views/permission/MenuManagementView.vue new file mode 100644 index 0000000..9a8238c --- /dev/null +++ b/src/views/permission/MenuManagementView.vue @@ -0,0 +1,78 @@ + + + diff --git a/src/views/permission/RoleCategoriesView.vue b/src/views/permission/RoleCategoriesView.vue new file mode 100644 index 0000000..bf3d0fd --- /dev/null +++ b/src/views/permission/RoleCategoriesView.vue @@ -0,0 +1,51 @@ + + + diff --git a/src/views/permission/RoleManagementView.vue b/src/views/permission/RoleManagementView.vue new file mode 100644 index 0000000..5ad6546 --- /dev/null +++ b/src/views/permission/RoleManagementView.vue @@ -0,0 +1,92 @@ + + + diff --git a/src/views/permission/composables/useMenuManagement.js b/src/views/permission/composables/useMenuManagement.js new file mode 100644 index 0000000..abaee07 --- /dev/null +++ b/src/views/permission/composables/useMenuManagement.js @@ -0,0 +1,106 @@ +import { computed, reactive, ref } from 'vue' +import { ElMessage, ElMessageBox } from 'element-plus' +import { customMenus, saveCustomMenus } from '../../../data/customMenus' +import { useRbac } from '../../../stores/rbac' +import router, { registerCustomMenu } from '../../../router' + +const builtInMenus = [ + { id: 'builtin-content', label: '内容管理', children: [ + { id: 'builtin-dramas', label: '短剧管理', route: '/dramas', permission: '查看短剧列表' }, + { id: 'builtin-episodes', label: '剧集管理', route: '/episodes', permission: '查看剧集列表' }, + ] }, + { id: 'builtin-admin', label: '后台管理', children: [ + { id: 'builtin-access', label: '角色权限配置', route: '/access', permission: '角色与权限配置' }, + { id: 'builtin-users', label: '管理员用户列表', route: '/admin-users', permission: '管理员用户列表' }, + { id: 'builtin-menus', label: '菜单管理', route: '/menus', permission: '菜单管理' }, + { id: 'builtin-categories', label: '角色分类', route: '/role-categories', permission: '角色分类管理' }, + ] }, +] + +function buildCustomNodes(parent) { + return customMenus.filter(menu => (menu.parent || '后台管理') === parent).map(menu => ({ + id: menu.key, + label: menu.title, + menu, + children: buildCustomNodes(menu.title), + })) +} + +export function useMenuManagement() { + const { createPermission, removePermission } = useRbac() + const selectedKey = ref('') + const selectedParent = ref('后台管理') + const form = reactive({ key: '', title: '', route: '', permission: '', parent: '后台管理', type: '页面', icon: 'Document', method: '', sort: 1 }) + const menuTree = computed(() => builtInMenus.map(group => ({ ...group, children: [...group.children, ...buildCustomNodes(group.label)] }))) + const parentOptions = computed(() => ['内容管理', '后台管理', ...customMenus.filter(menu => menu.type !== '按钮').map(menu => menu.title)]) + + function resetForm(parent = selectedParent.value) { + selectedKey.value = '' + Object.assign(form, { key: '', title: '', route: '', permission: '', parent, type: '页面', icon: 'Document', method: '', sort: 1 }) + } + function selectNode(node) { + if (node.menu) { + selectedKey.value = node.menu.key + selectedParent.value = node.menu.parent || '后台管理' + Object.assign(form, { + ...node.menu, + route: node.menu.route || `/custom/${node.menu.key}`, + permission: node.menu.permission || `访问菜单:${node.menu.title}`, + type: node.menu.type || '页面', + method: node.menu.method || '', + sort: node.menu.sort || 1, + }) + } else if (node.children) { + selectedParent.value = node.label + resetForm(node.label) + } + } + function saveMenu() { + if (!form.title.trim()) return ElMessage.warning('请填写菜单名称') + if (form.type !== '按钮' && !form.route.trim()) return ElMessage.warning('请填写对应路由') + const duplicate = customMenus.some(menu => menu.title === form.title.trim() && menu.key !== selectedKey.value) + if (duplicate) return ElMessage.warning('菜单名称已存在') + const isNew = !selectedKey.value + const key = selectedKey.value || `${Date.now()}` + const existingMenu = customMenus.find(menu => menu.key === key) + const permission = form.permission.trim() || `访问菜单:${form.title.trim()}` + if (existingMenu) { + if (router.hasRoute(`custom-menu-${key}`)) router.removeRoute(`custom-menu-${key}`) + if (existingMenu.permission !== permission) { + removePermission(existingMenu.permission) + createPermission(permission) + } + Object.assign(existingMenu, { ...form, key, title: form.title.trim(), permission, route: form.route.trim(), sort: Number(form.sort) || 1 }) + } else { + customMenus.push({ ...form, key, title: form.title.trim(), permission, route: form.route.trim(), sort: Number(form.sort) || 1 }) + createPermission(permission) + } + const savedMenu = customMenus.find(menu => menu.key === key) + if (savedMenu?.type !== '按钮') registerCustomMenu(savedMenu) + saveCustomMenus() + selectedKey.value = key + ElMessage.success(isNew ? '菜单已添加' : '菜单已保存') + } + async function deleteMenu() { + const menu = customMenus.find(item => item.key === selectedKey.value) + if (!menu) return ElMessage.warning('请先在左侧选择一个自定义菜单') + try { + await ElMessageBox.confirm(`确认删除「${menu.title}」菜单吗?`, '删除确认', { + confirmButtonText: '确认删除', + cancelButtonText: '取消', + type: 'warning', + confirmButtonClass: 'el-button--danger', + }) + customMenus.splice(customMenus.indexOf(menu), 1) + removePermission(menu.permission) + if (router.hasRoute(`custom-menu-${menu.key}`)) router.removeRoute(`custom-menu-${menu.key}`) + saveCustomMenus() + resetForm() + ElMessage.success('菜单已删除') + } catch { + // 用户取消时不删除菜单。 + } + } + + return { selectedKey, selectedParent, form, menuTree, parentOptions, resetForm, selectNode, saveMenu, deleteMenu } +} diff --git a/src/views/permission/composables/useRoleCategories.js b/src/views/permission/composables/useRoleCategories.js new file mode 100644 index 0000000..734ba98 --- /dev/null +++ b/src/views/permission/composables/useRoleCategories.js @@ -0,0 +1,62 @@ +import { computed, ref } from 'vue' +import { ElMessage, ElMessageBox } from 'element-plus' +import { roleData } from '../../../data/roles' + +export function useRoleCategories() { + const dialogVisible = ref(false) + const name = ref('') + const editingId = ref(null) + const categories = ref([ + { id: 1, name: '系统管理员', description: '负责账号、权限和菜单配置' }, + { id: 2, name: '内容管理', description: '负责短剧与剧集内容维护' }, + { id: 3, name: '数据分析', description: '负责查看运营数据' }, + ]) + const tableData = computed(() => categories.value.map(category => ({ + ...category, + roleCount: roleData.filter(role => role.category === category.name).length, + }))) + + function openCreateDialog() { + editingId.value = null + name.value = '' + dialogVisible.value = true + } + function editCategory(category) { + editingId.value = category.id + name.value = category.name + dialogVisible.value = true + } + function saveCategory() { + const value = name.value.trim() + if (!value) return ElMessage.warning('请输入分类名称') + if (categories.value.some(category => category.name === value && category.id !== editingId.value)) { + return ElMessage.warning('分类名称已存在') + } + if (editingId.value) { + const category = categories.value.find(item => item.id === editingId.value) + const oldName = category?.name + if (category) category.name = value + roleData.forEach(role => { if (role.category === oldName) role.category = value }) + } else { + categories.value.push({ id: Date.now(), name: value, description: '自定义角色分类' }) + } + dialogVisible.value = false + ElMessage.success('角色分类已保存') + } + async function deleteCategory(category) { + try { + await ElMessageBox.confirm(`确定删除「${category.name}」分类吗?`, '删除确认', { + confirmButtonText: '确认删除', + cancelButtonText: '取消', + type: 'warning', + confirmButtonClass: 'el-button--danger', + }) + categories.value = categories.value.filter(item => item.id !== category.id) + ElMessage.success('分类已删除') + } catch { + // 用户取消时不删除分类。 + } + } + + return { dialogVisible, name, editingId, tableData, openCreateDialog, editCategory, saveCategory, deleteCategory } +} diff --git a/src/views/permission/composables/useRolePermissionEditor.js b/src/views/permission/composables/useRolePermissionEditor.js new file mode 100644 index 0000000..b1158db --- /dev/null +++ b/src/views/permission/composables/useRolePermissionEditor.js @@ -0,0 +1,69 @@ +import { computed, nextTick, ref, watch } from 'vue' +import { ElMessage } from 'element-plus' +import { roleData } from '../../../data/roles' +import { customMenus } from '../../../data/customMenus' +import { permissionGroups } from '../../../permissions/permissions' +import { useAuth } from '../../../stores/auth' +import { useRbac } from '../../../stores/rbac' + +export function useRolePermissionEditor() { + const { rolePermissions, updateRolePermission, createRole } = useRbac() + const { currentRole } = useAuth() + const selectedRole = ref(roleData[0].name) + const dialogVisible = ref(false) + const newRoleName = ref('') + const treeRef = ref() + const syncingTree = ref(false) + const permissionTree = computed(() => { + const groups = permissionGroups.map(group => ({ + id: `group-${group.name}`, + label: group.name, + children: group.items.map(permission => ({ id: permission, label: permission, permission })), + })) + if (customMenus.length) { + groups.push({ + id: 'group-custom-menus', + label: '自定义菜单', + children: customMenus.map(menu => ({ id: menu.permission, label: menu.title, permission: menu.permission })), + }) + } + return groups + }) + const currentLevel = computed(() => roleData.find(role => role.name === currentRole.value)?.level || 1) + const selectedRoleRecord = computed(() => roleData.find(role => role.name === selectedRole.value)) + const canEditSelectedRole = computed(() => (selectedRoleRecord.value?.level || 1) >= currentLevel.value) + + watch(selectedRole, async role => { + syncingTree.value = true + await nextTick() + treeRef.value?.setCheckedKeys(rolePermissions[role] || []) + await nextTick() + syncingTree.value = false + }, { immediate: true }) + + function selectRole(row) { selectedRole.value = row.name } + function handleCheck(data, checked) { + if (!syncingTree.value && data.permission && canEditSelectedRole.value) { + updateRolePermission(selectedRole.value, data.permission, checked) + } + } + function addRole() { + const name = newRoleName.value.trim() + if (!name) return ElMessage.warning('请输入角色名称') + if (roleData.some(role => role.name === name)) return ElMessage.warning('角色名称已存在') + roleData.push({ + id: Date.now(), name, description: '自定义权限角色', memberCount: 0, + category: '自定义角色', level: currentLevel.value + 1, status: '启用', + }) + createRole(name) + selectedRole.value = name + newRoleName.value = '' + dialogVisible.value = false + ElMessage.success('角色已创建') + } + + return { + roleData, rolePermissions, selectedRole, selectedRoleRecord, canEditSelectedRole, + permissionTree, treeRef, dialogVisible, newRoleName, selectRole, handleCheck, addRole, + } +} diff --git a/src/views/systemManagement/SettingsView.vue b/src/views/systemManagement/SettingsView.vue new file mode 100644 index 0000000..49a5392 --- /dev/null +++ b/src/views/systemManagement/SettingsView.vue @@ -0,0 +1,4 @@ + diff --git a/src/views/userManagement/AdminUsersView.vue b/src/views/userManagement/AdminUsersView.vue new file mode 100644 index 0000000..e8d7afe --- /dev/null +++ b/src/views/userManagement/AdminUsersView.vue @@ -0,0 +1,131 @@ + + + diff --git a/src/views/userManagement/composables/useAdminUsers.js b/src/views/userManagement/composables/useAdminUsers.js new file mode 100644 index 0000000..9d21276 --- /dev/null +++ b/src/views/userManagement/composables/useAdminUsers.js @@ -0,0 +1,45 @@ +import { computed, reactive, ref } from 'vue' +import { ElMessage } from 'element-plus' +import { roleData } from '../../../data/roles' +import { demoAdminUsers } from '../data/adminUsers' + +export function useAdminUsers() { + const keyword = ref('') + const roleFilter = ref('') + const currentPage = ref(1) + const pageSize = ref(10) + const dialogVisible = ref(false) + const form = reactive({ username: '', name: '', role: '', ipWhitelist: '' }) + const users = ref(demoAdminUsers.map(user => ({ ...user }))) + const filtered = computed(() => users.value.filter(user => { + const query = keyword.value.trim().toLowerCase() + const matchesKeyword = !query || `${user.id} ${user.username} ${user.name}`.toLowerCase().includes(query) + return matchesKeyword && (!roleFilter.value || user.role === roleFilter.value) + })) + const paginated = computed(() => filtered.value.slice((currentPage.value - 1) * pageSize.value, currentPage.value * pageSize.value)) + + function queryUsers() { currentPage.value = 1 } + function toggleStatus(user) { user.status = user.status === '启用' ? '停用' : '启用' } + function addUser() { + if (!form.username.trim() || !form.name.trim() || !form.role) return ElMessage.warning('请填写用户名、姓名并选择权限角色') + const role = roleData.find(item => item.name === form.role) + users.value.unshift({ + id: Math.max(0, ...users.value.map(user => user.id)) + 1, + username: form.username.trim(), + name: form.name.trim(), + roleId: role?.id || 0, + role: form.role, + ipWhitelist: form.ipWhitelist.trim() || '不限', + status: '启用', + twoFactor: '未开启', + createdAt: new Date().toLocaleString('sv-SE'), + lastLogin: '—', + }) + Object.assign(form, { username: '', name: '', role: '', ipWhitelist: '' }) + dialogVisible.value = false + currentPage.value = 1 + ElMessage.success('管理员已添加') + } + + return { keyword, roleFilter, currentPage, pageSize, dialogVisible, form, users, filtered, paginated, queryUsers, toggleStatus, addUser } +} diff --git a/src/views/userManagement/data/adminUsers.js b/src/views/userManagement/data/adminUsers.js new file mode 100644 index 0000000..d9a7992 --- /dev/null +++ b/src/views/userManagement/data/adminUsers.js @@ -0,0 +1,7 @@ +export const demoAdminUsers = [ + { id: 1001, username: 'lin.xiaoman', name: '林小满', roleId: 1, role: '超级管理员', ipWhitelist: '不限', status: '启用', twoFactor: '已开启', createdAt: '2026-09-18 09:30:00', lastLogin: '2026-10-12 09:41:22' }, + { id: 1002, username: 'content.editor01', name: '陈一诺', roleId: 2, role: '内容运营', ipWhitelist: '不限', status: '启用', twoFactor: '未开启', createdAt: '2026-09-21 10:15:00', lastLogin: '2026-10-11 16:20:15' }, + { id: 1003, username: 'content.editor02', name: '周子瑜', roleId: 2, role: '内容运营', ipWhitelist: '不限', status: '启用', twoFactor: '未开启', createdAt: '2026-09-25 14:20:00', lastLogin: '2026-10-11 13:34:10' }, + { id: 1004, username: 'content.reviewer01', name: '林晓彤', roleId: 3, role: '审核专员', ipWhitelist: '不限', status: '启用', twoFactor: '已开启', createdAt: '2026-10-02 11:05:00', lastLogin: '2026-10-10 11:20:08' }, + { id: 1005, username: 'data.analyst01', name: '程予安', roleId: 4, role: '数据分析师', ipWhitelist: '不限', status: '启用', twoFactor: '未开启', createdAt: '2026-10-05 13:02:00', lastLogin: '2026-10-09 14:10:02' }, +] diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..a52797c --- /dev/null +++ b/vite.config.js @@ -0,0 +1,12 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import { fileURLToPath, URL } from 'node:url' + +export default defineConfig({ + plugins: [vue()], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + }, + }, +})