Commit 57e9254d authored by Andy-bubu's avatar Andy-bubu

党风廉政建设UI统一

parent 7bb0da56
......@@ -74,6 +74,9 @@ export default defineConfig({
}
},
esbuildMinifyIIFE: true,
jsMinifierOptions: {
target: ['chrome80', 'es2020'],
},
define: {
STREAMS_API_URL: '/api',
STREAMS_PARSER_URL: '/parser',
......
{
"name": "risk_case_library",
"name": "incorrupt_party_build",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"hasInstallScript": true,
"dependencies": {
"@ant-design/icons": "^6.3.2",
"@headlessui/react": "^2.2.10",
"@heroicons/react": "^2.2.0",
"@heroui-pro/react": "file:../project_manager_new/node_modules/@heroui-pro/react",
"@heroui/react": "^3.2.3",
"@heroui/styles": "^3.2.3",
"@react-aria/ssr": "^3.10.1",
"@react-aria/utils": "^3.34.1",
"@tailwindcss/postcss": "^4.2.0",
"@umijs/max": "^4.5.3",
"clsx": "^2.1.1",
......@@ -15,19 +21,132 @@
"dayjs": "^1.11.20",
"echarts": "^6.1.0",
"echarts-for-react": "^3.0.6",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react": "^19.2.8",
"react-aria": "^3.51.0",
"react-aria-components": "^1.20.0",
"react-dom": "^19.2.8",
"tailwindcss": "^4.2.0",
"umi-request": "^1.4.0",
"umi-request-progress": "^1.1.0",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@types/react": "^18.0.33",
"@types/react-dom": "^18.0.11",
"@types/react": "^19.2.18",
"@types/react-dom": "^19.2.4",
"typescript": "^5.0.3"
}
},
"../project_manager_new/node_modules/@heroui-pro/react": {
"version": "1.0.0-beta.7",
"license": "SEE LICENSE IN LICENSE",
"dependencies": {
"@internationalized/date": "3.12.2",
"@internationalized/number": "3.6.7",
"@react-aria/utils": "3.34.1",
"@react-stately/utils": "3.12.1"
},
"peerDependencies": {
"@heroui/react": ">=3.2.2",
"@heroui/styles": ">=3.2.2",
"@number-flow/react": ">=0.5.0",
"@tiptap/core": ">=3.23.6",
"@tiptap/extension-link": ">=3.23.6",
"@tiptap/extension-underline": ">=3.23.6",
"@tiptap/extensions": ">=3.23.6",
"@tiptap/pm": ">=3.23.6",
"@tiptap/react": ">=3.23.6",
"@tiptap/starter-kit": ">=3.23.6",
"@tiptap/suggestion": ">=3.23.6",
"embla-carousel": ">=8.6.0",
"embla-carousel-react": ">=8.6.0",
"maplibre-gl": ">=5.15.0",
"marked": ">=18.0.0",
"motion": ">=12.12.0",
"react": ">=19.0.0",
"react-aria-components": ">=1.19.0",
"react-dom": ">=19.0.0",
"react-markdown": ">=10.1.0",
"react-resizable-panels": ">=4.10.0",
"recharts": ">=2.0.0",
"remark-breaks": ">=4.0.0",
"remark-gfm": ">=4.0.0",
"shiki": ">=4.0.0",
"streamdown": ">=2.5.0",
"tailwind-merge": ">=3.4.0",
"tailwind-variants": ">=3.2.0",
"tailwindcss": ">=4.0.0"
}
},
"node_modules/@adobe/react-spectrum": {
"version": "3.47.3",
"resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.3.tgz",
"integrity": "sha512-tWZG59+xTbXIqeZB4uyuo2qljk3nRK66IkdBmMgbl2p6peApkKbrtJ/YOM8rW4gemdmv7qoJlbIHvy8aJkYsCQ==",
"license": "Apache-2.0",
"dependencies": {
"@internationalized/date": "^3.12.3",
"@react-types/shared": "^3.36.1",
"@spectrum-icons/ui": "^3.7.1",
"@spectrum-icons/workflow": "^4.3.1",
"@swc/helpers": "^0.5.0",
"client-only": "^0.0.1",
"clsx": "^2.0.0",
"react-aria": "3.51.0",
"react-aria-components": "1.20.0",
"react-stately": "3.49.0",
"react-transition-group": "^4.4.5",
"use-sync-external-store": "^1.6.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@adobe/react-spectrum-ui": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@adobe/react-spectrum-ui/-/react-spectrum-ui-1.2.1.tgz",
"integrity": "sha512-wcrbEE2O/9WnEn6avBnaVRRx88S5PLFsPLr4wffzlbMfXeQsy+RMQwaJd3cbzrn18/j04Isit7f7Emfn0dhrJA==",
"license": "Apache-2.0",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@adobe/react-spectrum-workflow": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@adobe/react-spectrum-workflow/-/react-spectrum-workflow-2.3.5.tgz",
"integrity": "sha512-b53VIPwPWKb/T5gzE3qs+QlGP5gVrw/LnWV3xMksDU+CRl3rzOKUwxIGiZO8ICyYh1WiyqY4myGlPU/nAynBUg==",
"license": "Apache-2.0",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@adobe/react-spectrum/node_modules/react-stately": {
"version": "3.49.0",
"resolved": "https://registry.npmjs.org/react-stately/-/react-stately-3.49.0.tgz",
"integrity": "sha512-13iNq2KzBrRAzxRc+n53hgROfIistiYY/sPtIhCw1qUB7/kmo+X1xEU2uiS5zcCIrc55AUPwoHqOIIpKWSwB9A==",
"license": "Apache-2.0",
"dependencies": {
"@internationalized/date": "^3.12.3",
"@internationalized/number": "^3.6.7",
"@internationalized/string": "^3.2.10",
"@react-types/shared": "^3.36.1",
"@swc/helpers": "^0.5.0",
"use-sync-external-store": "^1.6.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@adobe/react-spectrum/node_modules/use-sync-external-store": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@alloc/quick-lru": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
......@@ -90,17 +209,15 @@
}
},
"node_modules/@ant-design/icons": {
"version": "4.8.3",
"resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-4.8.3.tgz",
"integrity": "sha512-HGlIQZzrEbAhpJR6+IGdzfbPym94Owr6JZkJ2QCCnOkPVIWMO2xgIVcOKnl8YcpijIo39V7l2qQL5fmtw56cMw==",
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-6.3.2.tgz",
"integrity": "sha512-B6O5a5XJ4wjtNOfZejXYwHW5zvKV5gYkjGf11dHGLEbKn0ABDGndo41+gfIiXyTFhvESj4XTotuud33mUFid0g==",
"license": "MIT",
"dependencies": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons-svg": "^4.3.0",
"@babel/runtime": "^7.11.2",
"classnames": "^2.2.6",
"lodash": "^4.17.15",
"rc-util": "^5.9.4"
"@ant-design/colors": "^8.0.1",
"@ant-design/icons-svg": "^4.5.0",
"@rc-component/util": "^1.11.0",
"clsx": "^2.1.1"
},
"engines": {
"node": ">=8"
......@@ -111,11 +228,29 @@
}
},
"node_modules/@ant-design/icons-svg": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz",
"integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==",
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.5.0.tgz",
"integrity": "sha512-1BTUFyKPTBZ53MuTP8s0k5SFEXL7o3VHEOwLgzaoWKwnBeqIcqUtVshc4SKzhI6uACfqhJqBwBUE9FsWR3uULA==",
"license": "MIT"
},
"node_modules/@ant-design/icons/node_modules/@ant-design/colors": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-8.0.1.tgz",
"integrity": "sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ==",
"license": "MIT",
"dependencies": {
"@ant-design/fast-color": "^3.0.0"
}
},
"node_modules/@ant-design/icons/node_modules/@ant-design/fast-color": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-3.0.1.tgz",
"integrity": "sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw==",
"license": "MIT",
"engines": {
"node": ">=8.x"
}
},
"node_modules/@ant-design/moment-webpack-plugin": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/@ant-design/moment-webpack-plugin/-/moment-webpack-plugin-0.0.3.tgz",
......@@ -2604,6 +2739,58 @@
"react": ">= 16 || ^19.0.0-rc"
}
},
"node_modules/@heroui-pro/react": {
"resolved": "../project_manager_new/node_modules/@heroui-pro/react",
"link": true
},
"node_modules/@heroui/react": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/@heroui/react/-/react-3.2.3.tgz",
"integrity": "sha512-F75tkYMXbDHkaNm0N5XLqdZvn4BG7OFkE7W9na1CtQMVlCxXxoKyRilp62O9JTMapxpkyGFq1EL2TG+PacAOyQ==",
"license": "MIT",
"dependencies": {
"@heroui/styles": "3.2.3",
"@radix-ui/react-avatar": "1.1.11",
"@react-stately/utils": "3.12.1",
"@react-types/color": "3.2.0",
"@react-types/shared": "3.36.0",
"input-otp": "1.4.2",
"tailwind-merge": "3.4.0",
"tailwind-variants": "3.3.0"
},
"peerDependencies": {
"@react-aria/i18n": "^3.13.1",
"@react-aria/ssr": "^3.10.1",
"@react-aria/utils": "^3.34.1",
"react": ">=19.0.0",
"react-aria": "^3.50.0",
"react-aria-components": "^1.19.0",
"react-dom": ">=19.0.0",
"tailwindcss": ">=4.0.0"
}
},
"node_modules/@heroui/react/node_modules/@react-types/shared": {
"version": "3.36.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.36.0.tgz",
"integrity": "sha512-DkP/H0C2YjjS7gZWKNqOmU8a16qHPjQNdzMwmTq9SzplM6Iw0kVMTZ0OIoe6FOgGqa+FwMsE2QbPjh/n3g/jXQ==",
"license": "Apache-2.0",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@heroui/styles": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/@heroui/styles/-/styles-3.2.3.tgz",
"integrity": "sha512-sD9y6fGric5bHhmzsQKyHwj/m8xnxD7uRCxNDnkor30Eo+cOFNqu1IcEOiSCt3qVHb/np8n03y7IX9hQgngnBA==",
"license": "MIT",
"dependencies": {
"tailwind-variants": "3.3.0",
"tw-animate-css": "1.4.0"
},
"peerDependencies": {
"tailwindcss": ">=4.0.0"
}
},
"node_modules/@hono/node-server": {
"version": "1.19.14",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz",
......@@ -2688,27 +2875,27 @@
}
},
"node_modules/@internationalized/date": {
"version": "3.12.1",
"resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.1.tgz",
"integrity": "sha512-6IedsVWXyq4P9Tj+TxuU8WGWM70hYLl12nbYU8jkikVpa6WXapFazPUcHUMDMoWftIDE2ILDkFFte6W2nFCkRQ==",
"version": "3.12.3",
"resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.3.tgz",
"integrity": "sha512-fuLX+3ZKLsxI73y8b01EG/WjHb6gE6weCqlfawPO27kBWGMh9G1yH6Csv1uU7/cac9H2GHmOMt6CjmuQ1aia4Q==",
"license": "Apache-2.0",
"dependencies": {
"@swc/helpers": "^0.5.0"
}
},
"node_modules/@internationalized/number": {
"version": "3.6.6",
"resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.6.tgz",
"integrity": "sha512-iFgmQaXHE0vytNfpLZWOC2mEJCBRzcUxt53Xf/yCXG93lRvqas237i3r7X4RKMwO3txiyZD4mQjKAByFv6UGSQ==",
"version": "3.6.7",
"resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.7.tgz",
"integrity": "sha512-3ji1fcrT+FPAK86UqEhB/psHixYo6niWPJtt7+qRaYFynt/BaJG8GhAPimtWUpEiVSTq8ZM8L5psMxGquiB/Vg==",
"license": "Apache-2.0",
"dependencies": {
"@swc/helpers": "^0.5.0"
}
},
"node_modules/@internationalized/string": {
"version": "3.2.8",
"resolved": "https://registry.npmjs.org/@internationalized/string/-/string-3.2.8.tgz",
"integrity": "sha512-NdbMQUSfXLYIQol5VyMtinm9pZDciiMfN7RtmSuSB78io1hqwJ0naYfxyW6vgxWBkzWymQa/3uLDlbfmshtCaA==",
"version": "3.2.10",
"resolved": "https://registry.npmjs.org/@internationalized/string/-/string-3.2.10.tgz",
"integrity": "sha512-PDx6//vHSpRnHfxqMqto11zQvhsaU74O3mKv2F/0eicGZcl9NLjQmGlbHz/LsJh5tLKp4A4L7ZVTzN1/MmMTvA==",
"license": "Apache-2.0",
"dependencies": {
"@swc/helpers": "^0.5.0"
......@@ -3550,6 +3737,161 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@radix-ui/react-avatar": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.11.tgz",
"integrity": "sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-context": "1.1.3",
"@radix-ui/react-primitive": "2.1.4",
"@radix-ui/react-use-callback-ref": "1.1.1",
"@radix-ui/react-use-is-hydrated": "0.1.0",
"@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
"integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-context": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz",
"integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-primitive": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz",
"integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-slot": "1.2.4"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-slot": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz",
"integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
"integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-is-hydrated": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz",
"integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==",
"license": "MIT",
"dependencies": {
"use-sync-external-store": "^1.5.0"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-is-hydrated/node_modules/use-sync-external-store": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@radix-ui/react-use-layout-effect": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
"integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@rc-component/portal": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-1.1.2.tgz",
......@@ -3568,6 +3910,40 @@
"react-dom": ">=16.9.0"
}
},
"node_modules/@rc-component/util": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/@rc-component/util/-/util-1.12.0.tgz",
"integrity": "sha512-AEjPL8JVdohIITaiXokyjL9WQ6tKWWjAYK9QU16tGNE9JaQABBQy+hA4H2Lup5MgXy9yY3iLrbZJheuU13hTdQ==",
"license": "MIT",
"dependencies": {
"is-mobile": "^5.0.0",
"react-is": "^19.2.7"
},
"peerDependencies": {
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
}
},
"node_modules/@rc-component/util/node_modules/react-is": {
"version": "19.2.8",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz",
"integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==",
"license": "MIT"
},
"node_modules/@react-aria/color": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/@react-aria/color/-/color-3.2.1.tgz",
"integrity": "sha512-v/pZh1yBNeayQ5Kio0xRIX0gBf6uu+KjCRW4QSydTyRpmhvk6sMzHT9i9/vIMdaDHaYHBXlXF+Tp/mvS9CG4Zw==",
"license": "Apache-2.0",
"dependencies": {
"@swc/helpers": "^0.5.0",
"react-aria": "^3.48.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-aria/focus": {
"version": "3.22.0",
"resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.22.0.tgz",
......@@ -3582,6 +3958,36 @@
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-aria/focus/node_modules/react-aria": {
"version": "3.48.0",
"resolved": "https://registry.npmjs.org/react-aria/-/react-aria-3.48.0.tgz",
"integrity": "sha512-jQjd4rBEIMqecBaAKYJbVGK6EqIHLa5znVQ7jwFyK5vCyljoj6KhgtiahmcIPsG5vG5vEDLw+ba+bEWn6A2P4w==",
"license": "Apache-2.0",
"dependencies": {
"@internationalized/date": "^3.12.1",
"@internationalized/number": "^3.6.6",
"@internationalized/string": "^3.2.8",
"@react-types/shared": "^3.34.0",
"@swc/helpers": "^0.5.0",
"aria-hidden": "^1.2.3",
"clsx": "^2.0.0",
"react-stately": "3.46.0",
"use-sync-external-store": "^1.6.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-aria/focus/node_modules/use-sync-external-store": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@react-aria/interactions": {
"version": "3.28.0",
"resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.28.0.tgz",
......@@ -3597,20 +4003,181 @@
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-types/shared": {
"version": "3.34.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.34.0.tgz",
"integrity": "sha512-gp6xo/s2lX54AlTjOiqwDnxA7UW79BNvI9dB9pr3LZTzRKCd1ZA+ZbgKw/ReIiWuvvVw/8QFJpnqeeFyLocMcQ==",
"node_modules/@react-aria/interactions/node_modules/react-aria": {
"version": "3.48.0",
"resolved": "https://registry.npmjs.org/react-aria/-/react-aria-3.48.0.tgz",
"integrity": "sha512-jQjd4rBEIMqecBaAKYJbVGK6EqIHLa5znVQ7jwFyK5vCyljoj6KhgtiahmcIPsG5vG5vEDLw+ba+bEWn6A2P4w==",
"license": "Apache-2.0",
"dependencies": {
"@internationalized/date": "^3.12.1",
"@internationalized/number": "^3.6.6",
"@internationalized/string": "^3.2.8",
"@react-types/shared": "^3.34.0",
"@swc/helpers": "^0.5.0",
"aria-hidden": "^1.2.3",
"clsx": "^2.0.0",
"react-stately": "3.46.0",
"use-sync-external-store": "^1.6.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@sinclair/typebox": {
"version": "0.27.10",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz",
"integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==",
"license": "MIT"
"node_modules/@react-aria/interactions/node_modules/use-sync-external-store": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@react-aria/ssr": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.10.1.tgz",
"integrity": "sha512-jn038/ZYmu6DpfXJ6r2U9zFFppjbc9wnApPJSCxao2RZVEqep4YyoniHSy8qv6V21/xyS4IV7W9a+X2jOjSuag==",
"license": "Apache-2.0",
"dependencies": {
"@swc/helpers": "^0.5.0",
"react-aria": "^3.48.0"
},
"engines": {
"node": ">= 12"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-aria/utils": {
"version": "3.34.1",
"resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.34.1.tgz",
"integrity": "sha512-H6+rGZL+0f58bBNaUMfctEnT+NogqwAk+nHiB8sR3K+YlQ37GTuCijy2U/pPvQtFMS5mURrjZeBH5JNNXsx14A==",
"license": "Apache-2.0",
"dependencies": {
"@swc/helpers": "^0.5.0",
"react-aria": "^3.48.0",
"react-stately": "^3.46.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-spectrum/color": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/@react-spectrum/color/-/color-3.2.1.tgz",
"integrity": "sha512-FbB+GaVufsN+obNJmUAe8XGZTeJjEWvEf/k/7nxDxoDCBdg/o6eEPONAZyID5Dj1f8BZKNym8VURxDZx7IIyuQ==",
"license": "Apache-2.0",
"dependencies": {
"@adobe/react-spectrum": "^3.47.0",
"@swc/helpers": "^0.5.0",
"react-stately": "^3.46.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-stately/color": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/@react-stately/color/-/color-3.10.1.tgz",
"integrity": "sha512-v4YlLa/oRQpPa/M9Yq1e0Bl8Z7xzGZdNDAIbfIMZuiJtacaOKriPW+AOgjjT/Ab0fNX2YDbwIYvkiMOJBnTi5A==",
"license": "Apache-2.0",
"dependencies": {
"@swc/helpers": "^0.5.0",
"react-stately": "^3.46.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-stately/utils": {
"version": "3.12.1",
"resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.12.1.tgz",
"integrity": "sha512-NqKfzrknpfwiewx7R2vk1P+CneClInPDsIhw15+jOcUYSEfej0nta4cJywuKQJ2gsPwqX/ojDNixedCve9FWGw==",
"license": "Apache-2.0",
"dependencies": {
"@swc/helpers": "^0.5.0",
"react-stately": "^3.46.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-types/color": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@react-types/color/-/color-3.2.0.tgz",
"integrity": "sha512-beV3vz80nzZ1EuYUM7296Kyi3AHcMrbQw0qub/9yzHWVTKKc5sy/e4dCMKcWL/ArkeAyc7jDOiui190RQ4l0Fw==",
"license": "Apache-2.0",
"dependencies": {
"@react-aria/color": "^3.2.0",
"@react-spectrum/color": "^3.2.0",
"@react-stately/color": "^3.10.0"
},
"peerDependencies": {
"@react-spectrum/provider": "^3.0.0",
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-types/shared": {
"version": "3.36.1",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.36.1.tgz",
"integrity": "sha512-AzsuD9OfxTOZMMvTRhlN3oHBwOmFN7tDh27LzqmHt4+uOgPhJT7ZM7/kVs/8/o0WxayMUIk3hBmCFRHv1FUoag==",
"license": "Apache-2.0",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@sinclair/typebox": {
"version": "0.27.10",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz",
"integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==",
"license": "MIT"
},
"node_modules/@spectrum-icons/ui": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/@spectrum-icons/ui/-/ui-3.7.1.tgz",
"integrity": "sha512-veQymocUYo5OciXQajSailOdbWe+k6+2ehfF8D4d0V923D4xOUadtT253xXZ5vEQjPat6Kyp2WDKeQNjd7kL1w==",
"license": "Apache-2.0",
"dependencies": {
"@adobe/react-spectrum-ui": "1.2.1",
"@babel/runtime": "^7.24.4",
"@swc/helpers": "^0.5.0"
},
"peerDependencies": {
"@adobe/react-spectrum": "^3.47.0",
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@spectrum-icons/ui/node_modules/@babel/runtime": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz",
"integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@spectrum-icons/workflow": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/@spectrum-icons/workflow/-/workflow-4.3.1.tgz",
"integrity": "sha512-kDF+/EbFVyLGytotqqdYt4uSij4j/PQmDQO5km/C6DyzKjyuic3FnSBFinR+mA6oFv1OjMcLvrrDBqK3wbqRlA==",
"license": "Apache-2.0",
"dependencies": {
"@adobe/react-spectrum-workflow": "2.3.5",
"@swc/helpers": "^0.5.0"
},
"peerDependencies": {
"@adobe/react-spectrum": "^3.47.0",
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@stagewise/toolbar": {
"version": "0.6.2",
......@@ -4250,35 +4817,6 @@
"@babel/types": "^7.28.2"
}
},
"node_modules/@types/eslint": {
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
"integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/estree": "*",
"@types/json-schema": "*"
}
},
"node_modules/@types/eslint-scope": {
"version": "3.7.7",
"resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz",
"integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/eslint": "*",
"@types/estree": "*"
}
},
"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==",
"license": "MIT",
"peer": true
},
"node_modules/@types/graceful-fs": {
"version": "4.1.9",
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
......@@ -4294,13 +4832,6 @@
"integrity": "sha512-oOMFT8vmCTFncsF1engrs04jatz8/Anwx3De9uxnOK4chgSEgWBvFtpSoJo8u3784JNO+ql5tzRR6phHoRnscQ==",
"license": "MIT"
},
"node_modules/@types/history": {
"version": "4.7.11",
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz",
"integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==",
"license": "MIT",
"peer": true
},
"node_modules/@types/hoist-non-react-statics": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.7.tgz",
......@@ -4325,13 +4856,6 @@
"integrity": "sha512-IwpIMieE55oGWiXkQPSBY1nw1nFs6bsKXTFskNY8sdS17K24vyEBRQZEwlRS7ZmXCWnJcQtbxWzly+cODWGs2A==",
"license": "MIT"
},
"node_modules/@types/isomorphic-fetch": {
"version": "0.0.34",
"resolved": "https://registry.npmjs.org/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.34.tgz",
"integrity": "sha512-BmJKuPCZCR6pbYYgi5nKFJrPC4pLoBgsi/B1nFN64Ba+hLLGUcKPIh7eVlR2xG763Ap08hgQafq/Wx4RFb0omQ==",
"license": "MIT",
"peer": true
},
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
......@@ -4389,66 +4913,24 @@
"integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==",
"license": "MIT"
},
"node_modules/@types/prop-types": {
"version": "15.7.15",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
"license": "MIT"
},
"node_modules/@types/react": {
"version": "18.3.28",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
"integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
"version": "19.2.18",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz",
"integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.2.2"
}
},
"node_modules/@types/react-dom": {
"version": "18.3.7",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
"devOptional": true,
"version": "19.2.4",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz",
"integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"@types/react": "^18.0.0"
}
},
"node_modules/@types/react-router": {
"version": "5.1.20",
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz",
"integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/history": "^4.7.11",
"@types/react": "*"
}
},
"node_modules/@types/react-router-dom": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-4.3.5.tgz",
"integrity": "sha512-eFajSUASYbPHg2BDM1G8Btx+YqGgvROPIg6sBhl3O4kbDdYXdFdfrgQFf/pcBuQVObjfT9AL/dd15jilR5DIEA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/history": "*",
"@types/react": "*",
"@types/react-router": "*"
}
},
"node_modules/@types/react-router-redux": {
"version": "5.0.27",
"resolved": "https://registry.npmjs.org/@types/react-router-redux/-/react-router-redux-5.0.27.tgz",
"integrity": "sha512-qC5lbuP2K/kMR/HE3e5ltCJptyiQhmfV0wbklqcqWDbNdpJBDwUsBGP4f/0RDYJf09+OTbz43u6iG+8E0Zcwqw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/history": "^4.7.11",
"@types/react": "*",
"@types/react-router": "^5.1.0",
"redux": ">= 3.7.2"
"@types/react": "^19.2.0"
}
},
"node_modules/@types/resolve": {
......@@ -6167,6 +6649,36 @@
"react-dom": ">=16.0.0"
}
},
"node_modules/@umijs/plugins/node_modules/@ant-design/icons": {
"version": "4.8.3",
"resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-4.8.3.tgz",
"integrity": "sha512-HGlIQZzrEbAhpJR6+IGdzfbPym94Owr6JZkJ2QCCnOkPVIWMO2xgIVcOKnl8YcpijIo39V7l2qQL5fmtw56cMw==",
"license": "MIT",
"dependencies": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons-svg": "^4.3.0",
"@babel/runtime": "^7.11.2",
"classnames": "^2.2.6",
"lodash": "^4.17.15",
"rc-util": "^5.9.4"
},
"engines": {
"node": ">=8"
},
"peerDependencies": {
"react": ">=16.0.0",
"react-dom": ">=16.0.0"
}
},
"node_modules/@umijs/plugins/node_modules/@ant-design/icons/node_modules/@ant-design/colors": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-6.0.0.tgz",
"integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==",
"license": "MIT",
"dependencies": {
"@ctrl/tinycolor": "^3.4.0"
}
},
"node_modules/@umijs/plugins/node_modules/@ant-design/pro-components": {
"version": "2.8.10",
"resolved": "https://registry.npmjs.org/@ant-design/pro-components/-/pro-components-2.8.10.tgz",
......@@ -6472,32 +6984,6 @@
"styled-components": ">= 2"
}
},
"node_modules/@umijs/plugins/node_modules/dva": {
"version": "2.5.0-beta.2",
"resolved": "https://registry.npmjs.org/dva/-/dva-2.5.0-beta.2.tgz",
"integrity": "sha512-kc2+CHhF1cNIU3Rg1miMhHgOKJ/VDrq9d6ynVBZf1EN2YKWU3MVFq/uTTBqMr2qkR0m9f8VKHOFmfKLtfMI93Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.0.0",
"@types/isomorphic-fetch": "^0.0.34",
"@types/react-router-dom": "^4.2.7",
"@types/react-router-redux": "^5.0.13",
"dva-core": "^1.5.0-beta.2",
"global": "^4.3.2",
"history": "^4.6.3",
"invariant": "^2.2.2",
"isomorphic-fetch": "^2.2.1",
"react-redux": "^5.0.5",
"react-router-dom": "^4.1.2",
"react-router-redux": "5.0.0-alpha.9",
"redux": "^3.7.2"
},
"peerDependencies": {
"react": "15.x || ^16.0.0-0",
"react-dom": "15.x || ^16.0.0-0"
}
},
"node_modules/@umijs/plugins/node_modules/dva-core": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/dva-core/-/dva-core-2.0.4.tgz",
......@@ -6538,114 +7024,6 @@
"dva": "^2.5.0-0"
}
},
"node_modules/@umijs/plugins/node_modules/dva/node_modules/dva-core": {
"version": "1.5.0-beta.2",
"resolved": "https://registry.npmjs.org/dva-core/-/dva-core-1.5.0-beta.2.tgz",
"integrity": "sha512-xmtr/J63EZXBdVXNBW+QCD7p9CaE8kAo2U1faRyv3PIGcy0G3Y6IBDNtoBB/Cj3nzk/jvX0dv96Hnh1kpSnI7Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.0.0",
"flatten": "^1.0.2",
"global": "^4.3.2",
"invariant": "^2.2.1",
"is-plain-object": "^2.0.3",
"redux": "^3.7.1",
"redux-saga": "^0.16.0",
"warning": "^3.0.0"
},
"peerDependencies": {
"redux": "3.x"
}
},
"node_modules/@umijs/plugins/node_modules/dva/node_modules/react-redux": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.1.2.tgz",
"integrity": "sha512-Ns1G0XXc8hDyH/OcBHOxNgQx9ayH3SPxBnFCOidGKSle8pKihysQw2rG/PmciUQRoclhVBO8HMhiRmGXnDja9Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.1.2",
"hoist-non-react-statics": "^3.3.0",
"invariant": "^2.2.4",
"loose-envify": "^1.1.0",
"prop-types": "^15.6.1",
"react-is": "^16.6.0",
"react-lifecycles-compat": "^3.0.0"
},
"peerDependencies": {
"react": "^0.14.0 || ^15.0.0-0 || ^16.0.0-0",
"redux": "^2.0.0 || ^3.0.0 || ^4.0.0-0"
}
},
"node_modules/@umijs/plugins/node_modules/dva/node_modules/redux": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz",
"integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==",
"license": "MIT",
"peer": true,
"dependencies": {
"lodash": "^4.2.1",
"lodash-es": "^4.2.1",
"loose-envify": "^1.1.0",
"symbol-observable": "^1.0.3"
}
},
"node_modules/@umijs/plugins/node_modules/dva/node_modules/warning": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
"integrity": "sha512-jMBt6pUrKn5I+OGgtQ4YZLdhIeJmObddh6CsibPxyQ5yPZm1XExSyzC1LCNX7BzhxWgiHmizBWJTHJIjMjTQYQ==",
"license": "BSD-3-Clause",
"peer": true,
"dependencies": {
"loose-envify": "^1.0.0"
}
},
"node_modules/@umijs/plugins/node_modules/history": {
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
"integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.1.2",
"loose-envify": "^1.2.0",
"resolve-pathname": "^3.0.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0",
"value-equal": "^1.0.1"
}
},
"node_modules/@umijs/plugins/node_modules/react": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/@umijs/plugins/node_modules/react-dom": {
"version": "16.14.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz",
"integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"scheduler": "^0.19.1"
},
"peerDependencies": {
"react": "^16.14.0"
}
},
"node_modules/@umijs/plugins/node_modules/react-intl": {
"version": "3.12.1",
"resolved": "https://registry.npmjs.org/react-intl/-/react-intl-3.12.1.tgz",
......@@ -6714,61 +7092,6 @@
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"license": "MIT"
},
"node_modules/@umijs/plugins/node_modules/react-router": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz",
"integrity": "sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==",
"license": "MIT",
"peer": true,
"dependencies": {
"history": "^4.7.2",
"hoist-non-react-statics": "^2.5.0",
"invariant": "^2.2.4",
"loose-envify": "^1.3.1",
"path-to-regexp": "^1.7.0",
"prop-types": "^15.6.1",
"warning": "^4.0.1"
},
"peerDependencies": {
"react": ">=15"
}
},
"node_modules/@umijs/plugins/node_modules/react-router-dom": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.3.1.tgz",
"integrity": "sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA==",
"license": "MIT",
"peer": true,
"dependencies": {
"history": "^4.7.2",
"invariant": "^2.2.4",
"loose-envify": "^1.3.1",
"prop-types": "^15.6.1",
"react-router": "^4.3.1",
"warning": "^4.0.1"
},
"peerDependencies": {
"react": ">=15"
}
},
"node_modules/@umijs/plugins/node_modules/react-router/node_modules/hoist-non-react-statics": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
"integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==",
"license": "BSD-3-Clause",
"peer": true
},
"node_modules/@umijs/plugins/node_modules/scheduler": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
"integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
}
},
"node_modules/@umijs/plugins/node_modules/styled-components": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.1.tgz",
......@@ -6888,12 +7211,46 @@
"node": ">=8.6.0"
}
},
"node_modules/@umijs/preset-umi/node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/@umijs/preset-umi/node_modules/react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
},
"peerDependencies": {
"react": "^18.3.1"
}
},
"node_modules/@umijs/preset-umi/node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
"license": "MIT"
},
"node_modules/@umijs/preset-umi/node_modules/scheduler": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
}
},
"node_modules/@umijs/react-refresh-webpack-plugin": {
"version": "0.5.11",
"resolved": "https://registry.npmjs.org/@umijs/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.11.tgz",
......@@ -6975,23 +7332,57 @@
"react-dom": ">=16.8"
}
},
"node_modules/@umijs/route-utils": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@umijs/route-utils/-/route-utils-4.0.3.tgz",
"integrity": "sha512-zPEcYhl1cSfkSRDzzGgoD1mDvGjxoOTJFvkn55srfgdQ3NZe2ZMCScCU6DEnOxuKP1XDVf8pqyqCDVd2+RCQIw==",
"license": "MIT"
},
"node_modules/@umijs/server": {
"version": "4.6.51",
"resolved": "https://registry.npmjs.org/@umijs/server/-/server-4.6.51.tgz",
"integrity": "sha512-kQMIIKG2dWpbREcZO1pECsOFxE4ARnDyQ/fDLyKXXJqgIxeow5DJDgIv78o/PdU6bwoLEWxgw6IkKz8IqHYhZQ==",
"node_modules/@umijs/route-utils": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@umijs/route-utils/-/route-utils-4.0.3.tgz",
"integrity": "sha512-zPEcYhl1cSfkSRDzzGgoD1mDvGjxoOTJFvkn55srfgdQ3NZe2ZMCScCU6DEnOxuKP1XDVf8pqyqCDVd2+RCQIw==",
"license": "MIT"
},
"node_modules/@umijs/server": {
"version": "4.6.51",
"resolved": "https://registry.npmjs.org/@umijs/server/-/server-4.6.51.tgz",
"integrity": "sha512-kQMIIKG2dWpbREcZO1pECsOFxE4ARnDyQ/fDLyKXXJqgIxeow5DJDgIv78o/PdU6bwoLEWxgw6IkKz8IqHYhZQ==",
"license": "MIT",
"dependencies": {
"@umijs/bundler-utils": "4.6.51",
"history": "5.3.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-router-dom": "6.3.0"
}
},
"node_modules/@umijs/server/node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/@umijs/server/node_modules/react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
},
"peerDependencies": {
"react": "^18.3.1"
}
},
"node_modules/@umijs/server/node_modules/scheduler": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"license": "MIT",
"dependencies": {
"@umijs/bundler-utils": "4.6.51",
"history": "5.3.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-router-dom": "6.3.0"
"loose-envify": "^1.1.0"
}
},
"node_modules/@umijs/test": {
......@@ -7217,181 +7608,6 @@
"vite": "^4.2.0"
}
},
"node_modules/@webassemblyjs/ast": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz",
"integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@webassemblyjs/helper-numbers": "1.13.2",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2"
}
},
"node_modules/@webassemblyjs/floating-point-hex-parser": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz",
"integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==",
"license": "MIT",
"peer": true
},
"node_modules/@webassemblyjs/helper-api-error": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz",
"integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==",
"license": "MIT",
"peer": true
},
"node_modules/@webassemblyjs/helper-buffer": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz",
"integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==",
"license": "MIT",
"peer": true
},
"node_modules/@webassemblyjs/helper-numbers": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz",
"integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@webassemblyjs/floating-point-hex-parser": "1.13.2",
"@webassemblyjs/helper-api-error": "1.13.2",
"@xtuc/long": "4.2.2"
}
},
"node_modules/@webassemblyjs/helper-wasm-bytecode": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz",
"integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==",
"license": "MIT",
"peer": true
},
"node_modules/@webassemblyjs/helper-wasm-section": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz",
"integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
"@webassemblyjs/wasm-gen": "1.14.1"
}
},
"node_modules/@webassemblyjs/ieee754": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz",
"integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@xtuc/ieee754": "^1.2.0"
}
},
"node_modules/@webassemblyjs/leb128": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz",
"integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@xtuc/long": "4.2.2"
}
},
"node_modules/@webassemblyjs/utf8": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz",
"integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==",
"license": "MIT",
"peer": true
},
"node_modules/@webassemblyjs/wasm-edit": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz",
"integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
"@webassemblyjs/helper-wasm-section": "1.14.1",
"@webassemblyjs/wasm-gen": "1.14.1",
"@webassemblyjs/wasm-opt": "1.14.1",
"@webassemblyjs/wasm-parser": "1.14.1",
"@webassemblyjs/wast-printer": "1.14.1"
}
},
"node_modules/@webassemblyjs/wasm-gen": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz",
"integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
"@webassemblyjs/ieee754": "1.13.2",
"@webassemblyjs/leb128": "1.13.2",
"@webassemblyjs/utf8": "1.13.2"
}
},
"node_modules/@webassemblyjs/wasm-opt": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz",
"integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
"@webassemblyjs/wasm-gen": "1.14.1",
"@webassemblyjs/wasm-parser": "1.14.1"
}
},
"node_modules/@webassemblyjs/wasm-parser": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz",
"integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-api-error": "1.13.2",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
"@webassemblyjs/ieee754": "1.13.2",
"@webassemblyjs/leb128": "1.13.2",
"@webassemblyjs/utf8": "1.13.2"
}
},
"node_modules/@webassemblyjs/wast-printer": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz",
"integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@xtuc/long": "4.2.2"
}
},
"node_modules/@xtuc/ieee754": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
"integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
"license": "BSD-3-Clause",
"peer": true
},
"node_modules/@xtuc/long": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
"license": "Apache-2.0",
"peer": true
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
......@@ -7426,19 +7642,6 @@
"node": ">=0.4.0"
}
},
"node_modules/acorn-import-phases": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz",
"integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10.13.0"
},
"peerDependencies": {
"acorn": "^8.14.0"
}
},
"node_modules/acorn-jsx": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
......@@ -7509,48 +7712,6 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ajv-formats": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
"license": "MIT",
"peer": true,
"dependencies": {
"ajv": "^8.0.0"
},
"peerDependencies": {
"ajv": "^8.0.0"
},
"peerDependenciesMeta": {
"ajv": {
"optional": true
}
}
},
"node_modules/ajv-formats/node_modules/ajv": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ajv-formats/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"license": "MIT",
"peer": true
},
"node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
......@@ -7664,6 +7825,27 @@
"dayjs": "*"
}
},
"node_modules/antd/node_modules/@ant-design/icons": {
"version": "4.8.3",
"resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-4.8.3.tgz",
"integrity": "sha512-HGlIQZzrEbAhpJR6+IGdzfbPym94Owr6JZkJ2QCCnOkPVIWMO2xgIVcOKnl8YcpijIo39V7l2qQL5fmtw56cMw==",
"license": "MIT",
"dependencies": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons-svg": "^4.3.0",
"@babel/runtime": "^7.11.2",
"classnames": "^2.2.6",
"lodash": "^4.17.15",
"rc-util": "^5.9.4"
},
"engines": {
"node": ">=8"
},
"peerDependencies": {
"react": ">=16.0.0",
"react-dom": ">=16.0.0"
}
},
"node_modules/anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
......@@ -8755,16 +8937,6 @@
"fsevents": "~2.3.2"
}
},
"node_modules/chrome-trace-event": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
"integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6.0"
}
},
"node_modules/ci-info": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
......@@ -8839,8 +9011,7 @@
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/cliui": {
"version": "8.0.1",
......@@ -9952,6 +10123,16 @@
"utila": "~0.4"
}
},
"node_modules/dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.8.7",
"csstype": "^3.0.2"
}
},
"node_modules/dom-serializer": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
......@@ -10229,35 +10410,6 @@
"stream-shift": "^1.0.2"
}
},
"node_modules/dva-core": {
"version": "1.6.0-beta.7",
"resolved": "https://registry.npmjs.org/dva-core/-/dva-core-1.6.0-beta.7.tgz",
"integrity": "sha512-e+0yOEWUK+XbnqflX+KXoLZDGxn+kLKgcT6XYT8GyRe0xcbmLEbwDZO0DXUkdsbxfqSOlLkIprMUoYJ3D5B4Gg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.0.0",
"flatten": "^1.0.2",
"global": "^4.3.2",
"invariant": "^2.2.1",
"is-plain-object": "^2.0.3",
"redux-saga": "^0.16.0",
"warning": "^3.0.0"
},
"peerDependencies": {
"redux": "4.x"
}
},
"node_modules/dva-core/node_modules/warning": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
"integrity": "sha512-jMBt6pUrKn5I+OGgtQ4YZLdhIeJmObddh6CsibPxyQ5yPZm1XExSyzC1LCNX7BzhxWgiHmizBWJTHJIjMjTQYQ==",
"license": "BSD-3-Clause",
"peer": true,
"dependencies": {
"loose-envify": "^1.0.0"
}
},
"node_modules/dva-loading": {
"version": "3.0.25",
"resolved": "https://registry.npmjs.org/dva-loading/-/dva-loading-3.0.25.tgz",
......@@ -10591,13 +10743,6 @@
"node": ">= 0.4"
}
},
"node_modules/es-module-lexer": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz",
"integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==",
"license": "MIT",
"peer": true
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
......@@ -11924,13 +12069,6 @@
"node": ">= 6"
}
},
"node_modules/glob-to-regexp": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
"license": "BSD-2-Clause",
"peer": true
},
"node_modules/global": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz",
......@@ -12638,6 +12776,16 @@
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"license": "ISC"
},
"node_modules/input-otp": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.4.2.tgz",
"integrity": "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc"
}
},
"node_modules/internal-slot": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
......@@ -13035,6 +13183,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-mobile": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-5.0.0.tgz",
"integrity": "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==",
"license": "MIT"
},
"node_modules/is-negative-zero": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
......@@ -16266,22 +16420,6 @@
"node": ">= 0.8.0"
}
},
"node_modules/prettier": {
"version": "3.8.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz",
"integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==",
"license": "MIT",
"peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prettier-plugin-organize-imports": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.4.tgz",
......@@ -17275,31 +17413,28 @@
}
},
"node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"version": "19.2.8",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
"integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-aria": {
"version": "3.48.0",
"resolved": "https://registry.npmjs.org/react-aria/-/react-aria-3.48.0.tgz",
"integrity": "sha512-jQjd4rBEIMqecBaAKYJbVGK6EqIHLa5znVQ7jwFyK5vCyljoj6KhgtiahmcIPsG5vG5vEDLw+ba+bEWn6A2P4w==",
"version": "3.51.0",
"resolved": "https://registry.npmjs.org/react-aria/-/react-aria-3.51.0.tgz",
"integrity": "sha512-AyWLw0XR38cFPwBu/ErgGaVrc5dupLEKmRlMXTGvFKOtbaGRQ2+yQJkjVhpdHhoRhU4+G+tJDFeHDTS8tK3bfQ==",
"license": "Apache-2.0",
"dependencies": {
"@internationalized/date": "^3.12.1",
"@internationalized/number": "^3.6.6",
"@internationalized/string": "^3.2.8",
"@react-types/shared": "^3.34.0",
"@internationalized/date": "^3.12.3",
"@internationalized/number": "^3.6.7",
"@internationalized/string": "^3.2.10",
"@react-types/shared": "^3.36.1",
"@swc/helpers": "^0.5.0",
"aria-hidden": "^1.2.3",
"clsx": "^2.0.0",
"react-stately": "3.46.0",
"react-stately": "3.49.0",
"use-sync-external-store": "^1.6.0"
},
"peerDependencies": {
......@@ -17307,7 +17442,43 @@
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/react-aria/node_modules/use-sync-external-store": {
"node_modules/react-aria-components": {
"version": "1.20.0",
"resolved": "https://registry.npmjs.org/react-aria-components/-/react-aria-components-1.20.0.tgz",
"integrity": "sha512-BMbpIgoV9aELeBrB0Y120NgoigHb5OdcJwc+4e7uSnbTbamea6lo+gqcc4LAxzMaK3Jf+7LI1oCDE6yANsmxIQ==",
"license": "Apache-2.0",
"dependencies": {
"@internationalized/date": "^3.12.3",
"@internationalized/string": "^3.2.10",
"@react-types/shared": "^3.36.1",
"@swc/helpers": "^0.5.0",
"client-only": "^0.0.1",
"react-aria": "3.51.0",
"react-stately": "3.49.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/react-aria-components/node_modules/react-stately": {
"version": "3.49.0",
"resolved": "https://registry.npmjs.org/react-stately/-/react-stately-3.49.0.tgz",
"integrity": "sha512-13iNq2KzBrRAzxRc+n53hgROfIistiYY/sPtIhCw1qUB7/kmo+X1xEU2uiS5zcCIrc55AUPwoHqOIIpKWSwB9A==",
"license": "Apache-2.0",
"dependencies": {
"@internationalized/date": "^3.12.3",
"@internationalized/number": "^3.6.7",
"@internationalized/string": "^3.2.10",
"@react-types/shared": "^3.36.1",
"@swc/helpers": "^0.5.0",
"use-sync-external-store": "^1.6.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/react-aria-components/node_modules/use-sync-external-store": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
......@@ -17316,17 +17487,42 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"node_modules/react-aria/node_modules/react-stately": {
"version": "3.49.0",
"resolved": "https://registry.npmjs.org/react-stately/-/react-stately-3.49.0.tgz",
"integrity": "sha512-13iNq2KzBrRAzxRc+n53hgROfIistiYY/sPtIhCw1qUB7/kmo+X1xEU2uiS5zcCIrc55AUPwoHqOIIpKWSwB9A==",
"license": "Apache-2.0",
"dependencies": {
"@internationalized/date": "^3.12.3",
"@internationalized/number": "^3.6.7",
"@internationalized/string": "^3.2.10",
"@react-types/shared": "^3.36.1",
"@swc/helpers": "^0.5.0",
"use-sync-external-store": "^1.6.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/react-aria/node_modules/use-sync-external-store": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/react-dom": {
"version": "19.2.8",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
"integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
"scheduler": "^0.27.0"
},
"peerDependencies": {
"react": "^18.3.1"
"react": "^19.2.8"
}
},
"node_modules/react-error-overlay": {
......@@ -17415,63 +17611,6 @@
"react-dom": ">=16.8"
}
},
"node_modules/react-router-redux": {
"version": "5.0.0-alpha.9",
"resolved": "https://registry.npmjs.org/react-router-redux/-/react-router-redux-5.0.0-alpha.9.tgz",
"integrity": "sha512-euSgNIANnRXr4GydIuwA7RZCefrLQzIw5WdXspS8NPYbV+FxrKSS9MKG7U9vb6vsKHONnA4VxrVNWfnMUnUQAw==",
"deprecated": "This project is no longer maintained.",
"license": "MIT",
"peer": true,
"dependencies": {
"history": "^4.7.2",
"prop-types": "^15.6.0",
"react-router": "^4.2.0"
},
"peerDependencies": {
"react": ">=15"
}
},
"node_modules/react-router-redux/node_modules/history": {
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
"integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.1.2",
"loose-envify": "^1.2.0",
"resolve-pathname": "^3.0.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0",
"value-equal": "^1.0.1"
}
},
"node_modules/react-router-redux/node_modules/hoist-non-react-statics": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
"integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==",
"license": "BSD-3-Clause",
"peer": true
},
"node_modules/react-router-redux/node_modules/react-router": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz",
"integrity": "sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==",
"license": "MIT",
"peer": true,
"dependencies": {
"history": "^4.7.2",
"hoist-non-react-statics": "^2.5.0",
"invariant": "^2.2.4",
"loose-envify": "^1.3.1",
"path-to-regexp": "^1.7.0",
"prop-types": "^15.6.1",
"warning": "^4.0.1"
},
"peerDependencies": {
"react": ">=15"
}
},
"node_modules/react-stately": {
"version": "3.46.0",
"resolved": "https://registry.npmjs.org/react-stately/-/react-stately-3.46.0.tgz",
......@@ -17498,6 +17637,22 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
"license": "BSD-3-Clause",
"dependencies": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2"
},
"peerDependencies": {
"react": ">=16.6.0",
"react-dom": ">=16.6.0"
}
},
"node_modules/reactcss": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz",
......@@ -17888,13 +18043,6 @@
"node": ">=4"
}
},
"node_modules/resolve-pathname": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
"integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==",
"license": "MIT",
"peer": true
},
"node_modules/resolve-pkg-maps": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
......@@ -18277,13 +18425,10 @@
}
},
"node_modules/scheduler": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
}
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
"license": "MIT"
},
"node_modules/schema-utils": {
"version": "3.3.0",
......@@ -19172,30 +19317,6 @@
"integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==",
"license": "ISC"
},
"node_modules/styled-jsx": {
"version": "5.1.7",
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.7.tgz",
"integrity": "sha512-HPLmEIYprxCeWDMLYiaaAhsV3yGfIlCqzuVOybE6fjF3SUJmH67nCoMDO+nAvHNHo46OfvpCNu4Rcue82dMNFg==",
"license": "MIT",
"peer": true,
"dependencies": {
"client-only": "0.0.1"
},
"engines": {
"node": ">= 12.0.0"
},
"peerDependencies": {
"react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
},
"peerDependenciesMeta": {
"@babel/core": {
"optional": true
},
"babel-plugin-macros": {
"optional": true
}
}
},
"node_modules/stylelint": {
"version": "14.8.2",
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.8.2.tgz",
......@@ -19445,16 +19566,6 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/symbol-observable": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/synckit": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz",
......@@ -19521,6 +19632,38 @@
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"license": "MIT"
},
"node_modules/tailwind-merge": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz",
"integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/dcastil"
}
},
"node_modules/tailwind-variants": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-3.3.0.tgz",
"integrity": "sha512-t1QsB42dcwUdaCEArO0tRZcP0nbCcAmJKRYCs7jmwYSFOCXlGqgO8c0TrCYm9OranYmT6i9YOi8LFo2gxmxnow==",
"license": "MIT",
"engines": {
"node": ">=16.9.x",
"pnpm": ">=7.x"
},
"peerDependencies": {
"tailwind-merge": ">=3.0.0",
"tailwindcss": "*"
},
"peerDependenciesMeta": {
"tailwind-merge": {
"optional": true
},
"tailwindcss": {
"optional": true
}
}
},
"node_modules/tailwindcss": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.4.tgz",
......@@ -19558,128 +19701,6 @@
"node": ">=10"
}
},
"node_modules/terser-webpack-plugin": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.5.0.tgz",
"integrity": "sha512-UYhptBwhWvfIjKd/UuFo6D8uq9xpGLDK+z8EDsj/zWhrTaH34cKEbrkMKfV5YWqGBvAYA3tlzZbs2R+qYrbQJA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.25",
"jest-worker": "^27.4.5",
"schema-utils": "^4.3.0",
"terser": "^5.31.1"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"webpack": "^5.1.0"
},
"peerDependenciesMeta": {
"@swc/core": {
"optional": true
},
"esbuild": {
"optional": true
},
"uglify-js": {
"optional": true
}
}
},
"node_modules/terser-webpack-plugin/node_modules/ajv": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/terser-webpack-plugin/node_modules/ajv-keywords": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
"integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3"
},
"peerDependencies": {
"ajv": "^8.8.2"
}
},
"node_modules/terser-webpack-plugin/node_modules/jest-worker": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
"integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/node": "*",
"merge-stream": "^2.0.0",
"supports-color": "^8.0.0"
},
"engines": {
"node": ">= 10.13.0"
}
},
"node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"license": "MIT",
"peer": true
},
"node_modules/terser-webpack-plugin/node_modules/schema-utils": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
"integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/json-schema": "^7.0.9",
"ajv": "^8.9.0",
"ajv-formats": "^2.1.1",
"ajv-keywords": "^5.1.0"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/terser-webpack-plugin/node_modules/supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
"node_modules/terser/node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
......@@ -19736,20 +19757,6 @@
"node": ">=0.6.0"
}
},
"node_modules/tiny-invariant": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
"license": "MIT",
"peer": true
},
"node_modules/tiny-warning": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==",
"license": "MIT",
"peer": true
},
"node_modules/tinycolor2": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz",
......@@ -19889,6 +19896,15 @@
"integrity": "sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw==",
"license": "MIT"
},
"node_modules/tw-animate-css": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz",
"integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/Wombosvideo"
}
},
"node_modules/type": {
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz",
......@@ -20010,6 +20026,7 @@
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
......@@ -20310,13 +20327,6 @@
}
}
},
"node_modules/value-equal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
"integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==",
"license": "MIT",
"peer": true
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
......@@ -20794,20 +20804,6 @@
"loose-envify": "^1.0.0"
}
},
"node_modules/watchpack": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz",
"integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==",
"license": "MIT",
"peer": true,
"dependencies": {
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.1.2"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/wbuf": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz",
......@@ -20826,54 +20822,6 @@
"node": ">= 8"
}
},
"node_modules/webpack": {
"version": "5.106.2",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.106.2.tgz",
"integrity": "sha512-wGN3qcrBQIFmQ/c0AiOAQBvrZ5lmY8vbbMv4Mxfgzqd/B6+9pXtLo73WuS1dSGXM5QYY3hZnIbvx+K1xxe6FyA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.8",
"@types/json-schema": "^7.0.15",
"@webassemblyjs/ast": "^1.14.1",
"@webassemblyjs/wasm-edit": "^1.14.1",
"@webassemblyjs/wasm-parser": "^1.14.1",
"acorn": "^8.16.0",
"acorn-import-phases": "^1.0.3",
"browserslist": "^4.28.1",
"chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^5.20.0",
"es-module-lexer": "^2.0.0",
"eslint-scope": "5.1.1",
"events": "^3.2.0",
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.2.11",
"loader-runner": "^4.3.1",
"mime-db": "^1.54.0",
"neo-async": "^2.6.2",
"schema-utils": "^4.3.3",
"tapable": "^2.3.0",
"terser-webpack-plugin": "^5.3.17",
"watchpack": "^2.5.1",
"webpack-sources": "^3.3.4"
},
"bin": {
"webpack": "bin/webpack.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependenciesMeta": {
"webpack-cli": {
"optional": true
}
}
},
"node_modules/webpack-5-chain": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/webpack-5-chain/-/webpack-5-chain-8.0.1.tgz",
......@@ -20896,83 +20844,6 @@
"node": ">=0.10.0"
}
},
"node_modules/webpack-sources": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.4.1.tgz",
"integrity": "sha512-eACpxRN02yaawnt+uUNIF7Qje6A9zArxBbcAJjK1PK3S9Ycg5jIuJ8pW4q8EMnwNZCEGltcjkRx1QzOxOkKD8A==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/webpack/node_modules/ajv": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/webpack/node_modules/ajv-keywords": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
"integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3"
},
"peerDependencies": {
"ajv": "^8.8.2"
}
},
"node_modules/webpack/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"license": "MIT",
"peer": true
},
"node_modules/webpack/node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.6"
}
},
"node_modules/webpack/node_modules/schema-utils": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
"integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/json-schema": "^7.0.9",
"ajv": "^8.9.0",
"ajv-formats": "^2.1.1",
"ajv-keywords": "^5.1.0"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/whatwg-fetch": {
"version": "3.6.20",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz",
......
......@@ -8,8 +8,14 @@
"start": "npm run dev"
},
"dependencies": {
"@ant-design/icons": "^6.3.2",
"@headlessui/react": "^2.2.10",
"@heroicons/react": "^2.2.0",
"@heroui-pro/react": "file:../project_manager_new/node_modules/@heroui-pro/react",
"@heroui/react": "^3.2.3",
"@heroui/styles": "^3.2.3",
"@react-aria/ssr": "^3.10.1",
"@react-aria/utils": "^3.34.1",
"@tailwindcss/postcss": "^4.2.0",
"@umijs/max": "^4.5.3",
"clsx": "^2.1.1",
......@@ -17,16 +23,18 @@
"dayjs": "^1.11.20",
"echarts": "^6.1.0",
"echarts-for-react": "^3.0.6",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react": "^19.2.8",
"react-aria": "^3.51.0",
"react-aria-components": "^1.20.0",
"react-dom": "^19.2.8",
"tailwindcss": "^4.2.0",
"umi-request": "^1.4.0",
"umi-request-progress": "^1.1.0",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@types/react": "^18.0.33",
"@types/react-dom": "^18.0.11",
"@types/react": "^19.2.18",
"@types/react-dom": "^19.2.4",
"typescript": "^5.0.3"
}
}
import '@/global.less';
import '@/tailwind.css';
import '@heroui/styles';
import '@heroui-pro/react/css';
import '@/design-system/tokens.css';
import '@/design-system/shell.css';
import '@/xinyuan-portable.css';
import '@/global.less';
import { defineApp } from 'umi';
export async function getInitialState() {
......
import * as Headless from '@headlessui/react'
import clsx from 'clsx'
import React, { forwardRef } from 'react'
import { Link } from './link'
import { Button as HeroButton } from '@heroui/react/button';
import clsx from 'clsx';
import { forwardRef } from 'react';
const styles = {
base: [
// Base
'relative isolate inline-flex items-baseline justify-center gap-x-2 rounded-lg border text-base/6 font-semibold',
// Sizing
'px-[calc(--spacing(3.5)-1px)] py-[calc(--spacing(2.5)-1px)] sm:px-[calc(--spacing(3)-1px)] sm:py-[calc(--spacing(1.5)-1px)] sm:text-sm/6',
// Focus
'focus:not-data-focus:outline-hidden data-focus:outline-2 data-focus:outline-offset-2 data-focus:outline-[var(--pm-brand)]',
// Disabled
'data-disabled:opacity-50',
// Icon
'*:data-[slot=icon]:-mx-0.5 *:data-[slot=icon]:my-0.5 *:data-[slot=icon]:size-5 *:data-[slot=icon]:shrink-0 *:data-[slot=icon]:self-center *:data-[slot=icon]:text-(--btn-icon) sm:*:data-[slot=icon]:my-1 sm:*:data-[slot=icon]:size-4 forced-colors:[--btn-icon:ButtonText] forced-colors:data-hover:[--btn-icon:ButtonText]',
],
solid: [
// Optical border, implemented as the button background to avoid corner artifacts
'border-transparent bg-(--btn-border)',
// Dark mode: border is rendered on `after` so background is set to button background
,
// Button background, implemented as foreground layer to stack on top of pseudo-border layer
'before:absolute before:inset-0 before:-z-10 before:rounded-[calc(var(--radius-lg)-1px)] before:bg-(--btn-bg)',
// Drop shadow, applied to the inset `before` layer so it blends with the border
'before:shadow-sm',
// Background color is moved to control and shadow is removed in dark mode so hide `before` pseudo
,
// Dark mode: Subtle white outline is applied using a border
,
// Shim/overlay, inset to match button foreground and used for hover state + highlight shadow
'after:absolute after:inset-0 after:-z-10 after:rounded-[calc(var(--radius-lg)-1px)]',
// Inner highlight shadow
'after:shadow-[inset_0_1px_--theme(--color-white/15%)]',
// White overlay on hover
'data-active:after:bg-(--btn-hover-overlay) data-hover:after:bg-(--btn-hover-overlay)',
// Dark mode: `after` layer expands to cover entire button
,
// Disabled
'data-disabled:before:shadow-none data-disabled:after:shadow-none',
],
outline: [
// Base
'border-zinc-950/10 text-zinc-950 data-active:bg-zinc-950/2.5 data-hover:bg-zinc-950/2.5',
// Dark mode
,
// Icon
'[--btn-icon:var(--color-zinc-500)] data-active:[--btn-icon:var(--color-zinc-700)] data-hover:[--btn-icon:var(--color-zinc-700)]',
],
plain: [
// Base
'border-transparent text-zinc-950 data-active:bg-zinc-950/5 data-hover:bg-zinc-950/5',
// Dark mode
,
// Icon
'[--btn-icon:var(--color-zinc-500)] data-active:[--btn-icon:var(--color-zinc-700)] data-hover:[--btn-icon:var(--color-zinc-700)]',
],
colors: {
'dark/zinc': [
'text-white [--btn-bg:var(--color-zinc-900)] [--btn-border:var(--color-zinc-950)]/90 [--btn-hover-overlay:var(--color-white)]/10',
,
'[--btn-icon:var(--color-zinc-400)] data-active:[--btn-icon:var(--color-zinc-300)] data-hover:[--btn-icon:var(--color-zinc-300)]',
],
light: [
'text-zinc-950 [--btn-bg:white] [--btn-border:var(--color-zinc-950)]/10 [--btn-hover-overlay:var(--color-zinc-950)]/2.5 data-active:[--btn-border:var(--color-zinc-950)]/15 data-hover:[--btn-border:var(--color-zinc-950)]/15',
,
'[--btn-icon:var(--color-zinc-500)] data-active:[--btn-icon:var(--color-zinc-700)] data-hover:[--btn-icon:var(--color-zinc-700)]',
],
'dark/white': [
'text-white [--btn-bg:var(--color-zinc-900)] [--btn-border:var(--color-zinc-950)]/90 [--btn-hover-overlay:var(--color-white)]/10',
,
'[--btn-icon:var(--color-zinc-400)] data-active:[--btn-icon:var(--color-zinc-300)] data-hover:[--btn-icon:var(--color-zinc-300)]',
],
dark: [
'text-white [--btn-bg:var(--color-zinc-900)] [--btn-border:var(--color-zinc-950)]/90 [--btn-hover-overlay:var(--color-white)]/10',
,
'[--btn-icon:var(--color-zinc-400)] data-active:[--btn-icon:var(--color-zinc-300)] data-hover:[--btn-icon:var(--color-zinc-300)]',
],
white: [
'text-zinc-950 [--btn-bg:white] [--btn-border:var(--color-zinc-950)]/10 [--btn-hover-overlay:var(--color-zinc-950)]/2.5 data-active:[--btn-border:var(--color-zinc-950)]/15 data-hover:[--btn-border:var(--color-zinc-950)]/15',
,
'[--btn-icon:var(--color-zinc-400)] data-active:[--btn-icon:var(--color-zinc-500)] data-hover:[--btn-icon:var(--color-zinc-500)]',
],
zinc: [
'text-white [--btn-hover-overlay:var(--color-white)]/10 [--btn-bg:var(--color-zinc-600)] [--btn-border:var(--color-zinc-700)]/90',
,
'[--btn-icon:var(--color-zinc-400)] data-active:[--btn-icon:var(--color-zinc-300)] data-hover:[--btn-icon:var(--color-zinc-300)]',
],
indigo: [
'text-white [--btn-hover-overlay:var(--color-white)]/10 [--btn-bg:var(--color-indigo-500)] [--btn-border:var(--color-indigo-600)]/90',
'[--btn-icon:var(--color-indigo-300)] data-active:[--btn-icon:var(--color-indigo-200)] data-hover:[--btn-icon:var(--color-indigo-200)]',
],
cyan: [
'text-cyan-950 [--btn-bg:var(--color-cyan-300)] [--btn-border:var(--color-cyan-400)]/80 [--btn-hover-overlay:var(--color-white)]/25',
'[--btn-icon:var(--color-cyan-500)]',
],
red: [
'text-white [--btn-hover-overlay:var(--color-white)]/10 [--btn-bg:var(--color-red-600)] [--btn-border:var(--color-red-700)]/90',
'[--btn-icon:var(--color-red-300)] data-active:[--btn-icon:var(--color-red-200)] data-hover:[--btn-icon:var(--color-red-200)]',
],
orange: [
'text-white [--btn-hover-overlay:var(--color-white)]/10 [--btn-bg:var(--color-orange-500)] [--btn-border:var(--color-orange-600)]/90',
'[--btn-icon:var(--color-orange-300)] data-active:[--btn-icon:var(--color-orange-200)] data-hover:[--btn-icon:var(--color-orange-200)]',
],
amber: [
'text-amber-950 [--btn-hover-overlay:var(--color-white)]/25 [--btn-bg:var(--color-amber-400)] [--btn-border:var(--color-amber-500)]/80',
'[--btn-icon:var(--color-amber-600)]',
],
yellow: [
'text-yellow-950 [--btn-hover-overlay:var(--color-white)]/25 [--btn-bg:var(--color-yellow-300)] [--btn-border:var(--color-yellow-400)]/80',
'[--btn-icon:var(--color-yellow-600)] data-active:[--btn-icon:var(--color-yellow-700)] data-hover:[--btn-icon:var(--color-yellow-700)]',
],
lime: [
'text-lime-950 [--btn-hover-overlay:var(--color-white)]/25 [--btn-bg:var(--color-lime-300)] [--btn-border:var(--color-lime-400)]/80',
'[--btn-icon:var(--color-lime-600)] data-active:[--btn-icon:var(--color-lime-700)] data-hover:[--btn-icon:var(--color-lime-700)]',
],
green: [
'text-white [--btn-hover-overlay:var(--color-white)]/10 [--btn-bg:var(--color-green-600)] [--btn-border:var(--color-green-700)]/90',
'[--btn-icon:var(--color-white)]/60 data-active:[--btn-icon:var(--color-white)]/80 data-hover:[--btn-icon:var(--color-white)]/80',
],
emerald: [
'text-white [--btn-hover-overlay:var(--color-white)]/10 [--btn-bg:var(--color-emerald-600)] [--btn-border:var(--color-emerald-700)]/90',
'[--btn-icon:var(--color-white)]/60 data-active:[--btn-icon:var(--color-white)]/80 data-hover:[--btn-icon:var(--color-white)]/80',
],
teal: [
'text-white [--btn-hover-overlay:var(--color-white)]/10 [--btn-bg:var(--color-teal-600)] [--btn-border:var(--color-teal-700)]/90',
'[--btn-icon:var(--color-white)]/60 data-active:[--btn-icon:var(--color-white)]/80 data-hover:[--btn-icon:var(--color-white)]/80',
],
sky: [
'text-white [--btn-hover-overlay:var(--color-white)]/10 [--btn-bg:var(--color-sky-500)] [--btn-border:var(--color-sky-600)]/80',
'[--btn-icon:var(--color-white)]/60 data-active:[--btn-icon:var(--color-white)]/80 data-hover:[--btn-icon:var(--color-white)]/80',
],
blue: [
'text-white [--btn-hover-overlay:rgb(255_255_255_/_0.16)] [--btn-bg:var(--pm-brand)] [--btn-border:var(--pm-brand-strong)]',
'[--btn-icon:rgb(255_255_255_/_0.72)] data-active:[--btn-icon:var(--color-white)] data-hover:[--btn-icon:var(--color-white)]',
],
violet: [
'text-white [--btn-hover-overlay:var(--color-white)]/10 [--btn-bg:var(--color-violet-500)] [--btn-border:var(--color-violet-600)]/90',
'[--btn-icon:var(--color-violet-300)] data-active:[--btn-icon:var(--color-violet-200)] data-hover:[--btn-icon:var(--color-violet-200)]',
],
purple: [
'text-white [--btn-hover-overlay:var(--color-white)]/10 [--btn-bg:var(--color-purple-500)] [--btn-border:var(--color-purple-600)]/90',
'[--btn-icon:var(--color-purple-300)] data-active:[--btn-icon:var(--color-purple-200)] data-hover:[--btn-icon:var(--color-purple-200)]',
],
fuchsia: [
'text-white [--btn-hover-overlay:var(--color-white)]/10 [--btn-bg:var(--color-fuchsia-500)] [--btn-border:var(--color-fuchsia-600)]/90',
'[--btn-icon:var(--color-fuchsia-300)] data-active:[--btn-icon:var(--color-fuchsia-200)] data-hover:[--btn-icon:var(--color-fuchsia-200)]',
],
pink: [
'text-white [--btn-hover-overlay:var(--color-white)]/10 [--btn-bg:var(--color-pink-500)] [--btn-border:var(--color-pink-600)]/90',
'[--btn-icon:var(--color-pink-300)] data-active:[--btn-icon:var(--color-pink-200)] data-hover:[--btn-icon:var(--color-pink-200)]',
],
rose: [
'text-white [--btn-hover-overlay:var(--color-white)]/10 [--btn-bg:var(--color-rose-500)] [--btn-border:var(--color-rose-600)]/90',
'[--btn-icon:var(--color-rose-300)] data-active:[--btn-icon:var(--color-rose-200)] data-hover:[--btn-icon:var(--color-rose-200)]',
],
},
import { Link } from './link';
function hasLegacyClass(className, token) {
return String(className || '')
.split(/\s+/)
.includes(token);
}
function resolveButtonIntent({ className, color, outline, plain }) {
const legacyPrimary = hasLegacyClass(className, 'button--primary');
const legacyDanger = hasLegacyClass(className, 'button--danger');
const legacyOutline = hasLegacyClass(className, 'button--outline');
const legacyPlain =
hasLegacyClass(className, 'button--plain') ||
hasLegacyClass(className, 'button--ghost');
const isPrimary = color === 'blue' || legacyPrimary;
const isDanger = color === 'red' || legacyDanger;
const isPlain = plain || legacyPlain;
const isDangerEntry = isDanger && (outline || isPlain);
const isOutline =
outline || legacyOutline || (!isPrimary && !isDanger && !isPlain);
return {
isDanger,
isDangerEntry,
isOutline,
isPlain,
isPrimary,
variant: isPrimary
? 'primary'
: isDanger && !isDangerEntry
? 'danger'
: isPlain
? 'tertiary'
: 'secondary',
};
}
export const Button = forwardRef(function Button({ color, outline, plain, className, children, ...props }, ref) {
let classes = clsx(
export const Button = forwardRef(function Button(
{
color,
outline = false,
plain = false,
className,
styles.base,
outline ? styles.outline : plain ? styles.plain : clsx(styles.solid, styles.colors[color ?? 'dark/zinc'])
)
children,
disabled,
href,
size = 'sm',
...props
},
ref,
) {
const intent = resolveButtonIntent({ className, color, outline, plain });
const classes = clsx(
'rc-button',
(intent.isPrimary || (intent.isDanger && !intent.isDangerEntry)) &&
'rc-button--solid',
intent.isPrimary && 'rc-button--primary',
intent.isOutline && 'rc-button--secondary rc-button--outline',
intent.isPlain && 'rc-button--secondary rc-button--plain',
intent.isDanger && 'rc-button--danger',
className,
);
return typeof props.href === 'string' ? (
<Link {...props} className={classes} ref={ref}>
if (typeof href === 'string') {
return (
<Link
{...props}
aria-disabled={disabled || undefined}
className={classes}
href={href}
ref={ref}
>
<TouchTarget>{children}</TouchTarget>
</Link>
) : (
<Headless.Button {...props} className={clsx(classes, 'cursor-default')} ref={ref}>
);
}
return (
<HeroButton
{...props}
className={classes}
isDisabled={disabled}
ref={ref}
size={size}
variant={intent.variant}
>
<TouchTarget>{children}</TouchTarget>
</Headless.Button>
)
})
</HeroButton>
);
});
/**
* Expand the hit area to at least 44×44px on touch devices
*/
export function TouchTarget({ children }) {
return (
<>
<span
className="absolute top-1/2 left-1/2 size-[max(100%,2.75rem)] -translate-x-1/2 -translate-y-1/2 pointer-fine:hidden"
aria-hidden="true"
/>
{children}
</>
)
return <>{children}</>;
}
import * as Headless from '@headlessui/react'
import clsx from 'clsx'
import { Description } from '@heroui/react/description';
import { Modal } from '@heroui/react/modal';
import clsx from 'clsx';
import { Text } from './text'
import { Close } from '../../components/AppIcons';
const sizes = {
xs: 'sm:max-w-xs',
sm: 'sm:max-w-sm',
md: 'sm:max-w-md',
lg: 'sm:max-w-lg',
xl: 'sm:max-w-xl',
'2xl': 'sm:max-w-2xl',
'3xl': 'sm:max-w-3xl',
'4xl': 'sm:max-w-4xl',
'5xl': 'sm:max-w-5xl',
}
export function Dialog({ size = 'lg', className, children, ...props }) {
export function Dialog({ size = 'lg', className, children, open, onClose, ...props }) {
return (
<Headless.Dialog {...props}>
<Headless.DialogBackdrop
transition
className="fixed inset-0 z-50 flex w-screen justify-center overflow-y-auto bg-zinc-950/25 px-2 py-2 transition duration-100 focus:outline-0 data-closed:opacity-0 data-enter:ease-out data-leave:ease-in sm:px-6 sm:py-8 lg:px-8 lg:py-16"
/>
<div className="fixed inset-0 z-50 w-screen overflow-y-auto pt-6 sm:pt-0">
<div className="grid min-h-full grid-rows-[1fr_auto] justify-items-center sm:grid-rows-[1fr_auto_3fr] sm:p-4">
<Headless.DialogPanel
transition
className={clsx(
className,
sizes[size],
'row-start-2 w-full min-w-0 rounded-t-3xl bg-white p-(--gutter) shadow-lg ring-1 ring-zinc-950/10 [--gutter:--spacing(8)] sm:mb-auto sm:rounded-2xl forced-colors:outline',
'transition duration-100 will-change-transform data-closed:translate-y-12 data-closed:opacity-0 data-enter:ease-out data-leave:ease-in sm:data-closed:translate-y-0 sm:data-closed:data-enter:scale-95'
)}
<Modal.Root>
<Modal.Trigger aria-hidden="true" className="sr-only" tabIndex={-1} />
<Modal.Backdrop
{...props}
className="rc-dialog-backdrop"
isOpen={open}
onOpenChange={(isOpen) => {
if (!isOpen) onClose?.();
}}
variant="blur"
>
<Modal.Container placement="center" scroll="inside" size={size}>
<Modal.Dialog
className={clsx('rc-dialog-panel', `rc-dialog-panel--${size}`, className)}
>
<Modal.CloseTrigger
aria-label="关闭"
className="rc-dialog-close rc-dialog-close--automatic absolute inline-flex items-center justify-center rounded-full"
>
<Close size={16} />
</Modal.CloseTrigger>
{children}
</Headless.DialogPanel>
</div>
</div>
</Headless.Dialog>
)
</Modal.Dialog>
</Modal.Container>
</Modal.Backdrop>
</Modal.Root>
);
}
export function DialogTitle({ className, ...props }) {
return (
<Headless.DialogTitle
{...props}
className={clsx(className, 'text-lg/6 font-semibold text-balance text-zinc-950 sm:text-base/6')}
/>
)
<Modal.Header>
<Modal.Heading {...props} className={clsx('rc-dialog-title', className)} />
</Modal.Header>
);
}
export function DialogDescription({ className, ...props }) {
return <Headless.Description as={Text} {...props} className={clsx(className, 'mt-2 text-pretty')} />
return <Description {...props} className={clsx('rc-dialog-description', className)} />;
}
export function DialogBody({ className, ...props }) {
return <div {...props} className={clsx(className, 'mt-6')} />
return <Modal.Body {...props} className={clsx('rc-dialog-body', className)} />;
}
export function DialogActions({ className, ...props }) {
return (
<div
{...props}
className={clsx(
className,
'mt-8 flex flex-col-reverse items-center justify-end gap-3 *:w-full sm:flex-row sm:*:w-auto'
)}
/>
)
return <Modal.Footer {...props} className={clsx('rc-dialog-actions', className)} />;
}
import * as Headless from '@headlessui/react'
import clsx from 'clsx'
import React, { forwardRef } from 'react'
import { Input as HeroInput } from '@heroui/react/input';
import clsx from 'clsx';
import { forwardRef } from 'react';
export function InputGroup({ children }) {
export function InputGroup({ children, className }) {
return (
<span
data-slot="control"
className={clsx(
'relative isolate block',
'has-[[data-slot=icon]:first-child]:[&_input]:pl-10 has-[[data-slot=icon]:last-child]:[&_input]:pr-10 sm:has-[[data-slot=icon]:first-child]:[&_input]:pl-8 sm:has-[[data-slot=icon]:last-child]:[&_input]:pr-8',
'*:data-[slot=icon]:pointer-events-none *:data-[slot=icon]:absolute *:data-[slot=icon]:top-3 *:data-[slot=icon]:z-10 *:data-[slot=icon]:size-5 sm:*:data-[slot=icon]:top-2.5 sm:*:data-[slot=icon]:size-4',
'[&>[data-slot=icon]:first-child]:left-3 sm:[&>[data-slot=icon]:first-child]:left-2.5 [&>[data-slot=icon]:last-child]:right-3 sm:[&>[data-slot=icon]:last-child]:right-2.5',
'*:data-[slot=icon]:text-zinc-500'
)}
>
<span data-slot="control" className={clsx('rc-input-group relative block', className)}>
{children}
</span>
)
);
}
const dateTypes = ['date', 'datetime-local', 'month', 'time', 'week']
export const Input = forwardRef(function Input(
{ className, ...props },
ref
{ className, disabled, invalid, ...props },
ref,
) {
return (
<span
data-slot="control"
className={clsx([
className,
// Basic layout
'relative block w-full',
// Background color + shadow applied to inset pseudo element, so shadow blends with border in light mode
'before:absolute before:inset-px before:rounded-[calc(var(--radius-lg)-1px)] before:bg-white before:shadow-sm',
// Background color is moved to control and shadow is removed in dark mode so hide `before` pseudo
,
// Focus ring
'after:pointer-events-none after:absolute after:inset-0 after:rounded-lg after:ring-transparent after:ring-inset sm:focus-within:after:ring-2 sm:focus-within:after:ring-[var(--pm-brand)]',
// Disabled state
'has-data-disabled:opacity-50 has-data-disabled:before:bg-zinc-950/5 has-data-disabled:before:shadow-none',
])}
>
<Headless.Input
ref={ref}
<HeroInput
{...props}
className={clsx([
// Date classes
props.type &&
dateTypes.includes(props.type) && [
'[&::-webkit-datetime-edit-fields-wrapper]:p-0',
'[&::-webkit-date-and-time-value]:min-h-[1.5em]',
'[&::-webkit-datetime-edit]:inline-flex',
'[&::-webkit-datetime-edit]:p-0',
'[&::-webkit-datetime-edit-year-field]:p-0',
'[&::-webkit-datetime-edit-month-field]:p-0',
'[&::-webkit-datetime-edit-day-field]:p-0',
'[&::-webkit-datetime-edit-hour-field]:p-0',
'[&::-webkit-datetime-edit-minute-field]:p-0',
'[&::-webkit-datetime-edit-second-field]:p-0',
'[&::-webkit-datetime-edit-millisecond-field]:p-0',
'[&::-webkit-datetime-edit-meridiem-field]:p-0',
],
// Basic layout
'relative block w-full appearance-none rounded-lg px-[calc(--spacing(3.5)-1px)] py-[calc(--spacing(2.5)-1px)] sm:px-[calc(--spacing(3)-1px)] sm:py-[calc(--spacing(1.5)-1px)]',
// Typography
'text-base/6 text-zinc-950 placeholder:text-zinc-500 sm:text-sm/6',
// Border
'border border-zinc-950/10 data-hover:border-zinc-950/20',
// Background color
'bg-transparent',
// Hide default focus styles
'focus:outline-hidden',
// Invalid state
'data-invalid:border-red-500 data-invalid:data-hover:border-red-500',
// Disabled state
'data-disabled:border-zinc-950/20',
// System icons
,
])}
aria-invalid={invalid || undefined}
className={clsx('rc-form-input', className)}
disabled={disabled}
fullWidth
ref={ref}
variant="secondary"
/>
</span>
)
})
);
});
import * as Headless from '@headlessui/react'
import clsx from 'clsx'
import React, { forwardRef } from 'react'
import FormSelect from '@/components/FormSelect';
import { forwardRef } from 'react';
export const Select = forwardRef(function Select({ className, multiple, ...props }, ref) {
return (
<span
data-slot="control"
className={clsx([
className,
// Basic layout
'group relative block w-full',
// Background color + shadow applied to inset pseudo element, so shadow blends with border in light mode
'before:absolute before:inset-px before:rounded-[calc(var(--radius-lg)-1px)] before:bg-white before:shadow-sm',
// Background color is moved to control and shadow is removed in dark mode so hide `before` pseudo
,
// Focus ring
'after:pointer-events-none after:absolute after:inset-0 after:rounded-lg after:ring-transparent after:ring-inset has-data-focus:after:ring-2 has-data-focus:after:ring-[var(--pm-brand)]',
// Disabled state
'has-data-disabled:opacity-50 has-data-disabled:before:bg-zinc-950/5 has-data-disabled:before:shadow-none',
])}
>
<Headless.Select
ref={ref}
multiple={multiple}
{...props}
className={clsx([
// Basic layout
'relative block w-full appearance-none rounded-lg py-[calc(--spacing(2.5)-1px)] sm:py-[calc(--spacing(1.5)-1px)]',
// Horizontal padding
multiple
? 'px-[calc(--spacing(3.5)-1px)] sm:px-[calc(--spacing(3)-1px)]'
: 'pr-[calc(--spacing(10)-1px)] pl-[calc(--spacing(3.5)-1px)] sm:pr-[calc(--spacing(9)-1px)] sm:pl-[calc(--spacing(3)-1px)]',
// Options (multi-select)
'[&_optgroup]:font-semibold',
// Typography
'text-base/6 text-zinc-950 placeholder:text-zinc-500 sm:text-sm/6',
// Border
'border border-zinc-950/10 data-hover:border-zinc-950/20',
// Background color
'bg-transparent',
// Hide default focus styles
'focus:outline-hidden',
// Invalid state
'data-invalid:border-red-500 data-invalid:data-hover:border-red-500',
// Disabled state
'data-disabled:border-zinc-950/20 data-disabled:opacity-100',
])}
/>
{!multiple && (
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<svg
className="size-5 stroke-zinc-500 group-has-data-disabled:stroke-zinc-600 sm:size-4 forced-colors:stroke-[CanvasText]"
viewBox="0 0 16 16"
aria-hidden="true"
fill="none"
>
<path d="M5.75 10.75L8 13L10.25 10.75" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round" />
<path d="M10.25 5.25L8 3L5.75 5.25" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round" />
</svg>
</span>
)}
</span>
)
})
export const Select = forwardRef(function Select(props, _ref) {
return <FormSelect {...props} />;
});
import BankOutlined from '@ant-design/icons/BankOutlined';
import BarChartOutlined from '@ant-design/icons/BarChartOutlined';
import AppstoreOutlined from '@ant-design/icons/AppstoreOutlined';
import CalendarOutlined from '@ant-design/icons/CalendarOutlined';
import CheckOutlined from '@ant-design/icons/CheckOutlined';
import CheckCircleOutlined from '@ant-design/icons/CheckCircleOutlined';
import DownloadOutlined from '@ant-design/icons/DownloadOutlined';
import DownOutlined from '@ant-design/icons/DownOutlined';
import ClockCircleOutlined from '@ant-design/icons/ClockCircleOutlined';
import CloseOutlined from '@ant-design/icons/CloseOutlined';
import DeleteOutlined from '@ant-design/icons/DeleteOutlined';
import DollarOutlined from '@ant-design/icons/DollarOutlined';
import DatabaseOutlined from '@ant-design/icons/DatabaseOutlined';
import EditOutlined from '@ant-design/icons/EditOutlined';
import EyeOutlined from '@ant-design/icons/EyeOutlined';
import ExportOutlined from '@ant-design/icons/ExportOutlined';
import FileTextOutlined from '@ant-design/icons/FileTextOutlined';
import FolderOpenOutlined from '@ant-design/icons/FolderOpenOutlined';
import PaperClipOutlined from '@ant-design/icons/PaperClipOutlined';
import LeftOutlined from '@ant-design/icons/LeftOutlined';
import LogoutOutlined from '@ant-design/icons/LogoutOutlined';
import InboxOutlined from '@ant-design/icons/InboxOutlined';
import InfoCircleOutlined from '@ant-design/icons/InfoCircleOutlined';
import MoreOutlined from '@ant-design/icons/MoreOutlined';
import MoonOutlined from '@ant-design/icons/MoonOutlined';
import OrderedListOutlined from '@ant-design/icons/OrderedListOutlined';
import PieChartOutlined from '@ant-design/icons/PieChartOutlined';
import PlusOutlined from '@ant-design/icons/PlusOutlined';
import ProjectOutlined from '@ant-design/icons/ProjectOutlined';
import ReloadOutlined from '@ant-design/icons/ReloadOutlined';
import RightOutlined from '@ant-design/icons/RightOutlined';
import RiseOutlined from '@ant-design/icons/RiseOutlined';
import SearchOutlined from '@ant-design/icons/SearchOutlined';
import SendOutlined from '@ant-design/icons/SendOutlined';
import SunOutlined from '@ant-design/icons/SunOutlined';
import TeamOutlined from '@ant-design/icons/TeamOutlined';
import UserOutlined from '@ant-design/icons/UserOutlined';
import UploadOutlined from '@ant-design/icons/UploadOutlined';
import WarningOutlined from '@ant-design/icons/WarningOutlined';
function iconStyle(size, style) {
return size === undefined ? style : { fontSize: size, ...style };
}
function withSize(Icon) {
return function AppIcon({ size, style, ...props }) {
return (
<Icon aria-hidden="true" {...props} style={iconStyle(size, style)} />
);
};
}
export const Bank = withSize(BankOutlined);
export const Archive = withSize(InboxOutlined);
export const AppGrid = withSize(AppstoreOutlined);
export const BarChart = withSize(BarChartOutlined);
export const Calendar = withSize(CalendarOutlined);
export const Check = withSize(CheckOutlined);
export const ChevronDown = withSize(DownOutlined);
export const ChevronRight = withSize(RightOutlined);
export const Clock = withSize(ClockCircleOutlined);
export const Close = withSize(CloseOutlined);
export const Delete = withSize(DeleteOutlined);
export const Dollar = withSize(DollarOutlined);
export const Database = withSize(DatabaseOutlined);
export const Edit = withSize(EditOutlined);
export const Eye = withSize(EyeOutlined);
export const OpenExternal = withSize(ExportOutlined);
export const FileText = withSize(FileTextOutlined);
export const Info = withSize(InfoCircleOutlined);
export const Folder = withSize(FolderOpenOutlined);
export const LogOut = withSize(LogoutOutlined);
export function SidebarPanel({ size = 20, style, ...props }) {
return (
<span
aria-hidden="true"
role="img"
{...props}
style={iconStyle(size, style)}
className={['anticon', props.className].filter(Boolean).join(' ')}
>
<svg
viewBox="0 0 16 16"
width="1em"
height="1em"
fill="none"
xmlns="http://www.w3.org/2000/svg"
focusable="false"
aria-hidden="true"
>
<path
clipRule="evenodd"
d="M6 3.5h6A1.5 1.5 0 0 1 13.5 5v6a1.5 1.5 0 0 1-1.5 1.5H6zm-1.5 0H4A1.5 1.5 0 0 0 2.5 5v6A1.5 1.5 0 0 0 4 12.5h.5zM1 5a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3z"
fill="currentColor"
fillRule="evenodd"
/>
</svg>
</span>
);
}
export const More = withSize(MoreOutlined);
export const Moon = withSize(MoonOutlined);
export const Project = withSize(ProjectOutlined);
export const Search = withSize(SearchOutlined);
export const Send = withSize(SendOutlined);
export const Sun = withSize(SunOutlined);
export const Team = withSize(TeamOutlined);
export const User = withSize(UserOutlined);
export const Upload = withSize(UploadOutlined);
export const Back = withSize(LeftOutlined);
export const ArrowDownTrayIcon = withSize(DownloadOutlined);
export const ArrowPathIcon = withSize(ReloadOutlined);
export const ArrowTrendingUpIcon = withSize(RiseOutlined);
export const BanknotesIcon = withSize(DollarOutlined);
export const CalendarDaysIcon = Calendar;
export const ChartBarSquareIcon = BarChart;
export const ChartPieIcon = withSize(PieChartOutlined);
export const CheckCircleIcon = withSize(CheckCircleOutlined);
export const ChevronDownIcon = ChevronDown;
export const ChevronRightIcon = ChevronRight;
export const ClipboardDocumentListIcon = withSize(OrderedListOutlined);
export const ClockIcon = Clock;
export const ExclamationTriangleIcon = withSize(WarningOutlined);
export const FolderOpenIcon = Folder;
export const InformationCircleIcon = Info;
export const MagnifyingGlassIcon = Search;
export const PaperClipIcon = withSize(PaperClipOutlined);
export const PlusIcon = withSize(PlusOutlined);
export const UserGroupIcon = Team;
import { useLayoutEffect, useState } from 'react';
import { Sidebar } from '@heroui-pro/react/sidebar';
import { history } from 'umi';
import ApplicationSwitcherDialog from '@/pages/project_manager_xy/ApplicationSwitcherDialog';
import DashboardNavbar from './DashboardNavbar';
import DashboardSidebar from './DashboardSidebar';
const THEME_STORAGE_KEY = 'xinyuan_color_theme';
function getInitialTheme() {
if (typeof window === 'undefined') return 'light';
try {
return window.localStorage.getItem(THEME_STORAGE_KEY) === 'dark'
? 'dark'
: 'light';
} catch {
return 'light';
}
}
export default function AppShell({
children,
open,
onOpenChange,
title,
...sidebarProps
}) {
const [theme, setTheme] = useState(getInitialTheme);
const [isApplicationDialogOpen, setIsApplicationDialogOpen] = useState(false);
useLayoutEffect(() => {
const root = document.documentElement;
root.classList.toggle('dark', theme === 'dark');
root.classList.toggle('light', theme === 'light');
root.dataset.theme = theme;
root.style.colorScheme = theme;
try {
window.localStorage.setItem(THEME_STORAGE_KEY, theme);
} catch {
// The selected theme still applies for this session when storage is unavailable.
}
}, [theme]);
return (
<Sidebar.Provider
className={`template-dashboard-shell rc-app-shell ${theme}`}
collapsible="offcanvas"
navigate={(href) => history.push(href)}
open={open}
onOpenChange={onOpenChange}
>
<DashboardSidebar
{...sidebarProps}
onOpenApplicationSwitcher={() => setIsApplicationDialogOpen(true)}
/>
<Sidebar.Main className="app-main-glass rc-app-main">
<DashboardNavbar
theme={theme}
title={title}
onThemeChange={setTheme}
/>
<div className="app-shell-content-scroll min-h-0 flex-1 overflow-y-auto">
<div className="rc-app-page">{children}</div>
</div>
</Sidebar.Main>
<ApplicationSwitcherDialog
open={isApplicationDialogOpen}
onClose={() => setIsApplicationDialogOpen(false)}
userInfo={sidebarProps.userInfo}
/>
</Sidebar.Provider>
);
}
import { Navbar } from '@heroui-pro/react/navbar';
import { Sidebar, useSidebar } from '@heroui-pro/react/sidebar';
import { Button, Tooltip } from '@heroui/react';
import { Moon, SidebarPanel, Sun } from './AppIcons';
export default function DashboardNavbar({ onThemeChange, theme, title }) {
const { isMobile, isOpen } = useSidebar();
const isDark = theme === 'dark';
const themeLabel = isDark ? '切换至浅色模式' : '切换至深色模式';
return (
<Navbar className="app-navbar rc-navbar" maxWidth="full">
<Navbar.Header>
<Sidebar.Trigger
aria-label={isMobile ? '打开导航' : isOpen ? '收起侧栏' : '展开侧栏'}
className="app-navbar-trigger rc-navbar-trigger"
>
<SidebarPanel />
</Sidebar.Trigger>
<h1 className="app-navbar-page-title rc-navbar-title">{title}</h1>
<Navbar.Spacer />
<div className="rc-navbar-actions">
<Tooltip delay={0}>
<Button
isIconOnly
aria-label={themeLabel}
aria-pressed={isDark}
className="app-navbar-icon-button rc-navbar-icon-button"
size="sm"
variant="ghost"
onPress={() => onThemeChange(isDark ? 'light' : 'dark')}
>
{isDark ? <Sun size={16} /> : <Moon size={16} />}
</Button>
<Tooltip.Content>{themeLabel}</Tooltip.Content>
</Tooltip>
</div>
</Navbar.Header>
</Navbar>
);
}
import xyLogoRed from '@/assets/identity/xy-logo-red.png';
import { Sidebar } from '@heroui-pro/react/sidebar';
import {
Avatar,
Button,
Dropdown,
Label,
} from '@heroui/react';
import { Heading } from 'react-aria-components/Dialog';
import { AppGrid, LogOut } from './AppIcons';
const APP_NAME = '党风廉政建设';
function getUserDisplay(userInfo) {
const displayName =
userInfo?.display_name || userInfo?.username || '当前用户';
const username = userInfo?.username || userInfo?.email || '已登录';
return {
displayName,
username,
initials: String(displayName).trim().slice(0, 1).toUpperCase() || 'U',
};
}
function SidebarUserMenu({ userInfo, onLogout }) {
const user = getUserDisplay(userInfo);
return (
<Dropdown>
<Button
fullWidth
className="app-sidebar-user-trigger rc-sidebar-user-trigger"
size="sm"
variant="ghost"
>
<Avatar className="app-sidebar-user-avatar rc-sidebar-avatar">
{userInfo?.avatar ? (
<Avatar.Image alt={user.displayName} src={userInfo.avatar} />
) : null}
<Avatar.Fallback>{user.initials}</Avatar.Fallback>
</Avatar>
<span
className="app-sidebar-user-copy rc-sidebar-user-copy"
data-sidebar="label"
>
<span className="app-sidebar-user-name">{user.displayName}</span>
<span className="app-sidebar-user-description">{user.username}</span>
</span>
</Button>
<Dropdown.Popover placement="top start">
<Dropdown.Menu
aria-label="用户菜单"
onAction={(key) => {
if (key === 'logout') onLogout();
}}
>
<Dropdown.Item id="logout" textValue="退出登录" variant="danger">
<div className="rc-sidebar-user-menu-item">
<Label>退出登录</Label>
<LogOut size={14} />
</div>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown>
);
}
function SidebarContents({
loading,
error,
menuActionState = {},
menuGroups,
onLogout,
onMenuAction,
onOpenApplicationSwitcher,
pathname,
userInfo,
}) {
return (
<>
<Sidebar.Header>
<div className="app-sidebar-brand rc-sidebar-brand">
<div className="app-sidebar-brand-mark rc-sidebar-brand-mark">
<img src={xyLogoRed} alt="鑫元 Xinyuan" className="rc-sidebar-logo" />
</div>
<div
className="app-sidebar-brand-main rc-sidebar-brand-main"
data-sidebar="label"
>
<span className="app-sidebar-title rc-sidebar-title">
{APP_NAME}
</span>
</div>
<div
className="app-sidebar-app-switcher rc-sidebar-app-switcher"
data-sidebar="label"
>
<Button
isIconOnly
aria-label="切换应用"
className="app-sidebar-app-trigger rc-sidebar-app-trigger"
size="sm"
variant="ghost"
onPress={onOpenApplicationSwitcher}
>
<AppGrid size={16} />
</Button>
</div>
</div>
</Sidebar.Header>
<Sidebar.Content>
{loading ? (
<div className="rc-sidebar-state" aria-live="polite">
菜单加载中...
</div>
) : error ? (
<div className="rc-sidebar-state is-error" role="alert">
菜单权限加载失败
</div>
) : (
menuGroups.map((group) => (
<Sidebar.Group key={group.title}>
<Sidebar.GroupLabel>{group.title}</Sidebar.GroupLabel>
<Sidebar.Menu aria-label={group.title}>
{group.items.map((item) => {
const Icon = item.icon;
const actionState = item.action
? menuActionState[item.action]
: null;
const isActionLoading = actionState?.status === 'loading';
return (
<Sidebar.MenuItem
href={item.path}
isCurrent={pathname === item.path}
isDisabled={isActionLoading}
key={item.key || item.path || item.action}
onAction={
item.action
? () => onMenuAction?.(item.action)
: undefined
}
textValue={item.name}
tooltip={actionState?.message || item.name}
>
<Sidebar.MenuIcon>
<Icon
className={isActionLoading ? 'animate-spin' : ''}
size={16}
/>
</Sidebar.MenuIcon>
<Sidebar.MenuLabel>
{isActionLoading ? '正在获取产品手册' : item.name}
</Sidebar.MenuLabel>
</Sidebar.MenuItem>
);
})}
</Sidebar.Menu>
{group.items.some(
(item) =>
item.action && menuActionState[item.action]?.message,
) ? (
<p className="rc-sidebar-action-status" role="status">
{
group.items
.map((item) => menuActionState[item.action]?.message)
.find(Boolean)
}
</p>
) : null}
</Sidebar.Group>
))
)}
</Sidebar.Content>
<Sidebar.Footer>
<SidebarUserMenu userInfo={userInfo} onLogout={onLogout} />
</Sidebar.Footer>
</>
);
}
export default function DashboardSidebar(props) {
return (
<>
<Sidebar className="app-sidebar-glass rc-sidebar">
<SidebarContents {...props} />
</Sidebar>
<Sidebar.Mobile className="app-sidebar-glass rc-mobile-sidebar">
<Heading slot="title" className="sr-only">
党风廉政建设导航
</Heading>
<SidebarContents {...props} />
</Sidebar.Mobile>
</>
);
}
import {
Archive,
ChevronDown,
Close,
Database,
FileText,
} from '@/components/AppIcons';
import { ActionBar } from '@heroui-pro/react/action-bar';
import { DataGrid } from '@heroui-pro/react/data-grid';
import { EmptyState } from '@heroui-pro/react/empty-state';
import {
Button,
Chip,
Label,
ListBox,
Pagination,
Select,
Separator,
Spinner,
} from '@heroui/react';
import { Tooltip } from '@heroui/react/tooltip';
import {
Children,
Fragment,
isValidElement,
useCallback,
useRef,
useState,
} from 'react';
import { createPortal } from 'react-dom';
export const DATA_TABLE_PAGE_SIZE_OPTIONS = Object.freeze([10, 20, 50]);
export function normalizeDataTablePageSize(
value,
fallback = DATA_TABLE_PAGE_SIZE_OPTIONS[0],
) {
const nextValue = Number(value);
return DATA_TABLE_PAGE_SIZE_OPTIONS.includes(nextValue)
? nextValue
: fallback;
}
export function DataGridEmptyIllustration() {
return (
<div className="data-grid-empty-illustration" aria-hidden="true">
<span className="data-grid-empty-illustration-back">
<Database size={28} />
</span>
<span className="data-grid-empty-illustration-front">
<FileText size={32} />
<span className="data-grid-empty-illustration-lines">
<i />
<i />
<i />
</span>
</span>
<span className="data-grid-empty-illustration-badge">
<Archive size={18} />
</span>
</div>
);
}
function normalizeDataTableEmptyTitle(title) {
const text = String(title || '').trim();
if (!text) {
return '暂无数据';
}
if (/^(暂无数据){2,}$/.test(text)) {
return '暂无数据';
}
return title;
}
export function DataTableEmptyState({ title = '暂无数据' }) {
const resolvedTitle = normalizeDataTableEmptyTitle(title);
return (
<EmptyState className="data-grid-empty-state" size="md">
<EmptyState.Header>
<EmptyState.Media>
<DataGridEmptyIllustration />
</EmptyState.Media>
<EmptyState.Title>{resolvedTitle}</EmptyState.Title>
</EmptyState.Header>
</EmptyState>
);
}
function DataTableLoadingState() {
return (
<div className="data-grid-empty-loading" aria-label="数据加载中">
<Spinner size="sm" />
</div>
);
}
function resolveRowKey(row, index, getRowKey) {
const explicitKey = getRowKey?.(row, index);
if (explicitKey) {
return String(explicitKey);
}
return String(
row?.recordKey ??
row?.recordId ??
row?.id ??
row?._id ??
row?.name ??
index,
);
}
function resolveColumnWidth(width) {
if (typeof width === 'number') {
return width;
}
const match = String(width || '').match(/(\d+)px/);
return match ? Number(match[1]) : undefined;
}
export function DataTable({
ariaLabel = '数据表格',
columns,
rows,
isLoading = false,
getRowKey,
selectedRowKeys,
onSelectedRowKeysChange,
emptyTitle = '暂无数据',
emptyDescription = '当前筛选条件下没有可显示的记录。',
className = '',
isWide,
contentMinWidth,
disabledRowKeys,
onRowAction,
sortDescriptor,
onSortChange,
renderSelectionActions,
}) {
const selectionEnabled = Boolean(selectedRowKeys && onSelectedRowKeysChange);
const selectedKeys = selectionEnabled ? new Set(selectedRowKeys) : undefined;
const wide = isWide ?? columns.length > 6;
const gridColumns = columns.map((column, index) => {
const isActions = column.isActions || column.key === 'actions';
const isPrimary = column.isPrimary ?? (!isActions && index === 0);
const width = resolveColumnWidth(column.width);
const pinned = wide
? column.pin || (isActions ? 'end' : undefined)
: undefined;
const minWidth =
column.minWidth || width || (isActions ? 104 : pinned ? 220 : 128);
return {
id: String(column.key),
header: column.title,
accessorKey: typeof column.key === 'string' ? column.key : undefined,
align: column.align,
cell: (row) => {
const value = column.render ? column.render(row) : row?.[column.key];
if (value === undefined || value === null || value === '') {
return '-';
}
if (typeof value === 'string' || typeof value === 'number') {
return (
<DataTableText
text={value}
className={
isPrimary ? 'pm-data-grid-primary' : 'pm-data-grid-text'
}
/>
);
}
return value;
},
isRowHeader: index === 0,
allowsSorting: column.sortable === true,
headerClassName: column.headerClassName,
cellClassName: [
'pm-data-grid-cell',
isPrimary ? 'pm-data-grid-cell--primary' : '',
column.cellClassName || '',
]
.filter(Boolean)
.join(' '),
allowsResizing: true,
minWidth,
width: width || (pinned ? minWidth : undefined),
pinned,
};
});
const resolvedContentMinWidth = wide
? Math.max(
Number(contentMinWidth) || 0,
gridColumns.reduce(
(total, column) => total + (column.width || column.minWidth || 128),
selectionEnabled ? 40 : 0,
),
)
: undefined;
const handleSelectionChange = (selection) => {
if (!onSelectedRowKeysChange) {
return;
}
if (selection === 'all') {
onSelectedRowKeysChange(
rows.map((row, index) => resolveRowKey(row, index, getRowKey)),
);
return;
}
onSelectedRowKeysChange(Array.from(selection).map(String));
};
const emptyState = <DataTableEmptyState title={emptyTitle} />;
return (
<div
className={['pm-data-table-selection-scope', className]
.filter(Boolean)
.join(' ')}
>
<div
className={[
'pm-data-grid-wrap',
rows.length > 0 ? 'pm-data-grid-wrap--populated' : '',
!isLoading && rows.length === 0 ? 'pm-data-grid-wrap--empty' : '',
]
.filter(Boolean)
.join(' ')}
style={
resolvedContentMinWidth
? {
'--pm-data-grid-content-min-width': `${resolvedContentMinWidth}px`,
}
: undefined
}
>
{isLoading && rows.length > 0 ? (
<div className="pm-data-grid-loading" aria-label="数据加载中">
<Spinner size="sm" />
</div>
) : null}
<DataGrid
showSelectionCheckboxes={selectionEnabled}
allowsColumnResize
aria-label={ariaLabel}
className="pm-data-grid"
columns={gridColumns}
contentClassName={
wide
? 'pm-data-grid-content pm-data-grid-content--wide'
: 'pm-data-grid-content'
}
data={rows}
disabledKeys={disabledRowKeys}
getRowId={(row) => resolveRowKey(row, rows.indexOf(row), getRowKey)}
renderEmptyState={() =>
isLoading ? (
<DataTableLoadingState />
) : (
<span
className="pm-data-grid-native-empty-fallback"
aria-hidden="true"
/>
)
}
scrollContainerClassName="pm-data-grid-scroll"
selectedKeys={selectedKeys}
selectionMode={selectionEnabled ? 'multiple' : 'none'}
variant="primary"
verticalAlign="middle"
onRowAction={onRowAction}
sortDescriptor={sortDescriptor}
onSortChange={onSortChange}
onSelectionChange={
selectionEnabled ? handleSelectionChange : undefined
}
/>
{!isLoading && rows.length === 0 ? (
<div className="pm-data-grid-empty-overlay">{emptyState}</div>
) : null}
</div>
{selectionEnabled ? (
<DataTableActionBar
selectedCount={selectedRowKeys.length}
onClear={() => onSelectedRowKeysChange([])}
>
{renderSelectionActions ? (
renderSelectionActions(selectedRowKeys)
) : (
<span className="text-sm">已选择当前页记录</span>
)}
</DataTableActionBar>
) : null}
</div>
);
}
export function DataTableActionBar({
selectedCount,
onClear,
children,
ariaLabel = '批量操作',
}) {
return (
<ActionBar
className="pm-data-table-action-bar"
aria-label={ariaLabel}
isOpen={selectedCount > 0}
>
<ActionBar.Prefix>
<Chip className="shrink-0 tabular-nums" size="sm">
{selectedCount}
</Chip>
</ActionBar.Prefix>
<Separator orientation="vertical" />
<ActionBar.Content>{children}</ActionBar.Content>
<Separator orientation="vertical" />
<ActionBar.Suffix>
<Button
isIconOnly
aria-label="清空选择"
size="sm"
variant="ghost"
onPress={onClear}
>
<Close size={16} />
</Button>
</ActionBar.Suffix>
</ActionBar>
);
}
function flattenStructuralChildren(children) {
const flattened = [];
Children.forEach(children, (child) => {
if (child === null || child === undefined || typeof child === 'boolean') {
return;
}
if (isValidElement(child) && child.type === Fragment) {
flattened.push(...flattenStructuralChildren(child.props.children));
return;
}
flattened.push(child);
});
return flattened;
}
function parseTailwindWidth(className = '') {
const bracketMatch = className.match(
/(?:min-)?w-\[(\d+(?:\.\d+)?)(px|rem)\]/,
);
if (bracketMatch) {
const value = Number(bracketMatch[1]);
return bracketMatch[2] === 'rem' ? value * 16 : value;
}
const scaleMatch = className.match(/(?:min-)?w-(\d+)/);
return scaleMatch ? Number(scaleMatch[1]) * 4 : undefined;
}
function extractText(node) {
if (typeof node === 'string' || typeof node === 'number') {
return String(node);
}
if (!isValidElement(node)) {
return '';
}
return flattenStructuralChildren(node.props.children)
.map(extractText)
.join('')
.trim();
}
function isSafeTooltipContent(node) {
if (node === null || node === undefined || typeof node !== 'object') {
return true;
}
if (!isValidElement(node)) {
return true;
}
if (node.type === Fragment) {
return flattenStructuralChildren(node.props.children).every(
isSafeTooltipContent,
);
}
if (typeof node.type !== 'string') {
return false;
}
if (['a', 'button', 'input', 'select', 'textarea'].includes(node.type)) {
return false;
}
return flattenStructuralChildren(node.props.children).every(
isSafeTooltipContent,
);
}
function normalizeGridDateText(value) {
const match = String(value || '')
.trim()
.match(/^(\d{4})\/(\d{1,2})\/(\d{1,2})$/);
if (!match) return '';
return `${match[1]}-${match[2].padStart(2, '0')}-${match[3].padStart(
2,
'0',
)}`;
}
function sanitizeLegacyTableClassName(className = '') {
return String(className)
.split(/\s+/)
.filter(
(token) =>
token && !/^(?:hidden|(?:sm|md|lg|xl|2xl):table-cell)$/.test(token),
)
.join(' ');
}
export function DataTableText({
children,
text,
className = '',
showTooltip = false,
}) {
const fullText = String(text ?? children ?? '').trim();
const trigger = showTooltip ? (
<DataTableTextTooltipTrigger
className={className}
text={fullText}
>
{children ?? (fullText || '-')}
</DataTableTextTooltipTrigger>
) : (
<span
className={['pm-data-grid-truncate', className]
.filter(Boolean)
.join(' ')}
>
{children ?? (fullText || '-')}
</span>
);
if (
!fullText ||
fullText === '-' ||
(!showTooltip && fullText.length <= 18)
) {
return trigger;
}
return (
<DataTableTextTooltipTrigger className={className} text={fullText}>
{children ?? (fullText || '-')}
</DataTableTextTooltipTrigger>
);
}
function DataTableTextTooltipTrigger({
children,
className = '',
text,
truncate = true,
}) {
const triggerRef = useRef(null);
const [tooltip, setTooltip] = useState(null);
const showTooltip = useCallback(() => {
const element = triggerRef.current;
if (!element || typeof window === 'undefined') {
return;
}
const rect = element.getBoundingClientRect();
const viewportPadding = 16;
const maxWidth = Math.min(420, window.innerWidth - viewportPadding * 2);
const left = Math.min(
Math.max(rect.left, viewportPadding),
Math.max(viewportPadding, window.innerWidth - viewportPadding - maxWidth),
);
const shouldPlaceAbove = rect.bottom + 140 > window.innerHeight;
setTooltip({
left,
maxWidth,
placement: shouldPlaceAbove ? 'top' : 'bottom',
text,
top: shouldPlaceAbove ? rect.top - 8 : rect.bottom + 8,
});
}, [text]);
const hideTooltip = useCallback(() => {
setTooltip(null);
}, []);
return (
<>
<span
ref={triggerRef}
className={[
truncate ? 'pm-data-grid-truncate' : '',
'pm-data-grid-tooltip-trigger',
className,
]
.filter(Boolean)
.join(' ')}
tabIndex={0}
onBlur={hideTooltip}
onFocus={showTooltip}
onMouseEnter={showTooltip}
onMouseLeave={hideTooltip}
>
{children}
</span>
{tooltip && typeof document !== 'undefined'
? createPortal(
<div
className={[
'pm-data-grid-text-tooltip',
tooltip.placement === 'top'
? 'pm-data-grid-text-tooltip--top'
: '',
]
.filter(Boolean)
.join(' ')}
style={{
left: tooltip.left,
maxWidth: tooltip.maxWidth,
top: tooltip.top,
}}
>
{tooltip.text}
</div>,
document.body,
)
: null}
</>
);
}
export function DataTablePrimaryText({
children,
text,
className = '',
showTooltip = false,
}) {
return (
<DataTableText
text={text}
showTooltip={showTooltip}
className={['pm-data-grid-primary', className].filter(Boolean).join(' ')}
>
{children}
</DataTableText>
);
}
export function DataTablePrimaryAction({
children,
text,
className = '',
onPress,
onClick,
isDisabled = false,
disabled = false,
ariaLabel,
}) {
const fullText = String(text ?? children ?? '').trim() || '-';
const handlePress = onPress || onClick;
return (
<Tooltip>
<Button
aria-label={ariaLabel || fullText}
className={['pm-data-grid-primary-action', className]
.filter(Boolean)
.join(' ')}
isDisabled={isDisabled || disabled}
size="sm"
variant="ghost"
onPress={handlePress}
>
<span className="pm-data-grid-primary pm-data-grid-truncate">
{children ?? fullText}
</span>
</Button>
<Tooltip.Content>{fullText}</Tooltip.Content>
</Tooltip>
);
}
export function DataTableSecondaryText({
children,
text,
className = '',
showTooltip = false,
}) {
return (
<DataTableText
text={text}
showTooltip={showTooltip}
className={['pm-data-grid-secondary', className]
.filter(Boolean)
.join(' ')}
>
{children}
</DataTableText>
);
}
export function DataTableTextPair({
primary,
secondary,
className = '',
primaryClassName = '',
secondaryClassName = '',
}) {
const primaryText = String(primary ?? '').trim() || '-';
const hasSecondary =
secondary !== undefined && secondary !== null && secondary !== '';
const secondaryText = hasSecondary ? String(secondary).trim() : '';
const tooltipText = [primaryText, secondaryText].filter(Boolean).join('\n');
return (
<DataTableTextTooltipTrigger
className={['pm-data-grid-text-pair', className]
.filter(Boolean)
.join(' ')}
text={tooltipText}
truncate={false}
>
<span
className={['pm-data-grid-truncate pm-data-grid-primary', primaryClassName]
.filter(Boolean)
.join(' ')}
>
{primaryText}
</span>
{hasSecondary ? (
<span
className={[
'pm-data-grid-truncate pm-data-grid-secondary',
secondaryClassName,
]
.filter(Boolean)
.join(' ')}
>
{secondaryText}
</span>
) : null}
</DataTableTextTooltipTrigger>
);
}
export function DataTablePersonIdentity({ name, account, className = '' }) {
const primaryText = String(name || '').trim() || '-';
const secondaryText = String(account || '').trim();
return (
<DataTableTextPair
primary={primaryText}
secondary={secondaryText}
className={['pm-data-grid-person-identity', className]
.filter(Boolean)
.join(' ')}
/>
);
}
export function DataTableChip({ children, text, className = '' }) {
const fullText = String(text ?? children ?? '').trim();
const trigger = (
<span
className={['pm-data-grid-chip', className].filter(Boolean).join(' ')}
>
<span className="pm-data-grid-chip__label">
{children ?? (fullText || '-')}
</span>
</span>
);
if (!fullText || fullText === '-' || fullText.length <= 18) {
return trigger;
}
return (
<Tooltip delay={0}>
{trigger}
<Tooltip.Content>{fullText}</Tooltip.Content>
</Tooltip>
);
}
function TableHead() {
return null;
}
function TableBody() {
return null;
}
function TableRow() {
return null;
}
function TableHeader() {
return null;
}
function TableCell() {
return null;
}
const PRIMARY_COLUMN_TITLES = new Set([
'项目',
'项目名称',
'预算标题',
'客户全称',
'姓名',
'项目执行情况',
'申请人',
]);
function getSemanticColumnWidth(titleText, { isActions, isSelection }) {
if (isSelection) return 56;
if (isActions) return 112;
if (PRIMARY_COLUMN_TITLES.has(titleText)) return 280;
if (/休假说明|事由|备注|地址|说明/.test(titleText)) return 320;
if (/工作内容/.test(titleText)) return 220;
if (/时间范围|日期范围/.test(titleText)) return 220;
if (/更新时间|创建时间/.test(titleText)) return 180;
if (/工作日期/.test(titleText)) return 180;
if (/开始时间|结束时间|日期/.test(titleText)) return 140;
if (/项目类型|业务类型|分类|背景|职级/.test(titleText)) return 200;
if (/邮箱/.test(titleText)) return 220;
if (/手机|电话/.test(titleText)) return 150;
if (/代码|编码/.test(titleText)) return 140;
if (/状态|是否|同步/.test(titleText)) return 130;
if (
/申请人|预订人|成员|经理|合伙人|创建人|人员|部门|分组|职位/.test(titleText)
)
return 180;
return 128;
}
export { TableBody, TableCell, TableHead, TableHeader, TableRow };
export function Table({ children, className = '', ...props }) {
const sections = flattenStructuralChildren(children);
const head = sections.find((child) => child?.type === TableHead);
const body = sections.find((child) => child?.type === TableBody);
const headerRow = flattenStructuralChildren(head?.props?.children).find(
(child) => child?.type === TableRow,
);
const headers = flattenStructuralChildren(headerRow?.props?.children).filter(
(child) => child?.type === TableHeader,
);
const bodyRows = flattenStructuralChildren(body?.props?.children).filter(
(child) => child?.type === TableRow,
);
const inferredWide =
headers.length > 6 || /min-w-\[(?:1\d{3,}|[2-9]\d{3,})px\]/.test(className);
const parsedRows = bodyRows.map((row, rowIndex) => {
const cells = flattenStructuralChildren(row.props.children).filter(
(child) => child?.type === TableCell,
);
return {
__key: String(row.key ?? rowIndex),
__cells: cells,
__rowClassName: row.props.className || '',
};
});
const emptyRow = parsedRows.find((row) => {
const cell = row.__cells[0];
return (
row.__cells.length === 1 &&
Number(cell?.props?.colSpan || 0) >= Math.max(headers.length, 1)
);
});
const emptyText = emptyRow ? extractText(emptyRow.__cells[0]) : '';
const dataRows = emptyRow
? parsedRows.filter((row) => row !== emptyRow)
: parsedRows;
const columns = headers.map((header, index) => {
const headerClassName = header.props.className || '';
const title = header.props.children;
const titleText = extractText(title);
const width = parseTailwindWidth(headerClassName);
const isActions = titleText === '操作';
const isSelection = index === 0 && !titleText;
let pin;
if (
inferredWide &&
(/sticky[^]*left-0|left-0[^]*sticky/.test(headerClassName) ||
isSelection ||
PRIMARY_COLUMN_TITLES.has(titleText))
) {
pin = 'start';
} else if (
inferredWide &&
(/sticky[^]*right-0|right-0[^]*sticky/.test(headerClassName) || isActions)
) {
pin = 'end';
}
return {
key: `column-${index}`,
title,
width,
minWidth:
width || getSemanticColumnWidth(titleText, { isActions, isSelection }),
pin,
isActions,
isPrimary: PRIMARY_COLUMN_TITLES.has(titleText),
headerClassName: sanitizeLegacyTableClassName(headerClassName),
render: (row) => {
const cell = row.__cells[index];
if (!cell) {
return '-';
}
const content = cell.props.children;
const fullText = extractText(content);
const normalizedDate = normalizeGridDateText(fullText);
const renderedContent = normalizedDate ? (
normalizedDate
) : fullText.length > 18 && isSafeTooltipContent(content) ? (
<DataTableText text={fullText}>{content}</DataTableText>
) : (
content
);
return (
<div
className={[
'pm-data-grid-cell-content',
PRIMARY_COLUMN_TITLES.has(titleText) &&
(typeof content === 'string' || typeof content === 'number')
? 'pm-data-grid-primary'
: '',
sanitizeLegacyTableClassName(cell.props.className),
]
.filter(Boolean)
.join(' ')}
>
{renderedContent}
</div>
);
},
};
});
return (
<DataTable
{...props}
ariaLabel={props['aria-label'] || '业务数据列表'}
className="pm-data-table-compat"
columns={columns}
rows={dataRows}
getRowKey={(row) => row.__key}
isLoading={emptyText.includes('加载中')}
isWide={inferredWide}
emptyTitle={
emptyText && !emptyText.includes('加载中') ? emptyText : '暂无数据'
}
emptyDescription="当前没有可显示的记录。"
/>
);
}
export function DataTablePagination({
page,
pageSize,
total,
onPageChange,
pageSizeOptions = DATA_TABLE_PAGE_SIZE_OPTIONS,
onPageSizeChange,
isDisabled = false,
itemLabel = '条',
}) {
const totalPages = Math.max(1, Math.ceil(total / pageSize));
const pageWindowStart = Math.max(0, Math.min(page - 2, totalPages - 5));
const pageWindowEnd = total
? Math.min(totalPages, pageWindowStart + 5)
: pageWindowStart;
const hasPageSizeControl =
pageSizeOptions.length > 0 && Boolean(onPageSizeChange);
return (
<div className="pm-pagination-bar">
<Pagination size="sm" className="w-full">
<Pagination.Summary>
共 {total} {itemLabel},
{hasPageSizeControl ? null : <>每页 {pageSize} 条,</>}第{' '}
{total ? page + 1 : 0} / {total ? totalPages : 0} 页
</Pagination.Summary>
<Pagination.Content>
<Pagination.Item>
<Pagination.Previous
isDisabled={isDisabled || page <= 0}
onPress={() => onPageChange(page - 1)}
>
<Pagination.PreviousIcon />
<span>上一页</span>
</Pagination.Previous>
</Pagination.Item>
{Array.from(
{ length: pageWindowEnd - pageWindowStart },
(_, index) => {
const pageIndex = pageWindowStart + index;
return (
<Pagination.Item key={pageIndex}>
<Pagination.Link
isActive={pageIndex === page}
isDisabled={isDisabled}
onPress={() => onPageChange(pageIndex)}
>
{pageIndex + 1}
</Pagination.Link>
</Pagination.Item>
);
},
)}
<Pagination.Item>
<Pagination.Next
isDisabled={isDisabled || page + 1 >= totalPages || total === 0}
onPress={() => onPageChange(page + 1)}
>
<span>下一页</span>
<Pagination.NextIcon />
</Pagination.Next>
</Pagination.Item>
</Pagination.Content>
</Pagination>
{hasPageSizeControl ? (
<div className="pm-pagination-controls">
<Select
aria-label="每页条数"
className="pm-pagination-page-size-select"
isDisabled={isDisabled}
selectedKey={String(pageSize)}
variant="secondary"
onSelectionChange={(key) => onPageSizeChange(Number(key))}
>
<Select.Trigger>
<span className="pm-pagination-page-size-copy">
<span className="pm-pagination-page-size-label">每页</span>
<Select.Value className="pm-pagination-page-size-value">
{pageSize} 条/页
</Select.Value>
</span>
<ChevronDown className="pm-pagination-page-size-indicator" />
</Select.Trigger>
<Select.Popover
className="rc-form-select__popover rc-filter-select__popover"
placement="bottom start"
>
<ListBox aria-label="每页条数">
{pageSizeOptions.map((option) => (
<ListBox.Item
key={option}
id={String(option)}
className="rc-form-select__option rc-filter-select__option"
textValue={`${option} 条/页`}
>
<Label>{option} 条/页</Label>
<ListBox.ItemIndicator />
</ListBox.Item>
))}
</ListBox>
</Select.Popover>
</Select>
</div>
) : null}
</div>
);
}
import { Upload } from '@/components/AppIcons';
import { DropZone } from '@heroui-pro/react/drop-zone';
import { useCallback } from 'react';
function createFileChangeEvent(files) {
return {
target: { files },
currentTarget: { files },
};
}
export default function FileUploadField({
accept,
ariaLabel = '选择文件',
className = '',
description = '将文件拖到此处,或从本地选择。',
disabled = false,
fileName = '',
onChange,
triggerLabel = '选择文件',
}) {
const emitFiles = useCallback(
(files) => {
if (!disabled) {
onChange?.(createFileChangeEvent(files));
}
},
[disabled, onChange],
);
const handleDrop = useCallback(
async (event) => {
const fileItems = Array.from(event?.items || []).filter(
(item) => item?.kind === 'file' && typeof item.getFile === 'function',
);
if (fileItems.length === 0) return;
const files = await Promise.all(fileItems.map((item) => item.getFile()));
emitFiles(files.slice(0, 1));
},
[emitFiles],
);
return (
<DropZone className={`rc-file-upload ${className}`.trim()}>
<DropZone.Area
aria-label={ariaLabel}
isDisabled={disabled}
onDrop={handleDrop}
>
<DropZone.Input
accept={accept}
disabled={disabled}
onSelect={emitFiles}
/>
<DropZone.Icon>
<Upload aria-hidden="true" />
</DropZone.Icon>
<DropZone.Label>{fileName || '将文件拖到此处'}</DropZone.Label>
<DropZone.Description>
{fileName ? '已选择文件,可重新选择进行替换。' : description}
</DropZone.Description>
<DropZone.Trigger isDisabled={disabled}>
<Upload aria-hidden="true" />
{triggerLabel}
</DropZone.Trigger>
</DropZone.Area>
</DropZone>
);
}
import { Label, ListBox, Select } from '@heroui/react';
import clsx from 'clsx';
import { useMemo } from 'react';
import { ChevronDown } from './AppIcons';
const EMPTY_OPTION_KEY = '__rc_filter_empty__';
function normalizeOption(option) {
if (option && typeof option === 'object') {
return {
label: String(option.label ?? option.value ?? ''),
value: String(option.value ?? ''),
disabled: Boolean(option.disabled),
};
}
return {
label: String(option ?? ''),
value: String(option ?? ''),
disabled: false,
};
}
export default function FilterSelect({
'aria-label': ariaLabel,
className,
disabled = false,
emptyText = '暂无可选项',
onChange,
options = [],
placeholder = '请选择',
value = '',
}) {
const normalizedOptions = useMemo(
() => options.map(normalizeOption),
[options],
);
const selectedValue = String(value ?? '');
const selectedOption = normalizedOptions.find(
(option) => option.value === selectedValue,
);
const selectedKey = selectedValue === '' ? EMPTY_OPTION_KEY : selectedValue;
return (
<Select
aria-label={ariaLabel || placeholder}
className={clsx('rc-filter-select', className)}
fullWidth
isDisabled={disabled}
selectedKey={selectedKey}
variant="secondary"
onSelectionChange={(key) => {
const nextValue = key === EMPTY_OPTION_KEY ? '' : String(key ?? '');
onChange?.(nextValue);
}}
>
<Select.Trigger className="rc-filter-select__trigger">
<Select.Value className="rc-filter-select__value">
<span
className={clsx(
'block min-w-0 truncate',
!selectedOption && 'rc-filter-select__placeholder',
)}
title={selectedOption?.label || placeholder}
>
{selectedOption?.label || placeholder}
</span>
</Select.Value>
<ChevronDown className="rc-filter-select__indicator" />
</Select.Trigger>
<Select.Popover
className="rc-filter-select__popover"
placement="bottom start"
>
<ListBox
aria-label={ariaLabel || placeholder}
className="rc-filter-select__listbox"
>
{normalizedOptions.length > 0 ? (
normalizedOptions.map((option) => (
<ListBox.Item
key={option.value || EMPTY_OPTION_KEY}
id={option.value || EMPTY_OPTION_KEY}
className="rc-filter-select__option"
isDisabled={option.disabled}
textValue={option.label}
>
<Label
className="rc-filter-select__option-label"
title={option.label}
>
{option.label}
</Label>
<ListBox.ItemIndicator className="rc-filter-select__option-indicator" />
</ListBox.Item>
))
) : (
<div className="rc-filter-select__empty">{emptyText}</div>
)}
</ListBox>
</Select.Popover>
</Select>
);
}
import { Calendar as HeroCalendar } from '@heroui/react/calendar';
import { DateField } from '@heroui/react/date-field';
import { DatePicker } from '@heroui/react/date-picker';
import { TimeField } from '@heroui/react/time-field';
import { parseDate, parseDateTime, parseTime } from '@internationalized/date';
import clsx from 'clsx';
import { useMemo } from 'react';
import { Calendar as CalendarIcon } from './AppIcons';
function parseValue(value, mode) {
if (!value) {
return null;
}
try {
return mode === 'datetime'
? parseDateTime(String(value).slice(0, 16))
: parseDate(String(value).slice(0, 10));
} catch {
return null;
}
}
function formatValue(value, mode) {
if (!value) {
return '';
}
const rawValue = value.toString();
if (mode === 'date') {
return rawValue.slice(0, 10);
}
if (rawValue.includes('T')) {
return rawValue.slice(0, 16);
}
return `${rawValue.slice(0, 10)}T00:00`;
}
function parseTimeValue(value) {
if (!value || !String(value).includes('T')) {
return null;
}
try {
return parseTime(String(value).slice(11, 16));
} catch {
return null;
}
}
function formatTimeValue(value) {
return value ? value.toString().slice(0, 5) : '00:00';
}
function combineDateAndTime(value, dateValue, timeValue) {
const dateText = dateValue
? dateValue.toString().slice(0, 10)
: String(value || '').slice(0, 10);
if (!dateText) {
return '';
}
return `${dateText}T${formatTimeValue(timeValue)}`;
}
function emitDateChange(onChange, name, nextValue) {
onChange?.({
target: {
name,
value: nextValue,
},
});
}
export default function FormDatePicker({
'aria-label': ariaLabel,
className,
disabled = false,
granularity,
max,
min,
mode,
name,
onChange,
readOnly = false,
required = false,
type = 'date',
value,
...props
}) {
const resolvedMode =
mode === 'datetime' || type === 'datetime-local' ? 'datetime' : 'date';
const resolvedGranularity =
granularity || (resolvedMode === 'datetime' ? 'minute' : 'day');
const parsedValue = useMemo(
() => parseValue(value, resolvedMode),
[resolvedMode, value],
);
const minValue = useMemo(() => parseValue(min, resolvedMode), [min, resolvedMode]);
const maxValue = useMemo(() => parseValue(max, resolvedMode), [max, resolvedMode]);
const timeValue = useMemo(() => parseTimeValue(value), [value]);
return (
<DatePicker
{...props}
aria-label={ariaLabel || '选择日期'}
className={clsx('rc-form-date-picker', className)}
granularity={resolvedGranularity}
isDisabled={disabled}
isReadOnly={readOnly}
isRequired={required}
maxValue={maxValue || undefined}
minValue={minValue || undefined}
value={parsedValue}
onChange={(nextValue) =>
emitDateChange(onChange, name, formatValue(nextValue, resolvedMode))
}
>
<DateField.Group
className="rc-form-date-picker__group"
fullWidth
variant="secondary"
>
<DateField.Input className="rc-form-date-picker__input">
{(segment) => (
<DateField.Segment
className="rc-form-date-picker__segment"
segment={segment}
/>
)}
</DateField.Input>
<DateField.Suffix className="rc-form-date-picker__suffix">
<DatePicker.Trigger
className="rc-form-date-picker__trigger"
aria-label="打开日期选择"
>
<DatePicker.TriggerIndicator>
<CalendarIcon />
</DatePicker.TriggerIndicator>
</DatePicker.Trigger>
</DateField.Suffix>
</DateField.Group>
<DatePicker.Popover
className="rc-form-date-picker__popover"
placement="bottom start"
>
<HeroCalendar className="rc-form-date-picker__calendar">
<HeroCalendar.Header>
<HeroCalendar.NavButton slot="previous" />
<HeroCalendar.Heading />
<HeroCalendar.NavButton slot="next" />
</HeroCalendar.Header>
<HeroCalendar.Grid>
<HeroCalendar.GridHeader>
{(day) => <HeroCalendar.HeaderCell>{day}</HeroCalendar.HeaderCell>}
</HeroCalendar.GridHeader>
<HeroCalendar.GridBody>
{(date) => <HeroCalendar.Cell date={date} />}
</HeroCalendar.GridBody>
</HeroCalendar.Grid>
</HeroCalendar>
{resolvedMode === 'datetime' ? (
<div className="rc-form-date-picker__time-panel">
<span className="rc-form-date-picker__time-label">时间</span>
<TimeField
aria-label="选择时间"
className="rc-form-date-picker__time-field"
granularity="minute"
hideTimeZone
hourCycle={24}
value={timeValue}
onChange={(nextTime) =>
emitDateChange(
onChange,
name,
combineDateAndTime(value, parsedValue, nextTime),
)
}
>
<TimeField.Group
className="rc-form-date-picker__time-group"
variant="secondary"
>
<TimeField.Input className="rc-form-date-picker__time-input">
{(segment) => (
<TimeField.Segment
className="rc-form-date-picker__segment"
segment={segment}
/>
)}
</TimeField.Input>
</TimeField.Group>
</TimeField>
</div>
) : null}
</DatePicker.Popover>
</DatePicker>
);
}
import { Label, ListBox, Select } from '@heroui/react';
import clsx from 'clsx';
import { Children, Fragment, isValidElement, useMemo } from 'react';
import { ChevronDown } from './AppIcons';
const EMPTY_OPTION_KEY = '__rc_form_select_empty__';
function extractText(node) {
if (node === null || node === undefined || typeof node === 'boolean') {
return '';
}
if (typeof node === 'string' || typeof node === 'number') {
return String(node);
}
if (Array.isArray(node)) {
return node.map(extractText).join('');
}
if (isValidElement(node)) {
return extractText(node.props.children);
}
return '';
}
function normalizeOptions(children) {
const options = [];
Children.forEach(children, (child) => {
if (!isValidElement(child)) {
return;
}
if (child.type === Fragment || child.type === 'optgroup') {
options.push(...normalizeOptions(child.props.children));
return;
}
if (child.type !== 'option') {
return;
}
options.push({
disabled: Boolean(child.props.disabled),
label: extractText(child.props.children),
value: String(child.props.value ?? ''),
});
});
return options;
}
function toOptionKey(value) {
return value === '' ? EMPTY_OPTION_KEY : String(value);
}
function fromOptionKey(key) {
return key === EMPTY_OPTION_KEY ? '' : String(key ?? '');
}
export default function FormSelect({
'aria-label': ariaLabel,
children,
className,
disabled = false,
multiple = false,
name,
onChange,
placeholder,
required = false,
value,
...props
}) {
const options = useMemo(() => normalizeOptions(children), [children]);
const emptyOption = options.find((option) => option.value === '');
const resolvedPlaceholder = placeholder || emptyOption?.label || '请选择';
const selectedValues = multiple
? (Array.isArray(value) ? value : []).map(String)
: [String(value ?? '')];
const selectedOptions = options.filter(
(option) => option.value !== '' && selectedValues.includes(option.value),
);
const displayText = multiple
? selectedOptions.length <= 2
? selectedOptions.map((option) => option.label).join('、')
: `${selectedOptions
.slice(0, 2)
.map((option) => option.label)
.join('、')} 等 ${selectedOptions.length} 项`
: options.find((option) => option.value === selectedValues[0])?.label ||
resolvedPlaceholder;
const hasSelection = multiple
? selectedOptions.length > 0
: selectedValues[0] !== '';
const emitChange = (nextValues) => {
const normalizedValues = nextValues.map(fromOptionKey);
onChange?.({
target: {
name,
selectedOptions: normalizedValues.map((nextValue) => ({
value: nextValue,
})),
value: normalizedValues[0] || '',
},
});
};
const sharedProps = {
...props,
'aria-label': ariaLabel || resolvedPlaceholder,
className: clsx('rc-form-select rc-filter-select', className),
fullWidth: true,
isDisabled: disabled,
isRequired: required,
selectionMode: multiple ? 'multiple' : 'single',
variant: 'secondary',
};
if (multiple) {
sharedProps.selectedKeys = new Set(selectedValues.map(toOptionKey));
sharedProps.onSelectionChange = (keys) => {
const nextKeys =
keys === 'all' ? options.map((option) => option.value) : [...keys];
emitChange(nextKeys);
};
} else {
sharedProps.selectedKey = toOptionKey(selectedValues[0]);
sharedProps.onSelectionChange = (key) => emitChange([key]);
}
return (
<Select {...sharedProps}>
<Select.Trigger className="rc-form-select__trigger rc-filter-select__trigger">
<Select.Value className="rc-form-select__value rc-filter-select__value">
<span
className={clsx(
'block min-w-0 truncate',
!hasSelection && 'rc-filter-select__placeholder',
)}
title={displayText}
>
{displayText}
</span>
</Select.Value>
<ChevronDown className="rc-filter-select__indicator" />
</Select.Trigger>
<Select.Popover
className="rc-form-select__popover rc-filter-select__popover"
placement="bottom start"
>
<ListBox
aria-label={ariaLabel || resolvedPlaceholder}
className="rc-filter-select__listbox"
selectionMode={multiple ? 'multiple' : 'single'}
>
{options.map((option) => (
<ListBox.Item
key={toOptionKey(option.value)}
id={toOptionKey(option.value)}
className="rc-form-select__option rc-filter-select__option"
isDisabled={option.disabled}
textValue={option.label}
>
<Label
className="rc-filter-select__option-label"
title={option.label}
>
{option.label}
</Label>
<ListBox.ItemIndicator className="rc-filter-select__option-indicator" />
</ListBox.Item>
))}
</ListBox>
</Select.Popover>
</Select>
);
}
import {
Back,
Check,
Clock,
Close,
Delete,
Edit,
Eye,
More,
Send,
} from '@/components/AppIcons';
import { Button, Dropdown, Label, Tooltip } from '@heroui/react';
import clsx from 'clsx';
import { Children, Fragment, isValidElement } from 'react';
const STICKY_ACTION_BASE_HEADER_CLASS =
'sticky right-0 z-10 bg-[var(--rc-data-grid-header-background)] text-left';
const STICKY_ACTION_BASE_CELL_CLASS =
'sticky right-0 z-[1] bg-[var(--rc-bg-surface)] group-hover:bg-[var(--rc-data-grid-row-hover-background)] hover:z-40 focus-within:z-40';
const STICKY_FIRST_COLUMN_BASE_HEADER_CLASS =
'sticky left-0 z-20 bg-[var(--rc-data-grid-header-background)] text-left';
const STICKY_FIRST_COLUMN_BASE_CELL_CLASS =
'sticky left-0 z-[2] bg-[var(--rc-bg-surface)] group-hover:bg-[var(--rc-data-grid-row-hover-background)] hover:z-30 focus-within:z-30';
const ACTION_COLUMN_WIDTHS = {
icon: 'w-[4.5rem] min-w-[4.5rem]',
single: 'w-[5rem] min-w-[5rem]',
double: 'w-[7rem] min-w-[7rem]',
compact: 'w-[9rem] min-w-[9rem]',
medium: 'w-[11rem] min-w-[11rem]',
wide: 'w-[14rem] min-w-[14rem]',
xwide: 'w-[18rem] min-w-[18rem]',
};
export const PROJECT_MANAGER_LIST_TABLE_CLASS =
'rounded-lg border border-zinc-200 bg-white text-sm text-zinc-700 [&_thead]:bg-zinc-50/70 [&_thead_th]:border-b [&_thead_th]:border-zinc-200 [&_thead_th]:px-4 [&_thead_th]:py-3 [&_thead_th]:text-sm [&_thead_th]:font-semibold [&_thead_th]:text-zinc-500 [&_tbody_td]:px-4 [&_tbody_td]:text-sm [&_tbody_td]:text-zinc-700';
export const PROJECT_MANAGER_LIST_ROW_CLASS =
'group border-b border-zinc-100 transition-colors [&>td]:bg-zinc-50 [&>td]:py-3.5 hover:[&>td]:bg-zinc-100';
export const PROJECT_MANAGER_LIST_EDITABLE_CONTROL_CLASS =
'[&_input]:h-9 [&_input]:rounded-md [&_input]:border-zinc-200 [&_input]:bg-zinc-50/60 [&_input]:text-sm [&_input]:font-normal [&_input]:text-zinc-600 [&_input]:placeholder-zinc-400 [&_input]:shadow-none [&_input:disabled]:bg-zinc-50 [&_input:disabled]:text-zinc-400 [&_select]:h-9 [&_select]:rounded-md [&_select]:border-zinc-200 [&_select]:bg-zinc-50/60 [&_select]:text-sm [&_select]:text-zinc-600 [&_select]:shadow-none [&_select:disabled]:bg-zinc-50 [&_select:disabled]:text-zinc-400';
const ACTION_TONE_CLASSES = {
approve: 'pm-table-action--approve',
delete: 'pm-table-action--danger',
edit: 'pm-table-action--default',
history: 'pm-table-action--default',
reject: 'pm-table-action--danger',
revoke: 'pm-table-action--warning',
submit: 'pm-table-action--primary',
view: 'pm-table-action--default',
};
const ACTION_COLOR_TONE = {
blue: 'submit',
green: 'approve',
red: 'delete',
};
function resolveActionTone({ color, icon, label, outline, tone }) {
if (tone) {
return tone;
}
const iconTone = Object.entries(TABLE_ACTION_ICONS).find(
([, ActionIcon]) => ActionIcon === icon,
)?.[0];
if (iconTone) {
return iconTone;
}
if (/删除|移除|作废|清退|delete|remove|trash/i.test(String(label || ''))) {
return 'delete';
}
if (/驳回|拒绝|reject/i.test(String(label || ''))) {
return 'reject';
}
return ACTION_COLOR_TONE[color] || (outline ? 'edit' : 'view');
}
export function getStickyActionHeaderClass(size = 'medium') {
return clsx(
STICKY_ACTION_BASE_HEADER_CLASS,
ACTION_COLUMN_WIDTHS[size] || ACTION_COLUMN_WIDTHS.medium,
);
}
export function getStickyActionCellClass(size = 'medium') {
return clsx(
STICKY_ACTION_BASE_CELL_CLASS,
ACTION_COLUMN_WIDTHS[size] || ACTION_COLUMN_WIDTHS.medium,
);
}
export function getStickyFirstColumnHeaderClass(className) {
return clsx(STICKY_FIRST_COLUMN_BASE_HEADER_CLASS, className);
}
export function getStickyFirstColumnCellClass(className) {
return clsx(STICKY_FIRST_COLUMN_BASE_CELL_CLASS, className);
}
export const TABLE_ACTION_ICONS = {
approve: Check,
delete: Delete,
edit: Edit,
history: Clock,
reject: Close,
revoke: Back,
submit: Send,
view: Eye,
};
function flattenActions(children) {
const actions = [];
Children.forEach(children, (child) => {
if (!child) {
return;
}
if (isValidElement(child) && child.type === Fragment) {
actions.push(...flattenActions(child.props.children));
return;
}
actions.push(child);
});
return actions;
}
export function TableActionGroup({ className, children, ...props }) {
const actions = flattenActions(children);
const shouldCollapseOverflow = actions.length >= 4;
const visibleActions = shouldCollapseOverflow ? actions.slice(0, 2) : actions;
const overflowActions = shouldCollapseOverflow ? actions.slice(2) : [];
return (
<div
{...props}
className={clsx(
'icon-actions flex flex-nowrap items-center justify-end gap-1.5 whitespace-nowrap',
className,
)}
>
{visibleActions}
{overflowActions.length > 0 ? (
<Dropdown>
<Tooltip delay={0}>
<Button
isIconOnly
size="sm"
variant="ghost"
className="pm-table-action pm-table-action--default inline-flex size-8 items-center justify-center rounded-full"
aria-label="更多操作"
title="更多操作"
>
<More size={16} />
</Button>
<Tooltip.Content>更多操作</Tooltip.Content>
</Tooltip>
<Dropdown.Popover className="rc-dropdown-popover min-w-36" placement="bottom end">
<Dropdown.Menu
aria-label="更多操作"
onAction={(key) => overflowActions[Number(key)]?.props?.onClick?.()}
>
{overflowActions.map((action, index) => {
const label = action?.props?.label || `操作 ${index + 3}`;
const Icon = action?.props?.icon;
return (
<Dropdown.Item
id={String(index)}
isDisabled={action?.props?.disabled}
key={`${label}-${index}`}
className="rc-dropdown-option"
textValue={label}
>
<span className="flex items-center gap-2">
{Icon ? <Icon className="size-4" aria-hidden="true" /> : null}
<Label>{label}</Label>
</span>
</Dropdown.Item>
);
})}
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown>
) : null}
</div>
);
}
export function TableActionButton({
className,
color,
disabled,
icon: Icon,
label,
onClick,
outline = false,
tone,
...props
}) {
const resolvedTone = resolveActionTone({
color,
icon: Icon,
label,
outline,
tone,
});
return (
<Tooltip delay={0}>
<Button
{...props}
isIconOnly
aria-label={label}
title={label}
isDisabled={disabled}
size="sm"
variant="ghost"
onClick={onClick}
className={clsx(
'pm-table-action inline-flex size-8 items-center justify-center rounded-full transition-colors disabled:cursor-not-allowed disabled:opacity-50',
ACTION_TONE_CLASSES[resolvedTone] || ACTION_TONE_CLASSES.view,
className,
)}
>
<Icon size={16} />
</Button>
<Tooltip.Content>{label}</Tooltip.Content>
</Tooltip>
);
}
.rc-app-shell {
position: fixed;
inset: 0;
display: flex;
overflow: hidden;
color: var(--rc-text-primary);
background: var(--rc-bg-canvas);
}
.rc-sidebar,
.rc-mobile-sidebar {
display: flex;
flex-direction: column;
color: var(--foreground);
background: var(--rc-bg-canvas);
}
.rc-sidebar {
position: relative;
z-index: 30;
width: 15rem;
height: 100%;
flex: none;
padding: var(--rc-space-4) var(--rc-space-3) var(--rc-space-3);
transition: width var(--rc-motion-normal) var(--rc-ease-standard);
}
.rc-sidebar.is-collapsed {
width: 4rem;
}
.rc-sidebar-brand {
width: 100%;
min-width: 0;
display: flex;
min-height: 3rem;
align-items: center;
gap: 0.625rem;
padding: var(--rc-space-2) var(--rc-space-1);
color: var(--foreground);
font-size: var(--rc-text-sm);
font-weight: var(--rc-font-semibold);
}
.rc-sidebar-brand.is-collapsed {
justify-content: center;
padding-inline: 0;
}
.rc-sidebar-brand-mark {
display: flex;
width: 1.75rem;
height: 1.75rem;
flex: none;
align-items: center;
justify-content: center;
overflow: hidden;
}
.rc-sidebar-logo {
display: block;
width: 100%;
height: 100%;
object-fit: contain;
}
.rc-sidebar-brand-main {
display: flex;
min-width: 0;
flex: 1;
align-items: center;
gap: 0.5rem;
}
.rc-sidebar-app-icon {
display: inline-flex;
width: 1.75rem;
height: 1.75rem;
flex: none;
align-items: center;
justify-content: center;
border-radius: var(--rc-radius-control);
color: var(--rc-brand-light);
background: rgb(255 255 255 / 8%);
}
.rc-sidebar-title {
min-width: 0;
overflow: hidden;
color: var(--foreground);
font-size: 0.95rem;
font-weight: 650;
line-height: 1.2rem;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
text-overflow: ellipsis;
white-space: nowrap;
}
.rc-sidebar-brand-mark .rc-sidebar-logo {
width: 100%;
height: 100%;
object-fit: contain;
}
.rc-sidebar-app-switcher {
flex: none;
}
.rc-sidebar-app-popover {
width: min(24rem, calc(100vw - 2rem));
padding: 0;
border-radius: var(--rc-radius-overlay);
}
.rc-sidebar-app-dialog {
max-height: min(28rem, calc(100dvh - 7rem));
padding: 0.75rem;
overflow-y: auto;
outline: none;
}
.rc-sidebar-content {
min-height: 0;
flex: 1;
overflow-y: auto;
padding-top: var(--rc-space-4);
scrollbar-width: thin;
}
.rc-sidebar-group + .rc-sidebar-group {
margin-top: var(--rc-space-5);
}
.rc-sidebar-group-label {
margin: 0 0 var(--rc-space-2);
padding: 0 var(--rc-space-3);
color: var(--muted);
font-size: var(--rc-text-xs);
font-weight: var(--rc-font-medium);
line-height: var(--rc-line-normal);
}
.rc-sidebar-menu {
display: grid;
gap: var(--rc-space-1);
}
.rc-sidebar-item {
position: relative;
display: flex;
min-width: 0;
height: 2.5rem;
align-items: center;
gap: var(--rc-space-3);
padding: 0 var(--rc-space-3);
border-radius: var(--rc-radius-control);
color: var(--muted);
font-size: var(--rc-text-sm);
font-weight: var(--rc-font-medium);
text-decoration: none;
transition: color var(--rc-motion-fast) var(--rc-ease-standard),
background var(--rc-motion-fast) var(--rc-ease-standard);
}
.rc-sidebar-item:hover {
color: var(--foreground);
background: var(--default);
}
.rc-sidebar-item.is-active {
color: var(--foreground);
background: var(--default);
}
.rc-sidebar-item.is-active::before {
position: absolute;
inset-block: 0.5rem;
left: 0;
width: 0.1875rem;
border-radius: 0 999px 999px 0;
background: var(--rc-brand-primary);
content: '';
}
.rc-sidebar-item.is-collapsed {
justify-content: center;
padding-inline: 0;
}
.rc-sidebar-item-icon {
flex: none;
font-size: 1rem;
}
.rc-sidebar-item.is-active .rc-sidebar-item-icon {
color: var(--rc-brand-light);
}
.rc-sidebar-item-label {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.rc-sidebar-state {
margin: var(--rc-space-2);
padding: var(--rc-space-3);
border-radius: var(--rc-radius-control);
color: var(--muted);
background: var(--default);
font-size: var(--rc-text-xs);
}
.rc-sidebar-state.is-error {
color: rgb(254 202 202);
background: rgb(201 54 54 / 14%);
}
.rc-sidebar.is-collapsed .rc-sidebar-state {
display: none;
}
.rc-sidebar-user {
display: flex;
width: 100%;
min-height: 3rem;
align-items: center;
gap: var(--rc-space-3);
padding: 0.375rem var(--rc-space-2);
border: 0;
border-radius: var(--rc-radius-container);
color: var(--foreground);
background: transparent;
cursor: pointer;
text-align: left;
}
.rc-sidebar-user:hover,
.rc-sidebar-user:focus-visible {
background: rgb(255 255 255 / 7%);
}
.rc-sidebar-user:focus-visible,
.rc-sidebar-item:focus-visible {
outline: 2px solid var(--rc-brand-light);
outline-offset: 2px;
}
.rc-sidebar-user.is-collapsed {
justify-content: center;
padding-inline: 0;
}
.rc-sidebar-avatar {
flex: none;
background: var(--rc-brand-primary);
}
.rc-sidebar-user-copy {
display: flex;
min-width: 0;
flex: 1;
flex-direction: column;
}
.rc-sidebar-user-copy strong,
.rc-sidebar-user-copy small {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.rc-sidebar-user-copy strong {
font-size: var(--rc-text-sm);
font-weight: var(--rc-font-medium);
}
.rc-sidebar-user-copy small {
margin-top: 0.125rem;
color: var(--muted);
font-size: var(--rc-text-xs);
}
.rc-sidebar-logout {
flex: none;
color: var(--muted);
font-size: 0.875rem;
}
.rc-app-main {
--rc-page-title-inset: calc(
var(--rc-space-6) + var(--rc-navbar-trigger-size) + var(--rc-space-3)
);
display: flex;
min-width: 0;
min-height: 0;
flex: 1;
flex-direction: column;
overflow: hidden;
}
.rc-navbar {
flex: none;
height: 4rem;
min-height: 4rem;
max-height: 4rem;
border: 0;
background: transparent;
box-shadow: none;
backdrop-filter: none;
}
.rc-navbar .navbar__header {
width: 100%;
height: 4rem;
min-height: 4rem;
max-height: 4rem;
gap: var(--rc-space-3);
padding: 0 var(--rc-space-6);
}
.rc-navbar [data-slot='sidebar-trigger'] {
width: var(--rc-navbar-trigger-size);
min-width: var(--rc-navbar-trigger-size);
max-width: var(--rc-navbar-trigger-size);
height: var(--rc-navbar-trigger-size);
flex: 0 0 var(--rc-navbar-trigger-size);
}
.rc-navbar-icon-button.button,
.rc-navbar-icon-button.toggle-button {
width: var(--rc-navbar-icon-button-size);
min-width: var(--rc-navbar-icon-button-size);
max-width: var(--rc-navbar-icon-button-size);
height: var(--rc-navbar-icon-button-size);
flex: 0 0 var(--rc-navbar-icon-button-size);
align-items: center;
justify-content: center;
padding: 0;
border: 0;
border-radius: 50%;
color: var(--foreground);
box-shadow: none;
line-height: 1;
}
.rc-navbar [data-slot='sidebar-trigger'] {
background: transparent !important;
}
.rc-navbar-icon-button.button,
.rc-navbar-icon-button.toggle-button {
background: var(--default) !important;
}
.rc-navbar [data-slot='sidebar-trigger']:hover,
.rc-navbar [data-slot='sidebar-trigger'][data-hovered='true'],
.rc-navbar [data-slot='sidebar-trigger']:focus-visible {
color: var(--foreground);
background: var(--default) !important;
}
.rc-navbar [data-slot='sidebar-trigger']:active,
.rc-navbar [data-slot='sidebar-trigger'][data-pressed='true'] {
background: var(--default-hover) !important;
}
.rc-navbar-icon-button.button:hover,
.rc-navbar-icon-button.button[data-hovered='true'],
.rc-navbar-icon-button.toggle-button:hover,
.rc-navbar-icon-button.toggle-button[data-hovered='true'] {
color: var(--foreground);
background: var(--default-hover) !important;
}
.rc-navbar [data-slot='sidebar-trigger'] > span,
.rc-navbar-icon-button > span,
.rc-navbar-icon-button .anticon {
display: grid;
width: 1rem;
height: 1rem;
place-items: center;
line-height: 1;
}
.rc-navbar [data-slot='sidebar-trigger'] > svg,
.rc-navbar [data-slot='sidebar-trigger'] > span > svg,
.rc-navbar-icon-button > svg,
.rc-navbar-icon-button .anticon > svg {
display: block;
width: 1rem;
height: 1rem;
margin: 0;
}
.rc-navbar-actions {
display: flex;
align-items: center;
gap: var(--rc-space-2);
}
.rc-navbar-title {
min-width: 0;
margin: 0;
overflow: hidden;
color: var(--foreground);
font-size: var(--rc-navbar-title-size);
font-weight: var(--rc-navbar-title-weight);
line-height: var(--rc-navbar-title-line-height);
letter-spacing: 0 !important;
text-overflow: ellipsis;
white-space: nowrap;
}
.rc-navbar-spacer {
flex: 1;
}
.app-shell-content-scroll {
min-width: 0;
background: var(--rc-bg-canvas);
overscroll-behavior: contain;
}
.rc-app-page {
width: 100%;
max-width: none;
min-width: 0;
min-height: 100%;
margin: 0;
padding: var(--rc-space-4) var(--rc-page-title-inset) var(--rc-space-5);
overflow-x: clip;
}
.rc-mobile-sidebar-backdrop {
position: fixed;
inset: 0;
z-index: 200;
background: rgb(15 23 42 / 32%);
transition: opacity var(--rc-motion-normal) var(--rc-ease-standard);
}
.rc-mobile-sidebar-panel {
position: fixed;
inset-block: 0;
left: 0;
z-index: 210;
width: min(19rem, calc(100vw - 3rem));
transition: transform var(--rc-motion-normal) var(--rc-ease-standard);
}
.rc-mobile-sidebar {
position: relative;
width: 100%;
height: 100%;
padding: var(--rc-space-4) var(--rc-space-3) var(--rc-space-3);
box-shadow: var(--rc-shadow-overlay);
}
.rc-mobile-sidebar-close.button {
position: absolute;
top: var(--rc-space-3);
right: var(--rc-space-3);
z-index: 1;
width: 2rem;
min-width: 2rem;
height: 2rem;
border-radius: 999px;
color: rgb(212 212 216);
}
@media (max-width: 1023px) {
.rc-sidebar {
display: none;
}
.rc-navbar {
min-height: 4rem;
}
}
@media (max-width: 640px) {
.rc-app-main {
--rc-page-title-inset: calc(
var(--rc-space-6) + var(--rc-navbar-icon-button-size) + var(--rc-space-3)
);
}
.rc-app-page {
padding: var(--rc-space-4) var(--rc-page-title-inset) var(--rc-space-5);
}
.rc-navbar [data-slot='sidebar-trigger'] {
width: var(--rc-navbar-icon-button-size);
min-width: var(--rc-navbar-icon-button-size);
max-width: var(--rc-navbar-icon-button-size);
height: var(--rc-navbar-icon-button-size);
flex-basis: var(--rc-navbar-icon-button-size);
}
}
/* HeroUI Pro offcanvas shell contract */
.rc-app-shell.sidebar__provider {
position: fixed;
inset: 0;
width: 100%;
height: 100dvh;
min-width: 0;
min-height: 0;
overflow: hidden;
background: var(--rc-bg-canvas);
}
.rc-app-shell .sidebar__offcanvas-wrapper {
--sidebar-width: 15rem;
}
.rc-app-shell .rc-sidebar.sidebar,
.rc-mobile-sidebar {
width: 15rem;
height: 100%;
max-height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
border: 0;
border-right: 1px solid var(--rc-border-default);
border-radius: 0;
background: var(--rc-bg-canvas);
box-shadow: none;
}
.rc-app-shell .sidebar__header,
.rc-mobile-sidebar .sidebar__header {
padding: var(--rc-space-2) var(--rc-space-4) var(--rc-space-2);
}
.rc-sidebar-brand {
min-height: 3rem;
gap: 0.625rem;
padding: var(--rc-space-2) var(--rc-space-1);
}
.rc-sidebar-title {
min-width: 0;
color: var(--foreground);
font-size: 0.95rem;
font-weight: var(--rc-font-semibold);
line-height: 1.2rem;
}
.rc-sidebar-app-trigger.button {
display: grid;
width: var(--rc-sidebar-app-trigger-size);
height: var(--rc-sidebar-app-trigger-size);
min-width: var(--rc-sidebar-app-trigger-size);
max-width: var(--rc-sidebar-app-trigger-size);
flex: 0 0 var(--rc-sidebar-app-trigger-size);
place-items: center;
padding: 0;
border-radius: 50%;
color: var(--foreground);
background: transparent;
line-height: 1;
--button-bg-hover: var(--accent-soft);
--button-bg-pressed: var(--accent-soft);
}
@media (hover: hover) {
.rc-sidebar-app-trigger.button:hover,
.rc-sidebar-app-trigger.button[data-hovered='true'] {
color: var(--foreground);
background: var(--accent-soft);
}
}
.rc-sidebar-app-trigger.button[data-pressed='true'],
.rc-sidebar-app-trigger.button[aria-expanded='true'] {
color: var(--foreground);
background: var(--accent-soft);
}
.rc-sidebar-app-popover {
width: min(24rem, calc(100vw - 2rem));
padding: 0;
border-radius: var(--rc-radius-overlay);
}
.rc-sidebar-app-dialog {
max-height: min(28rem, calc(100dvh - 7rem));
padding: 0.75rem;
overflow-y: auto;
outline: none;
}
.rc-sidebar-app-card {
min-width: 0;
cursor: pointer;
}
.rc-sidebar-app-card[aria-disabled='true'] {
cursor: default;
opacity: 0.6;
}
.rc-sidebar-app-logo,
.rc-sidebar-app-initial {
width: 2rem;
height: 2rem;
border-radius: var(--rc-radius-container);
object-fit: contain;
}
.rc-sidebar-app-initial {
display: grid;
place-items: center;
color: var(--foreground);
background: var(--default);
font-size: var(--rc-text-sm);
font-weight: var(--rc-font-semibold);
}
.rc-sidebar-app-current {
display: inline-flex;
height: 1.25rem;
align-items: center;
border: 1px solid var(--rc-brand-primary-soft-border);
border-radius: 999px;
background: var(--rc-brand-primary-soft);
padding: 0 0.5rem;
color: var(--rc-brand-primary);
font-size: var(--rc-text-xs);
font-weight: var(--rc-font-medium);
line-height: 1;
white-space: nowrap;
}
.rc-sidebar-app-status {
border-radius: var(--rc-radius-control);
background: var(--rc-color-warning-soft);
padding: 0.5rem 0.625rem;
color: var(--rc-color-warning);
font-size: var(--rc-text-xs);
font-weight: var(--rc-font-medium);
line-height: 1rem;
}
.rc-app-shell .sidebar__content,
.rc-mobile-sidebar .sidebar__content {
min-height: 0;
padding: 0 var(--rc-space-3);
}
.rc-app-shell .sidebar__group,
.rc-mobile-sidebar .sidebar__group {
padding: 0;
}
.rc-app-shell .sidebar__group + .sidebar__group,
.rc-mobile-sidebar .sidebar__group + .sidebar__group {
margin-top: var(--rc-space-2);
}
.rc-app-shell .sidebar__group-label,
.rc-mobile-sidebar .sidebar__group-label {
padding: 0.375rem var(--rc-space-2);
color: var(--rc-text-muted);
}
.rc-app-shell .sidebar__menu-item-content,
.rc-mobile-sidebar .sidebar__menu-item-content {
min-height: 2.25rem;
border-radius: calc(var(--radius) * 2);
color: var(--foreground);
}
.rc-app-shell .sidebar__menu-icon,
.rc-app-shell .sidebar__menu-label,
.rc-mobile-sidebar .sidebar__menu-icon,
.rc-mobile-sidebar .sidebar__menu-label {
color: inherit;
}
.rc-app-shell
.sidebar__menu-item[data-current='true']
.sidebar__menu-item-content,
.rc-mobile-sidebar
.sidebar__menu-item[data-current='true']
.sidebar__menu-item-content {
color: var(--foreground);
background: var(--default);
}
.rc-app-shell .sidebar__menu-item:hover .sidebar__menu-item-content,
.rc-app-shell
.sidebar__menu-item[data-hovered='true']
.sidebar__menu-item-content,
.rc-mobile-sidebar .sidebar__menu-item:hover .sidebar__menu-item-content,
.rc-mobile-sidebar
.sidebar__menu-item[data-hovered='true']
.sidebar__menu-item-content {
color: var(--foreground);
background: var(--default);
}
.rc-app-shell .sidebar__footer,
.rc-mobile-sidebar .sidebar__footer {
padding: var(--rc-space-2) var(--rc-space-3) var(--rc-space-4);
}
.rc-sidebar-user-trigger.button {
display: flex;
width: 100%;
min-width: 0;
min-height: 3rem;
align-items: center;
justify-content: center;
gap: 0.625rem;
padding: 0.375rem 0.5rem;
border-radius: calc(var(--radius) * 2);
color: var(--foreground);
background: transparent;
text-align: left;
}
.rc-sidebar-user-trigger.button:hover,
.rc-sidebar-user-trigger.button[data-hovered='true'],
.rc-sidebar-user-trigger.button[aria-expanded='true'] {
background: var(--default);
}
.rc-sidebar-avatar {
width: 2rem;
height: 2rem;
min-width: 2rem;
flex: 0 0 2rem;
background: var(--default);
}
.rc-sidebar-user-copy {
gap: 0.125rem;
line-height: 1;
}
.rc-sidebar-user-menu-item {
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
gap: var(--rc-space-2);
}
.rc-app-main.sidebar__main {
height: 100dvh;
min-height: 0;
max-height: 100dvh;
margin: 0;
overflow: hidden;
border: 0;
border-radius: 0;
background: var(--rc-bg-canvas);
}
.rc-navbar {
flex: none;
height: 4rem;
min-height: 4rem;
max-height: 4rem;
border: 0;
background: transparent;
}
.rc-navbar [data-slot='sidebar-trigger'] {
width: var(--rc-navbar-trigger-size);
min-width: var(--rc-navbar-trigger-size);
max-width: var(--rc-navbar-trigger-size);
height: var(--rc-navbar-trigger-size);
flex: 0 0 var(--rc-navbar-trigger-size);
padding: 0;
}
.rc-navbar-icon-button.button,
.rc-navbar-icon-button.toggle-button {
width: var(--rc-navbar-icon-button-size);
min-width: var(--rc-navbar-icon-button-size);
max-width: var(--rc-navbar-icon-button-size);
height: var(--rc-navbar-icon-button-size);
flex: 0 0 var(--rc-navbar-icon-button-size);
padding: 0;
}
@media (max-width: 768px) {
.rc-navbar [data-slot='sidebar-trigger'] {
width: var(--rc-navbar-icon-button-size);
min-width: var(--rc-navbar-icon-button-size);
max-width: var(--rc-navbar-icon-button-size);
height: var(--rc-navbar-icon-button-size);
flex-basis: var(--rc-navbar-icon-button-size);
}
.rc-app-shell > .sidebar__offcanvas-wrapper {
display: none;
}
.rc-mobile-sidebar {
width: 100%;
border-right: 0;
}
}
@media (min-width: 769px) {
.rc-app-shell .rc-sidebar.sidebar {
display: flex;
}
}
.rc-login-page {
display: grid;
min-height: 100dvh;
place-items: center;
padding: var(--rc-space-6);
background: var(--app-background-gradient);
}
.rc-login-shell {
display: grid;
width: min(100%, var(--rc-login-panel-width));
gap: var(--rc-login-brand-gap);
}
.rc-login-brand {
display: flex;
align-items: center;
justify-content: center;
gap: var(--rc-space-3);
color: var(--rc-text-primary);
}
.rc-login-brand img {
width: auto;
max-width: 8rem;
height: 1.5rem;
filter: brightness(0) saturate(100%) invert(13%) sepia(17%) saturate(1318%)
hue-rotate(181deg) brightness(91%) contrast(92%);
object-fit: contain;
}
.rc-login-brand strong {
font-size: var(--rc-login-brand-size);
font-weight: var(--rc-font-semibold);
}
.rc-login-card.card {
border-radius: var(--rc-radius-overlay);
background: var(--rc-bg-surface);
box-shadow: var(--rc-shadow-raised);
}
.rc-login-form {
display: grid;
gap: var(--rc-space-5);
}
@media (max-width: 480px) {
.rc-login-page {
align-items: start;
padding: 12vh var(--rc-space-4) var(--rc-space-4);
}
.rc-login-brand {
align-items: flex-start;
flex-direction: column;
}
}
/* approved Xinyuan baseline Sidebar geometry and typography contract. */
.template-dashboard-shell .app-sidebar-glass,
.rc-mobile-sidebar.app-sidebar-glass {
width: 15rem;
height: 100%;
max-height: 100%;
margin: 0;
overflow: hidden;
border: 0;
border-right: 1px solid var(--rc-border-default);
border-radius: 0;
color: var(--foreground);
background: var(--rc-bg-canvas);
box-shadow: none;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
}
.template-dashboard-shell .sidebar__header,
.rc-mobile-sidebar .sidebar__header {
padding: 6px 1rem 0.5rem;
}
.app-sidebar-brand {
display: flex;
width: 100%;
min-width: 0;
min-height: 3rem;
align-items: center;
gap: 0.625rem;
padding: 0.5rem 0.25rem;
}
.app-sidebar-brand-main {
display: flex;
min-width: 0;
flex: 1;
align-items: center;
gap: 0.5rem;
}
.app-sidebar-brand-mark {
display: flex;
width: 1.75rem;
height: 1.75rem;
flex: none;
align-items: center;
justify-content: center;
overflow: hidden;
}
.app-sidebar-brand-mark img {
display: block;
width: 100%;
height: 100%;
object-fit: contain;
}
.app-sidebar-title {
min-width: 0;
overflow: hidden;
color: var(--foreground);
font-size: 0.95rem;
font-weight: var(--rc-font-semibold);
line-height: 1.2rem;
letter-spacing: 0;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
text-overflow: ellipsis;
white-space: nowrap;
}
.app-sidebar-app-switcher {
flex: none;
}
.app-sidebar-app-trigger.button {
display: grid;
width: var(--rc-sidebar-app-trigger-size);
height: var(--rc-sidebar-app-trigger-size);
min-width: var(--rc-sidebar-app-trigger-size);
max-width: var(--rc-sidebar-app-trigger-size);
flex: none;
place-items: center;
padding: 0;
border-radius: 50%;
color: var(--foreground);
background-color: transparent;
line-height: 1;
--button-bg-hover: var(--accent-soft);
--button-bg-pressed: var(--accent-soft);
--button-fg: var(--foreground);
}
.app-sidebar-app-trigger .anticon {
display: grid;
width: var(--rc-sidebar-app-trigger-icon-size);
height: var(--rc-sidebar-app-trigger-icon-size);
place-items: center;
line-height: 1;
}
.app-sidebar-app-trigger .anticon > svg {
display: block;
width: 100%;
height: 100%;
}
@media (hover: hover) {
.app-sidebar-app-trigger.button:hover,
.app-sidebar-app-trigger.button[data-hovered='true'] {
color: var(--foreground);
background-color: var(--accent-soft);
}
}
.app-sidebar-app-trigger.button:active,
.app-sidebar-app-trigger.button[data-pressed='true'],
.app-sidebar-app-trigger.button[aria-expanded='true'] {
color: var(--foreground);
background-color: var(--accent-soft);
}
.app-sidebar-app-dialog {
overflow: hidden;
outline: none;
}
.app-sidebar-app-scroll {
max-height: min(28rem, calc(100dvh - 7rem));
padding: 0.75rem;
overflow-y: auto;
}
.template-dashboard-shell .sidebar__content,
.rc-mobile-sidebar .sidebar__content {
min-height: 0;
padding-inline: 0.75rem;
}
.template-dashboard-shell .sidebar__group,
.rc-mobile-sidebar .sidebar__group {
padding: 0;
}
.template-dashboard-shell .sidebar__group + .sidebar__group,
.rc-mobile-sidebar .sidebar__group + .sidebar__group {
margin-top: 0.5rem;
}
.template-dashboard-shell .sidebar__group-label,
.rc-mobile-sidebar .sidebar__group-label {
padding: 0.375rem 0.5rem;
color: var(--rc-text-muted);
font-size: 0.75rem;
font-weight: 500;
line-height: 1rem;
letter-spacing: 0;
}
.template-dashboard-shell .sidebar__menu-item-content,
.rc-mobile-sidebar .sidebar__menu-item-content {
min-height: 2.25rem;
gap: 0.75rem;
padding: 0.375rem 0.5rem;
border-radius: calc(var(--radius) * 2);
color: var(--foreground);
font-weight: 400;
}
.template-dashboard-shell .sidebar__menu-icon,
.rc-mobile-sidebar .sidebar__menu-icon {
width: 1.25rem;
height: 1.25rem;
color: var(--muted);
}
.template-dashboard-shell .sidebar__menu-icon svg,
.rc-mobile-sidebar .sidebar__menu-icon svg {
width: 1rem;
height: 1rem;
}
.template-dashboard-shell .sidebar__menu-label,
.rc-mobile-sidebar .sidebar__menu-label {
color: var(--foreground);
font-size: 0.875rem;
font-weight: inherit;
line-height: 1.25rem;
}
.template-dashboard-shell
.sidebar__menu-item[data-current='true']
.sidebar__menu-item-content,
.template-dashboard-shell .sidebar__menu-item:hover .sidebar__menu-item-content,
.template-dashboard-shell
.sidebar__menu-item[data-hovered='true']
.sidebar__menu-item-content,
.rc-mobile-sidebar
.sidebar__menu-item[data-current='true']
.sidebar__menu-item-content,
.rc-mobile-sidebar .sidebar__menu-item:hover .sidebar__menu-item-content,
.rc-mobile-sidebar
.sidebar__menu-item[data-hovered='true']
.sidebar__menu-item-content {
color: var(--foreground);
background: var(--default);
}
.template-dashboard-shell
.sidebar__menu-item[data-current='true']
.sidebar__menu-item-content,
.rc-mobile-sidebar
.sidebar__menu-item[data-current='true']
.sidebar__menu-item-content {
font-weight: 500;
}
.template-dashboard-shell
.sidebar__menu-item[data-current='true']
:is(.sidebar__menu-icon, .sidebar__menu-label),
.rc-mobile-sidebar
.sidebar__menu-item[data-current='true']
:is(.sidebar__menu-icon, .sidebar__menu-label) {
color: var(--foreground);
}
.template-dashboard-shell .sidebar__footer,
.rc-mobile-sidebar .sidebar__footer {
padding: 0.5rem 0.75rem 1rem;
}
.app-sidebar-user-trigger.button {
display: flex;
width: 100%;
min-width: 0;
min-height: 3rem;
align-items: center;
justify-content: center;
gap: 0.625rem;
padding: 0.375rem 0.5rem !important;
border-radius: calc(var(--radius) * 2) !important;
color: var(--foreground);
background: transparent;
text-align: left;
}
@media (hover: hover) {
.app-sidebar-user-trigger.button:hover,
.app-sidebar-user-trigger.button[data-hovered='true'] {
background: var(--default);
}
}
.app-sidebar-user-trigger.button[aria-expanded='true'] {
background: var(--default);
}
.app-sidebar-user-avatar {
width: 2rem;
height: 2rem;
min-width: 2rem;
flex: 0 0 2rem;
background: var(--default);
}
.app-sidebar-user-copy {
display: flex;
min-width: 0;
flex: 1;
flex-direction: column;
gap: 0.125rem;
line-height: 1;
}
.app-sidebar-user-name,
.app-sidebar-user-description {
display: block;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.app-sidebar-user-name {
color: var(--foreground);
font-size: 0.875rem;
font-weight: 500;
line-height: 1.125rem;
}
.app-sidebar-user-description {
color: var(--muted);
font-size: 0.75rem;
font-weight: 500;
line-height: 1rem;
}
@media (max-width: 768px) {
.rc-mobile-sidebar.app-sidebar-glass {
width: 100%;
border-right: 0;
}
}
:root,
.light,
.default,
[data-theme='light'],
[data-theme='default'] {
/* Xinyuan brand and semantic colors */
--rc-brand-primary: #9e1b1b;
--rc-brand-light: #b83a3a;
--rc-brand-dark: #7e1515;
--rc-brand-primary-hover: var(--rc-brand-light);
--rc-brand-primary-pressed: var(--rc-brand-dark);
--rc-brand-primary-soft: rgb(158 27 27 / 8%);
--rc-brand-primary-soft-hover: rgb(158 27 27 / 12%);
--rc-brand-glass: rgb(158 27 27 / 4%);
--rc-brand-on-primary: #ffffff;
--rc-color-success: #16845b;
--rc-color-warning: #b46a00;
--rc-color-danger: #c93636;
--rc-color-info: var(--rc-brand-primary);
--rc-color-teal: #0f766e;
--rc-color-favorite: #d68a00;
--rc-color-favorite-soft: rgb(214 138 0 / 10%);
--rc-color-success-soft: rgb(22 132 91 / 9%);
--rc-color-success-border: rgb(22 132 91 / 24%);
--rc-color-warning-soft: rgb(180 106 0 / 10%);
--rc-color-warning-border: rgb(180 106 0 / 25%);
--rc-color-danger-soft: rgb(201 54 54 / 9%);
--rc-color-danger-border: rgb(201 54 54 / 22%);
--rc-color-info-soft: var(--rc-brand-primary-soft);
--rc-color-info-border: color-mix(in srgb, var(--rc-brand-primary) 24%, transparent);
--rc-color-teal-soft: rgb(15 118 110 / 9%);
--rc-color-teal-border: rgb(15 118 110 / 24%);
--rc-color-neutral-soft: #f4f4f5;
--rc-color-neutral-border: #e4e4e7;
/* Surfaces, borders and text */
--rc-bg-canvas: #f5f5f5;
--rc-bg-surface: #ffffff;
--rc-bg-subtle: #f8fafc;
--rc-layout-background: #f4f6f9;
--rc-sidebar-glass: rgb(255 255 255 / 42%);
--rc-main-glass: rgb(255 255 255 / 82%);
--rc-shell-glass: rgb(255 255 255 / 78%);
--rc-glass-border: rgb(255 255 255 / 60%);
--rc-sidebar-border: rgb(226 232 240 / 55%);
--rc-border-default: #e3e8ef;
--rc-border-strong: #cbd3de;
--rc-field-background: #ffffff;
--rc-field-border: #d9e1eb;
--rc-field-border-hover: #b8c4d2;
--rc-field-border-focus: var(--rc-brand-primary);
--rc-button-secondary-border: rgb(9 9 11 / 12%);
--rc-button-secondary-border-hover: rgb(9 9 11 / 20%);
--rc-button-secondary-background-hover: rgb(9 9 11 / 4%);
--rc-text-primary: #172033;
--rc-text-secondary: #4e5d73;
--rc-text-muted: #718096;
/* Typography */
--rc-font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC',
'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;
--rc-font-mono: 'SFMono-Regular', Consolas, 'Liberation Mono', monospace;
--rc-text-xs: 0.75rem;
--rc-text-sm: 0.875rem;
--rc-text-md: 1rem;
--rc-text-lg: 1.25rem;
--rc-text-xl: 1.5rem;
--rc-font-regular: 400;
--rc-font-medium: 500;
--rc-font-semibold: 600;
--rc-line-compact: 1.25;
--rc-line-normal: 1.5;
/* Spacing, radius and motion */
--rc-space-1: 0.25rem;
--rc-space-2: 0.5rem;
--rc-space-3: 0.75rem;
--rc-space-4: 1rem;
--rc-space-5: 1.25rem;
--rc-space-6: 1.5rem;
--rc-space-8: 2rem;
--rc-space-10: 2.5rem;
--rc-radius-control: 0.375rem;
--rc-radius-container: 0.5rem;
--rc-radius-overlay: 0.75rem;
--rc-motion-fast: 120ms;
--rc-motion-normal: 180ms;
--rc-ease-standard: cubic-bezier(0.2, 0, 0, 1);
/* Component contracts */
--rc-filter-radius: 0.75rem;
--rc-button-radius: 1.5rem;
--rc-button-sm-height: 2rem;
--rc-button-md-height: 2.25rem;
--rc-button-lg-height: 2.5rem;
--rc-table-action-width: 9rem;
--rc-table-action-gap: 0.5rem;
--rc-table-action-padding-inline: 0.75rem;
--rc-table-action-idle-opacity: 0.55;
--rc-navbar-trigger-size: 2rem;
--rc-navbar-icon-button-size: 2.25rem;
--rc-segment-track-padding: 0.125rem;
--rc-segment-track-radius: 0.75rem;
--rc-segment-item-height: 1.75rem;
--rc-segment-item-radius: 0.625rem;
--rc-segment-item-padding-inline: 0.75rem;
--rc-segment-item-gap: 0.5rem;
--rc-segment-font-size: var(--rc-text-sm);
--rc-segment-font-weight: var(--rc-font-medium);
--rc-segment-line-height: 1.25rem;
--rc-sidebar-app-trigger-size: 2.25rem;
--rc-sidebar-app-trigger-icon-size: 1rem;
--rc-navbar-border-width: 0px;
--rc-navbar-title-size: 1.25rem;
--rc-navbar-page-title-size: var(--rc-navbar-title-size);
--rc-navbar-title-weight: 600;
--rc-navbar-title-line-height: 1.75rem;
--rc-login-panel-width: 28rem;
--rc-login-brand-size: 1.5rem;
--rc-login-brand-weight: var(--rc-font-semibold);
--rc-login-brand-gap: 1.5rem;
--rc-toolbar-filter-background: var(--default);
--rc-toolbar-filter-background-hover: var(--default-hover);
--rc-toolbar-filter-foreground: var(--foreground);
--rc-toolbar-filter-icon: var(--muted);
--rc-toolbar-filter-radius: 999px;
--rc-toolbar-filter-content-gap: 0.5rem;
--rc-data-grid-radius: calc(var(--radius) * 2.5);
--rc-data-grid-header-background: var(--surface-secondary);
--rc-data-grid-header-height: 2.25rem;
--rc-data-grid-header-font-size: var(--rc-text-xs);
--rc-data-grid-header-font-weight: var(--rc-font-medium);
--rc-data-grid-header-line-height: var(--rc-line-compact);
--rc-data-grid-header-color: var(--rc-text-muted);
--rc-data-grid-body-font-size: var(--rc-text-sm);
--rc-data-grid-body-font-weight: var(--rc-font-regular);
--rc-data-grid-body-line-height: var(--rc-line-normal);
--rc-data-grid-body-color: var(--rc-text-secondary);
--rc-data-grid-primary-font-size: var(--rc-text-sm);
--rc-data-grid-primary-font-weight: 550;
--rc-data-grid-primary-line-height: var(--rc-line-normal);
--rc-data-grid-primary-color: var(--rc-text-primary);
--rc-data-grid-secondary-font-size: 0.8125rem;
--rc-data-grid-secondary-font-weight: var(--rc-font-regular);
--rc-data-grid-secondary-line-height: var(--rc-line-compact);
--rc-data-grid-secondary-color: #7a8596;
--rc-data-grid-text-pair-gap: 0.25rem;
--rc-data-grid-chip-font-size: var(--rc-text-xs);
--rc-data-grid-chip-font-weight: var(--rc-font-medium);
--rc-data-grid-chip-line-height: var(--rc-line-compact);
--rc-data-grid-row-hover-background: color-mix(
in oklab,
var(--default) 35%,
var(--surface)
);
--rc-modal-radius: 1.5rem;
--rc-modal-padding: 1.75rem;
--rc-modal-title-size: var(--rc-text-md);
--rc-modal-title-weight: var(--rc-font-medium);
--rc-modal-title-line-height: 1.5rem;
--rc-modal-section-gap: 0.75rem;
--rc-modal-footer-gap: 0.5rem;
--rc-modal-footer-margin: 1.5rem;
--rc-modal-backdrop-blur: 0.75rem;
--rc-modal-close-size: 1.5rem;
--rc-modal-close-inset: var(--rc-modal-padding);
--rc-modal-close-icon-size: 1rem;
--rc-modal-width-xs: 20rem;
--rc-modal-width-sm: 24rem;
--rc-modal-width-md: 30rem;
--rc-modal-width-lg: 40rem;
--rc-modal-width-xl: 46rem;
--rc-modal-width-2xl: 52.5rem;
--rc-modal-width-3xl: 60rem;
--rc-modal-width-4xl: 68rem;
--rc-modal-width-5xl: 76rem;
--rc-data-grid-action-size: 2rem;
--rc-data-grid-action-radius: 999px;
--rc-list-pagination-gap: 0.5rem;
--rc-checkbox-size: 1rem;
--rc-checkbox-radius: 0.25rem;
--rc-checkbox-background: var(--rc-field-background);
--rc-checkbox-border: var(--rc-field-border);
--rc-checkbox-border-hover: var(--rc-field-border-hover);
--rc-checkbox-disabled-background: #f3f5f8;
--rc-checkbox-disabled-border: #d8dee7;
--rc-checkbox-disabled-opacity: 0.56;
--rc-checkbox-checked-background: var(--rc-brand-primary);
--rc-checkbox-checked-border: var(--rc-brand-dark);
--rc-checkbox-check-color: var(--rc-brand-on-primary);
--rc-kbd-background: #f4f6f9;
--rc-content-max-width: 80rem;
--rc-page-gutter: 1.25rem;
/* Elevation */
--rc-shadow-card: 0 1px 2px rgb(15 23 42 / 2%), 0 4px 12px rgb(15 23 42 / 2%);
--rc-shadow-raised: 0 1px 2px rgb(15 23 42 / 6%),
0 8px 24px rgb(15 23 42 / 8%);
--rc-shadow-overlay: 0 16px 40px rgb(15 23 42 / 14%);
--rc-shadow-brand-glow: 0 4px 14px rgb(158 27 27 / 15%);
--rc-shadow-main-glass: 0 8px 28px rgb(148 163 184 / 14%),
inset 0 0 0 1px rgb(255 255 255 / 30%);
/* HeroUI semantic bridge */
--accent: var(--rc-brand-primary);
--accent-foreground: var(--rc-brand-on-primary);
--accent-hover: var(--rc-brand-primary-hover);
--accent-soft: var(--rc-brand-primary-soft);
--accent-soft-hover: var(--rc-brand-primary-soft-hover);
--accent-soft-foreground: color-mix(
in oklab,
var(--rc-brand-primary) 82%,
var(--rc-text-primary) 18%
);
--background: var(--rc-bg-canvas);
--surface: var(--rc-bg-surface);
--surface-shadow: var(--rc-shadow-card);
--foreground: var(--rc-text-primary);
--muted: var(--rc-text-muted);
--muted-foreground: var(--rc-text-secondary);
--separator: var(--rc-border-default);
--focus: var(--rc-brand-primary);
--app-background-gradient: radial-gradient(
circle at 0% 0%,
rgb(158 27 27 / 5%) 0%,
rgb(244 246 249 / 0%) 50%
),
var(--rc-layout-background);
}
.dark,
[data-theme='dark'] {
color-scheme: dark;
--rc-brand-primary-soft: rgb(158 27 27 / 16%);
--rc-brand-primary-soft-hover: rgb(158 27 27 / 22%);
--rc-brand-glass: rgb(158 27 27 / 10%);
--rc-color-success: #4fca92;
--rc-color-warning: #f0ad4e;
--rc-color-danger: #ff7272;
--rc-color-info: #f0a3a3;
--rc-color-teal: #5eead4;
--rc-color-favorite: #f2b84b;
--rc-color-favorite-soft: rgb(242 184 75 / 14%);
--rc-color-success-soft: rgb(79 202 146 / 14%);
--rc-color-success-border: rgb(79 202 146 / 30%);
--rc-color-warning-soft: rgb(240 173 78 / 15%);
--rc-color-warning-border: rgb(240 173 78 / 32%);
--rc-color-danger-soft: rgb(255 114 114 / 14%);
--rc-color-danger-border: rgb(255 114 114 / 30%);
--rc-color-info-soft: rgb(158 27 27 / 18%);
--rc-color-info-border: rgb(184 58 58 / 42%);
--rc-color-teal-soft: rgb(94 234 212 / 14%);
--rc-color-teal-border: rgb(94 234 212 / 30%);
--rc-color-neutral-soft: #27272a;
--rc-color-neutral-border: #3f3f46;
--rc-bg-canvas: #0f0f11;
--rc-bg-surface: #18181b;
--rc-bg-subtle: #202024;
--rc-layout-background: #0f0f11;
--rc-sidebar-glass: #0f0f11;
--rc-main-glass: #0f0f11;
--rc-shell-glass: #0f0f11;
--rc-glass-border: #303036;
--rc-sidebar-border: #303036;
--rc-border-default: #303036;
--rc-border-strong: #45454d;
--rc-field-background: #202024;
--rc-field-border: #3b3b42;
--rc-field-border-hover: #56565f;
--rc-button-secondary-border: rgb(255 255 255 / 18%);
--rc-button-secondary-border-hover: rgb(255 255 255 / 28%);
--rc-button-secondary-background-hover: rgb(255 255 255 / 7%);
--rc-text-primary: #f4f4f5;
--rc-text-secondary: #d4d4d8;
--rc-text-muted: #a1a1aa;
--rc-data-grid-secondary-color: #8b8b95;
--rc-checkbox-background: #202024;
--rc-checkbox-border: #56565f;
--rc-checkbox-border-hover: #6f6f7a;
--rc-checkbox-disabled-background: #1b1b1f;
--rc-checkbox-disabled-border: #36363d;
--rc-checkbox-disabled-opacity: 0.58;
--rc-kbd-background: #202024;
--rc-shadow-card: 0 1px 2px rgb(0 0 0 / 18%), 0 4px 12px rgb(0 0 0 / 12%);
--rc-shadow-raised: 0 1px 2px rgb(0 0 0 / 24%), 0 8px 24px rgb(0 0 0 / 24%);
--rc-shadow-overlay: 0 16px 40px rgb(0 0 0 / 38%);
--rc-shadow-main-glass: none;
--accent: var(--rc-brand-primary);
--accent-foreground: var(--rc-brand-on-primary);
--accent-hover: var(--rc-brand-light);
--accent-soft: var(--rc-brand-primary-soft);
--accent-soft-hover: var(--rc-brand-primary-soft-hover);
--accent-soft-foreground: #f0a3a3;
--background: var(--rc-bg-canvas);
--surface: var(--rc-bg-surface);
--surface-secondary: var(--rc-bg-subtle);
--surface-shadow: var(--rc-shadow-card);
--overlay: var(--rc-bg-surface);
--overlay-foreground: var(--rc-text-primary);
--foreground: var(--rc-text-primary);
--muted: var(--rc-text-muted);
--muted-foreground: var(--rc-text-secondary);
--default: #27272a;
--default-hover: #3f3f46;
--border: var(--rc-border-default);
--separator: var(--rc-border-default);
--field: var(--rc-field-background);
--field-foreground: var(--rc-text-primary);
--focus: var(--rc-brand-primary);
--backdrop: rgb(0 0 0 / 50%);
--app-background-gradient: var(--rc-bg-canvas);
}
:root {
--pm-brand: #b42318;
--pm-brand-strong: #991b1b;
--pm-surface: #ffffff;
--pm-bg: #f8fafc;
--pm-sidebar: #0f172a;
--pm-sidebar-soft: #1e293b;
--pm-text: #0f172a;
--pm-muted: #64748b;
--pm-brand: var(--rc-brand-primary);
--pm-brand-strong: var(--rc-brand-dark);
--pm-surface: var(--rc-bg-surface);
--pm-bg: var(--rc-bg-canvas);
--pm-sidebar: var(--rc-bg-canvas);
--pm-sidebar-soft: var(--rc-bg-subtle);
--pm-text: var(--rc-text-primary);
--pm-muted: var(--rc-text-muted);
--rc-login-panel-width: 28rem;
--rc-login-control-height: 2.5rem;
--rc-login-submit-height: 2.875rem;
--rc-login-submit-margin-top: 0.875rem;
--rc-login-brand-weight: var(--rc-font-semibold);
--rc-login-captcha-image-max-height: 1.875rem;
}
html,
......@@ -17,10 +23,7 @@ body,
body {
margin: 0;
background:
radial-gradient(circle at top left, rgba(251, 191, 188, 0.22), transparent 26%),
radial-gradient(circle at bottom right, rgba(251, 207, 232, 0.2), transparent 30%),
#f8fafc;
background: var(--rc-bg-canvas);
color: var(--pm-text);
font-family:
"PingFang SC",
......@@ -38,3 +41,621 @@ a {
color: inherit;
text-decoration: none;
}
.login-page {
min-height: 100vh;
background: var(--rc-bg-canvas);
color: var(--rc-text-primary);
}
.login-shell {
display: grid;
min-height: 100vh;
place-items: center;
padding: 2rem 1rem;
}
.login-card {
width: min(calc(100vw - 2rem), var(--rc-login-panel-width));
max-width: var(--rc-login-panel-width);
border: 1px solid var(--rc-border-default);
border-radius: 24px;
background: var(--rc-bg-surface);
box-shadow: var(--rc-shadow-overlay);
padding: 2.25rem;
}
.login-card.card {
width: min(calc(100vw - 2rem), var(--rc-login-panel-width));
max-width: var(--rc-login-panel-width);
border-color: var(--rc-border-default);
border-radius: 24px;
background: var(--rc-bg-surface);
box-shadow: var(--rc-shadow-overlay);
padding: 0;
}
.login-card > [data-slot='card-content'] {
padding: 2.25rem;
}
.login-card-brand {
display: flex;
flex-direction: column;
align-items: center;
gap: 14px;
margin-bottom: 32px;
text-align: center;
}
.login-brand-logo {
width: 36px;
height: 36px;
object-fit: contain;
}
.login-card-brand :is(h1, strong) {
margin: 0;
color: var(--rc-text-primary);
font-size: 32px;
font-weight: var(--rc-login-brand-weight);
letter-spacing: 0;
line-height: 1.15;
}
.login-form {
display: grid;
gap: 1rem;
}
.login-field {
display: grid;
gap: 0.5rem;
color: var(--rc-text-secondary);
font-size: 14px;
font-weight: 500;
}
.login-input,
.login-field :is(.input, .input--secondary, [data-slot='input'], [data-slot='input-wrapper'], [data-slot='control']) {
min-height: var(--rc-login-control-height);
height: var(--rc-login-control-height);
}
.login-input {
width: 100%;
border: 1px solid var(--rc-field-border);
border-radius: 8px;
background: var(--rc-field-background);
box-shadow: var(--field-shadow);
color: var(--rc-text-primary);
font: inherit;
outline: none;
padding: 0 0.875rem;
transition: border-color 160ms ease, box-shadow 160ms ease, background-color 160ms ease;
}
.login-input:hover {
border-color: var(--rc-field-border-hover);
}
.login-input:focus {
border-color: var(--rc-brand-primary);
box-shadow: var(--field-shadow), 0 0 0 2px rgb(158 27 27 / 14%);
}
.login-input[data-filled='true'],
.login-input:not(:placeholder-shown),
input.login-input.input--secondary:not(:placeholder-shown) {
background: #fbf1f1;
--input-bg: #fbf1f1;
--input-bg-hover: #faeaea;
--input-bg-focus: #fbf1f1;
}
.login-input[data-filled='true']:hover,
.login-input:not(:placeholder-shown):hover {
background: #faeaea;
}
.login-input:-webkit-autofill,
.login-input:-webkit-autofill:hover,
.login-input:-webkit-autofill:focus {
box-shadow: inset 0 0 0 1000px #fbf1f1, var(--field-shadow);
-webkit-box-shadow: inset 0 0 0 1000px #fbf1f1, var(--field-shadow);
-webkit-text-fill-color: var(--rc-text-primary);
}
.login-captcha-row {
display: grid;
grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
gap: 12px;
}
.login-captcha-button {
display: grid;
width: 100%;
height: var(--rc-login-control-height);
place-items: center;
border: 1px solid var(--rc-field-border);
border-radius: 8px;
background: var(--rc-bg-surface);
color: var(--rc-text-secondary);
cursor: pointer;
font-size: 12px;
}
.login-captcha-button:disabled {
cursor: not-allowed;
opacity: 0.7;
}
.captcha-image {
display: grid;
width: 100%;
height: 100%;
place-items: center;
}
.captcha-image svg,
.captcha-image img {
width: auto;
max-width: 100%;
max-height: var(--rc-login-captcha-image-max-height);
}
.login-submit-button {
display: inline-flex;
width: 100%;
height: var(--rc-login-submit-height);
align-items: center;
justify-content: center;
margin-top: var(--rc-login-submit-margin-top);
border: 0;
border-radius: 24px;
background: var(--rc-brand-primary);
color: #fff;
cursor: pointer;
font-size: 14px;
font-weight: 500;
line-height: 20px;
}
.login-submit-button:hover {
background: var(--rc-brand-primary-hover, var(--rc-brand-light));
}
.login-submit-button:disabled {
cursor: not-allowed;
opacity: 0.7;
}
.rc-alert {
border-radius: 12px;
border: 1px solid var(--rc-border-default);
padding: 0.75rem 1rem;
font-size: 14px;
line-height: 1.5;
}
.rc-alert--danger {
border-color: var(--rc-color-danger-border);
background: var(--rc-color-danger-soft);
color: var(--rc-color-danger);
}
.rc-alert--success {
border-color: var(--rc-color-success-border);
background: var(--rc-color-success-soft);
color: var(--rc-color-success);
}
.rc-app-page > .space-y-5,
.rc-app-page > .space-y-6 {
width: 100%;
}
.dark body {
background: var(--rc-bg-canvas);
}
.dark .login-card {
background: var(--rc-bg-surface);
}
.dark .login-input[data-filled='true'],
.dark .login-input:not(:placeholder-shown) {
background: rgb(158 27 27 / 16%);
}
.rc-app-shell {
display: flex;
width: 100%;
height: 100vh;
overflow: hidden;
background: var(--rc-bg-canvas);
}
.rc-sidebar {
display: flex;
width: 240px;
flex: 0 0 240px;
flex-direction: column;
border-right: 1px solid var(--rc-border-default);
background: var(--rc-bg-canvas);
transition: width 180ms ease, flex-basis 180ms ease;
}
.rc-sidebar.is-collapsed {
width: 0;
flex-basis: 0;
overflow: hidden;
border-right: 0;
}
.rc-sidebar-logo--dark {
display: none;
}
.dark .rc-sidebar-logo--light {
display: none;
}
.dark .rc-sidebar-logo--dark {
display: block;
}
.rc-sidebar-content {
min-height: 0;
flex: 1;
overflow-y: auto;
padding: 0 12px;
}
.rc-sidebar-menu {
display: grid;
gap: 4px;
}
.rc-sidebar-menu-item {
display: flex;
height: 36px;
align-items: center;
gap: 12px;
border-radius: 16px;
padding: 0 8px;
color: var(--rc-text-secondary);
font-size: 14px;
line-height: 20px;
}
.rc-sidebar-menu-item:hover,
.rc-sidebar-menu-item.is-current {
background: var(--default);
color: var(--rc-text-primary);
}
.rc-sidebar-menu-icon {
display: grid;
width: 20px;
place-items: center;
color: var(--rc-text-muted);
}
.rc-sidebar-menu-item.is-current .rc-sidebar-menu-icon {
color: var(--rc-text-primary);
}
.rc-sidebar-footer {
padding: 8px 12px 16px;
}
.rc-sidebar-user-trigger {
width: 100%;
}
.rc-sidebar-logout-icon {
margin-left: auto;
color: var(--rc-text-muted);
}
.rc-app-main {
display: flex;
min-width: 0;
flex: 1;
flex-direction: column;
background: var(--rc-bg-canvas);
}
.rc-navbar {
height: 64px;
flex: 0 0 64px;
background: transparent;
}
.rc-navbar-header {
display: flex;
height: 64px;
align-items: center;
gap: 12px;
padding: 0 24px;
}
.rc-navbar-spacer {
flex: 1;
}
.rc-navbar-trigger,
.rc-navbar-icon-button,
.rc-sidebar-app-trigger {
display: inline-grid;
place-items: center;
border: 0;
border-radius: 999px;
background: transparent;
color: var(--rc-text-secondary);
cursor: pointer;
padding: 0;
}
.rc-navbar-trigger {
width: 32px;
height: 32px;
}
.rc-navbar-icon-button,
.rc-sidebar-app-trigger {
width: 36px;
height: 36px;
}
.rc-navbar-trigger:hover,
.rc-navbar-icon-button:hover,
.rc-sidebar-app-trigger:hover {
background: var(--default);
color: var(--rc-text-primary);
}
.rc-navbar-title {
min-width: 0;
margin: 0;
overflow: hidden;
color: var(--rc-text-primary);
font-size: 20px;
font-weight: 600;
letter-spacing: 0;
line-height: 28px;
text-overflow: ellipsis;
white-space: nowrap;
}
.rc-file-upload {
display: block;
}
.rc-file-upload__area {
display: grid;
gap: 8px;
place-items: center;
border: 1px dashed var(--rc-border-default);
border-radius: 16px;
background: var(--rc-field-background);
color: var(--rc-text-secondary);
cursor: pointer;
padding: 24px;
}
.rc-file-upload__input {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
opacity: 0;
pointer-events: none;
}
.rc-file-upload__icon {
color: var(--rc-brand-primary);
}
.rc-file-upload__label {
max-width: 100%;
overflow: hidden;
color: var(--rc-text-primary);
font-size: 14px;
font-weight: 500;
text-overflow: ellipsis;
white-space: nowrap;
}
.rc-file-upload__description {
color: var(--rc-text-muted);
font-size: 13px;
}
.rc-file-upload__trigger {
display: inline-flex;
height: 32px;
align-items: center;
border: 1px solid var(--rc-button-secondary-border, var(--rc-border-default));
border-radius: 24px;
padding: 0 12px;
color: var(--rc-text-primary);
font-size: 14px;
font-weight: 500;
}
.pm-data-grid-wrap {
position: relative;
overflow: hidden;
border-radius: 20px;
background: var(--rc-bg-surface);
}
.pm-data-grid-scroll {
overflow-x: auto;
}
table.pm-data-grid {
width: 100%;
min-width: 1180px;
border-collapse: separate;
border-spacing: 0;
background: var(--rc-bg-surface);
color: var(--rc-text-secondary);
}
.pm-data-grid th {
height: 36px;
background: var(--rc-data-grid-header-background, var(--rc-bg-subtle));
color: var(--rc-text-muted);
font-size: 12px;
font-weight: 500;
padding: 0 16px;
text-align: left;
white-space: nowrap;
}
.pm-data-grid td {
border-top: 1px solid var(--rc-border-subtle, var(--rc-border-default));
color: var(--rc-text-secondary);
font-size: 14px;
font-weight: 400;
padding: 10px 16px;
vertical-align: middle;
}
.pm-data-grid tr:hover td {
background: var(--rc-data-grid-row-hover-background, var(--default));
}
.pm-data-grid-selection-cell {
width: 44px;
min-width: 44px;
}
.pm-data-grid-actions-cell {
width: 80px;
}
.pm-data-grid-truncate {
display: block;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.pm-data-grid-empty-overlay {
display: grid;
min-height: 12rem;
place-items: center;
color: var(--rc-text-muted);
}
.data-grid-empty-state {
display: grid;
gap: 12px;
place-items: center;
}
.data-grid-empty-title {
color: var(--rc-text-muted);
font-size: 14px;
}
.pm-data-table-action-bar {
position: sticky;
bottom: 24px;
z-index: 30;
display: flex;
width: max-content;
max-width: calc(100vw - 48px);
align-items: center;
gap: 8px;
margin: 12px auto 0;
border: 1px solid var(--rc-border-default);
border-radius: 999px;
background: var(--rc-bg-surface);
box-shadow: var(--rc-shadow-overlay);
padding: 8px;
}
.pm-data-table-action-bar > button {
display: inline-grid;
width: 32px;
height: 32px;
place-items: center;
border: 0;
border-radius: 50%;
background: transparent;
color: var(--rc-text-secondary);
}
.pm-data-grid-text-tooltip {
position: fixed;
z-index: 2000;
max-width: min(420px, calc(100vw - 32px));
border: 1px solid var(--rc-border-default);
border-radius: 12px;
background: var(--rc-bg-surface);
box-shadow: var(--rc-shadow-raised);
color: var(--rc-text-primary);
padding: 8px 10px;
pointer-events: none;
white-space: pre-wrap;
}
.app-switcher-card[data-current='true'] {
border-color: color-mix(
in oklab,
var(--rc-brand-primary) 34%,
var(--rc-border-default)
) !important;
background: var(--rc-brand-primary-soft) !important;
}
.app-switcher-card__logo {
display: block;
width: 2.25rem;
height: 2.25rem;
flex: none;
border-radius: var(--rc-radius-control);
object-fit: contain;
}
.app-switcher-card__initial {
display: inline-grid;
width: 2.25rem;
height: 2.25rem;
flex: none;
place-items: center;
border-radius: var(--rc-radius-control);
background: var(--rc-brand-primary);
color: #fff;
font-size: var(--rc-text-sm);
font-weight: var(--rc-font-semibold);
line-height: 1;
}
@media (max-width: 768px) {
.rc-navbar-trigger {
width: 36px;
height: 36px;
}
}
@media (max-width: 640px) {
.login-card {
width: min(calc(100vw - 2rem), var(--rc-login-panel-width));
padding: 0;
}
.login-card > [data-slot='card-content'] {
padding: 1.75rem;
}
.login-card-brand :is(h1, strong) {
font-size: 28px;
}
}
import xyLogoRed from '@/assets/identity/xy-logo-red.png';
import authService from '@/services/authService';
import { getCaptcha, userLogin } from '@/services/DataService';
import { SiteContext } from '@/wrapper/Site';
import { Card } from '@heroui/react';
import CryptoJS from 'crypto-js';
import { useContext, useEffect, useMemo, useState } from 'react';
import { useContext, useEffect, useState } from 'react';
import { history, useSearchParams } from 'umi';
const APP_NAME = '党风廉政建设';
function buildPassword(password, siteKey) {
if (!password) {
return password;
......@@ -25,27 +29,7 @@ export default function LoginPage() {
const [error, setError] = useState('');
const redirect = searchParams.get('redirect') || '/';
const siteTitle = site?.title || site?.name || '项目管理系统';
const siteSubtitle =
site?.subtitle || '项目下发、成员协作、甘特排期与个人日历统一协同';
const backgroundStyle = useMemo(() => {
const overlay =
'linear-gradient(135deg, rgba(5, 23, 41, 0.92) 0%, rgba(14, 62, 90, 0.9) 50%, rgba(0, 122, 181, 0.82) 100%)';
if (!site?.background) {
return {
backgroundImage: `${overlay}, radial-gradient(circle at top, #12385d 0%, #081a2c 55%, #040b15 100%)`,
};
}
return {
backgroundImage: `${overlay}, url(${site.background})`,
backgroundSize: 'cover',
backgroundPosition: 'center',
};
}, [site?.background]);
const siteTitle = APP_NAME;
const refreshCaptcha = async () => {
setCaptchaLoading(true);
......@@ -62,6 +46,7 @@ export default function LoginPage() {
};
useEffect(() => {
document.title = siteTitle;
refreshCaptcha();
}, []);
......@@ -95,96 +80,58 @@ export default function LoginPage() {
};
return (
<div
className="min-h-screen bg-[#061321] px-4 py-6 text-white md:px-8"
style={backgroundStyle}
>
<div className="mx-auto flex min-h-[calc(100vh-3rem)] w-full max-w-7xl overflow-hidden rounded-[32px] border border-white/10 bg-white/6 shadow-[0_28px_120px_rgba(0,0,0,0.38)] backdrop-blur-sm">
<section className="relative hidden w-[52%] flex-col justify-between overflow-hidden px-10 py-12 lg:flex">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top_left,rgba(255,255,255,0.2),transparent_36%),radial-gradient(circle_at_bottom_right,rgba(49,196,255,0.3),transparent_32%)]" />
<div className="relative z-10 max-w-xl">
<p className="text-xs font-medium uppercase tracking-[0.45em] text-white/60">
Streams Style Login
</p>
<h1 className="mt-6 text-4xl font-semibold leading-tight text-white">
{siteTitle}
</h1>
<p className="mt-5 max-w-lg text-base leading-7 text-white/72">
{siteSubtitle}
</p>
</div>
<div className="relative z-10 grid gap-4 xl:grid-cols-2">
<div className="rounded-3xl border border-white/10 bg-white/8 p-6">
<div className="text-sm text-white/65">协同视图</div>
<div className="mt-3 text-2xl font-semibold text-white">
甘特图 + 日历图
</div>
<p className="mt-3 text-sm leading-6 text-white/68">
领导下发项目后,成员可同步在个人日历中查看并共享进度状态。
</p>
</div>
<div className="rounded-3xl border border-white/10 bg-black/18 p-6">
<div className="text-sm text-white/65">状态追踪</div>
<div className="mt-3 text-2xl font-semibold text-white">完成与超时</div>
<p className="mt-3 text-sm leading-6 text-white/68">
计划周期、实际完成与超时状态统一记录,适合项目排期与资源预定。
</p>
</div>
</div>
</section>
<section className="flex flex-1 items-center justify-center bg-white/92 px-6 py-10 text-slate-900 sm:px-10">
<div className="w-full max-w-md">
<div className="mb-8">
<p className="text-sm font-medium uppercase tracking-[0.35em] text-sky-600/80">
Sign In
</p>
<h2 className="mt-3 text-3xl font-semibold text-slate-900">
登录项目管理系统
</h2>
<p className="mt-3 text-sm leading-6 text-slate-500">
参考 `streams-admin` 登录页改造,已接入验证码校验流程。
</p>
<main className="login-page">
<section className="login-shell">
<Card className="login-card">
<Card.Content>
<div className="login-brand login-card-brand">
<img src={xyLogoRed} alt="鑫元" className="login-brand-logo" />
<h1>{siteTitle}</h1>
</div>
<form onSubmit={handleSubmit} className="space-y-5">
<div>
<label className="mb-2 block text-sm font-medium text-slate-700">
账号
</label>
<form onSubmit={handleSubmit} className="login-form">
<label
className="login-field"
data-has-value={identity ? 'true' : undefined}
>
<span>账号</span>
<input
className="w-full rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-900 outline-none transition focus:border-sky-500 focus:bg-white"
className="login-input input input--secondary"
data-filled={identity ? 'true' : undefined}
value={identity}
onChange={(event) => setIdentity(event.target.value)}
disabled={loading}
placeholder="请输入账号"
required
/>
</div>
<div>
<label className="mb-2 block text-sm font-medium text-slate-700">
密码
</label>
<label
className="login-field"
data-has-value={password ? 'true' : undefined}
>
<span>密码</span>
<input
type="password"
className="w-full rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-900 outline-none transition focus:border-sky-500 focus:bg-white"
className="login-input input input--secondary"
data-filled={password ? 'true' : undefined}
value={password}
onChange={(event) => setPassword(event.target.value)}
disabled={loading}
placeholder="请输入密码"
required
/>
</div>
<div>
<label className="mb-2 block text-sm font-medium text-slate-700">
验证码
</label>
<div className="flex gap-3">
<label
className="login-field"
data-has-value={captchaCode ? 'true' : undefined}
>
<span>验证码</span>
<div className="login-captcha-row">
<input
className="min-w-0 flex-1 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-900 outline-none transition focus:border-sky-500 focus:bg-white"
className="login-input input input--secondary"
data-filled={captchaCode ? 'true' : undefined}
value={captchaCode}
onChange={(event) => setCaptchaCode(event.target.value)}
disabled={loading}
......@@ -198,11 +145,11 @@ export default function LoginPage() {
refreshCaptcha();
}}
disabled={loading || captchaLoading}
className="flex h-[56px] w-[148px] shrink-0 items-center justify-center overflow-hidden rounded-2xl border border-slate-200 bg-white px-2 text-xs text-slate-500 transition hover:border-sky-300 hover:text-sky-600 disabled:cursor-not-allowed disabled:opacity-70"
className="login-captcha-button"
>
{captchaData?.image ? (
<span
className="captcha-image flex h-full w-full items-center justify-center"
className="captcha-image"
dangerouslySetInnerHTML={{ __html: captchaData.image }}
/>
) : (
......@@ -210,10 +157,10 @@ export default function LoginPage() {
)}
</button>
</div>
</div>
</label>
{error ? (
<div className="rounded-2xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-600">
<div className="rc-alert rc-alert--danger" role="alert">
{error}
</div>
) : null}
......@@ -221,14 +168,14 @@ export default function LoginPage() {
<button
type="submit"
disabled={loading || captchaLoading}
className="flex w-full items-center justify-center rounded-2xl bg-[linear-gradient(135deg,#0b75b6_0%,#1095d3_55%,#18b6f6_100%)] px-4 py-3 text-sm font-medium text-white shadow-[0_16px_40px_rgba(17,128,196,0.28)] transition hover:brightness-105 disabled:cursor-not-allowed disabled:opacity-70"
className="login-submit-button"
>
{loading ? '登录中...' : '登录'}
</button>
</form>
</div>
</Card.Content>
</Card>
</section>
</div>
</div>
</main>
);
}
import { getApplications } from '@/services/DataService';
import * as Headless from '@headlessui/react';
import {
ArrowTopRightOnSquareIcon,
ExclamationCircleIcon,
SparklesIcon,
} from '@heroicons/react/24/outline';
Close as XMarkIcon,
ExclamationTriangleIcon,
} from '@/components/AppIcons';
import * as Headless from '@headlessui/react';
import clsx from 'clsx';
import { useEffect, useMemo, useState } from 'react';
const APP_DISPLAY_NAME = '党风廉政建设';
const APP_LOGIN_SUBTITLE = '党风廉政工作台';
const APP_ROUTE_NAME = 'incorrupt_party_build';
const CURRENT_APPLICATION = {
name: APP_ROUTE_NAME,
title: APP_DISPLAY_NAME,
description: APP_LOGIN_SUBTITLE,
};
function normalizeLabelList(items) {
if (!Array.isArray(items)) {
return [];
......@@ -33,26 +41,34 @@ function resolveApplicationUrl(application) {
}
function normalizeApplication(application, fallback = {}) {
const name = application?.name || fallback?.name || '';
const name = application?.name || application?.app_name || fallback?.name || '';
const isCurrentSystem = name === APP_ROUTE_NAME;
return {
name,
title:
application?.title ||
title: isCurrentSystem
? APP_DISPLAY_NAME
: application?.title ||
application?.display_name ||
fallback?.title ||
application?.name ||
fallback?.name ||
'未命名应用',
description:
application?.description ||
description: isCurrentSystem
? APP_LOGIN_SUBTITLE
: application?.description ||
application?.display_name ||
fallback?.description ||
fallback?.title ||
application?.title ||
'进入对应应用继续处理业务',
logo: application?.logo || fallback?.logo || null,
url: resolveApplicationUrl({
name,
}),
logo:
application?.logo ||
application?.logo_url ||
application?.icon ||
fallback?.logo ||
null,
url: resolveApplicationUrl({ name }),
access: application?.access || fallback?.access || '',
status: application?.status || fallback?.status || '',
type: application?.type || fallback?.type || '',
......@@ -66,26 +82,6 @@ function normalizeApplication(application, fallback = {}) {
};
}
function formatAccessLabel(access) {
if (access === 'public') {
return '公开';
}
if (access === 'protected') {
return '受限';
}
if (access === 'private') {
return '私有';
}
if (access === 'internal') {
return '内部';
}
return '';
}
function mergeApplications(fetchedApplications, fallbackApplications) {
const fallbackMap = new Map(
fallbackApplications.map((application) => [application.name, application]),
......@@ -122,59 +118,26 @@ function mergeApplications(fetchedApplications, fallbackApplications) {
});
}
function buildApplicationSections(applications, applicationGroups) {
if (!Array.isArray(applicationGroups) || applicationGroups.length === 0) {
return [
{
key: 'all',
label: '全部应用',
applications,
},
function unwrapApplications(response) {
const candidates = [
response,
response?.data,
response?.applications,
response?.result,
response?.list,
response?.records,
response?.items,
response?.data?.applications,
response?.data?.list,
response?.data?.records,
response?.data?.items,
response?.result?.applications,
response?.result?.list,
response?.result?.records,
response?.result?.items,
];
}
const applicationMap = new Map(
applications.map((application) => [application.name, application]),
);
const groupedApplicationNames = new Set();
const sections = applicationGroups
.map((group, index) => {
const groupApplications = Array.isArray(group?.applications)
? group.applications
.map((name) => {
const application = applicationMap.get(name);
if (application) {
groupedApplicationNames.add(name);
}
return application;
})
.filter(Boolean)
: [];
return {
key: group?.label || `group-${index}`,
label: group?.label || '应用分组',
applications: groupApplications,
};
})
.filter((section) => section.applications.length > 0);
const ungroupedApplications = applications.filter(
(application) => !groupedApplicationNames.has(application.name),
);
if (ungroupedApplications.length > 0) {
sections.push({
key: 'ungrouped',
label: '其他应用',
applications: ungroupedApplications,
});
}
return sections;
return candidates.find(Array.isArray) || [];
}
function isCurrentApplication(application) {
......@@ -202,102 +165,77 @@ function isCurrentApplication(application) {
);
}
function markCurrentApplication(applications) {
if (!Array.isArray(applications) || applications.length === 0) {
return [];
}
const currentApplication = applications.find(isCurrentApplication);
return applications.map((application) => ({
...application,
current: currentApplication?.name === application.name,
}));
}
function ApplicationCard({ application }) {
const current = isCurrentApplication(application);
const current = application.current === true;
const target = application?.url?.startsWith('http') ? '_blank' : undefined;
const accessLabel = formatAccessLabel(application.access);
const firstRole = application.roles?.[0];
const firstGroup = application.groups?.[0];
return (
<a
href={application.url}
target={target}
rel={target ? 'noreferrer' : undefined}
data-current={current ? 'true' : undefined}
className={clsx(
'group relative min-h-[84px] overflow-hidden rounded-2xl border border-zinc-200/80 bg-[linear-gradient(180deg,rgba(255,255,255,0.98),rgba(248,250,252,0.94))] p-2.5 shadow-[0_10px_24px_rgba(15,23,42,0.06)] transition duration-200 ease-out hover:-translate-y-0.5 hover:border-rose-200 hover:shadow-[0_16px_32px_rgba(244,63,94,0.12)] focus:outline-none focus-visible:ring-2 focus-visible:ring-rose-400/60',
current &&
'border-rose-300 bg-[linear-gradient(180deg,rgba(255,241,242,0.95),rgba(255,255,255,0.96))]',
'app-switcher-card group flex min-w-0 items-center gap-3 rounded-xl border border-[var(--rc-border-default)] bg-[var(--rc-bg-subtle)] px-3 py-3 text-left transition hover:bg-[var(--accent-soft)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--focus)]',
)}
>
<div className="absolute inset-x-0 top-0 h-14 bg-[radial-gradient(circle_at_top_right,rgba(251,113,133,0.14),transparent_50%)] opacity-0 transition duration-200 group-hover:opacity-100" />
<div className="relative flex items-start justify-between gap-3">
<div className="flex min-w-0 items-center gap-2.5">
{application.logo ? (
<img
src={application.logo}
alt={application.title}
className="size-9 rounded-xl border border-white/80 bg-white object-cover shadow-sm"
className="app-switcher-card__logo"
/>
) : (
<div className="flex size-9 shrink-0 items-center justify-center rounded-xl bg-[linear-gradient(135deg,#b42318,#f43f5e)] text-sm font-semibold text-white shadow-[0_8px_18px_rgba(180,35,24,0.22)]">
<span className="app-switcher-card__initial">
{application.title.slice(0, 1)}
</div>
</span>
)}
<div className="min-w-0">
<div className="truncate text-sm font-semibold text-slate-950">
<span className="min-w-0 flex-1">
<span className="block truncate text-sm font-medium leading-5 text-[var(--rc-text-primary)]">
{application.title}
</div>
<div className="mt-0.5 truncate text-[11px] leading-4 text-slate-500">
{application.description}
</div>
</div>
</div>
<div className="flex shrink-0 items-center gap-2">
{current ? (
<span className="rounded-full bg-rose-100 px-2 py-0.5 text-[11px] font-medium text-rose-700">
当前
</span>
) : null}
<span className="rounded-full bg-slate-100 p-1.5 text-slate-500 transition group-hover:bg-rose-50 group-hover:text-rose-600">
<ArrowTopRightOnSquareIcon className="size-3.5" />
<span className="mt-0.5 block truncate text-xs leading-4 text-[var(--rc-text-muted)]">
{application.description || application.name || '应用'}
</span>
</div>
</div>
{accessLabel || firstRole || firstGroup ? (
<div className="relative mt-2 flex flex-wrap gap-1.5">
{accessLabel ? (
<span className="rounded-full bg-zinc-100 px-2 py-0.5 text-[11px] font-medium text-zinc-700">
{accessLabel}
</span>
) : null}
{firstRole ? (
<span className="rounded-full bg-slate-100 px-2 py-0.5 text-[11px] font-medium text-slate-700">
{firstRole}
</span>
) : null}
{firstGroup ? (
<span className="rounded-full bg-rose-50 px-2 py-0.5 text-[11px] font-medium text-rose-700">
{firstGroup}
{current ? (
<span className="shrink-0 rounded-full bg-[var(--rc-brand-primary-soft)] px-2 py-0.5 text-xs font-medium text-[var(--rc-brand-primary)]">
当前
</span>
) : null}
</div>
) : null}
</a>
);
}
function LoadingSkeleton() {
return (
<div className="grid gap-2.5 sm:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-6">
<div className="grid gap-2 sm:grid-cols-2 lg:grid-cols-3">
{Array.from({ length: 6 }).map((_, index) => (
<div
key={index}
className="min-h-[84px] rounded-2xl border border-zinc-200 bg-white/90 p-2.5 shadow-[0_10px_24px_rgba(15,23,42,0.05)]"
className="rounded-xl border border-[var(--rc-border-default)] bg-[var(--rc-bg-subtle)] px-3 py-3"
>
<div className="animate-pulse">
<div className="flex items-start gap-3">
<div className="size-9 rounded-xl bg-zinc-200" />
<div className="flex items-center gap-3">
<div className="size-9 rounded-lg bg-zinc-200" />
<div className="min-w-0 flex-1 space-y-2">
<div className="h-4 w-2/5 rounded-full bg-zinc-200" />
<div className="h-3 w-full rounded-full bg-zinc-100" />
</div>
</div>
<div className="mt-2 flex gap-1.5">
<div className="h-5 w-12 rounded-full bg-zinc-100" />
<div className="h-5 w-14 rounded-full bg-zinc-100" />
</div>
</div>
</div>
))}
......@@ -309,31 +247,30 @@ export default function ApplicationSwitcherDialog({
open,
onClose,
userInfo,
siteInfo,
}) {
const fallbackApplications = useMemo(() => {
if (!Array.isArray(userInfo?.applications)) {
return [];
}
const userApplications = Array.isArray(userInfo?.applications)
? userInfo.applications
: [];
return userInfo.applications
.map((application) =>
return mergeApplications(
userApplications.map((application) =>
normalizeApplication(application, {
...application,
description: application?.title,
}),
)
.filter((application) => application.name);
),
[CURRENT_APPLICATION],
);
}, [userInfo]);
const [applications, setApplications] = useState(fallbackApplications);
const [applications, setApplications] = useState(() =>
markCurrentApplication(fallbackApplications),
);
const [status, setStatus] = useState(
fallbackApplications.length > 0 ? 'success' : 'idle',
);
const [errorMessage, setErrorMessage] = useState('');
const applicationSections = useMemo(() => {
return buildApplicationSections(applications, siteInfo?.group);
}, [applications, siteInfo?.group]);
useEffect(() => {
if (fallbackApplications.length === 0) {
......@@ -345,7 +282,7 @@ export default function ApplicationSwitcherDialog({
return previous;
}
return fallbackApplications;
return markCurrentApplication(fallbackApplications);
});
}, [fallbackApplications]);
......@@ -362,7 +299,7 @@ export default function ApplicationSwitcherDialog({
try {
const response = await getApplications();
const fetchedApplications = Array.isArray(response) ? response : [];
const fetchedApplications = unwrapApplications(response);
if (cancelled) {
return;
......@@ -373,14 +310,14 @@ export default function ApplicationSwitcherDialog({
fallbackApplications,
);
setApplications(mergedApplications);
setApplications(markCurrentApplication(mergedApplications));
setStatus('success');
} catch (error) {
if (cancelled) {
return;
}
setApplications(fallbackApplications);
setApplications(markCurrentApplication(fallbackApplications));
setStatus('error');
setErrorMessage('应用列表加载失败,已回退展示当前账号上的应用信息。');
}
......@@ -401,52 +338,38 @@ export default function ApplicationSwitcherDialog({
>
<Headless.DialogBackdrop
transition
className="fixed inset-0 bg-[rgba(15,23,42,0.48)] backdrop-blur-sm transition duration-300 ease-out data-closed:opacity-0"
className="fixed inset-0 bg-[rgba(15,23,42,0.42)] backdrop-blur-sm transition duration-200 ease-out data-closed:opacity-0"
/>
<div className="fixed inset-0 overflow-y-auto p-3 sm:p-4 lg:p-5">
<div className="fixed inset-0 overflow-y-auto p-4">
<div className="flex min-h-full items-center justify-center">
<Headless.DialogPanel
transition
className="flex max-h-[calc(100dvh-2rem)] w-full max-w-[92rem] flex-col overflow-hidden rounded-[28px] border border-white/70 bg-[linear-gradient(180deg,rgba(255,255,255,0.96),rgba(248,250,252,0.94))] p-4 shadow-[0_30px_80px_rgba(15,23,42,0.22)] ring-1 ring-black/5 transition duration-300 ease-out data-closed:translate-y-4 data-closed:scale-[0.97] data-closed:opacity-0 sm:p-5"
className="app-switcher-dialog flex max-h-[min(42rem,calc(100dvh-2rem))] w-full max-w-[46rem] flex-col overflow-hidden rounded-2xl border border-[var(--rc-border-default)] bg-[var(--rc-bg-surface)] shadow-[var(--rc-shadow-overlay)] transition duration-200 ease-out data-closed:translate-y-2 data-closed:scale-[0.98] data-closed:opacity-0"
>
<div className="flex shrink-0 flex-col gap-3 border-b border-zinc-200/80 pb-4 sm:flex-row sm:items-start sm:justify-between">
<div className="flex shrink-0 items-start justify-between gap-4 border-b border-[var(--rc-border-default)] px-5 py-4">
<div className="min-w-0">
<div className="inline-flex items-center gap-2 rounded-full bg-rose-50 px-2.5 py-0.5 text-[11px] font-semibold uppercase tracking-[0.18em] text-rose-600">
<SparklesIcon className="size-3.5" />
Applications
</div>
<Headless.DialogTitle className="mt-2 text-xl font-semibold tracking-tight text-slate-950 sm:text-2xl">
<Headless.DialogTitle className="text-base font-semibold leading-6 text-[var(--rc-text-primary)]">
应用切换
</Headless.DialogTitle>
<p className="mt-1 max-w-2xl text-xs leading-5 text-slate-500">
这里展示当前账号可访问的全部应用,点击卡片即可进入对应工作区。
<p className="mt-1 text-sm leading-5 text-[var(--rc-text-muted)]">
选择你可访问的应用
</p>
</div>
<div className="flex items-center gap-3">
<div className="rounded-2xl border border-rose-100 bg-rose-50/70 px-3 py-2 text-right">
<div className="text-[11px] font-medium tracking-wide text-rose-500">
可见应用
</div>
<div className="mt-0.5 text-xl font-semibold tracking-tight text-slate-950">
{applications.length}
</div>
</div>
<button
type="button"
onClick={onClose}
className="inline-flex h-10 items-center justify-center rounded-xl border border-zinc-200 bg-white px-3.5 text-sm font-medium text-slate-600 transition hover:border-zinc-300 hover:text-slate-900"
aria-label="关闭应用切换"
className="inline-grid size-6 shrink-0 place-items-center rounded-full bg-[var(--default)] p-1 text-[var(--rc-text-secondary)] transition hover:bg-[var(--default-hover)] hover:text-[var(--rc-text-primary)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--focus)] [&_.anticon]:grid [&_.anticon]:size-4 [&_.anticon]:place-items-center [&_.anticon]:leading-none [&_.anticon>svg]:block [&_.anticon>svg]:size-4 [&_svg]:m-0"
>
关闭
<XMarkIcon />
</button>
</div>
</div>
<div className="mt-4 min-h-0 flex-1 overflow-y-auto pr-1 [scrollbar-gutter:stable]">
<div className="min-h-0 flex-1 overflow-y-auto px-5 py-4 [scrollbar-gutter:stable]">
{status === 'error' && errorMessage ? (
<div className="mb-4 flex items-start gap-3 rounded-2xl border border-amber-200 bg-amber-50/80 px-4 py-3 text-sm text-amber-900">
<ExclamationCircleIcon className="mt-0.5 size-5 shrink-0 text-amber-500" />
<div className="mb-4 flex items-start gap-3 rounded-xl border border-amber-200 bg-amber-50/80 px-3 py-2 text-sm text-amber-900">
<ExclamationTriangleIcon className="mt-0.5 size-5 shrink-0 text-amber-500" />
<div>{errorMessage}</div>
</div>
) : null}
......@@ -454,35 +377,20 @@ export default function ApplicationSwitcherDialog({
{status === 'loading' && applications.length === 0 ? (
<LoadingSkeleton />
) : applications.length > 0 ? (
<div className="space-y-4">
{applicationSections.map((section) => (
<section key={section.key}>
<div className="mb-2 flex items-center gap-3">
<h3 className="text-sm font-semibold text-slate-900">
{section.label}
</h3>
<span className="h-px flex-1 bg-zinc-200/80" />
<span className="text-xs font-medium text-slate-400">
{section.applications.length}
</span>
</div>
<div className="grid gap-2.5 sm:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-6">
{section.applications.map((application) => (
<div className="grid gap-2 sm:grid-cols-2 lg:grid-cols-3">
{applications.map((application) => (
<ApplicationCard
key={application.name}
application={application}
/>
))}
</div>
</section>
))}
</div>
) : (
<div className="flex min-h-72 flex-col items-center justify-center rounded-[28px] border border-dashed border-zinc-200 bg-zinc-50/80 px-6 text-center">
<div className="text-lg font-semibold text-slate-900">
<div className="flex min-h-48 flex-col items-center justify-center rounded-xl border border-dashed border-[var(--rc-border-default)] bg-[var(--rc-bg-subtle)] px-6 text-center">
<div className="text-base font-semibold text-[var(--rc-text-primary)]">
暂无可切换应用
</div>
<p className="mt-2 max-w-md text-sm leading-6 text-slate-500">
<p className="mt-2 max-w-md text-sm leading-6 text-[var(--rc-text-muted)]">
当前账号还没有分配其它应用权限,后续分配后这里会自动展示。
</p>
</div>
......
import ApplicationSwitcherDialog from '@/pages/project_manager_xy/ApplicationSwitcherDialog';
import xyLogoSmall from '@/assets/xy-logo_small.png';
import AppShell from '@/components/AppShell';
import { BarChart, ClipboardDocumentListIcon } from '@/components/AppIcons';
import { UserContext } from '@/wrapper/Auth';
import { SiteContext } from '@/wrapper/Site';
import {
Bars3Icon,
ClipboardDocumentListIcon,
Squares2X2Icon,
XMarkIcon,
} from '@heroicons/react/24/outline';
import clsx from 'clsx';
import { useContext, useEffect, useMemo, useState } from 'react';
import { Link, Outlet, useLocation } from 'umi';
const APP_NAME = '党风廉政建设';
import { useContext, useMemo, useState } from 'react';
import { Outlet, useLocation } from 'umi';
const menuGroups = [
{
title: '党风廉政工作台',
items: [
{
key: 'dashboard',
name: '驾驶舱',
path: '/resource/stat',
icon: Squares2X2Icon,
icon: BarChart,
},
{
key: 'integrity-party',
name: '党风廉政建设',
path: '/integrity-party',
icon: ClipboardDocumentListIcon,
......@@ -32,372 +24,30 @@ const menuGroups = [
},
];
function NavbarItem({ className, ...props }) {
return (
<button
type="button"
className={clsx(
'inline-flex items-center justify-center rounded-lg p-2 text-zinc-500 transition hover:bg-zinc-100 hover:text-zinc-900',
className,
)}
{...props}
/>
);
}
function NavItem({ name, path, active, collapsed, icon: Icon, onNavigate }) {
return (
<Link
to={path}
onClick={onNavigate}
title={collapsed ? name : undefined}
aria-label={collapsed ? name : undefined}
className={clsx(
'group relative flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-all duration-200',
active
? 'bg-[var(--pm-brand)] text-white shadow-lg'
: 'text-white/90 hover:bg-zinc-800 hover:text-white',
collapsed && 'justify-center px-2',
)}
>
{Icon ? (
<Icon
className={clsx(
'size-5 shrink-0 transition-colors',
active ? 'text-white' : 'text-white/80 group-hover:text-white',
)}
/>
) : null}
{!collapsed ? (
<span
className={clsx(
'min-w-0 flex-1 truncate',
active ? 'text-white' : 'text-white/90 group-hover:text-white',
)}
>
{name}
</span>
) : null}
{active && !collapsed ? (
<span className="absolute right-2 size-1.5 rounded-full bg-white/50" />
) : null}
</Link>
);
}
function MobileNavItem({ name, path, active, icon: Icon, onNavigate }) {
return (
<Link
to={path}
onClick={onNavigate}
className={clsx(
'flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-all duration-200',
active
? 'bg-[var(--pm-brand)] text-white shadow-lg'
: 'text-white/90 hover:bg-zinc-800 hover:text-white',
)}
>
{Icon ? (
<Icon
className={clsx(
'size-5 shrink-0',
active ? 'text-white' : 'text-white/80',
)}
/>
) : null}
<span
className={clsx(
'min-w-0 flex-1 truncate',
active ? 'text-white' : 'text-white/90',
)}
>
{name}
</span>
</Link>
);
}
function CollapseSidebarIcon() {
return (
<svg
data-slot="icon"
viewBox="0 0 20 20"
aria-hidden="true"
fill="currentColor"
>
<path d="M12.78 15.53a.75.75 0 0 1-1.06 0l-4.5-4.5a.75.75 0 0 1 0-1.06l4.5-4.5a.75.75 0 1 1 1.06 1.06L8.81 10l3.97 3.97a.75.75 0 0 1 0 1.06Z" />
</svg>
);
}
function ExpandSidebarIcon() {
return (
<svg
data-slot="icon"
viewBox="0 0 20 20"
aria-hidden="true"
fill="currentColor"
>
<path d="M7.22 15.53a.75.75 0 0 0 1.06 0l4.5-4.5a.75.75 0 0 0 0-1.06l-4.5-4.5a.75.75 0 1 0-1.06 1.06L11.19 10l-3.97 3.97a.75.75 0 0 0 0 1.06Z" />
</svg>
);
}
function MobileSidebar({ open, close, children }) {
if (!open) {
return null;
}
return (
<div className="relative z-[140] lg:hidden">
<div
className="fixed inset-0 z-[140] bg-black/30"
onClick={close}
aria-hidden="true"
/>
<div className="fixed inset-y-0 z-[150] w-full max-w-80 p-2">
<div className="flex h-full flex-col rounded-lg bg-zinc-900 shadow-xs ring-1 ring-white/10">
<div className="-mb-3 flex justify-end px-4 pt-3">
<NavbarItem
onClick={close}
aria-label="Close navigation"
className="text-zinc-400 hover:text-white"
>
<XMarkIcon className="size-5" />
</NavbarItem>
</div>
{children}
</div>
</div>
</div>
);
}
export default function ProjectManagerLayout() {
const location = useLocation();
const [isMobile, setIsMobile] = useState(false);
const [isApplicationDialogOpen, setIsApplicationDialogOpen] = useState(false);
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false);
const [isMobileSidebarOpen, setIsMobileSidebarOpen] = useState(false);
const userInfo = useContext(UserContext);
const siteInfo = useContext(SiteContext);
useEffect(() => {
if (typeof window === 'undefined') {
return;
}
const [sidebarOpen, setSidebarOpen] = useState(true);
const media = window.matchMedia('(max-width: 1023px)');
const sync = () => {
const mobile = !!media.matches;
setIsMobile(mobile);
if (mobile) {
setIsMobileSidebarOpen(false);
setIsSidebarCollapsed(true);
} else {
const storedCollapsed = window.localStorage?.getItem?.(
'pm_sidebar_collapsed',
);
if (storedCollapsed !== null && storedCollapsed !== undefined) {
setIsSidebarCollapsed(storedCollapsed === '1');
const title = useMemo(() => {
for (const group of menuGroups) {
const current = group.items.find((item) => item.path === location.pathname);
if (current) return current.name;
}
}
};
sync();
media.addEventListener?.('change', sync);
return () => {
media.removeEventListener?.('change', sync);
};
}, []);
const sidebarCollapsed = isMobile ? true : isSidebarCollapsed;
const currentGroupTitle = useMemo(() => {
return (
menuGroups.find((group) =>
group.items.some((item) => item.path === location.pathname),
)?.title || ''
);
return '驾驶舱';
}, [location.pathname]);
const applicationSwitcherButton = (
<button
type="button"
onClick={() => setIsApplicationDialogOpen(true)}
aria-label="Open application switcher"
title="应用切换"
className={clsx(
'inline-flex items-center justify-center rounded-[20px] border border-[#f97316]/70 bg-[linear-gradient(180deg,rgba(39,39,42,0.92),rgba(24,24,27,0.96))] text-[#fb923c] shadow-[0_16px_30px_rgba(249,115,22,0.16)] transition duration-200 hover:-translate-y-0.5 hover:border-[#fb923c] hover:text-[#fdba74] hover:shadow-[0_18px_36px_rgba(249,115,22,0.26)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#fb923c]/50',
sidebarCollapsed ? 'size-11' : 'size-14',
)}
>
<Squares2X2Icon className={clsx(sidebarCollapsed ? 'size-4' : 'size-5')} />
</button>
);
return (
<div className="flex h-screen overflow-hidden bg-[radial-gradient(circle_at_top_left,_rgba(251,191,188,0.22),_transparent_26%),radial-gradient(circle_at_bottom_right,_rgba(251,207,232,0.2),_transparent_30%),#f8fafc]">
<aside
className={clsx(
'relative z-30 hidden shrink-0 flex-col border-r border-zinc-800 bg-zinc-900 p-5 lg:flex',
sidebarCollapsed ? 'w-20' : 'w-72',
)}
>
<div
className={clsx(
'mb-7',
sidebarCollapsed
? 'flex flex-col items-center gap-3'
: 'flex items-start justify-between gap-4 px-1',
)}
>
{sidebarCollapsed ? (
<>
<img
src={xyLogoSmall}
alt="XY Logo"
className="h-8 w-auto max-w-8 shrink-0 object-contain"
/>
{applicationSwitcherButton}
</>
) : (
<>
<div className="min-w-0 flex-1">
<h1 className="truncate text-sm font-semibold tracking-[0.02em] text-white/95">
{APP_NAME}
</h1>
<div className="mt-1 text-xs text-zinc-400">
党风廉政台账工作台
</div>
</div>
{applicationSwitcherButton}
</>
)}
</div>
<nav className="flex-1 space-y-5 overflow-y-auto">
{menuGroups.map((group) => (
<div key={group.title}>
{!sidebarCollapsed ? (
<h2 className="mb-2 px-1 text-xs font-medium tracking-wide text-zinc-500">
{group.title}
</h2>
) : null}
<div className="space-y-1">
{group.items.map((item) => (
<NavItem
key={item.path}
name={item.name}
path={item.path}
active={location.pathname === item.path}
collapsed={sidebarCollapsed}
icon={item.icon}
/>
))}
</div>
</div>
))}
</nav>
{!sidebarCollapsed ? (
<div className="mt-6 rounded-2xl border border-zinc-800 bg-zinc-950/70 p-4 text-sm text-zinc-300">
<div className="font-medium text-white/90">
{userInfo?.display_name || userInfo?.username || '当前用户'}
</div>
<div className="mt-1 text-xs text-zinc-500">
当前正在查看党风廉政建设
</div>
</div>
) : null}
</aside>
<div className="relative z-30 hidden lg:flex">
<button
type="button"
onClick={() =>
setIsSidebarCollapsed((prev) => {
const next = !prev;
window?.localStorage?.setItem?.(
'pm_sidebar_collapsed',
next ? '1' : '0',
);
return next;
})
}
aria-label={sidebarCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
className="absolute -left-3.5 top-16 z-10 flex size-7 items-center justify-center rounded-full border border-zinc-700 bg-zinc-800 text-zinc-400 shadow hover:bg-zinc-700 hover:text-white [&_svg]:size-4"
>
{sidebarCollapsed ? <ExpandSidebarIcon /> : <CollapseSidebarIcon />}
</button>
</div>
<MobileSidebar
open={isMobileSidebarOpen}
close={() => setIsMobileSidebarOpen(false)}
>
<div className="px-5 pt-6">
<div>
<h1 className="text-base font-semibold tracking-[0.02em] text-white">
{APP_NAME}
</h1>
<div className="text-xs text-zinc-500">党风廉政工作台</div>
</div>
</div>
<nav className="space-y-5 overflow-y-auto px-5 pb-6 pt-6">
{menuGroups.map((group) => (
<div key={group.title}>
<h2 className="mb-2 px-1 text-xs font-medium tracking-wide text-zinc-600">
{group.title}
</h2>
<div className="space-y-1">
{group.items.map((item) => (
<MobileNavItem
key={item.path}
name={item.name}
path={item.path}
active={location.pathname === item.path}
icon={item.icon}
onNavigate={() => setIsMobileSidebarOpen(false)}
/>
))}
</div>
</div>
))}
</nav>
</MobileSidebar>
<main className="flex flex-1 flex-col overflow-hidden p-4 sm:p-6">
<div className="mb-4 flex items-center justify-between gap-3 lg:hidden">
<NavbarItem
onClick={() => setIsMobileSidebarOpen(true)}
aria-label="Open navigation"
>
<Bars3Icon className="size-5" />
</NavbarItem>
<div className="text-sm text-zinc-500">{currentGroupTitle}</div>
<NavbarItem
onClick={() => setIsApplicationDialogOpen(true)}
aria-label="Open application switcher"
className="border border-rose-200 bg-white text-rose-500 shadow-sm hover:bg-rose-50 hover:text-rose-600"
<AppShell
title={title}
open={sidebarOpen}
onOpenChange={setSidebarOpen}
menuGroups={menuGroups}
pathname={location.pathname}
userInfo={userInfo}
onLogout={userInfo?.logout}
>
<Squares2X2Icon className="size-4" />
</NavbarItem>
</div>
<div className="flex-1 overflow-y-auto rounded-[32px] border border-white/70 bg-white/75 p-4 shadow-[0_24px_80px_rgba(15,23,42,0.06)] backdrop-blur sm:p-6">
<Outlet />
</div>
</main>
<ApplicationSwitcherDialog
open={isApplicationDialogOpen}
onClose={() => setIsApplicationDialogOpen(false)}
userInfo={userInfo}
siteInfo={siteInfo}
/>
</div>
</AppShell>
);
}
import {
ArrowDownTrayIcon,
ArrowPathIcon,
CheckIcon,
DocumentArrowDownIcon,
DocumentArrowUpIcon,
PencilSquareIcon,
PlusIcon,
TrashIcon,
} from '@heroicons/react/24/outline';
CheckCircleIcon,
ChevronDownIcon,
Delete,
Edit,
} from '@/components/AppIcons';
import {
DataTable,
DataTablePagination,
DataTableText,
} from '@/components/DataTable';
import FileUploadField from '@/components/FileUploadField';
import FormDatePicker from '@/components/FormDatePicker';
import FormSelect from '@/components/FormSelect';
import { TableActionButton, TableActionGroup } from '@/components/TableActionButton';
import { Tabs } from '@heroui/react/tabs';
import { TextArea } from '@heroui/react/textarea';
import { Tooltip } from '@heroui/react/tooltip';
import clsx from 'clsx';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { Button } from '../../../catalyst-ui-kit/javascript/button';
import {
Dialog,
......@@ -19,17 +28,6 @@ import {
DialogTitle,
} from '../../../catalyst-ui-kit/javascript/dialog';
import { Input } from '../../../catalyst-ui-kit/javascript/input';
import { Select } from '../../../catalyst-ui-kit/javascript/select';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '../../../catalyst-ui-kit/javascript/table';
import { Textarea } from '../../../catalyst-ui-kit/javascript/textarea';
import EmptyStatePage from '../EmptyStatePage';
import {
DEFAULT_INTEGRITY_PAGE_SIZE,
INTEGRITY_LEDGER_CONFIG_MAP,
......@@ -54,7 +52,7 @@ function Panel({ className, children }) {
return (
<section
className={clsx(
'rounded-[24px] border border-white/70 bg-white/92 shadow-[0_18px_60px_rgba(15,23,42,0.07)] backdrop-blur',
'rc-card-surface rc-workspace-module',
className,
)}
>
......@@ -63,15 +61,33 @@ function Panel({ className, children }) {
);
}
function LedgerCategoryTab({ tabKey, label }) {
return (
<Tabs.Tab id={tabKey}>
<span>{label}</span>
<Tabs.Indicator />
</Tabs.Tab>
);
}
function FieldLabel({ field }) {
return (
<label className="mb-2 block text-sm font-medium text-zinc-700">
<label className="rc-filter-label mb-2 block">
{field.label}
{field.required ? <span className="ml-1 text-red-600">*</span> : null}
</label>
);
}
function FilterField({ label, children, className }) {
return (
<div className={clsx('rc-filter-control min-w-0', className)}>
<div className="rc-filter-label mb-2">{label}</div>
{children}
</div>
);
}
function formatFileSize(size = 0) {
const numericSize = Number(size || 0);
if (!numericSize) {
......@@ -143,11 +159,7 @@ const TABLE_COLUMN_WIDTHS = {
};
const DEFAULT_TABLE_COLUMN_WIDTH = 156;
const TABLE_SELECTION_COLUMN_WIDTH = 52;
const TABLE_ACTION_COLUMN_WIDTH = 112;
function AttachmentField({ field, value = [], onChange, disabled }) {
const inputRef = useRef(null);
const [uploading, setUploading] = useState(false);
const files = Array.isArray(value) ? value : [];
......@@ -172,32 +184,22 @@ function AttachmentField({ field, value = [], onChange, disabled }) {
return (
<div>
<div className="flex flex-wrap items-center gap-3">
<input
ref={inputRef}
type="file"
multiple
className="hidden"
onChange={handleUpload}
<div className="grid gap-3">
<FileUploadField
ariaLabel={`上传${field.label}`}
disabled={disabled || uploading}
fileName={uploading ? '上传中...' : ''}
onChange={handleUpload}
triggerLabel={`上传${field.label}`}
/>
<Button
type="button"
outline
onClick={() => inputRef.current?.click()}
disabled={disabled || uploading}
>
<DocumentArrowUpIcon data-slot="icon" />
{uploading ? '上传中...' : `上传${field.label}`}
</Button>
<span className="text-sm text-zinc-500">已上传 {files.length} 个</span>
<span className="rc-attachment-count">已上传 {files.length} 个</span>
</div>
{files.length > 0 ? (
<div className="mt-3 space-y-2">
{files.map((file, index) => (
<div
key={`${file.name || file.url}-${index}`}
className="flex flex-col gap-2 rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2 text-sm sm:flex-row sm:items-center sm:justify-between"
className="rc-attachment-item"
>
<div className="min-w-0">
{file.url ? (
......@@ -210,11 +212,11 @@ function AttachmentField({ field, value = [], onChange, disabled }) {
{file.name || file.url}
</a>
) : (
<div className="truncate font-medium text-zinc-800">
<div className="rc-attachment-name">
{file.name || '未命名附件'}
</div>
)}
<div className="mt-0.5 text-xs text-zinc-500">
<div className="rc-attachment-meta">
{[file.type, formatFileSize(file.size)].filter(Boolean).join(' · ') || '系统附件'}
</div>
</div>
......@@ -224,7 +226,6 @@ function AttachmentField({ field, value = [], onChange, disabled }) {
onClick={() => onChange(files.filter((_, fileIndex) => fileIndex !== index))}
disabled={disabled}
>
<TrashIcon data-slot="icon" />
移除
</Button>
</div>
......@@ -243,31 +244,34 @@ function FieldControl({ field, value, onChange, compact = false }) {
if (field.type === 'select') {
return (
<Select {...commonProps}>
<FormSelect {...commonProps}>
<option value="">{field.emptyLabel || '请选择'}</option>
{(field.options || []).map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</Select>
</FormSelect>
);
}
if (field.type === 'boolean') {
return (
<Select {...commonProps}>
<FormSelect {...commonProps}>
<option value="">{field.emptyLabel || '未填写'}</option>
<option value="true">是</option>
<option value="false">否</option>
</Select>
</FormSelect>
);
}
if (field.type === 'long_text') {
return (
<Textarea
<TextArea
{...commonProps}
className="rc-form-textarea"
fullWidth
variant="secondary"
rows={compact ? 3 : 5}
placeholder={`请输入${field.label}`}
/>
......@@ -284,16 +288,14 @@ function FieldControl({ field, value, onChange, compact = false }) {
);
}
if (field.type === 'date') {
return <FormDatePicker {...commonProps} type="date" />;
}
return (
<Input
{...commonProps}
type={
field.type === 'number'
? 'number'
: field.type === 'date'
? 'date'
: 'text'
}
type={field.type === 'number' ? 'number' : 'text'}
placeholder={`请输入${field.label}`}
/>
);
......@@ -376,6 +378,19 @@ export default function IntegrityPartyPage() {
const [batchForm, setBatchForm] = useState(() => createEmptyLedgerForm(activeKey));
const [batchSaving, setBatchSaving] = useState(false);
const [deleteConfirm, setDeleteConfirm] = useState({ open: false, mode: 'single' });
const [filtersExpanded, setFiltersExpanded] = useState(false);
useEffect(() => {
if (!notice) {
return undefined;
}
const timer = window.setTimeout(() => {
setNotice('');
}, 3600);
return () => window.clearTimeout(timer);
}, [notice]);
const fieldMap = useMemo(() => {
return activeConfig.fields.reduce((accumulator, field) => {
......@@ -387,9 +402,6 @@ export default function IntegrityPartyPage() {
() => records.filter((record) => selectedIds.includes(record.id)),
[records, selectedIds],
);
const totalPages = Math.max(Math.ceil(total / pageSize), 1);
const pageStart = total === 0 ? 0 : page * pageSize + 1;
const pageEnd = Math.min((page + 1) * pageSize, total);
async function reload(nextPage = page) {
setLoading(true);
......@@ -464,6 +476,11 @@ export default function IntegrityPartyPage() {
setDetailOpen(true);
}
function openDeleteDialog(record) {
setActiveRecord(record);
setDeleteConfirm({ open: true, mode: 'single' });
}
async function saveDetail() {
setSaving(true);
setError('');
......@@ -617,105 +634,35 @@ export default function IntegrityPartyPage() {
options: focusFilter.options || [],
}
: null;
return (
<div className="space-y-5">
<Panel className="p-6">
<div className="flex flex-col gap-5 xl:flex-row xl:items-end xl:justify-between">
<div>
<div className="text-sm font-medium text-[var(--pm-brand)]">
党风廉政建设
</div>
<h1 className="mt-2 text-2xl font-semibold tracking-tight text-zinc-950 sm:text-3xl">
台账管理
</h1>
</div>
<div className="flex flex-wrap gap-2">
<Button type="button" outline onClick={handleTemplateExport}>
<DocumentArrowDownIcon data-slot="icon" />
下载模板
</Button>
<Button
type="button"
outline
onClick={() => {
setImportOpen(true);
setImportFile(null);
setImportPreview(null);
}}
>
<DocumentArrowUpIcon data-slot="icon" />
Excel 导入
</Button>
<Button type="button" outline onClick={handleWorkbookExport}>
<ArrowDownTrayIcon data-slot="icon" />
Excel 导出
</Button>
<Button type="button" color="blue" onClick={openCreateDialog}>
<PlusIcon data-slot="icon" />
新建记录
</Button>
</div>
</div>
<div className="mt-6 flex gap-2 overflow-x-auto pb-1">
{INTEGRITY_LEDGER_CONFIGS.map((config) => (
<button
key={config.key}
type="button"
onClick={() => {
setActiveKey(config.key);
setPage(0);
setSelectedIds([]);
}}
className={clsx(
'shrink-0 rounded-lg px-4 py-2 text-sm font-medium transition',
activeKey === config.key
? 'bg-[var(--pm-brand)] text-white shadow-sm'
: 'bg-white text-zinc-600 ring-1 ring-zinc-200 hover:bg-zinc-50 hover:text-zinc-950',
)}
>
{config.tabLabel}
</button>
))}
</div>
</Panel>
{notice ? (
<div className="rounded-2xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-700">
{notice}
</div>
) : null}
{error ? (
<div className="rounded-2xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-700">
{error}
</div>
) : null}
<Panel className="p-5">
<div className="flex flex-wrap items-end gap-4">
<div className="min-w-[220px] flex-1">
<div className="mb-2 text-sm font-medium text-zinc-600">关键词</div>
const filterItems = [
{
key: 'keyword',
label: '关键词',
node: (
<Input
value={draftFilters[activeKey]?.keyword || ''}
onChange={(event) => updateDraftFilter('keyword', event.target.value)}
placeholder="搜索标题、主要内容、部门、人员等"
/>
</div>
<div className="min-w-[140px] flex-1">
<div className="mb-2 text-sm font-medium text-zinc-600">年份</div>
),
className: 'md:col-span-2 xl:col-span-1',
},
{
key: 'year',
label: '年份',
node: (
<Input
type="number"
value={draftFilters[activeKey]?.year || ''}
onChange={(event) => updateDraftFilter('year', event.target.value)}
placeholder="YYYY"
placeholder="例如:2026"
/>
</div>
<div className="min-w-[180px] flex-1">
<div className="mb-2 text-sm font-medium text-zinc-600">
{focusFilter.label}
</div>
{focusField ? (
),
},
{
key: 'focusValue',
label: focusFilter.label,
node: focusField ? (
<FieldControl
field={focusField}
value={draftFilters[activeKey]?.focusValue || ''}
......@@ -725,221 +672,246 @@ export default function IntegrityPartyPage() {
<Input
value={draftFilters[activeKey]?.focusValue || ''}
onChange={(event) => updateDraftFilter('focusValue', event.target.value)}
placeholder={`输入${focusFilter.label}`}
placeholder={`例如:${focusFilter.label}`}
/>
)}
</div>
{configuredFilterFields.map((field) => (
<div key={field.name} className="min-w-[160px] flex-1">
<div className="mb-2 text-sm font-medium text-zinc-600">
{field.label}
</div>
),
},
...configuredFilterFields.map((field) => ({
key: field.name,
label: field.label,
node: (
<FieldControl
field={field}
value={draftFilters[activeKey]?.[field.name] || ''}
onChange={(value) => updateDraftFilter(field.name, value)}
compact
/>
),
})),
];
const primaryFilterItems = filterItems.slice(0, 3);
const expandedFilterItems = filterItems.slice(3);
const tableColumns = useMemo(
() => [
...activeConfig.tableColumns.map((fieldName) => ({
key: fieldName,
title: fieldMap[fieldName]?.label || fieldName,
minWidth: TABLE_COLUMN_WIDTHS[fieldName] || DEFAULT_TABLE_COLUMN_WIDTH,
render: (record) => (
<DataTableText
text={formatCellValue(fieldMap[fieldName], record.metadata[fieldName])}
showTooltip
/>
),
})),
{
key: 'actions',
title: '操作',
isActions: true,
minWidth: 112,
render: (record) => (
<TableActionGroup>
<TableActionButton
icon={Edit}
label="编辑"
onClick={() => openEditDialog(record)}
tone="edit"
/>
<TableActionButton
icon={Delete}
label="删除"
onClick={() => openDeleteDialog(record)}
tone="delete"
/>
</TableActionGroup>
),
},
],
[activeConfig, fieldMap],
);
return (
<div className="space-y-5">
<section className="rc-list-toolbar">
<div className="filter-tabs-wrap">
<Tabs
selectedKey={activeKey}
onSelectionChange={(key) => {
setActiveKey(String(key));
setPage(0);
setSelectedIds([]);
}}
>
<Tabs.ListContainer>
<Tabs.List aria-label="党风廉政台账分类">
{INTEGRITY_LEDGER_CONFIGS.map((config) => (
<LedgerCategoryTab
key={config.key}
tabKey={config.key}
label={config.tabLabel}
/>
))}
</Tabs.List>
</Tabs.ListContainer>
</Tabs>
</div>
</section>
{notice ? (
<div className="rc-toast-viewport" aria-live="polite">
<div className="rc-toast rc-toast--success" role="status">
<span className="rc-toast__icon" aria-hidden="true">
<CheckCircleIcon size={16} />
</span>
<span className="rc-toast__message">{notice}</span>
</div>
</div>
) : null}
{error ? (
<div className="rc-alert rc-alert--danger">
{error}
</div>
) : null}
<section className="rc-filter-card card p-5">
<div className="rc-filter-primary-row">
<div className="rc-filter-primary-fields">
{primaryFilterItems.map((item) => (
<FilterField key={item.key} label={item.label} className={item.className}>
{item.node}
</FilterField>
))}
<Button type="button" color="blue" onClick={applyFilters}>
<CheckIcon data-slot="icon" />
</div>
<div className="rc-filter-actions">
<Tooltip>
<Button
type="button"
isIconOnly
outline
className="rc-filter-reset-button"
aria-label="重置筛选"
onClick={resetFilters}
>
<ArrowPathIcon size={16} />
</Button>
<Tooltip.Content>重置筛选</Tooltip.Content>
</Tooltip>
<Button
type="button"
color="blue"
className="rc-filter-query-button"
onClick={applyFilters}
>
查询
</Button>
<Button type="button" outline onClick={resetFilters}>
<ArrowPathIcon data-slot="icon" />
重置
{expandedFilterItems.length > 0 ? (
<Button
type="button"
plain
className="rc-filter-toggle-button"
aria-expanded={filtersExpanded}
onClick={() => setFiltersExpanded((current) => !current)}
>
{filtersExpanded ? '收起筛选' : '展开筛选'}
<ChevronDownIcon
className={clsx('rc-filter-toggle-icon', filtersExpanded && 'is-expanded')}
size={14}
/>
</Button>
) : null}
</div>
</Panel>
<Panel className="overflow-hidden">
<div className="flex flex-col gap-3 border-b border-zinc-200 px-5 py-4 lg:flex-row lg:items-center lg:justify-between">
<div>
<div className="font-semibold text-zinc-950">{activeConfig.label}</div>
<div className="mt-1 text-sm text-zinc-500">
共 {total} 条,当前显示 {pageStart}-{pageEnd} 条
</div>
{filtersExpanded && expandedFilterItems.length > 0 ? (
<div className="rc-filter-expanded-grid">
{expandedFilterItems.map((item) => (
<FilterField key={item.key} label={item.label}>
{item.node}
</FilterField>
))}
</div>
<div className="flex flex-wrap gap-2">
<Button type="button" outline onClick={handleSelectedExport}>
<ArrowDownTrayIcon data-slot="icon" />
导出勾选
</Button>
<Button type="button" outline onClick={openBatchDialog}>
<PencilSquareIcon data-slot="icon" />
批量修改
) : null}
</section>
<section>
<div className="rc-list-action-bar">
<div className="rc-list-action-title">{activeConfig.label}</div>
<div className="rc-page-actions">
<Button type="button" outline onClick={handleTemplateExport}>
下载模板
</Button>
<Button
type="button"
outline
onClick={() => {
if (selectedRecords.length === 0) {
setError('请先勾选需要批量删除的记录。');
return;
}
setDeleteConfirm({ open: true, mode: 'batch' });
setImportOpen(true);
setImportFile(null);
setImportPreview(null);
}}
>
<TrashIcon data-slot="icon" />
批量删除
Excel 导入
</Button>
<Button type="button" outline onClick={handleWorkbookExport}>
Excel 导出
</Button>
<Button type="button" color="blue" onClick={openCreateDialog}>
新建记录
</Button>
</div>
</div>
{loading ? (
<div className="px-5 py-8 text-sm text-[var(--pm-brand)]">
正在加载台账...
</div>
) : records.length === 0 ? (
<EmptyStatePage title="当前筛选条件下还没有台账记录" />
) : (
<Table
dense
striped
className="[--gutter:--spacing(5)] [&_table]:table-fixed [&_table]:min-w-[1180px]"
>
<colgroup>
<col style={{ width: TABLE_SELECTION_COLUMN_WIDTH }} />
{activeConfig.tableColumns.map((fieldName) => (
<col
key={fieldName}
style={{
width: TABLE_COLUMN_WIDTHS[fieldName] || DEFAULT_TABLE_COLUMN_WIDTH,
}}
/>
))}
<col style={{ width: TABLE_ACTION_COLUMN_WIDTH }} />
</colgroup>
<TableHead>
<TableRow>
<TableHeader className="w-12">
<input
type="checkbox"
aria-label="全选当前页"
checked={
records.length > 0 &&
records.every((record) => selectedIds.includes(record.id))
}
onChange={(event) =>
setSelectedIds(
event.target.checked ? records.map((record) => record.id) : [],
)
}
className="size-4 rounded border-zinc-300 text-[var(--pm-brand)]"
/>
</TableHeader>
{activeConfig.tableColumns.map((fieldName) => (
<TableHeader key={fieldName}>
{fieldMap[fieldName]?.label || fieldName}
</TableHeader>
))}
<TableHeader className="text-right">操作</TableHeader>
</TableRow>
</TableHead>
<TableBody>
{records.map((record) => (
<TableRow key={record.id}>
<TableCell>
<input
type="checkbox"
aria-label={`勾选记录 ${record.title}`}
checked={selectedIds.includes(record.id)}
onChange={(event) => {
setSelectedIds((current) =>
event.target.checked
? [...new Set([...current, record.id])]
: current.filter((id) => id !== record.id),
);
}}
className="size-4 rounded border-zinc-300 text-[var(--pm-brand)]"
/>
</TableCell>
{activeConfig.tableColumns.map((fieldName) => (
<TableCell key={fieldName} className="overflow-hidden">
<span
className="block truncate"
title={formatCellValue(fieldMap[fieldName], record.metadata[fieldName])}
>
{formatCellValue(fieldMap[fieldName], record.metadata[fieldName])}
</span>
</TableCell>
))}
<TableCell className="text-right">
<Button type="button" plain onClick={() => openEditDialog(record)}>
<PencilSquareIcon data-slot="icon" />
编辑
<DataTable
ariaLabel={activeConfig.label}
columns={tableColumns}
rows={records}
isLoading={loading}
getRowKey={(record) => record.id}
selectedRowKeys={selectedIds}
onSelectedRowKeysChange={setSelectedIds}
emptyTitle="暂无数据"
isWide
renderSelectionActions={() => (
<>
<Button type="button" outline onClick={handleSelectedExport}>
导出勾选
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
<div className="flex flex-col gap-3 border-t border-zinc-200 px-5 py-4 sm:flex-row sm:items-center sm:justify-between">
<div className="text-sm text-zinc-500">已勾选 {selectedRecords.length} 条</div>
<div className="flex flex-wrap items-center gap-2">
<Select
aria-label="选择每页数量"
value={String(pageSize)}
onChange={(event) => {
setPageSize(Number(event.target.value));
setPage(0);
}}
className="w-28"
>
{INTEGRITY_PAGE_SIZE_OPTIONS.map((option) => (
<option key={option} value={option}>
{option} 条/页
</option>
))}
</Select>
<Button
type="button"
outline
disabled={page <= 0}
onClick={() => reload(Math.max(page - 1, 0))}
>
上一页
<Button type="button" outline onClick={openBatchDialog}>
批量修改
</Button>
<span className="px-2 text-sm text-zinc-500">
{page + 1} / {totalPages}
</span>
<Button
type="button"
outline
disabled={page + 1 >= totalPages}
onClick={() => reload(page + 1)}
color="red"
onClick={() => setDeleteConfirm({ open: true, mode: 'batch' })}
>
下一页
批量删除
</Button>
</div>
</div>
</Panel>
</>
)}
/>
<DataTablePagination
page={page}
pageSize={pageSize}
total={total}
pageSizeOptions={INTEGRITY_PAGE_SIZE_OPTIONS}
isDisabled={loading}
onPageChange={(nextPage) => reload(Math.max(nextPage, 0))}
onPageSizeChange={(nextPageSize) => {
setPageSize(nextPageSize);
setPage(0);
}}
/>
</section>
<Dialog open={detailOpen} onClose={setDetailOpen} size="5xl">
<DialogTitle>
{detailMode === 'create' ? `新建${activeConfig.tabLabel}记录` : '编辑台账记录'}
</DialogTitle>
<DialogDescription>
业务字段统一保存到 Record 的 metadata 中,附件为系统内上传材料。
</DialogDescription>
<DialogBody>
<LedgerForm config={activeConfig} form={detailForm} setForm={setDetailForm} />
</DialogBody>
<DialogActions>
{detailMode === 'edit' ? (
<Button
type="button"
outline
onClick={() => setDeleteConfirm({ open: true, mode: 'single' })}
>
<TrashIcon data-slot="icon" />
删除
</Button>
) : null}
<Button type="button" plain onClick={() => setDetailOpen(false)}>
<Button type="button" outline onClick={() => setDetailOpen(false)}>
取消
</Button>
<Button type="button" color="blue" onClick={saveDetail} disabled={saving}>
......@@ -950,33 +922,29 @@ export default function IntegrityPartyPage() {
<Dialog open={importOpen} onClose={setImportOpen} size="4xl">
<DialogTitle>Excel 导入{activeConfig.tabLabel}</DialogTitle>
<DialogDescription>
当前导入只处理台账字段,附件请在系统内上传维护。
</DialogDescription>
<DialogBody>
<div className="rounded-2xl border border-dashed border-zinc-300 bg-zinc-50 p-5">
<input
type="file"
<FileUploadField
accept=".xlsx,.xls,.csv"
ariaLabel="选择导入文件"
fileName={importFile?.name || ''}
onChange={(event) => handlePreviewImport(event.target.files?.[0] || null)}
className="block w-full text-sm text-zinc-600 file:mr-4 file:rounded-lg file:border-0 file:bg-[var(--pm-brand)] file:px-4 file:py-2 file:text-sm file:font-medium file:text-white"
triggerLabel="选择文件"
/>
</div>
{importPreview ? (
<div className="mt-5">
<div className="mb-3 text-sm text-zinc-600">
<div className="rc-import-preview mt-5">
<div className="rc-import-preview-summary">
工作表:{importPreview.sheetName},识别到 {importPreview.totalRows} 行
</div>
<div className="overflow-x-auto rounded-2xl border border-zinc-200">
<table className="min-w-full text-left text-sm">
<thead className="bg-zinc-50 text-zinc-500">
<div className="rc-import-preview-scroll">
<table className="rc-import-preview-table">
<thead>
<tr>
<th className="px-4 py-3">行号</th>
<th>行号</th>
{activeConfig.fields
.filter((field) => field.type !== 'file')
.slice(0, 6)
.map((field) => (
<th key={field.name} className="px-4 py-3">
<th key={field.name}>
{field.label}
</th>
))}
......@@ -984,13 +952,13 @@ export default function IntegrityPartyPage() {
</thead>
<tbody>
{importPreview.previewRows.map((row) => (
<tr key={row.rowNumber} className="border-t border-zinc-100">
<td className="px-4 py-3 text-zinc-500">{row.rowNumber}</td>
<tr key={row.rowNumber}>
<td>{row.rowNumber}</td>
{activeConfig.fields
.filter((field) => field.type !== 'file')
.slice(0, 6)
.map((field) => (
<td key={field.name} className="px-4 py-3">
<td key={field.name}>
{row.metadata[field.name] || '-'}
</td>
))}
......@@ -1006,7 +974,7 @@ export default function IntegrityPartyPage() {
<Button type="button" outline onClick={handleTemplateExport}>
下载模板
</Button>
<Button type="button" plain onClick={() => setImportOpen(false)}>
<Button type="button" outline onClick={() => setImportOpen(false)}>
取消
</Button>
<Button
......@@ -1022,10 +990,10 @@ export default function IntegrityPartyPage() {
<Dialog open={batchOpen} onClose={setBatchOpen} size="3xl">
<DialogTitle>批量修改{activeConfig.tabLabel}</DialogTitle>
<DialogDescription>
当前已勾选 {selectedRecords.length} 条记录。仅覆盖你填写的字段,留空字段保持原值。
</DialogDescription>
<DialogBody>
<div className="rc-dialog-notice mb-5">
当前已勾选 {selectedRecords.length} 条记录。仅覆盖你填写的字段,留空字段保持原值。
</div>
<LedgerForm
config={activeConfig}
form={batchForm}
......@@ -1035,7 +1003,7 @@ export default function IntegrityPartyPage() {
/>
</DialogBody>
<DialogActions>
<Button type="button" plain onClick={() => setBatchOpen(false)}>
<Button type="button" outline onClick={() => setBatchOpen(false)}>
取消
</Button>
<Button
......@@ -1067,7 +1035,7 @@ export default function IntegrityPartyPage() {
<DialogActions>
<Button
type="button"
plain
outline
onClick={() => setDeleteConfirm({ open: false, mode: 'single' })}
>
取消
......
......@@ -9,8 +9,8 @@ import {
import dayjs from 'dayjs';
import * as XLSX from 'xlsx';
export const INTEGRITY_PAGE_SIZE_OPTIONS = [20, 50, 100];
export const DEFAULT_INTEGRITY_PAGE_SIZE = 20;
export const INTEGRITY_PAGE_SIZE_OPTIONS = [10, 20, 50];
export const DEFAULT_INTEGRITY_PAGE_SIZE = 10;
export const TRAINING_CATEGORY_OPTIONS = [
'廉政党课讲授',
......
......@@ -2,17 +2,18 @@ import {
ArrowTrendingUpIcon,
CalendarDaysIcon,
ClipboardDocumentListIcon,
} from '@heroicons/react/24/outline';
} from '@/components/AppIcons';
import { Card } from '@heroui/react';
import clsx from 'clsx';
import ReactECharts from 'echarts-for-react';
import { useEffect, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { loadIntegrityPartyDashboard } from '../integrityPartyService';
function Panel({ className, children }) {
return (
<section
className={clsx(
'rounded-[28px] border border-white/70 bg-white/90 shadow-[0_24px_80px_rgba(15,23,42,0.08)] backdrop-blur',
'rc-card-surface rc-workspace-module',
className,
)}
>
......@@ -21,32 +22,32 @@ function Panel({ className, children }) {
);
}
function MetricCard({ icon: Icon, label, value, tone = 'blue' }) {
function MetricCard({ icon: Icon, label, value, tone = 'info' }) {
const toneClass =
tone === 'green'
? 'bg-emerald-100 text-emerald-700'
: tone === 'amber'
? 'bg-amber-100 text-amber-700'
: tone === 'rose'
? 'bg-rose-100 text-rose-700'
: 'bg-sky-100 text-sky-700';
tone === 'success'
? 'rc-tone-surface--success'
: tone === 'warning'
? 'rc-tone-surface--warning'
: tone === 'danger'
? 'rc-tone-surface--danger'
: 'rc-tone-surface--info';
return (
<div className="rounded-[24px] border border-zinc-200/80 bg-white/90 p-5 shadow-sm">
<div className="rc-card-surface rc-dashboard-card p-5">
<div className="flex items-start justify-between gap-4">
<div>
<div className="text-sm font-medium text-zinc-500">{label}</div>
<div className="mt-3 text-3xl font-semibold tracking-tight text-zinc-950">
<div className="text-sm font-medium text-[var(--rc-text-muted)]">{label}</div>
<div className="mt-3 text-3xl font-semibold text-[var(--rc-text-primary)]">
{value}
</div>
</div>
<div
className={clsx(
'flex h-11 w-11 items-center justify-center rounded-2xl',
'rc-metric-icon-surface',
toneClass,
)}
>
<Icon className="size-5" />
<Icon size={20} />
</div>
</div>
</div>
......@@ -56,28 +57,195 @@ function MetricCard({ icon: Icon, label, value, tone = 'blue' }) {
function SectionHeader({ title }) {
return (
<div className="mb-5">
<div className="text-lg font-semibold tracking-tight text-zinc-950">
<div className="text-base font-semibold text-[var(--rc-text-primary)]">
{title}
</div>
</div>
);
}
const CHART_COLORS = [
'#0ea5e9',
'#10b981',
'#f59e0b',
'#6366f1',
'#ef4444',
'#14b8a6',
];
function OverviewSection({ title, meta, children }) {
return (
<section className="rc-module-overview-section">
<div className="rc-module-overview-section__header">
<div className="rc-module-overview-section__title">{title}</div>
{meta ? <div className="rc-module-overview-section__meta">{meta}</div> : null}
</div>
<div className="rc-module-overview-section__body">{children}</div>
</section>
);
}
const CHART_FONT_FAMILY =
'-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
const FALLBACK_CHART_THEME = {
surface: '#ffffff',
subtle: '#f8fafc',
text: '#172033',
secondary: '#4a5568',
muted: '#718096',
border: '#e3e8ef',
brand: '#9E1B1B',
brandHover: '#B83A3A',
warning: '#B7791F',
success: '#6A7B5D',
danger: '#B83A3A',
teal: '#4E6E81',
splitLine: 'rgba(113, 128, 150, 0.18)',
axisLine: 'rgba(113, 128, 150, 0.32)',
axisPointer: 'rgba(158, 27, 27, 0.06)',
};
function token(name, fallback) {
if (typeof window === 'undefined') {
return fallback;
}
const rootValue = getComputedStyle(document.documentElement)
.getPropertyValue(name)
.trim();
if (!rootValue) {
return fallback;
}
if (!rootValue.includes('var(') && !rootValue.includes('color-mix(')) {
return rootValue;
}
if (!document.body) {
return rootValue;
}
const probe = document.createElement('span');
probe.style.position = 'absolute';
probe.style.visibility = 'hidden';
probe.style.pointerEvents = 'none';
probe.style.color = `var(${name})`;
document.body.appendChild(probe);
const resolvedValue = getComputedStyle(probe).color.trim();
probe.remove();
return resolvedValue || rootValue || fallback;
}
function withAlpha(color, alpha, fallback) {
if (!color) {
return fallback;
}
const hex = color.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i);
if (hex) {
const value = hex[1].length === 3
? hex[1].split('').map((part) => part + part).join('')
: hex[1];
const integer = Number.parseInt(value, 16);
return `rgba(${(integer >> 16) & 255}, ${(integer >> 8) & 255}, ${integer & 255}, ${alpha})`;
}
function getPieOption(groups = []) {
const rgb = color.match(/^rgba?\((.+)\)$/i);
if (rgb) {
const channels = rgb[1]
.split(/[,\s/]+/)
.filter(Boolean)
.slice(0, 3)
.map((part) => Number.parseFloat(part));
if (channels.length === 3 && channels.every(Number.isFinite)) {
return `rgba(${channels[0]}, ${channels[1]}, ${channels[2]}, ${alpha})`;
}
}
return fallback;
}
function getChartThemeTokens() {
const muted = token('--rc-text-muted', FALLBACK_CHART_THEME.muted);
const brand = token('--rc-brand-primary', FALLBACK_CHART_THEME.brand);
return {
surface: token('--rc-bg-surface', FALLBACK_CHART_THEME.surface),
subtle: token('--rc-bg-subtle', FALLBACK_CHART_THEME.subtle),
text: token('--rc-text-primary', FALLBACK_CHART_THEME.text),
secondary: token('--rc-text-secondary', FALLBACK_CHART_THEME.secondary),
muted,
border: token('--rc-border-default', FALLBACK_CHART_THEME.border),
brand,
brandHover: token('--rc-brand-primary-hover', FALLBACK_CHART_THEME.brandHover),
warning: token('--rc-color-warning', FALLBACK_CHART_THEME.warning),
success: token('--rc-color-success', FALLBACK_CHART_THEME.success),
danger: token('--rc-color-danger', FALLBACK_CHART_THEME.danger),
teal: token('--rc-color-teal', FALLBACK_CHART_THEME.teal),
splitLine: withAlpha(muted, 0.18, FALLBACK_CHART_THEME.splitLine),
axisLine: withAlpha(muted, 0.32, FALLBACK_CHART_THEME.axisLine),
axisPointer: withAlpha(brand, 0.06, FALLBACK_CHART_THEME.axisPointer),
};
}
function useChartThemeTokens() {
const [themeTokens, setThemeTokens] = useState(getChartThemeTokens);
useEffect(() => {
if (typeof window === 'undefined') {
return undefined;
}
const updateThemeTokens = () => setThemeTokens(getChartThemeTokens());
const observer = new MutationObserver(updateThemeTokens);
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class', 'data-theme', 'style'],
});
updateThemeTokens();
return () => observer.disconnect();
}, []);
return themeTokens;
}
function getTooltipOption(theme) {
return {
color: CHART_COLORS,
backgroundColor: theme.surface,
borderColor: theme.border,
borderWidth: 1,
confine: true,
appendToBody: true,
renderMode: 'html',
extraCssText:
'border-radius:12px;box-shadow:var(--rc-shadow-overlay);padding:8px 10px;',
textStyle: {
color: theme.text,
fontFamily: CHART_FONT_FAMILY,
fontSize: 12,
fontWeight: 500,
},
};
}
function getChartPalette(theme) {
return [
theme.brand,
theme.success,
theme.warning,
theme.danger,
theme.teal,
'#8B5E5E',
];
}
function getPieOption(groups = [], theme = FALLBACK_CHART_THEME) {
return {
backgroundColor: 'transparent',
color: getChartPalette(theme),
tooltip: {
trigger: 'item',
formatter: '{b}<br/>{c} 条 · {d}%',
...getTooltipOption(theme),
},
legend: {
bottom: 0,
......@@ -85,8 +253,10 @@ function getPieOption(groups = []) {
itemWidth: 8,
itemHeight: 8,
textStyle: {
color: '#52525b',
color: theme.secondary,
fontFamily: CHART_FONT_FAMILY,
fontSize: 12,
fontWeight: 500,
},
},
series: [
......@@ -97,7 +267,10 @@ function getPieOption(groups = []) {
center: ['50%', '42%'],
avoidLabelOverlap: true,
label: {
color: '#3f3f46',
color: theme.text,
fontFamily: CHART_FONT_FAMILY,
fontSize: 12,
fontWeight: 500,
formatter: '{b}\n{c} 条',
lineHeight: 18,
},
......@@ -106,7 +279,7 @@ function getPieOption(groups = []) {
length2: 8,
},
itemStyle: {
borderColor: '#ffffff',
borderColor: theme.subtle,
borderWidth: 3,
},
data: groups.map((item) => ({
......@@ -118,41 +291,55 @@ function getPieOption(groups = []) {
};
}
function getBarOption(groups = [], { horizontal = false } = {}) {
function getBarOption(groups = [], { horizontal = false, theme = FALLBACK_CHART_THEME } = {}) {
const visibleGroups = groups.slice(0, 8);
const labels = visibleGroups.map((item) => item.label);
const values = visibleGroups.map((item) => item.count);
return {
color: CHART_COLORS,
backgroundColor: 'transparent',
color: getChartPalette(theme),
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
shadowStyle: {
color: theme.axisPointer,
},
},
formatter: (params = []) => {
const item = params[0];
return item ? `${item.name}<br/>${item.value} 条` : '';
},
...getTooltipOption(theme),
},
grid: {
top: 18,
right: 14,
bottom: horizontal ? 18 : 48,
left: horizontal ? 84 : 36,
top: 20,
right: 18,
bottom: horizontal ? 20 : 44,
left: horizontal ? 84 : 42,
containLabel: true,
},
xAxis: horizontal
? {
type: 'value',
axisLine: {
show: false,
},
axisTick: {
show: false,
},
splitLine: {
lineStyle: {
color: '#e4e4e7',
type: 'dashed',
color: theme.splitLine,
type: 'solid',
},
},
axisLabel: {
color: '#71717a',
color: theme.muted,
fontFamily: CHART_FONT_FAMILY,
fontSize: 12,
fontWeight: 500,
},
}
: {
......@@ -163,12 +350,16 @@ function getBarOption(groups = [], { horizontal = false } = {}) {
},
axisLine: {
lineStyle: {
color: '#e4e4e7',
color: theme.axisLine,
},
},
axisLabel: {
color: '#71717a',
color: theme.text,
fontFamily: CHART_FONT_FAMILY,
fontSize: 12,
fontWeight: 500,
interval: 0,
margin: 12,
rotate: labels.some((label) => label.length > 5) ? 28 : 0,
},
},
......@@ -183,7 +374,10 @@ function getBarOption(groups = [], { horizontal = false } = {}) {
show: false,
},
axisLabel: {
color: '#52525b',
color: theme.secondary,
fontFamily: CHART_FONT_FAMILY,
fontSize: 12,
fontWeight: 500,
width: 74,
overflow: 'truncate',
},
......@@ -192,21 +386,29 @@ function getBarOption(groups = [], { horizontal = false } = {}) {
type: 'value',
splitLine: {
lineStyle: {
color: '#e4e4e7',
type: 'dashed',
color: theme.splitLine,
type: 'solid',
},
},
axisLabel: {
color: '#71717a',
color: theme.muted,
fontFamily: CHART_FONT_FAMILY,
fontSize: 12,
fontWeight: 500,
},
},
series: [
{
type: 'bar',
data: values,
barMaxWidth: horizontal ? 18 : 34,
itemStyle: {
barMaxWidth: horizontal ? 18 : 38,
showBackground: true,
backgroundStyle: {
color: withAlpha(theme.muted, 0.06, 'rgba(113, 128, 150, 0.06)'),
borderRadius: horizontal ? [0, 8, 8, 0] : [8, 8, 0, 0],
},
itemStyle: {
borderRadius: horizontal ? [0, 10, 10, 0] : [10, 10, 4, 4],
color: {
type: 'linear',
x: 0,
......@@ -214,8 +416,8 @@ function getBarOption(groups = [], { horizontal = false } = {}) {
x2: horizontal ? 1 : 0,
y2: horizontal ? 0 : 1,
colorStops: [
{ offset: 0, color: '#0ea5e9' },
{ offset: 1, color: '#10b981' },
{ offset: 0, color: theme.brand },
{ offset: 1, color: theme.brandHover },
],
},
},
......@@ -226,23 +428,34 @@ function getBarOption(groups = [], { horizontal = false } = {}) {
function ChartPanel({ title, groups, type = 'bar' }) {
const hasData = groups.length > 0;
const option =
const chartTheme = useChartThemeTokens();
const option = useMemo(
() =>
type === 'pie'
? getPieOption(groups)
: getBarOption(groups, { horizontal: type === 'horizontalBar' });
? getPieOption(groups, chartTheme)
: getBarOption(groups, {
horizontal: type === 'horizontalBar',
theme: chartTheme,
}),
[chartTheme, groups, type],
);
return (
<Panel className="p-6">
<SectionHeader title={title} />
<Panel className="rc-chart-card">
<div className="rc-chart-card__header">
<div className="rc-chart-card__title">{title}</div>
</div>
{hasData ? (
<div className="rc-chart-card__body">
<ReactECharts
option={option}
notMerge
lazyUpdate
style={{ height: type === 'pie' ? 320 : 300, width: '100%' }}
style={{ height: '100%', width: '100%' }}
/>
</div>
) : (
<div className="flex h-[300px] items-center justify-center rounded-[22px] border border-dashed border-zinc-200 bg-zinc-50 text-sm text-zinc-400">
<div className="rc-chart-card__empty">
暂无可展示的数据
</div>
)}
......@@ -292,48 +505,19 @@ export default function ResourceStatPage() {
return (
<div className="space-y-6">
<Panel className="relative overflow-hidden p-6 sm:p-8">
<div className="absolute inset-0 bg-[linear-gradient(135deg,rgba(255,247,237,0.94)_0%,rgba(255,255,255,0.9)_48%,rgba(224,242,254,0.86)_100%),radial-gradient(circle_at_top_left,rgba(245,158,11,0.14),transparent_36%),radial-gradient(circle_at_top_right,rgba(14,165,233,0.18),transparent_34%)]" />
<div className="relative">
<div className="mt-5 flex flex-col gap-6 xl:flex-row xl:items-end xl:justify-between">
<div className="max-w-3xl">
<h1 className="text-3xl font-semibold tracking-tight text-zinc-950 sm:text-4xl">
驾驶舱
</h1>
<div className="mt-4 text-sm leading-6 text-zinc-600">
汇总展示党风廉政台账的总量、本年度记录、最近更新与分类分布。
</div>
</div>
<div className="grid gap-3 sm:w-56">
<div className="rounded-[24px] border border-white/70 bg-white/80 p-4 shadow-sm">
<div className="text-xs font-semibold tracking-[0.18em] text-zinc-500">
最近 30 天更新
</div>
<div className="mt-2 text-2xl font-semibold text-zinc-950">
{integrityDashboard?.recentCount || 0}
</div>
</div>
</div>
</div>
{error ? (
<div
className={clsx(
'mt-6 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-700',
)}
className="rc-alert rc-alert--danger"
>
{error}
</div>
) : null}
{loading ? (
<div className="mt-6 rounded-2xl border border-sky-200 bg-sky-50 px-4 py-3 text-sm text-sky-700">
<div className="rc-alert">
正在加载驾驶舱...
</div>
) : null}
</div>
</Panel>
<IntegrityDashboardView dashboard={integrityDashboard} loading={loading} />
</div>
......@@ -362,24 +546,24 @@ function IntegrityDashboardView({ dashboard, loading }) {
return (
<>
<div className="grid gap-4 md:grid-cols-3">
<div className="rc-stat-metric-grid grid gap-4 md:grid-cols-3" data-count="3">
<MetricCard
icon={ClipboardDocumentListIcon}
label="台账总数"
value={safeDashboard.totalCount}
tone="blue"
tone="info"
/>
<MetricCard
icon={CalendarDaysIcon}
label="本年度记录"
value={safeDashboard.currentYearCount}
tone="green"
tone="success"
/>
<MetricCard
icon={ArrowTrendingUpIcon}
label="最近 30 天更新"
value={safeDashboard.recentCount}
tone="rose"
tone="warning"
/>
</div>
......@@ -411,16 +595,21 @@ function IntegrityModuleOverview({ overview, loading }) {
];
return (
<Panel className="p-6">
<SectionHeader title={`${safeOverview.year} 年模块概览`} />
<Card className="rc-card-surface rc-workspace-module rc-module-overview-card">
<Card.Content className="rc-module-overview-card__content">
<div className="rc-module-overview-heading">
{safeOverview.year} 年模块概览
</div>
{loading ? (
<div className="text-sm text-zinc-400">正在加载党风廉政统计...</div>
<div className="text-sm text-[var(--rc-text-muted)]">正在加载党风廉政统计...</div>
) : (
<div className="grid gap-6 xl:grid-cols-[1.35fr_0.9fr_1fr]">
<div>
<div className="mb-3 text-sm font-semibold text-zinc-900">培训类</div>
<div className="overflow-hidden rounded-2xl border border-zinc-200">
<div className="grid grid-cols-[1fr_88px_88px] bg-zinc-50 px-4 py-3 text-xs font-semibold text-zinc-500">
<div className="rc-module-overview-grid">
<OverviewSection
title="培训类"
meta={`${safeOverview.trainingCategories.length} 类`}
>
<div className="rc-stat-table">
<div className="rc-stat-table__head grid grid-cols-[minmax(0,1fr)_88px_88px]">
<div>培训类别</div>
<div className="text-right">场次</div>
<div className="text-right">人数</div>
......@@ -428,57 +617,59 @@ function IntegrityModuleOverview({ overview, loading }) {
{safeOverview.trainingCategories.map((item) => (
<div
key={item.key}
className="grid grid-cols-[1fr_88px_88px] border-t border-zinc-200 px-4 py-3 text-sm"
className="rc-stat-table__row grid grid-cols-[minmax(0,1fr)_88px_88px]"
>
<div className="min-w-0 truncate font-medium text-zinc-800">
<div className="rc-stat-table__primary">
{item.label}
</div>
<div className="text-right tabular-nums text-zinc-950">
<div className="rc-stat-table__number">
{item.sessionCount}
</div>
<div className="text-right tabular-nums text-zinc-950">
<div className="rc-stat-table__number">
{item.participantCount}
</div>
</div>
))}
</div>
</div>
</OverviewSection>
<div>
<div className="mb-3 text-sm font-semibold text-zinc-900">谈话类</div>
<div className="grid gap-3">
<OverviewSection title="谈话类" meta={`${talkItems.length} 项`}>
<div className="rc-stat-summary-list rc-stat-summary-list--compact">
{talkItems.map(([label, value]) => (
<div
key={label}
className="flex items-center justify-between rounded-2xl border border-zinc-200 bg-zinc-50 px-4 py-3"
className="rc-stat-summary-row rc-stat-summary-row--compact"
>
<div className="text-sm text-zinc-600">{label}</div>
<div className="text-2xl font-semibold tabular-nums text-zinc-950">
<div className="rc-stat-summary-row__label">{label}</div>
<div className="rc-stat-summary-row__value">
{value}
</div>
</div>
))}
</div>
</div>
</OverviewSection>
<div>
<div className="mb-3 text-sm font-semibold text-zinc-900">年度开展次数</div>
<div className="space-y-2">
<OverviewSection
title="年度开展次数"
meta={`${safeOverview.yearlyModuleCounts.length} 项`}
>
<div className="rc-stat-count-list rc-stat-count-list--compact">
{safeOverview.yearlyModuleCounts.map((item) => (
<div
key={item.key}
className="flex items-center justify-between border-b border-zinc-200 py-3 last:border-b-0"
className="rc-stat-count-row rc-stat-count-row--compact"
>
<div className="text-sm font-medium text-zinc-700">{item.label}</div>
<div className="text-xl font-semibold tabular-nums text-zinc-950">
<div className="rc-stat-count-row__label">{item.label}</div>
<div className="rc-stat-count-row__value">
{item.count}
</div>
</div>
))}
</div>
</div>
</OverviewSection>
</div>
)}
</Panel>
</Card.Content>
</Card>
);
}
......@@ -5,6 +5,8 @@ import { history, Navigate, Outlet, useLocation } from 'umi';
export const UserContext = createContext(null);
const LOGIN_PATH = '/login';
function getStoredUser() {
const value = authService.getCurrentUser();
......@@ -76,7 +78,7 @@ export default function AuthWrapper() {
}
} finally {
authService.clearUser();
history.push('/login');
history.push(LOGIN_PATH);
}
},
};
......
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment