提交 fdc9c511 authored 作者: 王鹏飞's avatar 王鹏飞

chore: update

上级 10aacd61
VITE_LOGIN_URL=https://login.ezijing.com/auth/login/index
{
"globals": {
"createRef": true,
"forwardRef": true,
"lazy": true,
"memo": true,
"startTransition": true,
"useCallback": true,
"useContext": true,
"useDebugValue": true,
"useDeferredValue": true,
"useEffect": true,
"useHref": true,
"useId": true,
"useImperativeHandle": true,
"useInRouterContext": true,
"useInsertionEffect": true,
"useLayoutEffect": true,
"useLocation": true,
"useMemo": true,
"useNavigate": true,
"useNavigationType": true,
"useOutlet": true,
"useOutletContext": true,
"useParams": true,
"useReducer": true,
"useRef": true,
"useResolvedPath": true,
"useRoutes": true,
"useState": true,
"useSyncExternalStore": true,
"useTransition": true
}
}
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
// biome-ignore lint: disable
export {}
declare global {
const createRef: typeof import('react')['createRef']
const forwardRef: typeof import('react')['forwardRef']
const lazy: typeof import('react')['lazy']
const memo: typeof import('react')['memo']
const startTransition: typeof import('react')['startTransition']
const useCallback: typeof import('react')['useCallback']
const useContext: typeof import('react')['useContext']
const useDebugValue: typeof import('react')['useDebugValue']
const useDeferredValue: typeof import('react')['useDeferredValue']
const useEffect: typeof import('react')['useEffect']
const useHref: typeof import('react-router')['useHref']
const useId: typeof import('react')['useId']
const useImperativeHandle: typeof import('react')['useImperativeHandle']
const useInRouterContext: typeof import('react-router')['useInRouterContext']
const useInsertionEffect: typeof import('react')['useInsertionEffect']
const useLayoutEffect: typeof import('react')['useLayoutEffect']
const useLocation: typeof import('react-router')['useLocation']
const useMemo: typeof import('react')['useMemo']
const useNavigate: typeof import('react-router')['useNavigate']
const useNavigationType: typeof import('react-router')['useNavigationType']
const useOutlet: typeof import('react-router')['useOutlet']
const useOutletContext: typeof import('react-router')['useOutletContext']
const useParams: typeof import('react-router')['useParams']
const useReducer: typeof import('react')['useReducer']
const useRef: typeof import('react')['useRef']
const useResolvedPath: typeof import('react-router')['useResolvedPath']
const useRoutes: typeof import('react-router')['useRoutes']
const useState: typeof import('react')['useState']
const useSyncExternalStore: typeof import('react')['useSyncExternalStore']
const useTransition: typeof import('react')['useTransition']
}
...@@ -9,14 +9,16 @@ ...@@ -9,14 +9,16 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@ant-design/icons": "^5.6.1", "@ant-design/icons": "^5.6.1",
"@fortaine/fetch-event-source": "^3.0.6",
"@tanstack/react-query": "^5.67.1", "@tanstack/react-query": "^5.67.1",
"antd": "^5.24.3", "antd": "^5.24.3",
"axios": "^1.8.1", "axios": "^1.8.1",
"classnames": "^2.5.1", "blueimp-md5": "^2.19.0",
"lucide-react": "^0.477.0", "lucide-react": "^0.477.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router": "^7.3.0", "react-router": "^7.3.0",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
"zustand": "^5.0.3" "zustand": "^5.0.3"
}, },
"devDependencies": { "devDependencies": {
...@@ -31,7 +33,6 @@ ...@@ -31,7 +33,6 @@
"eslint-plugin-react-refresh": "^0.4.6", "eslint-plugin-react-refresh": "^0.4.6",
"sass-embedded": "^1.85.1", "sass-embedded": "^1.85.1",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"unplugin-auto-import": "^19.1.1",
"vite": "^5.2.0", "vite": "^5.2.0",
"vite-plugin-mkcert": "^1.17.7" "vite-plugin-mkcert": "^1.17.7"
} }
...@@ -642,6 +643,15 @@ ...@@ -642,6 +643,15 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
} }
}, },
"node_modules/@fortaine/fetch-event-source": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@fortaine/fetch-event-source/-/fetch-event-source-3.0.6.tgz",
"integrity": "sha512-621GAuLMvKtyZQ3IA6nlDWhV1V/7PGOTNIGLUifxt0KzM+dZIweJ6F3XvQF3QnqeNfS1N7WQ0Kil1Di/lhChEw==",
"license": "MIT",
"engines": {
"node": ">=16.15"
}
},
"node_modules/@humanwhocodes/config-array": { "node_modules/@humanwhocodes/config-array": {
"version": "0.13.0", "version": "0.13.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
...@@ -704,13 +714,6 @@ ...@@ -704,13 +714,6 @@
"dev": true, "dev": true,
"license": "BSD-3-Clause" "license": "BSD-3-Clause"
}, },
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@nodelib/fs.scandir": { "node_modules/@nodelib/fs.scandir": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
...@@ -1852,6 +1855,12 @@ ...@@ -1852,6 +1855,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/blueimp-md5": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz",
"integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==",
"license": "MIT"
},
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
...@@ -1980,13 +1989,6 @@ ...@@ -1980,13 +1989,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/confbox": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.1.tgz",
"integrity": "sha512-hkT3yDPFbs95mNCy1+7qNKC6Pro+/ibzYxtM2iqEigpf0sVw+bg4Zh9/snjsBcf990vfIsg5+1U7VyiyBb3etg==",
"dev": true,
"license": "MIT"
},
"node_modules/cookie": { "node_modules/cookie": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
...@@ -2391,16 +2393,6 @@ ...@@ -2391,16 +2393,6 @@
"node": ">=4.0" "node": ">=4.0"
} }
}, },
"node_modules/estree-walker": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0"
}
},
"node_modules/esutils": { "node_modules/esutils": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
...@@ -2411,13 +2403,6 @@ ...@@ -2411,13 +2403,6 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/exsolve": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.2.tgz",
"integrity": "sha512-ZEcIMbthn2zeX4/wD/DLxDUjuCltHXT8Htvm/JFlTkdYgWh2+HGppgwwNUnIVxzxP7yJOPtuBAec0dLx6lVY8w==",
"dev": true,
"license": "MIT"
},
"node_modules/fast-deep-equal": { "node_modules/fast-deep-equal": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
...@@ -2997,24 +2982,6 @@ ...@@ -2997,24 +2982,6 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/local-pkg": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.1.tgz",
"integrity": "sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"mlly": "^1.7.4",
"pkg-types": "^2.0.1",
"quansync": "^0.2.8"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/locate-path": { "node_modules/locate-path": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
...@@ -3059,16 +3026,6 @@ ...@@ -3059,16 +3026,6 @@
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
} }
}, },
"node_modules/magic-string": {
"version": "0.30.17",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
"integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0"
}
},
"node_modules/math-intrinsics": { "node_modules/math-intrinsics": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
...@@ -3139,38 +3096,6 @@ ...@@ -3139,38 +3096,6 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/mlly": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz",
"integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==",
"dev": true,
"license": "MIT",
"dependencies": {
"acorn": "^8.14.0",
"pathe": "^2.0.1",
"pkg-types": "^1.3.0",
"ufo": "^1.5.4"
}
},
"node_modules/mlly/node_modules/confbox": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
"integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
"dev": true,
"license": "MIT"
},
"node_modules/mlly/node_modules/pkg-types": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
"integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"confbox": "^0.1.8",
"mlly": "^1.7.4",
"pathe": "^2.0.1"
}
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
...@@ -3317,13 +3242,6 @@ ...@@ -3317,13 +3242,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/pathe": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
"dev": true,
"license": "MIT"
},
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
...@@ -3344,18 +3262,6 @@ ...@@ -3344,18 +3262,6 @@
"url": "https://github.com/sponsors/jonschlinkert" "url": "https://github.com/sponsors/jonschlinkert"
} }
}, },
"node_modules/pkg-types": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.1.0.tgz",
"integrity": "sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==",
"dev": true,
"license": "MIT",
"dependencies": {
"confbox": "^0.2.1",
"exsolve": "^1.0.1",
"pathe": "^2.0.3"
}
},
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.5.3", "version": "8.5.3",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
...@@ -3411,23 +3317,6 @@ ...@@ -3411,23 +3317,6 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/quansync": {
"version": "0.2.8",
"resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.8.tgz",
"integrity": "sha512-4+saucphJMazjt7iOM27mbFCk+D9dd/zmgMDCzRZ8MEoBfYp7lAvoN38et/phRQF6wOPMy/OROBGgoWeSKyluA==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/antfu"
},
{
"type": "individual",
"url": "https://github.com/sponsors/sxzz"
}
],
"license": "MIT"
},
"node_modules/queue-microtask": { "node_modules/queue-microtask": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
...@@ -4652,13 +4541,6 @@ ...@@ -4652,13 +4541,6 @@
"compute-scroll-into-view": "^3.0.2" "compute-scroll-into-view": "^3.0.2"
} }
}, },
"node_modules/scule": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz",
"integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==",
"dev": true,
"license": "MIT"
},
"node_modules/semver": { "node_modules/semver": {
"version": "7.7.1", "version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
...@@ -4753,26 +4635,6 @@ ...@@ -4753,26 +4635,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/strip-literal": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz",
"integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==",
"dev": true,
"license": "MIT",
"dependencies": {
"js-tokens": "^9.0.1"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/strip-literal/node_modules/js-tokens": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
"integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
"dev": true,
"license": "MIT"
},
"node_modules/stylis": { "node_modules/stylis": {
"version": "4.3.6", "version": "4.3.6",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz",
...@@ -4831,51 +4693,6 @@ ...@@ -4831,51 +4693,6 @@
"node": ">=12.22" "node": ">=12.22"
} }
}, },
"node_modules/tinyglobby": {
"version": "0.2.12",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz",
"integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.4.3",
"picomatch": "^4.0.2"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/tinyglobby/node_modules/fdir": {
"version": "6.4.3",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz",
"integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/tinyglobby/node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/to-regex-range": { "node_modules/to-regex-range": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
...@@ -4961,13 +4778,6 @@ ...@@ -4961,13 +4778,6 @@
"node": ">=14.17" "node": ">=14.17"
} }
}, },
"node_modules/ufo": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz",
"integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==",
"dev": true,
"license": "MIT"
},
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "6.20.0", "version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
...@@ -4975,167 +4785,6 @@ ...@@ -4975,167 +4785,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/unimport": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/unimport/-/unimport-4.1.2.tgz",
"integrity": "sha512-oVUL7PSlyVV3QRhsdcyYEMaDX8HJyS/CnUonEJTYA3//bWO+o/4gG8F7auGWWWkrrxBQBYOO8DKe+C53ktpRXw==",
"dev": true,
"license": "MIT",
"dependencies": {
"acorn": "^8.14.0",
"escape-string-regexp": "^5.0.0",
"estree-walker": "^3.0.3",
"local-pkg": "^1.0.0",
"magic-string": "^0.30.17",
"mlly": "^1.7.4",
"pathe": "^2.0.3",
"picomatch": "^4.0.2",
"pkg-types": "^1.3.1",
"scule": "^1.3.0",
"strip-literal": "^3.0.0",
"tinyglobby": "^0.2.11",
"unplugin": "^2.2.0",
"unplugin-utils": "^0.2.4"
},
"engines": {
"node": ">=18.12.0"
}
},
"node_modules/unimport/node_modules/confbox": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
"integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
"dev": true,
"license": "MIT"
},
"node_modules/unimport/node_modules/escape-string-regexp": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
"integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/unimport/node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/unimport/node_modules/pkg-types": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
"integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"confbox": "^0.1.8",
"mlly": "^1.7.4",
"pathe": "^2.0.1"
}
},
"node_modules/unplugin": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.2.0.tgz",
"integrity": "sha512-m1ekpSwuOT5hxkJeZGRxO7gXbXT3gF26NjQ7GdVHoLoF8/nopLcd/QfPigpCy7i51oFHiRJg/CyHhj4vs2+KGw==",
"dev": true,
"license": "MIT",
"dependencies": {
"acorn": "^8.14.0",
"webpack-virtual-modules": "^0.6.2"
},
"engines": {
"node": ">=18.12.0"
}
},
"node_modules/unplugin-auto-import": {
"version": "19.1.1",
"resolved": "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-19.1.1.tgz",
"integrity": "sha512-sCGZZrSR1Bc8RfN8Q0RUDxXtC20rdAt7UB4lDyq8MNtKVHiXXh+5af6Nz4JRp9Q+7HjnbgQfQox0TkEymjdUAQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"local-pkg": "^1.0.0",
"magic-string": "^0.30.17",
"picomatch": "^4.0.2",
"unimport": "^4.1.2",
"unplugin": "^2.2.0",
"unplugin-utils": "^0.2.4"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@nuxt/kit": "^3.2.2",
"@vueuse/core": "*"
},
"peerDependenciesMeta": {
"@nuxt/kit": {
"optional": true
},
"@vueuse/core": {
"optional": true
}
}
},
"node_modules/unplugin-auto-import/node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/unplugin-utils": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.2.4.tgz",
"integrity": "sha512-8U/MtpkPkkk3Atewj1+RcKIjb5WBimZ/WSLhhR3w6SsIj8XJuKTacSP8g+2JhfSGw0Cb125Y+2zA/IzJZDVbhA==",
"dev": true,
"license": "MIT",
"dependencies": {
"pathe": "^2.0.2",
"picomatch": "^4.0.2"
},
"engines": {
"node": ">=18.12.0"
},
"funding": {
"url": "https://github.com/sponsors/sxzz"
}
},
"node_modules/unplugin-utils/node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/uri-js": { "node_modules/uri-js": {
"version": "4.4.1", "version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
...@@ -5231,13 +4880,6 @@ ...@@ -5231,13 +4880,6 @@
"vite": ">=3" "vite": ">=3"
} }
}, },
"node_modules/webpack-virtual-modules": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
"dev": true,
"license": "MIT"
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
...@@ -5271,6 +4913,18 @@ ...@@ -5271,6 +4913,18 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/xlsx": {
"version": "0.20.3",
"resolved": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
"integrity": "sha512-oLDq3jw7AcLqKWH2AhCpVTZl8mf6X2YReP+Neh0SJUzV/BdZYjth94tG5toiMB1PPrYtxOCfaoUCkvtuH+3AJA==",
"license": "Apache-2.0",
"bin": {
"xlsx": "bin/xlsx.njs"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/yocto-queue": { "node_modules/yocto-queue": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
......
...@@ -11,14 +11,16 @@ ...@@ -11,14 +11,16 @@
}, },
"dependencies": { "dependencies": {
"@ant-design/icons": "^5.6.1", "@ant-design/icons": "^5.6.1",
"@fortaine/fetch-event-source": "^3.0.6",
"@tanstack/react-query": "^5.67.1", "@tanstack/react-query": "^5.67.1",
"antd": "^5.24.3", "antd": "^5.24.3",
"axios": "^1.8.1", "axios": "^1.8.1",
"classnames": "^2.5.1", "blueimp-md5": "^2.19.0",
"lucide-react": "^0.477.0", "lucide-react": "^0.477.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router": "^7.3.0", "react-router": "^7.3.0",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
"zustand": "^5.0.3" "zustand": "^5.0.3"
}, },
"devDependencies": { "devDependencies": {
...@@ -33,7 +35,6 @@ ...@@ -33,7 +35,6 @@
"eslint-plugin-react-refresh": "^0.4.6", "eslint-plugin-react-refresh": "^0.4.6",
"sass-embedded": "^1.85.1", "sass-embedded": "^1.85.1",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"unplugin-auto-import": "^19.1.1",
"vite": "^5.2.0", "vite": "^5.2.0",
"vite-plugin-mkcert": "^1.17.7" "vite-plugin-mkcert": "^1.17.7"
} }
......
...@@ -81,6 +81,7 @@ textarea:focus { ...@@ -81,6 +81,7 @@ textarea:focus {
html, html,
body, body,
#root { #root {
width: 100%;
height: 100%; height: 100%;
} }
.app-card { .app-card {
......
...@@ -28,3 +28,8 @@ export async function uploadFile(data: Record<string, any>) { ...@@ -28,3 +28,8 @@ export async function uploadFile(data: Record<string, any>) {
}) })
return data return data
} }
// 获取公共字典列表
export function getMapList() {
return httpRequest.get('/api/resource/v1/util/get-data-dictionary-list')
}
import { useState, useImperativeHandle, forwardRef } from 'react'
import { Table, Form, Button, Space, Flex } from 'antd'
import type { TableProps } from 'antd'
import { useQuery } from '@tanstack/react-query'
export interface QueryParams {
[key: string]: any
page: number
pageSize: number
}
export interface AppListProps extends TableProps {
fetchApi?: (params: QueryParams) => Promise<{ list: any[]; total: number }>
filters?: any[]
filterAside?: React.ReactNode
}
export interface AppListRef {
refresh: () => void
reset: () => void
}
const AppList = forwardRef<AppListRef, AppListProps>((props, ref) => {
const { fetchApi, filters = [], filterAside, dataSource = [], ...rest } = props
const [form] = Form.useForm()
const [queryParams, setQueryParams] = useState<QueryParams>({ page: 1, pageSize: 10 })
const { data, refetch, isLoading } = useQuery({
queryKey: ['appList', queryParams],
queryFn: async () => {
const result = fetchApi ? await fetchApi(queryParams) : { list: dataSource || [], total: dataSource?.length || 0 }
return { ...result, list: result.list as any[] }
},
})
// 暴露方法
useImperativeHandle(ref, () => ({
refresh: refetch,
reset: handleReset,
}))
// 提交筛选
const handleSearch = (values: Record<string, any>) => {
console.log(values)
setQueryParams((prev) => ({ ...prev, ...values, page: 1 }))
}
const handleReset = () => {
form.resetFields()
setQueryParams({ page: 1, pageSize: 10 })
}
// 处理分页
const handlePageChange = (page: number, pageSize: number) => {
setQueryParams((prev) => ({ ...prev, page, pageSize }))
}
const pagination = {
showSizeChanger: true,
showQuickJumper: true,
pageSizeOptions: [10, 20, 30, 40, 50],
showTotal: (total: number) => `共${total}条数据`,
onChange: handlePageChange,
}
const filterElement = (
<div className="app-list-filter" style={{ marginBottom: 20 }}>
<Flex justify="space-between">
<Form layout="inline" autoComplete="off" form={form} onFinish={handleSearch}>
{filters.map((item, index) => (
<Form.Item label={item.label} name={item.name} key={index}>
{item.element}
</Form.Item>
))}
<Space>
<Button type="primary" htmlType="button" ghost onClick={handleReset}>
重置
</Button>
<Button type="primary" htmlType="submit">
查询
</Button>
</Space>
</Form>
{filterAside}
</Flex>
</div>
)
return (
<div className="app-list">
{filters.length > 0 && filterElement}
<div className="app-list-table">
<Table rowKey="id" dataSource={data?.list} loading={isLoading} pagination={pagination} {...rest} />
</div>
</div>
)
})
export default AppList
.ai-chat {
height: 100%;
width: 400px;
.ai-chat-container {
height: 100%;
display: flex;
flex-direction: column;
}
.ant-card-extra {
cursor: pointer;
}
.ant-card-body {
height: calc(100% - 56px);
}
.input-container {
margin-top: 10px;
fill: #fff;
border: 1px solid #e0e3e6;
border-radius: 16px;
stroke-width: 2px;
stroke: #d9d9d9;
filter: drop-shadow(0px 0px 11px rgba(0, 0, 0, 0.05));
transition: border-color 0.3s;
overflow: hidden;
}
.input-box {
display: flex;
align-items: center;
padding: 10px;
background-color: #fff;
.edit-area {
flex: 1;
position: relative;
max-height: 160px;
overflow: hidden auto;
}
.pre-prompt {
position: absolute;
top: 0;
left: 0;
z-index: 1;
line-height: 1.7;
background-color: #e6eaf0;
border-radius: 4px;
padding: 4px 8px 3px;
font-size: 14px;
font-weight: 500;
}
.content {
padding: 0;
border: 0;
background: none;
box-shadow: none;
font-size: 14px;
line-height: 30px;
}
.input-tools {
align-self: flex-end;
display: flex;
align-items: center;
gap: 10px;
}
}
.message-scroll {
margin: 10px 0;
// height: 600px;
height: 100%;
overflow-x: hidden;
overflow-y: auto;
}
.message-item {
display: flex;
margin: 10px 0;
&.user {
justify-content: flex-end;
.message-box {
background-color: #0000000d;
}
}
&.assistant {
.message-box {
background-color: #fae3e5;
}
}
.message-box {
max-width: 75%;
border-radius: 12px;
}
.message-content {
padding: 16px;
// max-height: 300px;
// overflow-y: auto;
}
.message-tools {
padding: 16px;
border-top: 1px solid #ececec;
}
.result-item {
margin-top: 12px;
p {
margin-bottom: 12px;
word-break: break-word !important;
}
}
}
}
import { useState, KeyboardEvent } from 'react'
import { Button, Card, FloatButton, Select } from 'antd'
import { CircleArrowLeft, CircleArrowRight } from 'lucide-react'
import './AIChat.scss'
import { OpenAIOutlined, SendOutlined } from '@ant-design/icons'
import TextArea from 'antd/es/input/TextArea'
import { useAI } from '@/hooks/useAI'
export default function AIChat() {
const [collapsed, setCollapsed] = useState(true)
const toggleCollapsed = () => {
setCollapsed(!collapsed)
}
const { ai, setAI, options, post, messages, isLoading } = useAI()
const [content, setContent] = useState('')
const handleEnterSearch = (e: KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault()
handleSearch()
}
}
const handleSearch = () => {
setContent('')
post({ content })
}
const messageItemRender = (message) => {
return (
<div className={`message-item ${message.role}`}>
<div className="message-box">
<div className="message-content">{message.content}</div>
</div>
</div>
)
}
if (collapsed) {
return (
<Card
className="app-card ai-chat"
title="AI对话"
extra={<span onClick={toggleCollapsed}>{collapsed ? <CircleArrowRight /> : <CircleArrowLeft />}</span>}>
<div className="ai-chat-container">
<div className="message-scroll">
{messages.map((message) => {
return messageItemRender(message)
})}
</div>
<Select value={ai} options={options} onChange={setAI} style={{ width: '100%' }}></Select>
<div className="input-container">
<div className="input-box">
<div className="edit-area">
<TextArea
className="content"
autoSize
value={content}
placeholder="今天需要我做些什么?shift+enter换行"
onChange={(e) => setContent(e.target.value)}
onKeyDown={handleEnterSearch}
/>
</div>
<div className="input-tools">
<Button
type="primary"
size="large"
icon={<SendOutlined />}
onClick={handleSearch}
loading={isLoading}
/>
</div>
</div>
<div className="upload-list"></div>
</div>
</div>
</Card>
)
} else {
return <FloatButton icon={<OpenAIOutlined />} onClick={toggleCollapsed} />
}
}
import { Button, Card, Flex } from 'antd'
import { ReactNode } from 'react'
export default function DataWrap({
title,
buttons,
children,
}: {
title: string
buttons: ReactNode
children: ReactNode
}) {
return (
<Flex gap={20} style={{ height: '100%' }}>
<Card className="app-card" title={title} style={{ flex: 1, overflowX: 'hidden' }}>
<Flex justify="space-between" style={{ marginBottom: '20px' }}>
<Flex wrap gap={10}>
{buttons}
</Flex>
<Button>查看我的数据集</Button>
</Flex>
{children}
</Card>
</Flex>
)
}
import { Table, TableProps } from 'antd'
export default function DataRender(props: TableProps) {
const pagination = {
showSizeChanger: true,
pageSize: 100,
pageSizeOptions: [100, 200, 500],
showTotal: (total: number) => `共${total}条数据`,
}
return (
<Table
bordered
scroll={{ x: 'max-content', y: 600 }}
tableLayout="auto"
size="middle"
pagination={pagination}
{...props}
/>
)
}
import { Button, Card, Flex } from 'antd'
import AIChat from '@/components/ai/AIChat'
import ViewData from './ViewData'
import { ReactNode } from 'react'
export default function DataWrap({ title, buttons }: { title: string; buttons: ReactNode }) {
return (
<Flex gap={20} style={{ height: '100%' }}>
<Card className="app-card" title={title} style={{ flex: 1, overflowX: 'hidden' }}>
<Flex justify="space-between" style={{ marginBottom: '20px' }}>
<Flex wrap gap={10}>
{buttons}
</Flex>
<Button>查看字段详细信息</Button>
</Flex>
<ViewData />
</Card>
<AIChat></AIChat>
</Flex>
)
}
import DataRender from './DataRender'
export default function FormModal(props) {
const columns = [
{
title: '订单编号',
dataIndex: '订单编号',
align: 'center',
minWidth: 120,
},
{
title: '品牌',
dataIndex: '品牌',
align: 'center',
minWidth: 120,
},
{
title: '店铺',
dataIndex: '店铺',
align: 'center',
minWidth: 120,
},
{
title: '负责人',
dataIndex: '负责人',
align: 'center',
minWidth: 120,
},
{
title: '商品',
dataIndex: '商品',
align: 'center',
minWidth: 120,
},
{
title: '颜色',
dataIndex: '颜色',
align: 'center',
minWidth: 120,
},
{
title: '客户购买类型',
dataIndex: '客户购买类型',
align: 'center',
minWidth: 120,
},
{
title: '销量',
dataIndex: '销量',
align: 'center',
minWidth: 120,
},
{
title: '单价',
dataIndex: '单价',
align: 'center',
minWidth: 120,
},
{
title: '销售额',
dataIndex: '销售额',
align: 'center',
minWidth: 120,
},
{
title: '是否优惠',
dataIndex: '是否优惠',
align: 'center',
minWidth: 120,
},
{
title: '优惠金额',
dataIndex: '优惠金额',
align: 'center',
minWidth: 120,
},
{
title: '实际单价',
dataIndex: '实际单价',
align: 'center',
minWidth: 120,
},
{
title: '实际付款',
dataIndex: '实际付款',
align: 'center',
minWidth: 120,
},
{
title: '成本',
dataIndex: '成本',
align: 'center',
minWidth: 120,
},
{
title: '利润',
dataIndex: '利润',
align: 'center',
minWidth: 120,
},
{
title: '客户性别',
dataIndex: '客户性别',
align: 'center',
minWidth: 120,
},
{
title: '客户年龄',
dataIndex: '客户年龄',
align: 'center',
minWidth: 120,
},
{
title: '会员情况',
dataIndex: '会员情况',
align: 'center',
minWidth: 120,
},
{
title: '是否访问页面',
dataIndex: '是否访问页面',
align: 'center',
minWidth: 120,
},
{
title: '访问页面时长',
dataIndex: '访问页面时长',
align: 'center',
minWidth: 120,
},
{
title: '交易状态',
dataIndex: '交易状态',
align: 'center',
minWidth: 120,
},
{
title: '商品状态',
dataIndex: '商品状态',
align: 'center',
minWidth: 120,
},
{
title: '收货人姓名',
dataIndex: '收货人姓名',
align: 'center',
minWidth: 120,
},
{
title: '收货人电话',
dataIndex: '收货人电话',
align: 'center',
minWidth: 120,
},
{
title: '发货地址',
dataIndex: '发货地址',
align: 'center',
minWidth: 120,
},
{
title: '收货地址省份',
dataIndex: '收货地址省份',
align: 'center',
minWidth: 120,
},
{
title: '收货地址',
dataIndex: '收货地址',
align: 'center',
minWidth: 120,
},
{
title: '物流公司',
dataIndex: '物流公司',
align: 'center',
minWidth: 120,
},
{
title: '运单号',
dataIndex: '运单号',
align: 'center',
minWidth: 120,
},
{
title: '运送方式',
dataIndex: '运送方式',
align: 'center',
minWidth: 120,
},
{
title: '支付时间',
dataIndex: '支付时间',
align: 'center',
minWidth: 120,
},
{
title: '预计到达时间',
dataIndex: '预计到达时间',
align: 'center',
minWidth: 120,
},
{
title: '实际到达时间',
dataIndex: '实际到达时间',
align: 'center',
minWidth: 120,
},
{
title: '快递反馈',
dataIndex: '快递反馈',
align: 'center',
minWidth: 120,
},
{
title: '是否退货',
dataIndex: '是否退货',
align: 'center',
minWidth: 120,
},
{
title: '退款原因',
dataIndex: '退款原因',
align: 'center',
minWidth: 120,
},
{
title: '客户满意度',
dataIndex: '客户满意度',
align: 'center',
minWidth: 120,
},
{
title: '出生日期',
dataIndex: '出生日期',
align: 'center',
minWidth: 120,
},
{
title: '品类',
dataIndex: '品类',
align: 'center',
minWidth: 120,
},
{
title: '材质',
dataIndex: '材质',
align: 'center',
minWidth: 120,
},
]
const data = [
{
订单编号: 'A929818',
品牌: '丽丽',
店铺: '卡卡家女装',
负责人: '袁英',
商品: '安全裤',
颜色: '红色',
客户购买类型: '零售',
销量: 256,
单价: 156.14,
销售额: 156.14,
是否优惠: '店铺活动优惠',
优惠金额: 12,
实际单价: 144.14,
实际付款: 144.14,
成本: 92.25,
利润: 51.89,
客户性别: 0,
客户年龄: '33岁',
会员情况: '无会员',
是否访问页面: '是',
访问页面时长: 2,
交易状态: '交易成功',
商品状态: '已收货',
收货人姓名: '孙倩',
收货人电话: '181****4056',
发货地址: '福建省',
收货地址省份: '陕西省',
收货地址: '陕西省延安市子长县',
物流公司: ' 百世快递',
运单号: 'L810590',
运送方式: '公路运输',
支付时间: 44003,
预计到达时间: 44008,
实际到达时间: 44008,
快递反馈: '准时',
是否退货: '否',
退款原因: '无',
客户满意度: '中评',
出生日期: 32807,
品类: '女裤',
材质: '麻质',
},
{
订单编号: 'A451538',
品牌: '雅羊人',
店铺: '秋兰女装专卖店',
负责人: '刘佳',
商品: '不规则连衣裙',
颜色: '粉色',
客户购买类型: '零售',
销量: 198,
单价: 247.44,
销售额: 247.44,
是否优惠: '无',
优惠金额: 0,
实际单价: 247.44,
实际付款: 247.44,
成本: 133.62,
利润: 113.82,
客户性别: 0,
客户年龄: '36岁',
会员情况: '无会员',
是否访问页面: '是',
访问页面时长: 8,
交易状态: '交易成功',
商品状态: '已收货',
收货人姓名: '余玉英',
收货人电话: '181****8027',
发货地址: '广东省',
收货地址省份: '⼴西壮族⾃治区',
收货地址: '广西壮族自治区贵港市',
物流公司: '极兔快 递',
运单号: 'L806051',
运送方式: '公路运输',
支付时间: 45029,
预计到达时间: 45031,
实际到达时间: 45031,
快递反馈: '准时',
是否退货: '否',
退款原因: '无',
客户满意度: '好评',
出生日期: 31610,
品类: '连衣裙',
材质: '氨纶',
},
{
订单编号: 'A479212',
品牌: '威兰西',
店铺: '威兰西旗舰店',
负责人: '陈建平',
商品: '网红同款连衣裙',
颜色: '白色',
客户购买类型: '零售',
销量: 128,
单价: 245.49,
销售额: 245.49,
是否优惠: '双12优惠',
优惠金额: 15,
实际单价: 230.49,
实际付款: 230.49,
成本: 147.51,
利润: 82.98,
客户性别: 0,
客户年龄: '22岁',
会员情况: '无会员',
是否访问页面: '是',
访问页面时长: 4,
交易状态: '交易成功',
商品状态: '已收货',
收货人姓名: '杨军',
收货人电话: '139****7024',
发货地址: '浙江省',
收货地址省份: '河北省',
收货地址: '河北省衡水市武强县',
物流公司: '顺丰快递',
运单号: 'L356804',
运送方式: '公路运输',
支付时间: 43800,
预计到达时间: 43805,
实际到达时间: 43806,
快递反馈: '延后',
是否退货: '否',
退款原因: '无',
客户满意度: '好评',
出生日期: 36649,
品类: '连衣裙',
材质: '棉质',
},
{
订单编号: 'A389270',
品牌: 'H&R',
店铺: '壹佰女装',
负责人: '杨兰英',
商品: '蕾丝花边T恤',
颜色: '紫色',
客户购买类型: '批发',
销量: 13,
单价: 168.3,
销售额: 2187.9,
是否优惠: '双11优惠',
优惠金额: 200,
实际单价: 152.92,
实际付款: 1987.9,
成本: 1212.62,
利润: 775.28,
客户性别: 0,
客户年龄: '26岁',
会员情况: '无会员',
是否访问页面: '是',
访问页面时长: 4,
交易状态: '交易成功',
商品状态: '已收货',
收货人姓名: '郑强',
收货人电话: '159****6299',
发货地址: '四川省',
收货地址省份: '河南省',
收货地址: '河南省郑州市新密市',
物流公司: '顺丰快递',
运单号: 'L335437',
运送方式: '公路运输',
支付时间: 43779,
预计到达时间: 43783,
实际到达时间: 43784,
快递反馈: '延后',
是否退货: '否',
退款原因: '无',
客户满意度: '好评',
出生日期: 35189,
品类: '上衣',
材质: '棉质',
},
{
订单编号: 'A327122',
品牌: 'H&R',
店铺: 'H&R旗舰店',
负责人: '袁英',
商品: '春夏新款连衣裙',
颜色: '红色',
客户购买类型: '批发',
销量: 13,
单价: 119.64,
销售额: 1555.32,
是否优惠: '无',
优惠金额: 0,
实际单价: 119.64,
实际付款: 1555.32,
成本: 855.43,
利润: 699.89,
客户性别: 0,
客户年龄: '22岁',
会员情况: '无会员',
是否访问页面: '是',
访问页面时长: 8,
交易状态: '交易成功',
商品状态: '已收货',
收货人姓名: '刘冬梅',
收货人电话: '185****4586',
发货地址: '广东省',
收货地址省份: '陕西省',
收货地址: '陕西省延安市富县',
物流公司: '中通快递',
运单号: 'L209106',
运送方式: '公路运输',
支付时间: 43583,
预计到达时间: 43585,
实际到达时间: 43585,
快递反馈: '准时',
是否退货: '否',
退款原因: '无',
客户满意度: '无评论',
出生日期: 36710,
品类: '连衣裙',
材质: '氨纶',
},
{
订单编号: 'A577484',
品牌: 'H&R',
店铺: 'COCO女装',
负责人: '刘佳',
商品: '秋冬款连衣裙',
颜色: '粉色',
客户购买类型: '批发',
销量: 12,
单价: 173.7,
销售额: 2084.39999999999,
是否优惠: '店铺活动优惠',
优惠金额: 100,
实际单价: 165.37,
实际付款: 1984.39999999999,
成本: 1170.8,
利润: 813.59999999999,
客户性别: 0,
客户年龄: '61岁',
会员情况: '钻石会员',
是否访问页面: '是',
访问页面时长: 7,
交易状态: '交易成功',
商品状态: '已收货',
收货人姓名: '谢博',
收货人电话: '147****5024',
发货地址: '广东省',
收货地址省份: '⽢肃省',
收货地址: '甘肃省甘南藏族自治州卓尼县',
物流公司: '申通快递',
运单号: 'L913348',
运送方式: '航空运输',
支付时间: 43562,
预计到达时间: 43566,
实际到达时间: 43566,
快递反馈: '准时',
是否退货: '否',
退款原因: '无',
客户满意度: '中评',
出生日期: 22505,
品类: '连衣裙',
材质: '麻质',
},
{
订单编号: 'A293474',
品牌: '拉夏贝尔',
店铺: '拉夏贝尔旗舰店',
负责人: '刘佳',
商品: '泫雅风T恤',
颜色: '粉色',
客户购买类型: '批发',
销量: 12,
单价: 162.02,
销售额: 1944.24,
是否优惠: '双11优惠',
优惠金额: 200,
实际单价: 145.35,
实际付款: 1744.24,
成本: 1011.66,
利润: 732.58,
客户性别: 0,
客户年龄: '59岁',
会员情况: '普通会员',
是否访问页面: '是',
访问页面时长: 2,
交易状态: '交易成功',
商品状态: '已收货',
收货人姓名: '赵丽丽',
收货人电话: '186****8566',
发货地址: '浙江省',
收货地址省份: '⼴东省',
收货地址: '广东省梅州市',
物流公司: '圆通快递',
运单号: 'L841704',
运送方式: '铁路运输',
支付时间: 43779,
预计到达时间: 43784,
实际到达时间: 43784,
快递反馈: '准时',
是否退货: '否',
退款原因: '无',
客户满意度: '中评',
出生日期: 23158,
品类: '上衣',
材质: '氨纶',
},
{
订单编号: 'A424004',
品牌: '艾米丽',
店铺: '卡卡家女装',
负责人: '宋建华',
商品: '大码牛仔裤',
颜色: '红色',
客户购买类型: '批发',
销量: 11,
单价: 73.54,
销售额: 808.94,
是否优惠: '双11优惠',
优惠金额: 100,
实际单价: 64.45,
实际付款: 708.94,
成本: 361.56,
利润: 347.38,
客户性别: 0,
客户年龄: '23岁',
会员情况: '无会员',
是否访问页面: '是',
访问页面时长: 8,
交易状态: '交易成功',
商品状态: '已收货',
收货人姓名: '谢雷',
收货人电话: '150****7487',
发货地址: '浙江省',
收货地址省份: '宁夏回族自治区',
收货地址: '宁夏回族自治区银川市贺兰县',
物流公司: ' 百世快递',
运单号: 'L451724',
运送方式: '公路运输',
支付时间: 45257,
预计到达时间: 45261,
实际到达时间: 45261,
快递反馈: '准时',
是否退货: '否',
退款原因: '无',
客户满意度: '好评',
出生日期: 36512,
品类: '女裤',
材质: '锦纶',
},
{
订单编号: 'A561021',
品牌: 'TARA',
店铺: 'TARA旗舰店',
负责人: '邹勇',
商品: '春夏季新款裤子',
颜色: '橙色',
客户购买类型: '批发',
销量: 11,
单价: 57.25,
销售额: 629.75,
是否优惠: '双12优惠',
优惠金额: 150,
实际单价: 43.61,
实际付款: 479.75,
成本: 283.05,
利润: 196.7,
客户性别: 0,
客户年龄: '36岁',
会员情况: '普通会员',
是否访问页面: '是',
访问页面时长: 7,
交易状态: '交易成功',
商品状态: '已收货',
收货人姓名: '王桂珍',
收货人电话: '137****8424',
发货地址: '福建省',
收货地址省份: '湖南省',
收货地址: '湖南省永州市市辖区',
物流公司: '申通快递',
运单号: 'L948843',
运送方式: '公路运输',
支付时间: 43816,
预计到达时间: 43818,
实际到达时间: 43818,
快递反馈: '准时',
是否退货: '否',
退款原因: '无',
客户满意度: '好评',
出生日期: 31564,
品类: '女裤',
材质: '锦纶',
},
{
订单编号: 'A191677',
品牌: '富贵人',
店铺: '富贵人旗舰店',
负责人: '刘佳',
商品: '蕾丝花边T恤',
颜色: '粉色',
客户购买类型: '批发',
销量: 11,
单价: 117.92,
销售额: 1297.12,
是否优惠: '无',
优惠金额: 0,
实际单价: 117.92,
实际付款: 1297.12,
成本: 648.56,
利润: 648.56,
客户性别: 0,
客户年龄: '38岁',
会员情况: '无会员',
是否访问页面: '否',
访问页面时长: 0,
交易状态: '交易取消',
商品状态: '已取消',
收货人姓名: '梁秀华',
收货人电话: '136****1451',
发货地址: '广东省',
收货地址省份: '⼭东省',
收货地址: '山东省聊城市冠县',
物流公司: '申通快递',
运单号: 'L385723',
运送方式: '航空运输',
支付时间: 43802,
预计到达时间: 43807,
实际到达时间: 43807,
快递反馈: '准时',
是否退货: '是',
退款原因: '不喜欢',
客户满意度: '无评论',
出生日期: 30739,
品类: '上衣',
材质: '棉质',
},
]
return <DataRender dataSource={data} columns={columns}></DataRender>
}
import { Flex, Modal } from 'antd'
import DataRender from './DataRender'
export default function FormModal(props) {
const columns = [
{
title: '订单编号',
dataIndex: '订单编号',
align: 'center',
minWidth: 120,
},
{
title: '品牌',
dataIndex: '品牌',
align: 'center',
minWidth: 120,
},
{
title: '店铺',
dataIndex: '店铺',
align: 'center',
minWidth: 120,
},
{
title: '负责人',
dataIndex: '负责人',
align: 'center',
minWidth: 120,
},
{
title: '商品',
dataIndex: '商品',
align: 'center',
minWidth: 120,
},
{
title: '颜色',
dataIndex: '颜色',
align: 'center',
minWidth: 120,
},
{
title: '客户购买类型',
dataIndex: '客户购买类型',
align: 'center',
minWidth: 120,
},
{
title: '销量',
dataIndex: '销量',
align: 'center',
minWidth: 120,
},
{
title: '单价',
dataIndex: '单价',
align: 'center',
minWidth: 120,
},
{
title: '销售额',
dataIndex: '销售额',
align: 'center',
minWidth: 120,
},
{
title: '是否优惠',
dataIndex: '是否优惠',
align: 'center',
minWidth: 120,
},
{
title: '优惠金额',
dataIndex: '优惠金额',
align: 'center',
minWidth: 120,
},
{
title: '实际单价',
dataIndex: '实际单价',
align: 'center',
minWidth: 120,
},
{
title: '实际付款',
dataIndex: '实际付款',
align: 'center',
minWidth: 120,
},
{
title: '成本',
dataIndex: '成本',
align: 'center',
minWidth: 120,
},
{
title: '利润',
dataIndex: '利润',
align: 'center',
minWidth: 120,
},
{
title: '客户性别',
dataIndex: '客户性别',
align: 'center',
minWidth: 120,
},
{
title: '客户年龄',
dataIndex: '客户年龄',
align: 'center',
minWidth: 120,
},
{
title: '会员情况',
dataIndex: '会员情况',
align: 'center',
minWidth: 120,
},
{
title: '是否访问页面',
dataIndex: '是否访问页面',
align: 'center',
minWidth: 120,
},
{
title: '访问页面时长',
dataIndex: '访问页面时长',
align: 'center',
minWidth: 120,
},
{
title: '交易状态',
dataIndex: '交易状态',
align: 'center',
minWidth: 120,
},
{
title: '商品状态',
dataIndex: '商品状态',
align: 'center',
minWidth: 120,
},
{
title: '收货人姓名',
dataIndex: '收货人姓名',
align: 'center',
minWidth: 120,
},
{
title: '收货人电话',
dataIndex: '收货人电话',
align: 'center',
minWidth: 120,
},
{
title: '发货地址',
dataIndex: '发货地址',
align: 'center',
minWidth: 120,
},
{
title: '收货地址省份',
dataIndex: '收货地址省份',
align: 'center',
minWidth: 120,
},
{
title: '收货地址',
dataIndex: '收货地址',
align: 'center',
minWidth: 120,
},
{
title: '物流公司',
dataIndex: '物流公司',
align: 'center',
minWidth: 120,
},
{
title: '运单号',
dataIndex: '运单号',
align: 'center',
minWidth: 120,
},
{
title: '运送方式',
dataIndex: '运送方式',
align: 'center',
minWidth: 120,
},
{
title: '支付时间',
dataIndex: '支付时间',
align: 'center',
minWidth: 120,
},
{
title: '预计到达时间',
dataIndex: '预计到达时间',
align: 'center',
minWidth: 120,
},
{
title: '实际到达时间',
dataIndex: '实际到达时间',
align: 'center',
minWidth: 120,
},
{
title: '快递反馈',
dataIndex: '快递反馈',
align: 'center',
minWidth: 120,
},
{
title: '是否退货',
dataIndex: '是否退货',
align: 'center',
minWidth: 120,
},
{
title: '退款原因',
dataIndex: '退款原因',
align: 'center',
minWidth: 120,
},
{
title: '客户满意度',
dataIndex: '客户满意度',
align: 'center',
minWidth: 120,
},
{
title: '出生日期',
dataIndex: '出生日期',
align: 'center',
minWidth: 120,
},
{
title: '品类',
dataIndex: '品类',
align: 'center',
minWidth: 120,
},
{
title: '材质',
dataIndex: '材质',
align: 'center',
minWidth: 120,
},
]
const data = [
{
订单编号: 'A929818',
品牌: '丽丽',
店铺: '卡卡家女装',
负责人: '袁英',
商品: '安全裤',
颜色: '红色',
客户购买类型: '零售',
销量: 256,
单价: 156.14,
销售额: 156.14,
是否优惠: '店铺活动优惠',
优惠金额: 12,
实际单价: 144.14,
实际付款: 144.14,
成本: 92.25,
利润: 51.89,
客户性别: 0,
客户年龄: '33岁',
会员情况: '无会员',
是否访问页面: '是',
访问页面时长: 2,
交易状态: '交易成功',
商品状态: '已收货',
收货人姓名: '孙倩',
收货人电话: '181****4056',
发货地址: '福建省',
收货地址省份: '陕西省',
收货地址: '陕西省延安市子长县',
物流公司: ' 百世快递',
运单号: 'L810590',
运送方式: '公路运输',
支付时间: 44003,
预计到达时间: 44008,
实际到达时间: 44008,
快递反馈: '准时',
是否退货: '否',
退款原因: '无',
客户满意度: '中评',
出生日期: 32807,
品类: '女裤',
材质: '麻质',
},
{
订单编号: 'A451538',
品牌: '雅羊人',
店铺: '秋兰女装专卖店',
负责人: '刘佳',
商品: '不规则连衣裙',
颜色: '粉色',
客户购买类型: '零售',
销量: 198,
单价: 247.44,
销售额: 247.44,
是否优惠: '无',
优惠金额: 0,
实际单价: 247.44,
实际付款: 247.44,
成本: 133.62,
利润: 113.82,
客户性别: 0,
客户年龄: '36岁',
会员情况: '无会员',
是否访问页面: '是',
访问页面时长: 8,
交易状态: '交易成功',
商品状态: '已收货',
收货人姓名: '余玉英',
收货人电话: '181****8027',
发货地址: '广东省',
收货地址省份: '⼴西壮族⾃治区',
收货地址: '广西壮族自治区贵港市',
物流公司: '极兔快 递',
运单号: 'L806051',
运送方式: '公路运输',
支付时间: 45029,
预计到达时间: 45031,
实际到达时间: 45031,
快递反馈: '准时',
是否退货: '否',
退款原因: '无',
客户满意度: '好评',
出生日期: 31610,
品类: '连衣裙',
材质: '氨纶',
},
{
订单编号: 'A479212',
品牌: '威兰西',
店铺: '威兰西旗舰店',
负责人: '陈建平',
商品: '网红同款连衣裙',
颜色: '白色',
客户购买类型: '零售',
销量: 128,
单价: 245.49,
销售额: 245.49,
是否优惠: '双12优惠',
优惠金额: 15,
实际单价: 230.49,
实际付款: 230.49,
成本: 147.51,
利润: 82.98,
客户性别: 0,
客户年龄: '22岁',
会员情况: '无会员',
是否访问页面: '是',
访问页面时长: 4,
交易状态: '交易成功',
商品状态: '已收货',
收货人姓名: '杨军',
收货人电话: '139****7024',
发货地址: '浙江省',
收货地址省份: '河北省',
收货地址: '河北省衡水市武强县',
物流公司: '顺丰快递',
运单号: 'L356804',
运送方式: '公路运输',
支付时间: 43800,
预计到达时间: 43805,
实际到达时间: 43806,
快递反馈: '延后',
是否退货: '否',
退款原因: '无',
客户满意度: '好评',
出生日期: 36649,
品类: '连衣裙',
材质: '棉质',
},
{
订单编号: 'A389270',
品牌: 'H&R',
店铺: '壹佰女装',
负责人: '杨兰英',
商品: '蕾丝花边T恤',
颜色: '紫色',
客户购买类型: '批发',
销量: 13,
单价: 168.3,
销售额: 2187.9,
是否优惠: '双11优惠',
优惠金额: 200,
实际单价: 152.92,
实际付款: 1987.9,
成本: 1212.62,
利润: 775.28,
客户性别: 0,
客户年龄: '26岁',
会员情况: '无会员',
是否访问页面: '是',
访问页面时长: 4,
交易状态: '交易成功',
商品状态: '已收货',
收货人姓名: '郑强',
收货人电话: '159****6299',
发货地址: '四川省',
收货地址省份: '河南省',
收货地址: '河南省郑州市新密市',
物流公司: '顺丰快递',
运单号: 'L335437',
运送方式: '公路运输',
支付时间: 43779,
预计到达时间: 43783,
实际到达时间: 43784,
快递反馈: '延后',
是否退货: '否',
退款原因: '无',
客户满意度: '好评',
出生日期: 35189,
品类: '上衣',
材质: '棉质',
},
{
订单编号: 'A327122',
品牌: 'H&R',
店铺: 'H&R旗舰店',
负责人: '袁英',
商品: '春夏新款连衣裙',
颜色: '红色',
客户购买类型: '批发',
销量: 13,
单价: 119.64,
销售额: 1555.32,
是否优惠: '无',
优惠金额: 0,
实际单价: 119.64,
实际付款: 1555.32,
成本: 855.43,
利润: 699.89,
客户性别: 0,
客户年龄: '22岁',
会员情况: '无会员',
是否访问页面: '是',
访问页面时长: 8,
交易状态: '交易成功',
商品状态: '已收货',
收货人姓名: '刘冬梅',
收货人电话: '185****4586',
发货地址: '广东省',
收货地址省份: '陕西省',
收货地址: '陕西省延安市富县',
物流公司: '中通快递',
运单号: 'L209106',
运送方式: '公路运输',
支付时间: 43583,
预计到达时间: 43585,
实际到达时间: 43585,
快递反馈: '准时',
是否退货: '否',
退款原因: '无',
客户满意度: '无评论',
出生日期: 36710,
品类: '连衣裙',
材质: '氨纶',
},
{
订单编号: 'A577484',
品牌: 'H&R',
店铺: 'COCO女装',
负责人: '刘佳',
商品: '秋冬款连衣裙',
颜色: '粉色',
客户购买类型: '批发',
销量: 12,
单价: 173.7,
销售额: 2084.39999999999,
是否优惠: '店铺活动优惠',
优惠金额: 100,
实际单价: 165.37,
实际付款: 1984.39999999999,
成本: 1170.8,
利润: 813.59999999999,
客户性别: 0,
客户年龄: '61岁',
会员情况: '钻石会员',
是否访问页面: '是',
访问页面时长: 7,
交易状态: '交易成功',
商品状态: '已收货',
收货人姓名: '谢博',
收货人电话: '147****5024',
发货地址: '广东省',
收货地址省份: '⽢肃省',
收货地址: '甘肃省甘南藏族自治州卓尼县',
物流公司: '申通快递',
运单号: 'L913348',
运送方式: '航空运输',
支付时间: 43562,
预计到达时间: 43566,
实际到达时间: 43566,
快递反馈: '准时',
是否退货: '否',
退款原因: '无',
客户满意度: '中评',
出生日期: 22505,
品类: '连衣裙',
材质: '麻质',
},
{
订单编号: 'A293474',
品牌: '拉夏贝尔',
店铺: '拉夏贝尔旗舰店',
负责人: '刘佳',
商品: '泫雅风T恤',
颜色: '粉色',
客户购买类型: '批发',
销量: 12,
单价: 162.02,
销售额: 1944.24,
是否优惠: '双11优惠',
优惠金额: 200,
实际单价: 145.35,
实际付款: 1744.24,
成本: 1011.66,
利润: 732.58,
客户性别: 0,
客户年龄: '59岁',
会员情况: '普通会员',
是否访问页面: '是',
访问页面时长: 2,
交易状态: '交易成功',
商品状态: '已收货',
收货人姓名: '赵丽丽',
收货人电话: '186****8566',
发货地址: '浙江省',
收货地址省份: '⼴东省',
收货地址: '广东省梅州市',
物流公司: '圆通快递',
运单号: 'L841704',
运送方式: '铁路运输',
支付时间: 43779,
预计到达时间: 43784,
实际到达时间: 43784,
快递反馈: '准时',
是否退货: '否',
退款原因: '无',
客户满意度: '中评',
出生日期: 23158,
品类: '上衣',
材质: '氨纶',
},
{
订单编号: 'A424004',
品牌: '艾米丽',
店铺: '卡卡家女装',
负责人: '宋建华',
商品: '大码牛仔裤',
颜色: '红色',
客户购买类型: '批发',
销量: 11,
单价: 73.54,
销售额: 808.94,
是否优惠: '双11优惠',
优惠金额: 100,
实际单价: 64.45,
实际付款: 708.94,
成本: 361.56,
利润: 347.38,
客户性别: 0,
客户年龄: '23岁',
会员情况: '无会员',
是否访问页面: '是',
访问页面时长: 8,
交易状态: '交易成功',
商品状态: '已收货',
收货人姓名: '谢雷',
收货人电话: '150****7487',
发货地址: '浙江省',
收货地址省份: '宁夏回族自治区',
收货地址: '宁夏回族自治区银川市贺兰县',
物流公司: ' 百世快递',
运单号: 'L451724',
运送方式: '公路运输',
支付时间: 45257,
预计到达时间: 45261,
实际到达时间: 45261,
快递反馈: '准时',
是否退货: '否',
退款原因: '无',
客户满意度: '好评',
出生日期: 36512,
品类: '女裤',
材质: '锦纶',
},
{
订单编号: 'A561021',
品牌: 'TARA',
店铺: 'TARA旗舰店',
负责人: '邹勇',
商品: '春夏季新款裤子',
颜色: '橙色',
客户购买类型: '批发',
销量: 11,
单价: 57.25,
销售额: 629.75,
是否优惠: '双12优惠',
优惠金额: 150,
实际单价: 43.61,
实际付款: 479.75,
成本: 283.05,
利润: 196.7,
客户性别: 0,
客户年龄: '36岁',
会员情况: '普通会员',
是否访问页面: '是',
访问页面时长: 7,
交易状态: '交易成功',
商品状态: '已收货',
收货人姓名: '王桂珍',
收货人电话: '137****8424',
发货地址: '福建省',
收货地址省份: '湖南省',
收货地址: '湖南省永州市市辖区',
物流公司: '申通快递',
运单号: 'L948843',
运送方式: '公路运输',
支付时间: 43816,
预计到达时间: 43818,
实际到达时间: 43818,
快递反馈: '准时',
是否退货: '否',
退款原因: '无',
客户满意度: '好评',
出生日期: 31564,
品类: '女裤',
材质: '锦纶',
},
{
订单编号: 'A191677',
品牌: '富贵人',
店铺: '富贵人旗舰店',
负责人: '刘佳',
商品: '蕾丝花边T恤',
颜色: '粉色',
客户购买类型: '批发',
销量: 11,
单价: 117.92,
销售额: 1297.12,
是否优惠: '无',
优惠金额: 0,
实际单价: 117.92,
实际付款: 1297.12,
成本: 648.56,
利润: 648.56,
客户性别: 0,
客户年龄: '38岁',
会员情况: '无会员',
是否访问页面: '否',
访问页面时长: 0,
交易状态: '交易取消',
商品状态: '已取消',
收货人姓名: '梁秀华',
收货人电话: '136****1451',
发货地址: '广东省',
收货地址省份: '⼭东省',
收货地址: '山东省聊城市冠县',
物流公司: '申通快递',
运单号: 'L385723',
运送方式: '航空运输',
支付时间: 43802,
预计到达时间: 43807,
实际到达时间: 43807,
快递反馈: '准时',
是否退货: '是',
退款原因: '不喜欢',
客户满意度: '无评论',
出生日期: 30739,
品类: '上衣',
材质: '棉质',
},
]
return (
<Modal title="查阅数据集" destroyOnClose width={1000} {...props}>
<Flex justify="space-between">
<div>数据集名称:数据集名称</div>
<div>共计:1000条数据</div>
</Flex>
<DataRender dataSource={data} columns={columns}></DataRender>
</Modal>
)
}
...@@ -2,11 +2,16 @@ import { Link, NavLink, useLocation } from 'react-router' ...@@ -2,11 +2,16 @@ import { Link, NavLink, useLocation } from 'react-router'
import { Menu, Popover, Button } from 'antd' import { Menu, Popover, Button } from 'antd'
import { CaretDownOutlined } from '@ant-design/icons' import { CaretDownOutlined } from '@ant-design/icons'
import './AppHeader.scss' import './AppHeader.scss'
import { useUserStore } from '@/stores/user'
export default function AppHeader({ title = 'AI数据分析实验室' }) { export default function AppHeader({ title = 'AI数据分析实验室' }) {
const location = useLocation() const location = useLocation()
const defaultSelectedKeys = [location.pathname] const defaultSelectedKeys = [location.pathname]
const user = useUserStore((state) => state.info)
const name = user?.name || user?.username
const avatar = user?.avatar || 'https://webapp-pub.ezijing.com/website/base/images/avatar.svg'
const menus = [ const menus = [
{ name: '首页', path: '/' }, { name: '首页', path: '/' },
{ name: '我的实验', path: '/data' }, { name: '我的实验', path: '/data' },
...@@ -40,11 +45,11 @@ export default function AppHeader({ title = 'AI数据分析实验室' }) { ...@@ -40,11 +45,11 @@ export default function AppHeader({ title = 'AI数据分析实验室' }) {
content={ content={
<div className="app-header-user"> <div className="app-header-user">
<div className="app-header-user-avatar"> <div className="app-header-user-avatar">
<img src="https://webapp-pub.ezijing.com/website/base/images/avatar.svg" /> <img src={avatar} />
</div> </div>
<div className="app-header-user-main"> <div className="app-header-user-main">
<h3>王鹏飞</h3> <h3>{name}</h3>
<p>wangpengfei@ezijing.com</p> <p>{user?.email}</p>
</div> </div>
<div className="app-header-user-buttons"> <div className="app-header-user-buttons">
<Button shape="round">退出登录</Button> <Button shape="round">退出登录</Button>
...@@ -53,9 +58,9 @@ export default function AppHeader({ title = 'AI数据分析实验室' }) { ...@@ -53,9 +58,9 @@ export default function AppHeader({ title = 'AI数据分析实验室' }) {
}> }>
<div className="user"> <div className="user">
<div className="avatar"> <div className="avatar">
<img src="https://webapp-pub.ezijing.com/website/base/images/avatar.svg" /> <img src={avatar} />
</div> </div>
<p>王鹏飞</p> <p>{name}</p>
<CaretDownOutlined /> <CaretDownOutlined />
</div> </div>
</Popover> </Popover>
......
import { Outlet } from 'react-router' import { Outlet } from 'react-router'
import AppHeader from './AppHeader' import AppHeader from './AppHeader'
import './AppLayout.scss' import './AppLayout.scss'
import { useUserQuery, useMapQuery } from '@/hooks/useQuery'
export default function AppLayout() { export default function AppLayout() {
const { isPending: queryUserIsPending } = useUserQuery()
const { isPending: queryMapIsPending } = useMapQuery()
if (queryUserIsPending || queryMapIsPending) {
return
}
return ( return (
<div className="app-layout"> <div className="app-layout">
<AppHeader /> <AppHeader />
......
...@@ -59,5 +59,7 @@ ...@@ -59,5 +59,7 @@
} }
&-container { &-container {
flex: 1; flex: 1;
overflow-y: auto;
overflow-x: hidden;
} }
} }
...@@ -43,9 +43,9 @@ export default function DataLayout() { ...@@ -43,9 +43,9 @@ export default function DataLayout() {
{ name: '我的数据集', path: '/data/write/my' }, { name: '我的数据集', path: '/data/write/my' },
{ name: '数据复制', path: '/data/write/copy' }, { name: '数据复制', path: '/data/write/copy' },
{ name: '数据导入', path: '/data/write/upload' }, { name: '数据导入', path: '/data/write/upload' },
{ name: '数据爬虫', path: '/data/write/crawler' }, { name: '数据爬虫', path: '/data/write/crawler', disabled: true },
{ name: 'API数据采集', path: '/data/write/api' }, { name: 'API数据采集', path: '/data/write/api', disabled: true },
{ name: '数据库对接', path: '/data/write/db' }, { name: '数据库对接', path: '/data/write/db', disabled: true },
{ name: '内置数据集管理', path: '/data/write/built' }, { name: '内置数据集管理', path: '/data/write/built' },
], ],
}, },
...@@ -54,13 +54,12 @@ export default function DataLayout() { ...@@ -54,13 +54,12 @@ export default function DataLayout() {
name: '数据理解与探索', name: '数据理解与探索',
path: '/data/read', path: '/data/read',
children: [ children: [
{ name: '数据理解', path: '/data/read/crawler' }, { name: '数据理解', path: '/data/read/understanding' },
{ name: '数据探索', path: '/data/read/api' }, { name: '数据探索', path: '/data/read/exploration' },
], ],
}, },
{ {
icon: <Filter />, icon: <Filter />,
name: '数据预处理', name: '数据预处理',
path: '/data/preprocess', path: '/data/preprocess',
children: [ children: [
...@@ -74,19 +73,18 @@ export default function DataLayout() { ...@@ -74,19 +73,18 @@ export default function DataLayout() {
{ name: '数据去标点', path: '/data/preprocess/symbol' }, { name: '数据去标点', path: '/data/preprocess/symbol' },
{ name: '数据类型转换', path: '/data/preprocess/type' }, { name: '数据类型转换', path: '/data/preprocess/type' },
{ name: '数据排序', path: '/data/preprocess/sort' }, { name: '数据排序', path: '/data/preprocess/sort' },
{ name: '数据拼接', path: '/data/preprocess/concat' }, { name: '数据拼接', path: '/data/preprocess/splice' },
], ],
}, },
{ {
icon: <Bolt />, icon: <Bolt />,
name: '数据加工', name: '数据加工',
path: '/data/process', path: '/data/process',
children: [ children: [
{ name: '值映射', path: '/data/process/mapping' }, { name: '值映射', path: '/data/process/mapping' },
{ name: '数据分箱', path: '/data/process/binning' }, { name: '数据分箱', path: '/data/process/binning' },
{ name: '数据分组', path: '/data/process/group' }, { name: '数据分组', path: '/data/process/group' },
{ name: '数据脱敏', path: '/data/process/4' }, { name: '数据脱敏', path: '/data/process/desensitization' },
{ name: '日期计算', path: '/data/process/date' }, { name: '日期计算', path: '/data/process/date' },
{ name: '文本计算', path: '/data/process/string' }, { name: '文本计算', path: '/data/process/string' },
{ name: '数值计算', path: '/data/process/number' }, { name: '数值计算', path: '/data/process/number' },
...@@ -116,7 +114,7 @@ export default function DataLayout() { ...@@ -116,7 +114,7 @@ export default function DataLayout() {
name: '数据可视化组件', name: '数据可视化组件',
path: '/data/chart', path: '/data/chart',
children: [ children: [
{ name: '柱状图', path: '/data/chart/1' }, { name: '柱状图', path: '/data/chart/bar' },
{ name: '折线图', path: '/data/chart/2' }, { name: '折线图', path: '/data/chart/2' },
{ name: '饼状图', path: '/data/chart/3' }, { name: '饼状图', path: '/data/chart/3' },
{ name: '雷达图', path: '/data/chart/4' }, { name: '雷达图', path: '/data/chart/4' },
......
import { useState, useEffect, useCallback } from 'react'
import md5 from 'blueimp-md5'
import axios from 'axios'
import { fetchEventSource } from '@fortaine/fetch-event-source'
export function useAI() {
const options = [
{ label: '文心一言', value: 'yiyan' },
{ label: 'DeepSeek', value: 'deepseek' },
{ label: '通义千问', value: 'qwen' },
{ label: '天工', value: 'tiangong' },
]
const [ai, setAI] = useState(localStorage.getItem('ai') || 'yiyan')
const [messages, setMessages] = useState([])
const [isLoading, setIsLoading] = useState(false)
useEffect(() => {
localStorage.setItem('ai', ai)
}, [ai])
const post = useCallback(
async (data) => {
setIsLoading(true)
setMessages((prevMessages) => [...prevMessages, { role: 'user', content: data.content }])
try {
switch (ai) {
case 'yiyan':
await yiyan(data)
break
case 'deepseek':
await deepseek(data)
break
case 'qwen':
await qwen(data)
break
case 'tiangong':
await tiangong(data)
break
default:
throw new Error('未找到对应的 AI 配置')
}
} catch (err) {
console.error('AI 请求失败:', err)
} finally {
setIsLoading(false)
}
},
[ai] // 依赖 `ai`,当 `ai` 变化时,重新创建 `post`
)
async function yiyan(data) {
const getAccessToken = async () => {
const AK = 'wY7bvMpkWeZbDVq9w3EDvpjU'
const SK = 'XJwpiJWxs5HXkOtbo6tQrvYPZFJAWdAy'
const resp = await axios.post(
`/api/qianfan/oauth/2.0/token?grant_type=client_credentials&client_id=${AK}&client_secret=${SK}`
)
return resp.data.access_token
}
const resp = await axios.post(
`/api/qianfan/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/eb-instant?access_token=${await getAccessToken()}`,
{
messages: [{ role: 'user', content: data.content }],
}
)
setMessages((prevMessages) => [...prevMessages, { role: 'assistant', content: resp.data.result }])
}
async function deepseek(data) {
const apiKey = 'sk-f1a6f0a7013241de8393cb2cb108e777'
const resp = await axios.post(
'/api/deepseek/chat/completions',
{
model: 'deepseek-chat',
messages: [{ role: 'user', content: data.content }],
},
{
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
}
)
if (resp.data) {
const [choice = {}] = resp.data.choices
setMessages((prevMessages) => [...prevMessages, { role: 'assistant', content: choice.message.content }])
}
}
async function qwen(data) {
const apiKey = 'sk-afd0fcdb53bf4058b2068b8548820150'
const resp = await axios.post(
'/api/qwen/compatible-mode/v1/chat/completions',
{
model: 'qwen-max',
messages: [{ role: 'user', content: data.content }],
},
{
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
}
)
if (resp.data) {
const [choice = {}] = resp.data.choices
setMessages((prevMessages) => [...prevMessages, { role: 'assistant', content: choice.message.content }])
}
}
async function tiangong(data) {
const appKey = 'a8701b73637562d33a53c668a90ee3be'
const appSecret = 'e191593f486bb88a39c634f46926762dddc97b9082e192af'
const timestamp = Math.floor(Date.now() / 1000)
const sign = md5(`${appKey}${appSecret}${timestamp}`)
return await fetchEventSource('/api/tiangong/sky-saas-writing/api/v1/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json', app_key: appKey, sign, timestamp, stream: 'true' },
body: JSON.stringify({
chat_history: [{ role: 'user', content: data.content }],
stream_resp_type: 'update',
}),
async onopen(response) {
console.log(response)
if (response.ok) {
return response
} else {
throw response
}
},
onmessage(res) {
console.log(res.data)
const message = JSON.parse(res.data)
if (message.type !== 1) return
setMessages((prevMessages) => {
const messageId = message.conversation_id
const messageIndex = prevMessages.findIndex((message) => message.id === messageId)
const content = message?.arguments?.[0]?.messages?.[0]?.text || ''
if (messageIndex === -1) {
return [...prevMessages, { id: messageId, role: 'assistant', content }]
} else {
return prevMessages.map((msg) => (msg.id === messageId ? { ...msg, content } : msg))
}
})
setIsLoading(false)
},
onerror(err) {
setIsLoading(false)
throw err
},
})
}
return { ai, setAI, options, post, messages, isLoading }
}
import { useEffect } from 'react'
import { useQuery } from '@tanstack/react-query'
import { getUser, getMapList } from '@/api/base'
import { useUserStore } from '@/stores/user'
import { useMapStore } from '@/stores/map'
export function useUserQuery() {
const { setUser } = useUserStore()
const query = useQuery({ queryKey: ['user'], queryFn: getUser, select: (res) => res.data })
useEffect(() => {
if (query.data) {
setUser(query.data)
}
}, [query.data, setUser])
return query
}
export function useMapQuery() {
const { setMap } = useMapStore()
const query = useQuery({ queryKey: ['dictionary'], queryFn: getMapList, select: (res) => res.data })
useEffect(() => {
if (query.data) {
setMap(query.data)
}
}, [query.data, setMap])
return query
}
...@@ -3,6 +3,7 @@ import ReactDOM from 'react-dom/client' ...@@ -3,6 +3,7 @@ import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router' import { BrowserRouter } from 'react-router'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ConfigProvider } from 'antd' import { ConfigProvider } from 'antd'
import zhCN from 'antd/locale/zh_CN'
import App from './App.tsx' import App from './App.tsx'
const queryClient = new QueryClient() const queryClient = new QueryClient()
...@@ -18,7 +19,7 @@ const antdTheme = { ...@@ -18,7 +19,7 @@ const antdTheme = {
ReactDOM.createRoot(document.getElementById('root')!).render( ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode> <React.StrictMode>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<ConfigProvider theme={antdTheme}> <ConfigProvider theme={antdTheme} locale={zhCN}>
<BrowserRouter> <BrowserRouter>
<App /> <App />
</BrowserRouter> </BrowserRouter>
......
import ChartWrap from '@/components/data/ChartWrap'
import { Button } from 'antd'
export default function DataProcess() {
return <ChartWrap title="可视化:柱状图" buttons={<Button type="primary">新建柱状图</Button>}></ChartWrap>
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return (
<DataWrap
title="数据预处理:逻辑错误值处理"
buttons={
<>
<Button type="primary">处理逻辑错误值</Button>
</>
}></DataWrap>
)
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return (
<DataWrap
title="数据预处理:过大值处理"
buttons={
<>
<Button type="primary">处理过大值</Button>
</>
}></DataWrap>
)
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return (
<DataWrap
title="数据预处理:过小值处理"
buttons={
<>
<Button type="primary">处理过小值</Button>
</>
}></DataWrap>
)
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return (
<DataWrap
title="数据预处理:缺失值处理"
buttons={
<>
<Button type="primary">处理缺失值</Button>
</>
}></DataWrap>
)
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return (
<DataWrap
title="数据预处理:重复值处理"
buttons={
<>
<Button type="primary">处理重复值</Button>
</>
}></DataWrap>
)
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return <DataWrap title="数据预处理:数据排序" buttons={<Button type="primary">数据排序</Button>}></DataWrap>
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return (
<DataWrap
title="数据预处理:数据去空格"
buttons={
<>
<Button type="primary">数据去空格</Button>
</>
}></DataWrap>
)
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return <DataWrap title="数据预处理:数据拼接" buttons={<Button type="primary">数据拼接</Button>}></DataWrap>
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return (
<DataWrap
title="数据预处理:数据拆分"
buttons={
<>
<Button type="primary">数据拆分</Button>
</>
}></DataWrap>
)
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return (
<DataWrap
title="数据预处理:数据去标点"
buttons={
<>
<Button type="primary">数据去标点</Button>
</>
}></DataWrap>
)
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return <DataWrap title="数据预处理:数据类型转换" buttons={<Button type="primary">数据类型转换</Button>}></DataWrap>
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return <DataWrap title="数据加工:数据分箱" buttons={<Button type="primary">数据分箱</Button>}></DataWrap>
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return <DataWrap title="数据加工:日期计算" buttons={<Button type="primary">日期计算</Button>}></DataWrap>
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return <DataWrap title="数据加工:数据脱敏" buttons={<Button type="primary">数据脱敏</Button>}></DataWrap>
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return <DataWrap title="数据加工:数据分组" buttons={<Button type="primary">数据分组</Button>}></DataWrap>
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return <DataWrap title="数据加工:逻辑计算" buttons={<Button type="primary">逻辑计算</Button>}></DataWrap>
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return <DataWrap title="数据加工:值映射" buttons={<Button type="primary">值映射</Button>}></DataWrap>
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return <DataWrap title="数据加工:数值计算" buttons={<Button type="primary">数值计算</Button>}></DataWrap>
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return <DataWrap title="数据加工:数据透视" buttons={<Button type="primary">数据透视</Button>}></DataWrap>
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return <DataWrap title="数据加工:文本计算" buttons={<Button type="primary">文本计算</Button>}></DataWrap>
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataWriteUpload() {
const buttonsRender = () => {
const buttons = [
{ name: '缺失值' },
{ name: '重复值' },
{ name: '过大值' },
{ name: '过小值' },
{ name: '逻辑错误值' },
{ name: '最大值' },
{ name: '最小值' },
{ name: '平均值' },
{ name: '中位数' },
{ name: '众数' },
{ name: '1/4位数' },
{ name: '3/4位数' },
{ name: '方差' },
{ name: '标准差' },
{ name: '极差' },
]
return buttons.map((item) => {
return (
<Button type="primary" key={item.name}>
{item.name}
</Button>
)
})
}
return <DataWrap title="数据探索" buttons={<>{buttonsRender()}</>}></DataWrap>
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataWriteUpload() {
return (
<DataWrap
title="数据理解"
buttons={
<>
<Button type="primary">一键理解数据字段</Button>
<Button type="primary">一键梳理字段关系</Button>
</>
}></DataWrap>
)
}
...@@ -12,11 +12,44 @@ export const routes: RouteObject[] = [ ...@@ -12,11 +12,44 @@ export const routes: RouteObject[] = [
children: [ children: [
{ index: true, element: <Navigate to="dashboard" /> }, { index: true, element: <Navigate to="dashboard" /> },
{ path: 'dashboard', Component: lazy(() => import('./dashboard/views/Index')) }, { path: 'dashboard', Component: lazy(() => import('./dashboard/views/Index')) },
// 数据采集
{ path: 'write', element: <Navigate to="my" /> }, { path: 'write', element: <Navigate to="my" /> },
{ path: 'write/my', Component: lazy(() => import('./write/my/views/Index')) }, { path: 'write/my', Component: lazy(() => import('./write/my/views/Index')) },
{ path: 'write/copy', Component: lazy(() => import('./write/copy/views/Index')) }, { path: 'write/copy', Component: lazy(() => import('./write/copy/views/Index')) },
{ path: 'write/upload', Component: lazy(() => import('./write/upload/views/Index')) }, { path: 'write/upload', Component: lazy(() => import('./write/upload/views/Index')) },
{ path: 'write/copy', Component: lazy(() => import('./write/copy/views/Index')) }, { path: 'write/copy', Component: lazy(() => import('./write/copy/views/Index')) },
{ path: 'write/built', Component: lazy(() => import('./write/built/views/Index')) },
// 数据理解与探索
{ path: 'read', element: <Navigate to="understanding" /> },
{ path: 'read/understanding', Component: lazy(() => import('./read/understanding/views/Index')) },
{ path: 'read/exploration', Component: lazy(() => import('./read/exploration/views/Index')) },
// 数据预处理
{ path: 'preprocess', element: <Navigate to="null" /> },
{ path: 'preprocess/null', Component: lazy(() => import('./preprocess/null/views/Index')) },
{ path: 'preprocess/repeat', Component: lazy(() => import('./preprocess/repeat/views/Index')) },
{ path: 'preprocess/max', Component: lazy(() => import('./preprocess/max/views/Index')) },
{ path: 'preprocess/min', Component: lazy(() => import('./preprocess/min/views/Index')) },
{ path: 'preprocess/error', Component: lazy(() => import('./preprocess/error/views/Index')) },
{ path: 'preprocess/split', Component: lazy(() => import('./preprocess/split/views/Index')) },
{ path: 'preprocess/space', Component: lazy(() => import('./preprocess/space/views/Index')) },
{ path: 'preprocess/symbol', Component: lazy(() => import('./preprocess/symbol/views/Index')) },
{ path: 'preprocess/type', Component: lazy(() => import('./preprocess/type/views/Index')) },
{ path: 'preprocess/sort', Component: lazy(() => import('./preprocess/sort/views/Index')) },
{ path: 'preprocess/splice', Component: lazy(() => import('./preprocess/splice/views/Index')) },
// 数据预处理
{ path: 'process', element: <Navigate to="binning" /> },
{ path: 'process/binning', Component: lazy(() => import('./process/binning/views/Index')) },
{ path: 'process/mapping', Component: lazy(() => import('./process/mapping/views/Index')) },
{ path: 'process/group', Component: lazy(() => import('./process/group/views/Index')) },
{ path: 'process/desensitization', Component: lazy(() => import('./process/desensitization/views/Index')) },
{ path: 'process/date', Component: lazy(() => import('./process/date/views/Index')) },
{ path: 'process/string', Component: lazy(() => import('./process/string/views/Index')) },
{ path: 'process/number', Component: lazy(() => import('./process/number/views/Index')) },
{ path: 'process/logic', Component: lazy(() => import('./process/logic/views/Index')) },
{ path: 'process/perspective', Component: lazy(() => import('./process/perspective/views/Index')) },
// 数据可视化组件
{ path: 'chart', element: <Navigate to="bar" /> },
{ path: 'chart/bar', Component: lazy(() => import('./chart/bar/views/Index')) },
], ],
}, },
], ],
......
import { Form, Input, Modal, Select } from 'antd'
import { useMapStore } from '@/stores/map'
export default function FormModal(props) {
const getMapValuesByKey = useMapStore((state) => state.getMapValuesByKey)
const industryList = getMapValuesByKey('bi_data_industry')
const dataSourceList = getMapValuesByKey('bi_data_source')
const sensitivityLevelList = getMapValuesByKey('bi_data_sensitivity_level')
const accessPermissionsList = getMapValuesByKey('bi_data_access_permissions')
return (
<Modal title="添加数据集" {...props}>
<Form labelCol={{ span: 4 }}>
<Form.Item label="数据集名称" name="name">
<Input placeholder="请输入" />
</Form.Item>
<Form.Item label="所属行业" name="industry">
<Select options={industryList}></Select>
</Form.Item>
<Form.Item label="数据来源" name="data_source">
<Select options={dataSourceList}></Select>
</Form.Item>
<Form.Item label="敏感等级" name="level">
<Select options={sensitivityLevelList}></Select>
</Form.Item>
<Form.Item label="访问权限" name="access">
<Select options={accessPermissionsList}></Select>
</Form.Item>
<Form.Item label="说明" name="desc">
<Input.TextArea rows={4} placeholder="请输入" />
</Form.Item>
</Form>
</Modal>
)
}
import { lazy, useState } from 'react'
import { Button, Card, Input, Select } from 'antd'
import AppList, { AppListProps } from '@/components/AppList'
import { useMapStore } from '@/stores/map'
const FormModal = lazy(() => import('../components/FormModal'))
const ViewDataModal = lazy(() => import('@/components/data/ViewDataModal'))
export default function DataWriteBuilt() {
const getMapValuesByKey = useMapStore((state) => state.getMapValuesByKey)
const industryList = getMapValuesByKey('bi_data_industry')
const [isModalOpen, setIsModalOpen] = useState(false)
const showModal = () => {
setIsModalOpen(true)
}
const handleOk = () => {
setIsModalOpen(false)
}
const handleCancel = () => {
setIsModalOpen(false)
}
const [viewModalIsOpen, setViewModalIsOpen] = useState(false)
const handleView = (record: any) => {
console.log(record)
setViewModalIsOpen(true)
}
const handleRemove = (record: any) => {
console.log(record)
}
const listOptions: AppListProps = {
filters: [
{
name: 'industry',
element: <Select placeholder="请选择所属行业" options={industryList} />,
},
{
name: 'name',
element: <Input placeholder="请输入数据集名称" />,
},
],
columns: [
{
title: '序号',
key: 'index',
render(_value, _record, index) {
return index + 1
},
width: 62,
align: 'center',
},
{ title: '数据集名称', dataIndex: 'name' },
{ title: '数据量', dataIndex: 'count' },
{ title: '所属行业', dataIndex: 'industry' },
{ title: '数据来源', dataIndex: 'sourc' },
{ title: '敏感等级', dataIndex: 'level' },
{ title: '访问权限', dataIndex: 'auth' },
{ title: '说明', dataIndex: 'desc' },
{ title: '创建人', dataIndex: 'create' },
{ title: '创建时间', dataIndex: 'create_time' },
{ title: '更新时间', dataIndex: 'update_time' },
{
title: '操作',
key: 'x',
width: 160,
align: 'center',
render(_value, record) {
return (
<>
<Button color="primary" variant="text" onClick={() => handleView(record)}>
查阅
</Button>
<Button color="danger" variant="text" onClick={() => handleRemove(record)}>
删除
</Button>
</>
)
},
},
],
dataSource: [
{ id: 1, name: '《商务数据分析基础》数据集' },
{ id: 2, name: '智慧交通数据集' },
{ id: 3, name: '信用卡数字营销数据集' },
],
}
return (
<Card className="app-card" title="内置数据集管理">
<AppList
bordered
{...listOptions}
filterAside={
<Button type="primary" onClick={showModal}>
添加数据集
</Button>
}></AppList>
<FormModal open={isModalOpen} onOk={handleOk} onCancel={handleCancel}></FormModal>
<ViewDataModal open={viewModalIsOpen} onCancel={() => setViewModalIsOpen(false)}></ViewDataModal>
</Card>
)
}
import { Form, Input, Modal } from 'antd'
export default function FormModal(props) {
return (
<Modal title="复制数据集" destroyOnClose {...props}>
<Form labelCol={{ span: 4 }} preserve={false}>
<Form.Item label="数据集名称" name="name">
<Input placeholder="请输入" />
</Form.Item>
<Form.Item label="原始数据集">电子商务案例数据集</Form.Item>
<Form.Item label="数据量">2000</Form.Item>
<Form.Item label="所属行业">电子商务</Form.Item>
<Form.Item label="数据来源">案例数据</Form.Item>
<Form.Item label="敏感等级">L1一般敏感</Form.Item>
<Form.Item label="访问权限">完全开放</Form.Item>
<Form.Item label="创建人">张三疯</Form.Item>
<Form.Item label="创建时间">2024-12-12 13:13:13</Form.Item>
<Form.Item label="更新时间">2024-12-12 13:13:13</Form.Item>
</Form>
</Modal>
)
}
import { Card } from 'antd' import { Button, Card, Input, Select } from 'antd'
import AppList, { AppListProps } from '@/components/AppList'
import { useMapStore } from '@/stores/map'
import { lazy, useState } from 'react'
export default function DataWriteUpload() { const CopyModal = lazy(() => import('../components/CopyModal'))
const ViewDataModal = lazy(() => import('@/components/data/ViewDataModal'))
export default function DataWriteCopy() {
const getMapValuesByKey = useMapStore((state) => state.getMapValuesByKey)
const industryList = getMapValuesByKey('bi_data_industry')
const generateDataSource = (count: number) => {
return Array.from({ length: count }, (_, index) => ({
id: index,
key: index,
name: `数据集名称 ${index + 1}`,
count: Math.floor(Math.random() * 1000),
industry: `行业 ${index + 1}`,
source: `来源 ${index + 1}`,
level: `等级 ${index + 1}`,
auth: `权限 ${index + 1}`,
desc: `说明 ${index + 1}`,
create: `创建人 ${index + 1}`,
create_time: new Date().toLocaleString(),
update_time: new Date().toLocaleString(),
}))
}
const [viewModalIsOpen, setViewModalIsOpen] = useState(false)
const handleView = (record: any) => {
console.log(record)
setViewModalIsOpen(true)
}
const [copyModalIsOpen, setCopyModalIsOpen] = useState(false)
const handleCopy = (record: any) => {
console.log(record)
setCopyModalIsOpen(true)
}
const listOptions: AppListProps = {
filters: [
{
name: 'industry',
element: <Select placeholder="请选择所属行业" options={industryList} />,
},
{
name: 'name',
element: <Input placeholder="请输入数据集名称" />,
},
],
columns: [
{
title: '序号',
key: 'index',
render(_value, _record, index) {
return index + 1
},
width: 62,
align: 'center',
},
{ title: '数据集名称', dataIndex: 'name', align: 'center' },
{ title: '数据量', dataIndex: 'count', align: 'center' },
{ title: '所属行业', dataIndex: 'industry', align: 'center' },
{ title: '数据来源', dataIndex: 'source', align: 'center' },
{ title: '敏感等级', dataIndex: 'level', align: 'center' },
{ title: '访问权限', dataIndex: 'auth', align: 'center' },
{ title: '说明', dataIndex: 'desc', align: 'center' },
{ title: '创建人', dataIndex: 'create', align: 'center' },
{ title: '创建时间', dataIndex: 'create_time', align: 'center' },
{ title: '更新时间', dataIndex: 'update_time', align: 'center' },
{
title: '操作',
key: 'x',
width: 160,
align: 'center',
render(_value, record) {
return (
<>
<Button color="primary" variant="text" onClick={() => handleView(record)}>
查阅
</Button>
<Button color="primary" variant="text" onClick={() => handleCopy(record)}>
复制
</Button>
</>
)
},
},
],
dataSource: generateDataSource(20),
}
return ( return (
<div> <Card className="app-card" title="数据复制">
<Card className="app-card" title="数据复制"></Card> <AppList bordered {...listOptions}></AppList>
</div> <CopyModal open={copyModalIsOpen} onCancel={() => setCopyModalIsOpen(false)}></CopyModal>
<ViewDataModal open={viewModalIsOpen} onCancel={() => setViewModalIsOpen(false)}></ViewDataModal>
</Card>
) )
} }
import { Card } from 'antd' import { Button, Card, Empty, Flex, Space } from 'antd'
import { Link } from 'react-router'
export default function DataWriteMy() {
const isEmpty = true
// 无数据渲染
const emptyRender = () => {
return (
<>
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="无数据"></Empty>
<Flex justify="center" align="middle">
<Space>
<Link to="/data/write/copy">
<Button type="primary">复制数据</Button>
</Link>
<Link to="/data/write/upload">
<Button type="primary">导入数据</Button>
</Link>
<Button type="primary" disabled>
爬取数据
</Button>
<Button type="primary" disabled>
本地数据库连接
</Button>
<Button type="primary" disabled>
API数据获取
</Button>
</Space>
</Flex>
</>
)
}
export default function DataWriteUpload() {
return ( return (
<div> <Card className="app-card" title="我的数据集">
<Card className="app-card" title="我的数据集"></Card> <Flex justify="space-between" align="middle">
</div> <h4>数据集名称:电子商务案例分析数据集</h4>
<Button type="primary" disabled={isEmpty}>
数据质量分析报告
</Button>
</Flex>
{!isEmpty && (
<Flex justify="space-between" align="middle">
<p>《商务数据分析基础》数据集-电子商务2025.xlsx</p>
<p>共计:1000条数据</p>
</Flex>
)}
{isEmpty && emptyRender()}
</Card>
) )
} }
import { Card } from 'antd' import { Button, Card, Flex, Form, Input, Upload } from 'antd'
import type { UploadProps } from 'antd'
import { InboxOutlined, PlusOutlined } from '@ant-design/icons'
import { useState } from 'react'
import { read, utils } from 'xlsx'
import DataRender from '@/components/data/DataRender'
export default function DataWriteUpload() { export default function DataWriteUpload() {
const [file, setFile] = useState<File | null>(null)
const [data, setData] = useState<any[]>([])
const columns: any =
data.length > 0
? Object.keys(data[0]).map((key) => ({
title: key,
dataIndex: key,
align: 'center',
}))
: []
console.log(columns)
console.log(data)
const props: UploadProps = {
showUploadList: false,
accept: '.xlsx,.csv',
beforeUpload: async (file) => {
setFile(file)
const data = await file.arrayBuffer()
const workbook = read(data)
const sheetName = workbook.SheetNames[0]
const worksheet = workbook.Sheets[sheetName]
const jsonData = utils.sheet_to_json(worksheet)
setData(jsonData)
return false
},
}
return ( return (
<Card className="app-card" title="数据导入">
{file ? (
<div> <div>
<Card className="app-card" title="数据导入"></Card> <Form layout="inline">
<Form.Item label="数据集名称">
<Input placeholder="请输入" style={{ width: '220px' }}></Input>
</Form.Item>
</Form>
<Flex align="center" gap={100} style={{ margin: '10px 0 30px' }}>
<div>
<Upload {...props}>
<Button type="primary" shape="circle" size="large" icon={<PlusOutlined />}></Button>
</Upload>
<span style={{ marginLeft: '10px' }}>{file?.name}</span>
</div>
<p>共计:{data.length}条数据</p>
<Button type="primary">保存</Button>
</Flex>
<DataRender dataSource={data} columns={columns}></DataRender>
</div>
) : (
<Upload.Dragger {...props}>
<div style={{ padding: '100px 0' }}>
<p className="ant-upload-drag-icon">
<InboxOutlined />
</p>
<p>此操作将会把上传的.xlsx文件或.csv文件,导入到后台数据库中,以便于后续操作处理。</p>
<p>
需要注意的是,每个实验中,一个账号只能同时操作一个文件,
<span style={{ color: 'var(--main-color)' }}>
如果重复上传,那么最新一次的上传数据将会替换原先上传的数据,导致原先对数据做的所有操作均会无效!
</span>
</p>
</div> </div>
</Upload.Dragger>
)}
</Card>
) )
} }
import { create } from 'zustand'
import { getMapList } from '@/api/base'
interface State {
mapList: MapState[]
setMap: (data: MapState[]) => void
getMap: () => Promise<void>
getMapValuesByKey: (key: string) => MapValues[]
getNameByValue: (value: string, list: MapValues[]) => string
}
interface MapState {
id: string
key: string
name: string
remark: string
values: MapValues[]
}
interface MapValues {
data_dictionary_id: string
id: string
label: string
remark: string
sort: string
value: string
}
export const useMapStore = create<State>((set, get) => ({
mapList: [],
setMap: (data: MapState[]) => {
set({ mapList: data })
},
getMap: async () => {
const { data } = await getMapList()
set({ mapList: data })
},
getMapValuesByKey: (key) => {
return get().mapList.find((map) => map.key === key)?.values || []
},
getNameByValue: (value, list) => {
return list.find((item) => item.value == value)?.label || value
},
}))
import { getUser } from '@/api/base'
import { create } from 'zustand' import { create } from 'zustand'
import { getUser } from '@/api/base'
// 用户信息
export interface UserType {
id: string
mobile: string
name: string
email: string
username: string
avatar: string
}
// 角色信息(1学员;5教师; 6管理员)
export interface RoleType {
id: 1 | 5 | 6
name: string
}
export const useUserStore = create((set) => ({ // 权限信息
user: null, export interface PermissionType {
desc: string
effect_uris: string
id: string
name: string
parent_id: string
system_tag: number
type: number
tag: string
}
interface State {
info: UserType | null
role: RoleType | null
permissions: PermissionType[]
setUser: (data: any) => void
getUser: () => Promise<void>
}
export const useUserStore = create<State>((set) => ({
info: null,
role: null,
permissions: [],
setUser: (data) => {
set({ info: data.info, role: data.role, permissions: data.permissions.permissions })
},
getUser: async () => { getUser: async () => {
const res = await getUser() const { data } = await getUser()
set({ user: res.data }) set({ info: data.info, role: data.role, permissions: data.permissions.permissions })
}, },
})) }))
import axios from 'axios' import axios from 'axios'
import { message } from 'antd'
const axiosInstance = axios.create({ const axiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_URL || 'https://api.example.com', // timeout: 60000,
timeout: 10000, // withCredentials: true,
headers: { 'Content-Type': 'application/json' }, // headers: {
// 'Content-Type': 'application/x-www-form-urlencoded',
// },
}) })
axiosInstance.interceptors.response.use( axiosInstance.interceptors.response.use(
(response) => response, (response) => {
const { data } = response
// 未登录
if (data.code === 4001) {
location.href = `${import.meta.env.VITE_LOGIN_URL}?rd=${encodeURIComponent(location.href)}`
return Promise.reject(data)
}
if (data.code === 1 || data.code === -1) {
message.error(data.message || data.msg)
return Promise.reject(data)
}
return data
},
(error) => { (error) => {
console.error('API Error:', error) if (error.response) {
return Promise.reject(error) const { status, message } = error.response.data
// 未登录
if (status === 403) {
location.href = `${import.meta.env.VITE_LOGIN_URL}?rd=${encodeURIComponent(location.href)}`
} else {
message.error(message || error.message)
console.error(`${status}: ${message}`)
}
} else {
console.error(error)
}
return Promise.reject(error.response || error)
} }
) )
......
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc' import react from '@vitejs/plugin-react-swc'
import { fileURLToPath, URL } from 'node:url' import { fileURLToPath, URL } from 'node:url'
import AutoImport from 'unplugin-auto-import/vite'
import mkcert from 'vite-plugin-mkcert' import mkcert from 'vite-plugin-mkcert'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [react(), mkcert()],
react(),
AutoImport({
imports: ['react', 'react-router'],
dts: true,
eslintrc: { enabled: true },
}),
mkcert(),
],
server: { server: {
open: true, open: true,
host: 'dev.ezijing.com', host: 'dev.ezijing.com',
proxy: { proxy: {
'/api': 'https://saas-dml.ezijing.com', '/api': 'https://saas-lab.ezijing.com',
}, },
}, },
resolve: { resolve: {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论