sync: Add files via upload (A@f25beaaf2f1fcd0c8fca85fa622d846392968047)
This commit is contained in:
parent
6517a3f4cd
commit
37ecf1a056
1
.microcompact/mpm69s0q/functions.Bash:5.txt
Normal file
1
.microcompact/mpm69s0q/functions.Bash:5.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":"total 1152\ndrwxr-xr-x 2 root root 4096 May 25 20:10 .\ndrwxr-xr-x 1 root root 4096 May 25 19:48 ..\n-rw-r--r-- 1 root root 77 May 25 19:45 1feff74faaf0efc6a044\n-rw-r--r-- 1 root root 148268 May 25 19:45 core.6fdc2188.js\n-rw-r--r-- 1 root root 112078 May 25 19:45 framer-motion.022bba29.js\n-rw-r--r-- 1 root root 1256 May 25 20:10 index.html\n-rw-r--r-- 1 root root 669973 May 25 20:10 main.1bee7fcd.js\n-rw-r--r-- 1 root root 1910 May 25 19:45 runtime.6db664f3.js\n-rw-r--r-- 1 root root 21712 May 25 19:45 ui.8f0f0b07.js\n-rw-r--r-- 1 root root 192832 May 25 19:45 vendors.750695a4.js"}
|
||||||
1
.microcompact/mpm69s40/functions.Bash:8.txt
Normal file
1
.microcompact/mpm69s40/functions.Bash:8.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":"[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production\n\n> procurement-planning@1.0.0 typecheck /home/project\n> tsc --noEmit\n\n[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production"}
|
||||||
1
.microcompact/mpm69s62/functions.Bash:9.txt
Normal file
1
.microcompact/mpm69s62/functions.Bash:9.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":"[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production\n\n> procurement-planning@1.0.0 dev /home/project\n> webpack serve -- --port 3015\n\n[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production\n<i> [webpack-dev-server] Project is running at:\n<i> [webpack-dev-server] Loopback: http://localhost:3015/\n<i> [webpack-dev-server] Content not from webpack is served from '/home/project/public' directory\n<i> [webpack-dev-server] 404s will fallback to '/index.html'\n"}
|
||||||
1
.microcompact/mpm69sar/functions.Bash:13.txt
Normal file
1
.microcompact/mpm69sar/functions.Bash:13.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":"total 128\ndrwxr-xr-x 4 root root 4096 May 25 18:00 .\ndrwxr-xr-x 13 root root 4096 May 25 18:00 ..\n-rw-r--r-- 1 root root 5763 May 25 18:00 ComingSoonModal.tsx\n-rw-r--r-- 1 root root 7598 May 25 18:00 DemoDisclaimerModal.tsx\n-rw-r--r-- 1 root root 316 May 25 18:00 DemoWatermark.tsx\n-rw-r--r-- 1 root root 5020 May 25 18:00 ErrorBoundary.tsx\n-rw-r--r-- 1 root root 8129 May 25 19:43 InventoryRecordsModal.tsx\n-rw-r--r-- 1 root root 3884 May 25 18:00 NetworkStatusBar.tsx\n-rw-r--r-- 1 root root 9933 May 25 18:00 NotificationCenter.tsx\n-rw-r--r-- 1 root root 10814 May 25 18:00 OnboardingGuide.tsx\n-rw-r--r-- 1 root root 4638 May 25 18:00 PageTransition.tsx\n-rw-r--r-- 1 root root 3113 May 25 18:00 PlanCard.tsx\n-rw-r--r-- 1 root root 17566 May 25 19:43 ProcessFlow.tsx\n-rw-r--r-- 1 root root 1327 May 25 18:00 ProgressBar.tsx\n-rw-r--r-- 1 root root 1211 May 25 18:00 StatusBadge.tsx\n-rw-r--r-- 1 root root 7450 May 25 18:00 VirtualList.tsx\ndrwxr-xr-x 2 root root 4096 May 25 18:00 plans\ndrwxr-xr-x 2 root root 4096 May 25 18:00 warehouse"}
|
||||||
1
.microcompact/mpm69sdx/functions.Bash:15.txt
Normal file
1
.microcompact/mpm69sdx/functions.Bash:15.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":"total 96\ndrwxr-xr-x 2 root root 4096 May 25 18:00 .\ndrwxr-xr-x 5 root root 4096 May 25 18:00 ..\n-rw-r--r-- 1 root root 8287 May 25 19:43 CompletedFabric.tsx\n-rw-r--r-- 1 root root 15750 May 25 18:00 Dashboard.tsx\n-rw-r--r-- 1 root root 13730 May 25 19:43 FinishedWarehouse.tsx\n-rw-r--r-- 1 root root 6782 May 25 18:00 PaymentPending.tsx\n-rw-r--r-- 1 root root 6464 May 25 18:00 PendingFabric.tsx\n-rw-r--r-- 1 root root 26721 May 25 19:43 PlanOverview.tsx"}
|
||||||
1
.microcompact/mpm69sg1/functions.Bash:16.txt
Normal file
1
.microcompact/mpm69sg1/functions.Bash:16.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":"total 172\ndrwxr-xr-x 2 root root 4096 May 25 18:00 .\ndrwxr-xr-x 5 root root 4096 May 25 18:00 ..\n-rw-r--r-- 1 root root 14798 May 25 19:43 AccountsPayable.tsx\n-rw-r--r-- 1 root root 15336 May 25 19:27 Dashboard.tsx\n-rw-r--r-- 1 root root 19115 May 25 18:00 FactoryManage.tsx\n-rw-r--r-- 1 root root 13401 May 25 19:43 FinishedWarehouse.tsx\n-rw-r--r-- 1 root root 20754 May 25 19:38 NewPlan.tsx\n-rw-r--r-- 1 root root 21162 May 25 18:00 NewWashingPlan.tsx\n-rw-r--r-- 1 root root 14074 May 25 18:00 PlanOverview.tsx\n-rw-r--r-- 1 root root 29645 May 25 19:43 WarehouseManage.tsx"}
|
||||||
1
.microcompact/mpm69sja/functions.Grep:18.txt
Normal file
1
.microcompact/mpm69sja/functions.Grep:18.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"results":[{"file":"src/types/index.ts","matches":[{"line":29,"content":"export type WashingPlan = Tables<'washing_plans'>;"},{"line":31,"content":"export type WashingPlanCompletion = Tables<'washing_plan_completions'>;"}]},{"file":"src/pages/washing/PlanOverview.tsx","matches":[{"line":36,"content":"export function WashingPlanOverview() {"}]},{"file":"src/pages/purchaser/NewWashingPlan.tsx","matches":[{"line":28,"content":"export function NewWashingPlan() {"}]}],"pattern":"export.*WashingPlan","path":"src","totalMatches":4}
|
||||||
1
.microcompact/mpm69smj/functions.Grep:20.txt
Normal file
1
.microcompact/mpm69smj/functions.Grep:20.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"results":[{"file":"src/supabase/types.ts","matches":[{"line":441,"content":" referencedRelation: \"washing_plans\""},{"line":1239,"content":" referencedRelation: \"washing_plans\""},{"line":1244,"content":" washing_plans: {"},{"line":1310,"content":" foreignKeyName: \"washing_plans_company_id_fkey\""},{"line":1316,"content":" foreignKeyName: \"washing_plans_created_by_fkey\""},{"line":1322,"content":" foreignKeyName: \"washing_plans_product_id_fkey\""},{"line":1328,"content":" foreignKeyName: \"washing_plans_washing_factory_id_fkey\""},{"line":1388,"content":" referencedRelation: \"washing_plans\""}]}],"pattern":"washing_plans","path":"src/supabase","totalMatches":8}
|
||||||
1
.microcompact/mpm69sq2/functions.Bash:22.txt
Normal file
1
.microcompact/mpm69sq2/functions.Bash:22.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":"[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production\n\n> procurement-planning@1.0.0 dev /home/project\n> webpack serve -- --port 3015\n\n[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production\n<i> [webpack-dev-server] Project is running at:\n<i> [webpack-dev-server] Loopback: http://localhost:3015/\n<i> [webpack-dev-server] Content not from webpack is served from '/home/project/public' directory\n<i> [webpack-dev-server] 404s will fallback to '/index.html'\n"}
|
||||||
1
.microcompact/mpm69ss6/functions.Bash:23.txt
Normal file
1
.microcompact/mpm69ss6/functions.Bash:23.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":-1,"output":"","stderr":"Command execution timed out","timedOut":true}
|
||||||
1
.microcompact/mpm69svw/functions.Bash:25.txt
Normal file
1
.microcompact/mpm69svw/functions.Bash:25.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":"ls: cannot access '/home/project/public/': No such file or directory\npublic directory does not exist"}
|
||||||
1
.microcompact/mpm69sxw/functions.Bash:26.txt
Normal file
1
.microcompact/mpm69sxw/functions.Bash:26.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":"drwxr-xr-x 2 root root 4096 May 25 20:10 dist"}
|
||||||
1
.microcompact/mpm69t0n/functions.Bash:27.txt
Normal file
1
.microcompact/mpm69t0n/functions.Bash:27.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":"total 8\ndrwxr-xr-x 2 root root 4096 May 25 20:20 .\ndrwxr-xr-x 1 root root 4096 May 25 20:20 .."}
|
||||||
1
.microcompact/mpm69t2r/functions.Bash:28.txt
Normal file
1
.microcompact/mpm69t2r/functions.Bash:28.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":-1,"output":"","stderr":"🛡️ 命令已被拦截 - 该命令可能会破坏 sandbox 运行环境\n\n❌ 拦截原因:\n以下命令可能会杀掉 sandbox 内置服务进程(SDK server、log server、dev server、cors proxy 等),导致沙箱崩溃:\n - `pkill -f \"webpack serve\" 2>/dev/null` 中的模式 \"webpack serve\" 过于宽泛\n\n💡 建议:\n请使用更精确的方式停止目标进程:\n 1. 通过端口查找:`lsof -i :PORT -t | xargs kill` 只杀占用特定端口的进程\n 2. 通过精确路径匹配:`pkill -f \"/home/project/.*serve\"` 只杀用户项目内的进程\n 3. 通过 PID:先 `ps aux | grep xxx` 确认目标 PID,再 `kill PID`\n 4. 如果要停止 dev server,直接运行新的 `npm run dev` 即可(会自动替换旧进程)"}
|
||||||
1
.microcompact/mpm69t50/functions.Bash:30.txt
Normal file
1
.microcompact/mpm69t50/functions.Bash:30.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":"[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production\n\n> procurement-planning@1.0.0 dev /home/project\n> webpack serve -- --port 3015\n\n[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production\n<i> [webpack-dev-server] Project is running at:\n<i> [webpack-dev-server] Loopback: http://localhost:3015/\n<i> [webpack-dev-server] Content not from webpack is served from '/home/project/public' directory\n<i> [webpack-dev-server] 404s will fallback to '/index.html'\n"}
|
||||||
1
.microcompact/mpm69t72/functions.Bash:30.txt
Normal file
1
.microcompact/mpm69t72/functions.Bash:30.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":-1,"output":"","stderr":"Command execution timed out","timedOut":true}
|
||||||
1
.microcompact/mpm69t94/functions.Bash:31.txt
Normal file
1
.microcompact/mpm69t94/functions.Bash:31.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":"COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME\nwebpack 25764 root 18u IPv4 260876 0t0 TCP *:3015 (LISTEN)\nwebpack 25764 root 23u IPv4 262983 0t0 TCP localhost:3015->localhost:44306 (ESTABLISHED)\nwebpack 25764 root 25u IPv4 260974 0t0 TCP localhost:3015->localhost:44322 (ESTABLISHED)\nwebpack 25764 root 26u IPv4 260975 0t0 TCP localhost:3015->localhost:44336 (ESTABLISHED)\ncurl 25827 root 5u IPv4 260944 0t0 TCP localhost:44336->localhost:3015 (ESTABLISHED)"}
|
||||||
1
.microcompact/mpm69tbp/functions.Bash:32.txt
Normal file
1
.microcompact/mpm69tbp/functions.Bash:32.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":"root 25763 0.0 0.0 2584 896 pts/0 S 20:21 0:00 sh -c webpack serve -- --port 3015\nroot 25764 57.9 4.2 33027056 360500 pts/0 Sl 20:21 0:14 webpack"}
|
||||||
1
.microcompact/mpm69teb/functions.Bash:33.txt
Normal file
1
.microcompact/mpm69teb/functions.Bash:33.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":-1,"output":"","stderr":"Command execution timed out","timedOut":true}
|
||||||
25
.microcompact/mpm69tmb/functions.Edit:40.txt
Normal file
25
.microcompact/mpm69tmb/functions.Edit:40.txt
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
The file /home/project/webpack.config.js has been updated. Made 1 replacement.
|
||||||
|
|
||||||
|
Here's the result of running `cat -n` on a snippet of the edited file:
|
||||||
|
131→ },
|
||||||
|
132→ resolve: {
|
||||||
|
133→ extensions: ['.mjs', '.ts', '.tsx', '.js', '.jsx']
|
||||||
|
134→ },
|
||||||
|
135→ devServer: {
|
||||||
|
136→ // 约束 A:沙箱只开放一个代理端口,所有 builder(webpack/vite/electron)统一 3015
|
||||||
|
137→ port: 3015,
|
||||||
|
138→ host: '0.0.0.0',
|
||||||
|
139→ allowedHosts: 'all',
|
||||||
|
140→ hot: true,
|
||||||
|
141→ historyApiFallback: {
|
||||||
|
142→ index: '/index.html',
|
||||||
|
143→ rewrites: [
|
||||||
|
144→ { from: /^\/_p\/\d+\//, to: '/index.html' }
|
||||||
|
145→ ]
|
||||||
|
146→ }
|
||||||
|
147→ },
|
||||||
|
148→ plugins: [
|
||||||
|
149→ new HtmlWebpackPlugin({
|
||||||
|
150→ template: './index.html',
|
||||||
|
151→ inject: 'body',
|
||||||
|
152→ minify: !isDev ? {
|
||||||
1
.microcompact/mpm69toa/functions.Bash:43.txt
Normal file
1
.microcompact/mpm69toa/functions.Bash:43.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":"[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production\n\n> procurement-planning@1.0.0 build /home/project\n> webpack --mode production\n\n[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production\n[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production\n[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production\n[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production\nassets by status 1.09 MiB [cached] 7 assets\nasset index.html 1.23 KiB [compared for emit]\nEntrypoint main [big] 1.09 MiB = 6 assets\norphan modules 3.11 MiB [orphan] 1697 modules\nruntime modules 4.72 KiB 9 modules\ncacheable modules 2.62 MiB\n modules by path ./node_modules/.pnpm/lucide-react@0.294.0_react@18.3.1/node_modules/lucide-r...(truncated) 38.7 KiB 67 modules\n modules by path ./node_modules/.pnpm/framer-motion@11.18.2_@emotion+is-prop-valid@1.4.0_react-do...(truncated) 394 KiB 9 modules\n modules by path ./node_modules/.pnpm/style-loader@3.3.4_webpack@5.105.4/node_modules/style-loade...(truncated) 5.84 KiB 6 modules\n modules by path ./node_modules/.pnpm/motion-utils@11.18.1/node_modules/motion-utils...(truncated) 1.63 KiB 5 modules\n modules by path ./node_modules/.pnpm/react@18.3.1/node_modules/react/ 8 KiB 4 modules\n modules by path ./node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/ 131 KiB 3 modules\n modules by path ./src/ 1.18 MiB 2 modules\n modules by path ./node_modules/.pnpm/css-loader@6.11.0_webpack@5.105.4/node_modules/css-loader/d...(truncated) 2.31 KiB 2 modules\n modules by path ./node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/ 4.33 KiB 2 modules\n + 7 modules\n\nWARNING in asset size limit: The following asset(s) exceed the recommended size limit (500 KiB).\nThis can impact web performance.\nAssets: \n main.1bee7fcd.js (654 KiB)\n\nWARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (500 KiB). This can impact web performance.\nEntrypoints:\n main (1.09 MiB)\n runtime.6db664f3.js\n core.6fdc2188.js\n framer-motion.022bba29.js\n ui.8f0f0b07.js\n vendors.750695a4.js\n main.1bee7fcd.js\n\nWARNING in webpack performance recommendations: \nYou can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application.\nFor more info visit https://webpack.js.org/guides/code-splitting/\n\nwebpack 5.105.4 compiled with 3 warnings in 10487 ms"}
|
||||||
1
.microcompact/mpm69tqf/functions.Bash:43.txt
Normal file
1
.microcompact/mpm69tqf/functions.Bash:43.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":"[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production\n\n> procurement-planning@1.0.0 dev /home/project\n> webpack serve -- --port 3015\n\n[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production\n<i> [webpack-dev-server] Project is running at:\n<i> [webpack-dev-server] Loopback: http://localhost:3015/\n<i> [webpack-dev-server] Content not from webpack is served from '/home/project/public' directory\n<i> [webpack-dev-server] 404s will fallback to '/index.html'\n"}
|
||||||
1
.microcompact/mpm69tsi/functions.Bash:44.txt
Normal file
1
.microcompact/mpm69tsi/functions.Bash:44.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":-1,"output":"","stderr":"Command execution timed out","timedOut":true}
|
||||||
1
.microcompact/mpm69tup/functions.Bash:45.txt
Normal file
1
.microcompact/mpm69tup/functions.Bash:45.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":"COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME\nwebpack 26361 root 18u IPv4 269636 0t0 TCP *:3015 (LISTEN)\nwebpack 26361 root 21u IPv4 269867 0t0 TCP localhost:3015->localhost:36858 (ESTABLISHED)\nwebpack 26361 root 25u IPv4 269865 0t0 TCP localhost:3015->localhost:36834 (ESTABLISHED)\nwebpack 26361 root 26u IPv4 269866 0t0 TCP localhost:3015->localhost:36842 (ESTABLISHED)"}
|
||||||
1
.microcompact/mpm69twp/functions.Bash:46.txt
Normal file
1
.microcompact/mpm69twp/functions.Bash:46.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":""}
|
||||||
1
.microcompact/mpm69u13/functions.Bash:51.txt
Normal file
1
.microcompact/mpm69u13/functions.Bash:51.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":"root 26360 0.0 0.0 2584 896 pts/0 S 20:22 0:00 sh -c webpack serve -- --port 3015\nroot 26361 9.6 2.9 32924056 253052 pts/0 Sl 20:22 0:15 webpack"}
|
||||||
1
.microcompact/mpm69u38/functions.Bash:52.txt
Normal file
1
.microcompact/mpm69u38/functions.Bash:52.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":"COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME\nwebpack 26361 root 18u IPv4 269636 0t0 TCP *:3015 (LISTEN)\nwebpack 26361 root 19u IPv4 273341 0t0 TCP localhost:3015->localhost:35426 (ESTABLISHED)\nwebpack 26361 root 21u IPv4 269867 0t0 TCP localhost:3015->localhost:36858 (ESTABLISHED)\nwebpack 26361 root 23u IPv4 275503 0t0 TCP localhost:3015->localhost:51008 (ESTABLISHED)\ncurl 26424 root 5u IPv4 268715 0t0 TCP localhost:36858->localhost:3015 (ESTABLISHED)"}
|
||||||
1
.microcompact/mpm69u5h/functions.Bash:53.txt
Normal file
1
.microcompact/mpm69u5h/functions.Bash:53.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":"total 1152\ndrwxr-xr-x 2 root root 4096 May 25 20:10 .\ndrwxr-xr-x 1 root root 4096 May 25 20:20 ..\n-rw-r--r-- 1 root root 77 May 25 19:45 1feff74faaf0efc6a044\n-rw-r--r-- 1 root root 148268 May 25 19:45 core.6fdc2188.js\n-rw-r--r-- 1 root root 112078 May 25 19:45 framer-motion.022bba29.js\n-rw-r--r-- 1 root root 1256 May 25 20:10 index.html\n-rw-r--r-- 1 root root 669973 May 25 20:10 main.1bee7fcd.js\n-rw-r--r-- 1 root root 1910 May 25 19:45 runtime.6db664f3.js\n-rw-r--r-- 1 root root 21712 May 25 19:45 ui.8f0f0b07.js\n-rw-r--r-- 1 root root 192832 May 25 19:45 vendors.750695a4.js"}
|
||||||
1
.microcompact/mpm69u88/functions.Bash:54.txt
Normal file
1
.microcompact/mpm69u88/functions.Bash:54.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":"<!doctype html><html lang=\"zh-CN\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><title>织机 - 以AI为梭,织天地经纬</title><link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css\"><style>body,html{margin:0;padding:0;width:100%;height:100%}html.light,html.light body{background-color:#fff!important;color:#000}html.dark,html.dark body{background-color:#1a1a1a!important;color:#fff}</style><script>!function(){function t(t){document.documentElement.classList.remove(\"light\",\"dark\"),document.documentElement.classList.add(t),document.documentElement.setAttribute(\"data-theme\",t)}window.self!==window.top?window.addEventListener(\"message\",function(e){if(e.data&&\"string\"==typeof e.data.theme){const n=e.data.theme;\"light\"!==n&&\"dark\"!==n||t(n)}}):t(\"light\")}()</script></head><body><div id=\"root\"></div><script defer=\"defer\" src=\"runtime.6db664f3.js\"></script><script defer=\"defer\" src=\"core.6fdc2188.js\"></script><script defer=\"defer\" src=\"framer-motion.022bba29.js\"></script><script defer=\"defer\" src=\"ui.8f0f0b07.js\"></script><script defer=\"defer\" src=\"vendors.750695a4.js\"></script><script defer=\"defer\" src=\"main.1bee7fcd.js\"></script></body></html>"}
|
||||||
1
.microcompact/mpm69uam/functions.Bash:55.txt
Normal file
1
.microcompact/mpm69uam/functions.Bash:55.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":-1,"output":"","stderr":"Command execution timed out","timedOut":true}
|
||||||
1
.microcompact/mpm69ucp/functions.Bash:56.txt
Normal file
1
.microcompact/mpm69ucp/functions.Bash:56.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":-1,"output":"","stderr":"Command execution timed out","timedOut":true}
|
||||||
32
.microcompact/mpm69uev/functions.Edit:57.txt
Normal file
32
.microcompact/mpm69uev/functions.Edit:57.txt
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
The file /home/project/webpack.config.js has been updated. Made 1 replacement.
|
||||||
|
|
||||||
|
Here's the result of running `cat -n` on a snippet of the edited file:
|
||||||
|
131→ },
|
||||||
|
132→ resolve: {
|
||||||
|
133→ extensions: ['.mjs', '.ts', '.tsx', '.js', '.jsx']
|
||||||
|
134→ },
|
||||||
|
135→ devServer: {
|
||||||
|
136→ // 约束 A:沙箱只开放一个代理端口,所有 builder(webpack/vite/electron)统一 3015
|
||||||
|
137→ port: 3015,
|
||||||
|
138→ host: '0.0.0.0',
|
||||||
|
139→ allowedHosts: 'all',
|
||||||
|
140→ hot: true,
|
||||||
|
141→ static: {
|
||||||
|
142→ directory: path.join(__dirname, 'public'),
|
||||||
|
143→ publicPath: '/',
|
||||||
|
144→ },
|
||||||
|
145→ historyApiFallback: {
|
||||||
|
146→ index: '/index.html',
|
||||||
|
147→ rewrites: [
|
||||||
|
148→ { from: /^\/_p\/\d+\//, to: '/index.html' }
|
||||||
|
149→ ]
|
||||||
|
150→ },
|
||||||
|
151→ devMiddleware: {
|
||||||
|
152→ writeToDisk: true,
|
||||||
|
153→ },
|
||||||
|
154→ },
|
||||||
|
155→ plugins: [
|
||||||
|
156→ new HtmlWebpackPlugin({
|
||||||
|
157→ template: './index.html',
|
||||||
|
158→ inject: 'body',
|
||||||
|
159→ minify: !isDev ? {
|
||||||
1
.microcompact/mpm69ugu/functions.Bash:58.txt
Normal file
1
.microcompact/mpm69ugu/functions.Bash:58.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":"[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production\n\n> procurement-planning@1.0.0 dev /home/project\n> webpack serve -- --port 3015\n\n[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production\n<i> [webpack-dev-server] Project is running at:\n<i> [webpack-dev-server] Loopback: http://localhost:3015/\n<i> [webpack-dev-server] Content not from webpack is served from '/home/project/public' directory\n<i> [webpack-dev-server] 404s will fallback to '/index.html'\n"}
|
||||||
1
.microcompact/mpm69uj3/functions.Bash:59.txt
Normal file
1
.microcompact/mpm69uj3/functions.Bash:59.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":""}
|
||||||
1
.microcompact/mpm69ulx/functions.Bash:60.txt
Normal file
1
.microcompact/mpm69ulx/functions.Bash:60.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":""}
|
||||||
16
.microcompact/mpm69uo3/functions.Edit:61.txt
Normal file
16
.microcompact/mpm69uo3/functions.Edit:61.txt
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
The file /home/project/webpack.config.js has been updated. Made 1 replacement.
|
||||||
|
|
||||||
|
Here's the result of running `cat -n` on a snippet of the edited file:
|
||||||
|
137→ port: 3015,
|
||||||
|
138→ host: '0.0.0.0',
|
||||||
|
139→ allowedHosts: 'all',
|
||||||
|
140→ hot: true,
|
||||||
|
141→ static: {
|
||||||
|
142→ directory: path.join(__dirname, 'dist'),
|
||||||
|
143→ publicPath: '/',
|
||||||
|
144→ },
|
||||||
|
145→ historyApiFallback: {
|
||||||
|
146→ index: '/index.html',
|
||||||
|
147→ rewrites: [
|
||||||
|
148→ { from: /^\/_p\/\d+\//, to: '/index.html' }
|
||||||
|
149→ ]
|
||||||
1
.microcompact/mpm69ure/functions.Bash:63.txt
Normal file
1
.microcompact/mpm69ure/functions.Bash:63.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":"[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production\n\n> procurement-planning@1.0.0 typecheck /home/project\n> tsc --noEmit\n\n[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production"}
|
||||||
1
.microcompact/mpm69uti/functions.Bash:65.txt
Normal file
1
.microcompact/mpm69uti/functions.Bash:65.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":"[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production\n\n> procurement-planning@1.0.0 typecheck /home/project\n> tsc --noEmit\n\n[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production"}
|
||||||
1
.microcompact/mpm69uvh/functions.Bash:65.txt
Normal file
1
.microcompact/mpm69uvh/functions.Bash:65.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":"[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production\n\n> procurement-planning@1.0.0 typecheck /home/project\n> tsc --noEmit\n\n[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production"}
|
||||||
19
.microcompact/mpmithzp/call_dbd90df7f25e4c038b26bc7f.txt
Normal file
19
.microcompact/mpmithzp/call_dbd90df7f25e4c038b26bc7f.txt
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
The file /home/project/webpack.config.js has been updated. Made 1 replacement.
|
||||||
|
|
||||||
|
Here's the result of running `cat -n` on a snippet of the edited file:
|
||||||
|
8→
|
||||||
|
9→ return {
|
||||||
|
10→ mode: isDev ? 'development' : 'production',
|
||||||
|
11→ entry: './src/index.tsx',
|
||||||
|
12→ output: {
|
||||||
|
13→ path: path.resolve(__dirname, 'dist'),
|
||||||
|
14→ filename: '[name].js',
|
||||||
|
15→ chunkFilename: '[name].chunk.js',
|
||||||
|
16→ publicPath: '/',
|
||||||
|
17→ clean: true,
|
||||||
|
18→ },
|
||||||
|
19→ optimization: {
|
||||||
|
20→ minimize: !isDev,
|
||||||
|
21→ minimizer: [
|
||||||
|
22→ new TerserPlugin({
|
||||||
|
23→ terserOptions: {
|
||||||
1
.microcompact/mpmiti1r/call_a7d6704dc75044f2bb0e3b21.txt
Normal file
1
.microcompact/mpmiti1r/call_a7d6704dc75044f2bb0e3b21.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":""}
|
||||||
1
.microcompact/mpmiti3q/call_0b1219b5e4bb4b819fb4fd7a.txt
Normal file
1
.microcompact/mpmiti3q/call_0b1219b5e4bb4b819fb4fd7a.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":"[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production\n\n> procurement-planning@1.0.0 dev /home/project\n> webpack serve -- --port 3015\n\n[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production\n<i> [webpack-dev-server] Project is running at:\n<i> [webpack-dev-server] Loopback: http://localhost:3015/\n<i> [webpack-dev-server] Content not from webpack is served from '/home/project/public' directory\n<i> [webpack-dev-server] 404s will fallback to '/index.html'\n"}
|
||||||
1
.microcompact/mpmiti5i/call_71a86a3d4aa74dc096d82402.txt
Normal file
1
.microcompact/mpmiti5i/call_71a86a3d4aa74dc096d82402.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"success":true,"message":"Cloud 已开通;页面中通过以下方式获取baseUrl: function getSupabaseUrl(): string {\n return `${(window as any).MEOO_CONFIG?.meoo_app_access_url || location.origin}/sb-api`;\n},anonKey: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJvbGUiOiJhbm9uIiwiaWF0IjoxNzc5MzM2NjUyLCJleHAiOjEzMjg5OTc2NjUyfQ.NQrukIeqa-IEBtFEf8YOsVK4vxbi6kQqIi-eyRsb2pA","data":{"enabled":true,"instance":{"id":120671,"appId":"uf9sduwnc7f2","status":2,"name":"077f3cdf-5086-454f-bfeb-2740fc01b755","description":"纺织采购计划管理系统","sbInfo":{"vpcHost":"","anonKey":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJvbGUiOiJhbm9uIiwiaWF0IjoxNzc5MzM2NjUyLCJleHAiOjEzMjg5OTc2NjUyfQ.NQrukIeqa-IEBtFEf8YOsVK4vxbi6kQqIi-eyRsb2pA","serviceKey":""},"pgInfo":{"dbInstanceStorage":null},"appInfos":[{"id":"uf9sduwnc7f2","description":""}],"hasEdgeFunctions":true,"needResume":null},"errCode":null,"errMessage":null}}
|
||||||
1
.microcompact/mpmiti7h/call_18821fda1c934ddfa115e957.txt
Normal file
1
.microcompact/mpmiti7h/call_18821fda1c934ddfa115e957.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"results":[{"file":"src/hooks/useNetworkStatus.ts","matches":[{"line":61,"content":" await fetch(`${window.location.origin}/sb-api/health`, { "}]}],"pattern":"/sb-api/health|health.*check|healthCheck","path":"src","totalMatches":1}
|
||||||
1
.microcompact/mpmiti9m/call_22a133fc115245fd9672a518.txt
Normal file
1
.microcompact/mpmiti9m/call_22a133fc115245fd9672a518.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"results":[{"file":"src/supabase/client.ts","matches":[{"line":10,"content":" return `${(window as any).MEOO_CONFIG?.meoo_app_access_url || location.origin}/sb-api`;"}]}],"pattern":"MEOO_CONFIG|meoo_app_access_url","path":"src","totalMatches":1}
|
||||||
30
.microcompact/mpmitich/call_f9b326e7cd5e4b75922cc608.txt
Normal file
30
.microcompact/mpmitich/call_f9b326e7cd5e4b75922cc608.txt
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
The file /home/project/src/hooks/useNetworkStatus.ts has been updated. Made 1 replacement.
|
||||||
|
|
||||||
|
Here's the result of running `cat -n` on a snippet of the edited file:
|
||||||
|
50→
|
||||||
|
51→ window.addEventListener('online', handleOnline);
|
||||||
|
52→ window.addEventListener('offline', handleOffline);
|
||||||
|
53→
|
||||||
|
54→ // 检测慢网络(使用轻量级 DNS 预解析代替 HTTP 请求)
|
||||||
|
55→ const checkConnectionSpeed = async () => {
|
||||||
|
56→ if (!navigator.onLine) return;
|
||||||
|
57→
|
||||||
|
58→ // 仅通过 navigator.connection API 检测(如果可用)
|
||||||
|
59→ const connection = (navigator as any).connection;
|
||||||
|
60→ if (connection) {
|
||||||
|
61→ setState(prev => ({
|
||||||
|
62→ ...prev,
|
||||||
|
63→ isSlowConnection: connection.effectiveType === 'slow-2g' || connection.effectiveType === '2g'
|
||||||
|
64→ }));
|
||||||
|
65→ }
|
||||||
|
66→ // 如果不支持 Connection API,则不进行主动探测,避免产生大量 502 错误
|
||||||
|
67→ };
|
||||||
|
68→
|
||||||
|
69→ // 每60秒检测一次网络质量(降低频率)
|
||||||
|
70→ const interval = setInterval(checkConnectionSpeed, 60000);
|
||||||
|
71→ checkConnectionSpeed();
|
||||||
|
72→
|
||||||
|
73→ return () => {
|
||||||
|
74→ window.removeEventListener('online', handleOnline);
|
||||||
|
75→ window.removeEventListener('offline', handleOffline);
|
||||||
|
76→ clearInterval(interval);
|
||||||
1
.microcompact/mpmitifp/call_a1548098a56e417d9d442e55.txt
Normal file
1
.microcompact/mpmitifp/call_a1548098a56e417d9d442e55.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":"[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production\n\n> procurement-planning@1.0.0 typecheck /home/project\n> tsc --noEmit\n\n[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production"}
|
||||||
1
.microcompact/mpmitihq/call_590a76c4fdb945cba1ba5df4.txt
Normal file
1
.microcompact/mpmitihq/call_590a76c4fdb945cba1ba5df4.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":-1,"output":"","stderr":"🛡️ 命令已被拦截 - 该命令可能会破坏 sandbox 运行环境\n\n❌ 拦截原因:\n以下命令可能会杀掉 sandbox 内置服务进程(SDK server、log server、dev server、cors proxy 等),导致沙箱崩溃:\n - `pkill -f \"webpack serve\" 2>/dev/null` 中的模式 \"webpack serve\" 过于宽泛\n\n💡 建议:\n请使用更精确的方式停止目标进程:\n 1. 通过端口查找:`lsof -i :PORT -t | xargs kill` 只杀占用特定端口的进程\n 2. 通过精确路径匹配:`pkill -f \"/home/project/.*serve\"` 只杀用户项目内的进程\n 3. 通过 PID:先 `ps aux | grep xxx` 确认目标 PID,再 `kill PID`\n 4. 如果要停止 dev server,直接运行新的 `npm run dev` 即可(会自动替换旧进程)"}
|
||||||
1
.microcompact/mpmitijh/call_703aaa645073487da205bea3.txt
Normal file
1
.microcompact/mpmitijh/call_703aaa645073487da205bea3.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":"25521"}
|
||||||
1
.microcompact/mpmitilo/call_2ba9f4b08bc74d3491ea8e8d.txt
Normal file
1
.microcompact/mpmitilo/call_2ba9f4b08bc74d3491ea8e8d.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"results":[],"pattern":"sb-api/health|/health","path":"src","totalMatches":0}
|
||||||
1
.microcompact/mpmitint/call_10b6713135b547e3a28f5f07.txt
Normal file
1
.microcompact/mpmitint/call_10b6713135b547e3a28f5f07.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"results":[{"file":"src/hooks/useNetworkStatus.ts","matches":[{"line":55,"content":" const checkConnectionSpeed = async () => {"},{"line":70,"content":" const interval = setInterval(checkConnectionSpeed, 60000);"},{"line":71,"content":" checkConnectionSpeed();"}]}],"pattern":"checkConnectionSpeed|setInterval","path":"src/hooks/useNetworkStatus.ts","totalMatches":3}
|
||||||
1
.microcompact/mpmitipp/call_2df0758bd69741ef80829471.txt
Normal file
1
.microcompact/mpmitipp/call_2df0758bd69741ef80829471.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"files":["src/App.tsx","src/pages/MemberManage.tsx","src/pages/RoleSelectPage.tsx","src/pages/RegisterPage.tsx","src/pages/ImportPlanPage.tsx","src/pages/LoginPage.tsx","src/components/ErrorBoundary.tsx","src/components/NotificationCenter.tsx","src/contexts/AuthContext.tsx","src/pages/purchaser/FinishedWarehouse.tsx","src/pages/purchaser/FactoryManage.tsx","src/pages/purchaser/WarehouseManage.tsx","src/pages/purchaser/Dashboard.tsx","src/pages/purchaser/AccountsPayable.tsx","src/pages/purchaser/NewWashingPlan.tsx","src/pages/purchaser/NewPlan.tsx","src/pages/purchaser/PlanOverview.tsx","src/pages/textile/PaymentPending.tsx","src/pages/textile/PlanOverview.tsx","src/pages/textile/Dashboard.tsx","src/pages/textile/YarnWarehouse.tsx","src/pages/textile/FabricWarehouse.tsx","src/pages/washing/CompletedFabric.tsx","src/pages/washing/FinishedWarehouse.tsx","src/pages/washing/PaymentPending.tsx","src/pages/washing/PlanOverview.tsx","src/pages/washing/Dashboard.tsx","src/pages/washing/PendingFabric.tsx"],"pattern":"AuthContext|useAuth|navigate.*login|window\\.location.*reload","path":"src","totalFiles":28}
|
||||||
1
.microcompact/mpmitirh/call_ac2e5bd57e12414d95ece967.txt
Normal file
1
.microcompact/mpmitirh/call_ac2e5bd57e12414d95ece967.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"files":["src/supabase/client.ts","src/hooks/useNetworkStatus.ts"],"pattern":"sb-api|supabase.*error|502|Bad Gateway","path":"src","totalFiles":2}
|
||||||
1
.microcompact/mpmitix7/call_797b57a3f9a440f89bee346f.txt
Normal file
1
.microcompact/mpmitix7/call_797b57a3f9a440f89bee346f.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"results":[{"file":"src/pages/MemberManage.tsx","matches":[{"line":170,"content":" window.location.reload();"}]},{"file":"src/components/ErrorBoundary.tsx","matches":[{"line":61,"content":" window.location.reload();"}]}],"pattern":"window\\.location\\.reload|location\\.reload|SIGNED_OUT.*navigate","path":"src","totalMatches":2}
|
||||||
1
.microcompact/mpmitizq/call_59129bb5991845198b5228e3.txt
Normal file
1
.microcompact/mpmitizq/call_59129bb5991845198b5228e3.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"results":[{"file":"src/pages/MemberManage.tsx","matches":[{"line":158,"content":" const { error: passwordError } = await supabase.auth.updateUser({"},{"line":226,"content":" const { data: authData, error: signUpError } = await supabase.auth.signUp({"}]},{"file":"src/contexts/AuthContext.tsx","matches":[{"line":38,"content":" const { data: { session } } = await supabase.auth.getSession();"},{"line":69,"content":" const { data: { subscription } } = supabase.auth.onAuthStateChange(async (event, session) => {"},{"line":96,"content":" } else if (event === 'SIGNED_OUT') {"},{"line":110,"content":" const { error } = await supabase.auth.signInWithPassword({"},{"line":140,"content":" const { data: authData, error: authError } = await supabase.auth.signUp({"},{"line":191,"content":" await supabase.auth.signOut();"}]},{"file":"src/pages/textile/PlanOverview.tsx","matches":[{"line":355,"content":" const session = (await supabase.auth.getSession()).data.session;"}]}],"pattern":"supabase\\.auth|onAuthStateChange|SIGNED_OUT","path":"src","totalMatches":71}
|
||||||
1
.microcompact/mpmitj1i/call_145320e6530847b8b1912fa7.txt
Normal file
1
.microcompact/mpmitj1i/call_145320e6530847b8b1912fa7.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"results":[{"file":"src/pages/LoginPage.tsx","matches":[{"line":1,"content":"import React, { useState, useEffect } from 'react';"},{"line":223,"content":" useEffect(() => {"},{"line":277,"content":" const navigate = useNavigate();"},{"line":293,"content":" navigate('/role-select');"},{"line":305,"content":" navigate('/role-select');"}]}],"pattern":"useEffect|navigate|isLoggedIn","path":"src/pages/LoginPage.tsx","totalMatches":54}
|
||||||
8
.microcompact/mpmitj4k/call_02fd31f4ed0044c8b6aace6d.txt
Normal file
8
.microcompact/mpmitj4k/call_02fd31f4ed0044c8b6aace6d.txt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
The file /home/project/src/hooks/useNetworkStatus.ts has been updated using line-based fuzzy matching (handled indentation). Made 1 replacement.
|
||||||
|
|
||||||
|
Here's the result of running `cat -n` on a snippet of the edited file:
|
||||||
|
229→ }, []);
|
||||||
|
230→
|
||||||
|
231→ return { optimisticUpdate };
|
||||||
|
232→}
|
||||||
|
233→
|
||||||
1
.microcompact/mpmitj7i/call_74e7270b2b074587b8d2e335.txt
Normal file
1
.microcompact/mpmitj7i/call_74e7270b2b074587b8d2e335.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"results":[{"file":"src/hooks/useNetworkStatus.ts","matches":[{"line":64,"content":" // 注意:不再发起 /sb-api/health 请求,该接口在当前环境返回 502"}]}],"pattern":"fetch|sb-api|health","path":"src/hooks/useNetworkStatus.ts","totalMatches":1}
|
||||||
1
.microcompact/mpmitj94/call_23756f7f84a24c63a655e8d6.txt
Normal file
1
.microcompact/mpmitj94/call_23756f7f84a24c63a655e8d6.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":"[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production\n\n> procurement-planning@1.0.0 typecheck /home/project\n> tsc --noEmit\n\n[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production"}
|
||||||
1
.microcompact/mpmitjb1/call_210a7e1c8a35461885b0f06e.txt
Normal file
1
.microcompact/mpmitjb1/call_210a7e1c8a35461885b0f06e.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"files":["src/pages/RegisterPage.tsx","src/pages/LoginPage.tsx","src/pages/purchaser/WarehouseManage.tsx","src/pages/purchaser/FinishedWarehouse.tsx","src/pages/purchaser/PlanOverview.tsx","src/pages/purchaser/Dashboard.tsx","src/pages/purchaser/NewWashingPlan.tsx","src/pages/purchaser/AccountsPayable.tsx","src/pages/washing/PaymentPending.tsx","src/pages/washing/PlanOverview.tsx","src/pages/washing/Dashboard.tsx","src/pages/textile/PaymentPending.tsx","src/pages/washing/FinishedWarehouse.tsx","src/pages/textile/PlanOverview.tsx","src/pages/textile/YarnWarehouse.tsx","src/pages/textile/Dashboard.tsx","src/pages/textile/FabricWarehouse.tsx"],"pattern":"sm:hidden|flex-col|whitespace-nowrap|truncate","path":"src/pages","totalFiles":17}
|
||||||
25
.microcompact/mpmjrpjl/functions.Edit:17.txt
Normal file
25
.microcompact/mpmjrpjl/functions.Edit:17.txt
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
The file /home/project/src/components/warehouse/ProductForm.tsx has been updated. Made 1 replacement.
|
||||||
|
|
||||||
|
Here's the result of running `cat -n` on a snippet of the edited file:
|
||||||
|
5→import { supabase } from '../../supabase/client';
|
||||||
|
6→import { decode } from 'base64-arraybuffer';
|
||||||
|
7→import type { Product, ProductYarnRatio } from '../../types';
|
||||||
|
8→
|
||||||
|
9→interface ProductFormData {
|
||||||
|
10→ product_name: string;
|
||||||
|
11→ weight: string;
|
||||||
|
12→ warp_weight: string;
|
||||||
|
13→ weft_weight: string;
|
||||||
|
14→ total_weight: string;
|
||||||
|
15→ color: string;
|
||||||
|
16→ fabric_code: string;
|
||||||
|
17→ color_code: string;
|
||||||
|
18→ production_price: string;
|
||||||
|
19→ yarn_ratios: { yarn_name: string; ratio: number; yarn_type: 'warp' | 'weft' | 'all' }[];
|
||||||
|
20→ image_url?: string;
|
||||||
|
21→}
|
||||||
|
22→
|
||||||
|
23→interface ProductFormProps {
|
||||||
|
24→ isOpen: boolean;
|
||||||
|
25→ onClose: () => void;
|
||||||
|
26→ onSave: () => void;
|
||||||
18
.microcompact/mpmjrpl6/functions.Edit:18.txt
Normal file
18
.microcompact/mpmjrpl6/functions.Edit:18.txt
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
The file /home/project/src/components/warehouse/ProductForm.tsx has been updated. Made 1 replacement.
|
||||||
|
|
||||||
|
Here's the result of running `cat -n` on a snippet of the edited file:
|
||||||
|
286→ const triggerFileSelect = () => {
|
||||||
|
287→ fileInputRef.current?.click();
|
||||||
|
288→ };
|
||||||
|
289→
|
||||||
|
290→ const addYarnRatio = () => {
|
||||||
|
291→ setFormData(prev => ({
|
||||||
|
292→ ...prev,
|
||||||
|
293→ yarn_ratios: [...prev.yarn_ratios, { yarn_name: '', ratio: 100, yarn_type: 'all' }]
|
||||||
|
294→ }));
|
||||||
|
295→ };
|
||||||
|
296→
|
||||||
|
297→ const removeYarnRatio = (index: number) => {
|
||||||
|
298→ setFormData(prev => ({
|
||||||
|
299→ ...prev,
|
||||||
|
300→ yarn_ratios: prev.yarn_ratios.filter((_, i) => i !== index)
|
||||||
19
.microcompact/mpmjrpn3/functions.Edit:20.txt
Normal file
19
.microcompact/mpmjrpn3/functions.Edit:20.txt
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
The file /home/project/src/components/warehouse/ProductForm.tsx has been updated. Made 1 replacement.
|
||||||
|
|
||||||
|
Here's the result of running `cat -n` on a snippet of the edited file:
|
||||||
|
300→ yarn_ratios: prev.yarn_ratios.filter((_, i) => i !== index)
|
||||||
|
301→ }));
|
||||||
|
302→ };
|
||||||
|
303→
|
||||||
|
304→ const updateYarnRatio = (index: number, field: 'yarn_name' | 'ratio' | 'yarn_type', value: string | number) => {
|
||||||
|
305→ setFormData(prev => {
|
||||||
|
306→ const newRatios = [...prev.yarn_ratios];
|
||||||
|
307→ newRatios[index] = { ...newRatios[index], [field]: value };
|
||||||
|
308→ return { ...prev, yarn_ratios: newRatios };
|
||||||
|
309→ });
|
||||||
|
310→ };
|
||||||
|
311→
|
||||||
|
312→ if (!isOpen) return null;
|
||||||
|
313→
|
||||||
|
314→ return (
|
||||||
|
315→ <motion.div
|
||||||
31
.microcompact/mpmjrpp8/functions.Edit:20.txt
Normal file
31
.microcompact/mpmjrpp8/functions.Edit:20.txt
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
The file /home/project/src/components/warehouse/ProductForm.tsx has been updated. Made 1 replacement.
|
||||||
|
|
||||||
|
Here's the result of running `cat -n` on a snippet of the edited file:
|
||||||
|
489→ />
|
||||||
|
490→ </div>
|
||||||
|
491→ </div>
|
||||||
|
492→
|
||||||
|
493→ {/* 克重细分:经线克重、纬线克重、总克重 */}
|
||||||
|
494→ <div>
|
||||||
|
495→ <label className="flex items-center gap-2 text-sm font-medium text-gray-700 mb-2">
|
||||||
|
496→ <Hash className="w-4 h-4 text-gray-400" />
|
||||||
|
497→ 克重(m/g)
|
||||||
|
498→ </label>
|
||||||
|
499→ <div className={`grid ${isMobile ? 'grid-cols-1 gap-3' : 'grid-cols-3 gap-3'}`}>
|
||||||
|
500→ <div>
|
||||||
|
501→ <label className="text-xs text-gray-500 mb-1 block">经线克重</label>
|
||||||
|
502→ <input
|
||||||
|
503→ type="number"
|
||||||
|
|
||||||
|
<system-reminder>
|
||||||
|
⚠️ REPEATED FILE MODIFICATION WARNING: You have modified "/home/project/src/components/warehouse/ProductForm.tsx" 5 times (Write + Edit combined)!
|
||||||
|
|
||||||
|
You keep rewriting or editing the same file. This is a strong sign your current approach is not working.
|
||||||
|
|
||||||
|
🛑 REQUIRED ACTIONS:
|
||||||
|
1. STOP and think: why do you keep modifying the same file?
|
||||||
|
2. If the content keeps being wrong, try a fundamentally different approach
|
||||||
|
3. If you're making incremental fixes, consolidate them into ONE write instead of many small changes
|
||||||
|
|
||||||
|
⏰ 3 more attempt(s) before modifications to this file are AUTOMATICALLY BLOCKED.
|
||||||
|
</system-reminder>
|
||||||
1
.microcompact/mpmowvpp/functions.Bash:0.txt
Normal file
1
.microcompact/mpmowvpp/functions.Bash:0.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"exitCode":0,"output":"[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production\n\n> procurement-planning@1.0.0 typecheck /home/project\n> tsc --noEmit\n\n[OpenTelemetry] Trace 已启动,serviceName=oneday-sandbox-sdk-pro version=v0.1.0 env=production"}
|
||||||
46
.microcompact/mpmowvvy/functions.Edit:3.txt
Normal file
46
.microcompact/mpmowvvy/functions.Edit:3.txt
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
The file /home/project/AGENTS.md has been updated. Made 1 replacement.
|
||||||
|
|
||||||
|
Here's the result of running `cat -n` on a snippet of the edited file:
|
||||||
|
431→- `src/pages/textile/PaymentPending.tsx` - 动画和移动端适配
|
||||||
|
432→- `src/components/ProcessFlow.tsx` - 动画优化和移动端输入框
|
||||||
|
433→- `src/styles/index.css` - 移动端触控优化CSS
|
||||||
|
434→
|
||||||
|
435→## 更新日志
|
||||||
|
436→
|
||||||
|
437→### 2025-05-26
|
||||||
|
438→- **计划详情字段完善**
|
||||||
|
439→ - 采购商和纺织厂计划总览页面统一显示所有技术字段
|
||||||
|
440→ - 新增显示字段:克重、经线克重、纬线克重、总克重、生产采购价、产品图片、纱线配比、每米纱用量
|
||||||
|
441→ - 新增时间字段:创建时间、计划开始时间
|
||||||
|
442→ - 新增备注字段显示
|
||||||
|
443→ - 纺织厂页面隐藏敏感信息(成品名称、颜色、色号)以保护采购商数据
|
||||||
|
444→- **移动端 UI 优化**
|
||||||
|
445→ - 计划卡片移动端适配:缩小图片尺寸(64x64)、字体调整为 text-xs
|
||||||
|
446→ - 字段标签简化(如"计划编号:"→"编号:")
|
||||||
|
447→ - 网格布局优化,避免内容溢出
|
||||||
|
448→ - 所有工作台退出按钮在移动端变为图标形式(LogOut图标)
|
||||||
|
449→- **拒绝计划功能增强**
|
||||||
|
450→ - 纺织厂拒绝计划后立即显示灰色卡片和"已拒绝"标签
|
||||||
|
451→ - 被拒绝的计划自动折叠
|
||||||
|
452→ - 被拒绝的计划从 Dashboard "最近计划"中消失
|
||||||
|
453→ - 使用 localStorage 持久化拒绝状态,跨页面保持同步
|
||||||
|
454→
|
||||||
|
455→### 2025-05-25
|
||||||
|
456→- **产品图片上传功能**
|
||||||
|
457→ - 为采购商原坯布仓库产品管理添加图片上传功能
|
||||||
|
458→ - 支持 JPG、PNG 格式图片上传,最大 5MB
|
||||||
|
459→ - 产品列表展示缩略图,无图片时显示默认占位图标
|
||||||
|
460→ - 产品表单支持图片预览、重新上传和删除功能
|
||||||
|
461→ - 使用 Supabase Storage 存储图片文件
|
||||||
|
462→- **数据库变更**
|
||||||
|
463→ - `products` 表新增 `image_url` 字段
|
||||||
|
464→ - 创建 `product_images` 公共存储桶
|
||||||
|
465→ - 添加存储桶 RLS 策略
|
||||||
|
466→- **UI 优化**
|
||||||
|
467→ - 纺织厂工作台"原坯布仓库"更名为"已生产坯布"
|
||||||
|
468→ - 更新首页快捷操作按钮标签
|
||||||
|
469→
|
||||||
|
470→## Bug 修复记录
|
||||||
|
471→
|
||||||
|
472→### 2025-05-25 TypeScript 类型兼容性问题
|
||||||
|
473→
|
||||||
134
.microcompact/mpmowvxw/functions.Edit:4.txt
Normal file
134
.microcompact/mpmowvxw/functions.Edit:4.txt
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
The file /home/project/AGENTS.md has been updated. Made 1 replacement.
|
||||||
|
|
||||||
|
Here's the result of running `cat -n` on a snippet of the edited file:
|
||||||
|
466→- **UI 优化**
|
||||||
|
467→ - 纺织厂工作台"原坯布仓库"更名为"已生产坯布"
|
||||||
|
468→ - 更新首页快捷操作按钮标签
|
||||||
|
469→
|
||||||
|
470→## Bug 修复记录
|
||||||
|
471→
|
||||||
|
472→### 2025-05-26 计划字段显示不一致问题
|
||||||
|
473→
|
||||||
|
474→#### 问题描述
|
||||||
|
475→采购商和纺织厂的计划总览页面显示字段不一致,纺织厂缺少克重、纱线配比、生产采购价等关键字段。
|
||||||
|
476→
|
||||||
|
477→#### 根本原因
|
||||||
|
478→- `usePlanData.ts` Hook 未查询 `products` 表关联数据
|
||||||
|
479→- `PlanGroup.tsx` 组件未展示 weight、yarn_ratios、production_price 等字段
|
||||||
|
480→- 纺织厂 `PlanOverview.tsx` 缺少展开详情视图
|
||||||
|
481→
|
||||||
|
482→#### 修复方案
|
||||||
|
483→1. **数据层**:在 `usePlanData.ts` 中添加 products 表查询,获取 weight、image_url 等字段
|
||||||
|
484→2. **组件层**:在 `PlanGroup.tsx` 中添加完整字段展示,包括:
|
||||||
|
485→ - 克重信息(经线/纬线/总克重)
|
||||||
|
486→ - 纱线配比列表
|
||||||
|
487→ - 生产采购价
|
||||||
|
488→ - 产品图片
|
||||||
|
489→ - 创建时间、计划开始时间、备注
|
||||||
|
490→3. **页面层**:纺织厂 `PlanOverview.tsx` 添加展开详情功能,与采购商保持一致
|
||||||
|
491→
|
||||||
|
492→#### 代码变更
|
||||||
|
493→- `src/hooks/usePlanData.ts` - 添加 products 表关联查询
|
||||||
|
494→- `src/components/plans/PlanGroup.tsx` - 扩展字段展示
|
||||||
|
495→- `src/pages/textile/PlanOverview.tsx` - 添加展开详情视图
|
||||||
|
496→- `src/pages/purchaser/PlanOverview.tsx` - 同步优化字段展示
|
||||||
|
497→
|
||||||
|
498→#### 经验教训
|
||||||
|
499→- **数据一致性**:同一业务对象在不同页面的展示应保持一致,仅根据角色权限控制敏感字段
|
||||||
|
500→- **Hook 复用**:通过统一的数据获取 Hook 确保各页面数据一致性
|
||||||
|
501→- **类型安全**:扩展类型定义时注意与数据库生成的类型保持兼容
|
||||||
|
502→
|
||||||
|
503→---
|
||||||
|
504→
|
||||||
|
505→### 2025-05-26 移动端退出按钮适配问题
|
||||||
|
506→
|
||||||
|
507→#### 问题描述
|
||||||
|
508→工作台右上角退出按钮在移动端显示为"退出登录"文字,占用过多空间,与整体移动端设计不协调。
|
||||||
|
509→
|
||||||
|
510→#### 根本原因
|
||||||
|
511→- 退出按钮未做响应式适配
|
||||||
|
512→- 移动端空间宝贵,文字按钮过于拥挤
|
||||||
|
513→
|
||||||
|
514→#### 修复方案
|
||||||
|
515→使用响应式设计,桌面端显示"退出登录"文字,移动端仅显示 LogOut 图标:
|
||||||
|
516→```tsx
|
||||||
|
517→<button className="flex items-center gap-2 ...">
|
||||||
|
518→ <span className="hidden sm:inline">退出登录</span>
|
||||||
|
519→ <LogOut className="w-5 h-5" />
|
||||||
|
520→</button>
|
||||||
|
521→```
|
||||||
|
522→
|
||||||
|
523→#### 代码变更
|
||||||
|
524→- `src/pages/purchaser/Dashboard.tsx` - 退出按钮响应式优化
|
||||||
|
525→- `src/pages/textile/Dashboard.tsx` - 退出按钮响应式优化
|
||||||
|
526→- `src/pages/washing/Dashboard.tsx` - 退出按钮响应式优化
|
||||||
|
527→
|
||||||
|
528→---
|
||||||
|
529→
|
||||||
|
530→### 2025-05-26 拒绝计划状态同步问题
|
||||||
|
531→
|
||||||
|
532→#### 问题描述
|
||||||
|
533→纺织厂拒绝计划后,计划卡片未立即更新视觉状态,且仍显示在 Dashboard "最近计划"中。
|
||||||
|
534→
|
||||||
|
535→#### 根本原因
|
||||||
|
536→- 拒绝操作仅更新数据库,未更新本地状态
|
||||||
|
537→- Dashboard 和 PlanOverview 之间没有共享拒绝状态
|
||||||
|
538→- 页面刷新后拒绝状态丢失
|
||||||
|
539→
|
||||||
|
540→#### 修复方案
|
||||||
|
541→1. **本地状态管理**:在 `PlanOverview.tsx` 中使用 `Set<string>` 跟踪被拒绝的计划ID
|
||||||
|
542→2. **持久化存储**:使用 localStorage 存储拒绝状态,键名为 `textile_rejected_plans`
|
||||||
|
543→3. **跨页面同步**:Dashboard 从 localStorage 读取拒绝状态并过滤最近计划
|
||||||
|
544→4. **视觉反馈**:被拒绝的计划显示灰色背景、"已拒绝"标签,并自动折叠
|
||||||
|
545→
|
||||||
|
546→#### 代码变更
|
||||||
|
547→- `src/pages/textile/PlanOverview.tsx` - 添加 rejectedPlans 状态和 localStorage 持久化
|
||||||
|
548→- `src/pages/textile/Dashboard.tsx` - 添加 rejectedPlanIds 状态,过滤最近计划
|
||||||
|
549→
|
||||||
|
550→#### 关键代码
|
||||||
|
551→```typescript
|
||||||
|
552→// PlanOverview.tsx - 拒绝时持久化
|
||||||
|
553→const handleRejectStep = async (step: PlanProcessStep) => {
|
||||||
|
554→ // ... 提交拒绝原因到数据库 ...
|
||||||
|
555→
|
||||||
|
556→ // 更新本地状态
|
||||||
|
557→ setRejectedPlans(prev => new Set([...prev, step.plan_id]));
|
||||||
|
558→
|
||||||
|
559→ // 持久化到 localStorage
|
||||||
|
560→ const stored = localStorage.getItem('textile_rejected_plans');
|
||||||
|
561→ const rejectedArray = stored ? JSON.parse(stored) : [];
|
||||||
|
562→ if (!rejectedArray.includes(step.plan_id)) {
|
||||||
|
563→ rejectedArray.push(step.plan_id);
|
||||||
|
564→ localStorage.setItem('textile_rejected_plans', JSON.stringify(rejectedArray));
|
||||||
|
565→ }
|
||||||
|
566→};
|
||||||
|
567→
|
||||||
|
568→// Dashboard.tsx - 读取并过滤
|
||||||
|
569→useEffect(() => {
|
||||||
|
570→ // ... 获取计划列表 ...
|
||||||
|
571→
|
||||||
|
572→ // 从 localStorage 读取拒绝状态
|
||||||
|
573→ const stored = localStorage.getItem('textile_rejected_plans');
|
||||||
|
574→ if (stored) {
|
||||||
|
575→ setRejectedPlanIds(new Set(JSON.parse(stored)));
|
||||||
|
576→ }
|
||||||
|
577→}, []);
|
||||||
|
578→
|
||||||
|
579→// 过滤最近计划
|
||||||
|
580→const filteredPlans = plans.filter(p => !rejectedPlanIds.has(p.id));
|
||||||
|
581→const recentPlans = filteredPlans.slice(0, 5);
|
||||||
|
582→```
|
||||||
|
583→
|
||||||
|
584→#### 经验教训
|
||||||
|
585→- **状态共享**:跨页面状态可考虑使用 localStorage、URL 参数或全局状态管理
|
||||||
|
586→- **乐观更新**:用户操作后应立即更新 UI,再同步后端状态
|
||||||
|
587→- **数据过滤**:Dashboard 展示的数据应根据业务规则进行过滤,而非直接展示原始数据
|
||||||
|
588→
|
||||||
|
589→---
|
||||||
|
590→
|
||||||
|
591→### 2025-05-25 TypeScript 类型兼容性问题
|
||||||
|
592→
|
||||||
|
593→#### 问题描述
|
||||||
|
594→添加图片功能后出现类型错误:
|
||||||
|
595→```
|
||||||
|
596→Types of property 'image_url' are incompatible.
|
||||||
58
.microcompact/mpmowvzq/functions.Edit:5.txt
Normal file
58
.microcompact/mpmowvzq/functions.Edit:5.txt
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
The file /home/project/README.md has been updated. Made 1 replacement.
|
||||||
|
|
||||||
|
Here's the result of running `cat -n` on a snippet of the edited file:
|
||||||
|
209→- Safari 14+
|
||||||
|
210→- Firefox 88+
|
||||||
|
211→- Edge 90+
|
||||||
|
212→
|
||||||
|
213→## 更新日志
|
||||||
|
214→
|
||||||
|
215→### 2025-05-26
|
||||||
|
216→- **计划详情字段完善**
|
||||||
|
217→ - 采购商和纺织厂计划总览页面统一显示所有技术字段
|
||||||
|
218→ - 新增显示字段:克重、经线克重、纬线克重、总克重、生产采购价、产品图片、纱线配比、每米纱用量
|
||||||
|
219→ - 新增时间字段:创建时间、计划开始时间
|
||||||
|
220→ - 新增备注字段显示
|
||||||
|
221→ - 纺织厂页面隐藏敏感信息(成品名称、颜色、色号)以保护采购商数据
|
||||||
|
222→- **移动端 UI 优化**
|
||||||
|
223→ - 计划卡片移动端适配:缩小图片尺寸(64x64)、字体调整为 text-xs
|
||||||
|
224→ - 字段标签简化(如"计划编号:"→"编号:")
|
||||||
|
225→ - 网格布局优化,避免内容溢出
|
||||||
|
226→ - 所有工作台退出按钮在移动端变为图标形式(LogOut图标)
|
||||||
|
227→- **拒绝计划功能增强**
|
||||||
|
228→ - 纺织厂拒绝计划后立即显示灰色卡片和"已拒绝"标签
|
||||||
|
229→ - 被拒绝的计划自动折叠
|
||||||
|
230→ - 被拒绝的计划从 Dashboard "最近计划"中消失
|
||||||
|
231→ - 使用 localStorage 持久化拒绝状态,跨页面保持同步
|
||||||
|
232→
|
||||||
|
233→### 2025-05-25
|
||||||
|
234→- **产品图片上传功能**
|
||||||
|
235→ - 为采购商原坯布仓库产品管理添加图片上传功能
|
||||||
|
236→ - 支持在产品创建和编辑时上传产品图片(JPG、PNG格式,最大5MB)
|
||||||
|
237→ - 产品列表展示40x40px缩略图,无图片时显示默认占位图标
|
||||||
|
238→ - 产品表单支持图片预览、重新上传和删除功能
|
||||||
|
239→ - 使用 Supabase Storage `product_images` 存储桶存储图片
|
||||||
|
240→ - 数据库 `products` 表新增 `image_url` 字段
|
||||||
|
241→- **UI 优化**
|
||||||
|
242→ - 纺织厂工作台"原坯布仓库"更名为"已生产坯布"(页面标题和首页快捷操作)
|
||||||
|
243→
|
||||||
|
244→### 2025-05-24
|
||||||
|
245→- **新增出入库历史功能**
|
||||||
|
246→ - 采购商原坯布仓库产品列表新增"出入库历史"按钮
|
||||||
|
247→ - 支持查看每个产品的完整库存变动记录(批号、匹数、米数、时间)
|
||||||
|
248→ - 桌面端和移动端均支持
|
||||||
|
249→- **类型系统优化**
|
||||||
|
250→ - 修复 `ProductInRecord` 类型定义,与数据库表结构保持一致
|
||||||
|
251→ - 区分数据库必需字段和前端展示扩展字段
|
||||||
|
252→- **新增组件**
|
||||||
|
253→ - `OutboundForm.tsx` - 出库表单组件
|
||||||
|
254→ - `PlanEditModal.tsx` - 计划编辑弹窗
|
||||||
|
255→ - `NotificationCenter.tsx` - 通知中心
|
||||||
|
256→ - `NetworkStatusBar.tsx` - 网络状态栏
|
||||||
|
257→ - `VirtualList.tsx` - 虚拟列表组件
|
||||||
|
258→ - `ErrorBoundary.tsx` - 错误边界组件
|
||||||
|
259→
|
||||||
|
260→### 2025-05-23
|
||||||
|
261→- 优化纺织厂工作台UI动画流畅度
|
||||||
|
262→- 全面适配移动端,优化触控体验
|
||||||
|
263→- 添加实时数据订阅,入库记录自动更新
|
||||||
1
.microcompact/mpmoww1h/functions.Grep:6.txt
Normal file
1
.microcompact/mpmoww1h/functions.Grep:6.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"files":["src/utils/helpers.ts","src/supabase/types.ts","src/pages/DemoDataSharing.tsx","src/utils/notifications.ts","src/__tests__/utils/helpers.test.ts","src/pages/washing/PendingFabric.tsx","src/pages/washing/CompletedFabric.tsx","src/pages/washing/PlanOverview.tsx","src/pages/washing/FinishedWarehouse.tsx","src/pages/purchaser/WarehouseManage.tsx","src/pages/purchaser/NewWashingPlan.tsx","src/pages/purchaser/PlanOverview.tsx","src/pages/purchaser/AccountsPayable.tsx","src/pages/purchaser/FinishedWarehouse.tsx","src/pages/purchaser/NewPlan.tsx","src/pages/purchaser/Dashboard.tsx","src/components/plans/PlanGroup.tsx","src/components/plans/PlanEditModal.tsx","src/components/warehouse/OutboundForm.tsx","src/components/warehouse/ProductForm.tsx","src/components/warehouse/ProductList.tsx","src/components/warehouse/InboundForm.tsx","src/components/warehouse/InboundRecords.tsx"],"pattern":"product_name|产品名称|成品名称","path":"src","totalFiles":23}
|
||||||
8
.todo/051e2fa2-d6c7-41df-86e8-d3292833c577/todos.json
Normal file
8
.todo/051e2fa2-d6c7-41df-86e8-d3292833c577/todos.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "6",
|
||||||
|
"status": "completed",
|
||||||
|
"content": "验证构建和类型检查",
|
||||||
|
"priority": "high"
|
||||||
|
}
|
||||||
|
]
|
||||||
26
.todo/14a0e10b-0e7f-4545-bc7f-74e23ff56dbf/todos.json
Normal file
26
.todo/14a0e10b-0e7f-4545-bc7f-74e23ff56dbf/todos.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"status": "completed",
|
||||||
|
"content": "纺织厂工作台集成帮助系统",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2",
|
||||||
|
"status": "completed",
|
||||||
|
"content": "水洗厂工作台集成帮助系统",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3",
|
||||||
|
"status": "completed",
|
||||||
|
"content": "通知中心按钮适配主题色",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4",
|
||||||
|
"status": "completed",
|
||||||
|
"content": "运行类型检查",
|
||||||
|
"priority": "medium"
|
||||||
|
}
|
||||||
|
]
|
||||||
26
.todo/1cfb1759-dec5-43a7-b654-b291f96aa2de/todos.json
Normal file
26
.todo/1cfb1759-dec5-43a7-b654-b291f96aa2de/todos.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"content": "修改采购商 Dashboard.tsx 退出按钮移动端变图标",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2",
|
||||||
|
"content": "修改纺织厂 Dashboard.tsx 退出按钮移动端变图标",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3",
|
||||||
|
"content": "修改水洗厂 Dashboard.tsx 退出按钮移动端变图标",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4",
|
||||||
|
"content": "运行类型检查",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "medium"
|
||||||
|
}
|
||||||
|
]
|
||||||
17
.todo/2affe4b5-985d-4172-a1df-bc06f7d20b16/todos.json
Normal file
17
.todo/2affe4b5-985d-4172-a1df-bc06f7d20b16/todos.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"content": "修改 LoginPage 添加记住密码和免密登录 UI",
|
||||||
|
"id": "1",
|
||||||
|
"status": "completed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": "修改 AuthContext 支持本地存储凭据",
|
||||||
|
"id": "2",
|
||||||
|
"status": "completed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": "实现自动登录逻辑",
|
||||||
|
"id": "3",
|
||||||
|
"status": "completed"
|
||||||
|
}
|
||||||
|
]
|
||||||
20
.todo/4229bfaf-76b6-4009-bb1a-4678909f20f7/todos.json
Normal file
20
.todo/4229bfaf-76b6-4009-bb1a-4678909f20f7/todos.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"content": "分析库存数据不一致问题(记录8000m显示14200m)",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2",
|
||||||
|
"content": "定位数据计算逻辑错误",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3",
|
||||||
|
"content": "修复库存统计显示问题",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "high"
|
||||||
|
}
|
||||||
|
]
|
||||||
26
.todo/5d42c7c8-0326-46a3-9321-2d7263afc9aa/todos.json
Normal file
26
.todo/5d42c7c8-0326-46a3-9321-2d7263afc9aa/todos.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"status": "completed",
|
||||||
|
"content": "纺织厂工作台集成帮助系统",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2",
|
||||||
|
"status": "completed",
|
||||||
|
"content": "水洗厂工作台集成帮助系统",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3",
|
||||||
|
"status": "completed",
|
||||||
|
"content": "通知中心按钮适配主题色",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4",
|
||||||
|
"status": "completed",
|
||||||
|
"content": "运行类型检查",
|
||||||
|
"priority": "medium"
|
||||||
|
}
|
||||||
|
]
|
||||||
20
.todo/6da0cb9b-ac38-4253-b891-30df64a7bb0a/todos.json
Normal file
20
.todo/6da0cb9b-ac38-4253-b891-30df64a7bb0a/todos.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"content": "修改分享功能,添加工厂名和对方公司名称二次确认",
|
||||||
|
"status": "in_progress",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2",
|
||||||
|
"content": "限制采购商和纺织厂之间仅有一条计划折叠项",
|
||||||
|
"status": "pending",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3",
|
||||||
|
"content": "运行类型检查",
|
||||||
|
"status": "pending",
|
||||||
|
"priority": "medium"
|
||||||
|
}
|
||||||
|
]
|
||||||
17
.todo/8924520c-e3ef-4e82-8417-555a4b16b76d/todos.json
Normal file
17
.todo/8924520c-e3ef-4e82-8417-555a4b16b76d/todos.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"content": "修改 LoginPage 添加记住密码和免密登录 UI",
|
||||||
|
"id": "1",
|
||||||
|
"status": "in_progress"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": "修改 AuthContext 支持本地存储凭据",
|
||||||
|
"id": "2",
|
||||||
|
"status": "pending"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": "实现自动登录逻辑",
|
||||||
|
"id": "3",
|
||||||
|
"status": "pending"
|
||||||
|
}
|
||||||
|
]
|
||||||
32
.todo/8fb830f5-1dbc-4aa9-bab2-39f4ffd02d08/todos.json
Normal file
32
.todo/8fb830f5-1dbc-4aa9-bab2-39f4ffd02d08/todos.json
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"content": "更新新建纺织计划页面 - 显示经线克重、纬线克重、总克重、产品图片",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2",
|
||||||
|
"content": "更新新建纺织计划页面 - 完善纱线类型显示(经线/纬线/全部)",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3",
|
||||||
|
"content": "更新计划总览页面 - 显示克重、生产采购价、纱线配比",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4",
|
||||||
|
"content": "更新 PlanGroup 组件 - 显示更多产品详情",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5",
|
||||||
|
"content": "验证类型检查",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "medium"
|
||||||
|
}
|
||||||
|
]
|
||||||
62
.todo/999c0326-647f-4e6c-acb6-2a5825dc72ff/todos.json
Normal file
62
.todo/999c0326-647f-4e6c-acb6-2a5825dc72ff/todos.json
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"content": "查看原坯布仓库新建产品相关代码",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2",
|
||||||
|
"content": "修改克重标签为 克重(m/g)",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3",
|
||||||
|
"content": "修改色号识别码为 色号",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4",
|
||||||
|
"content": "修改所需纱线类型及占比为 纱线名称/占比",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5",
|
||||||
|
"content": "添加经线/纬线/全部选项到纱线名称前",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6",
|
||||||
|
"content": "修改 WarehouseManage.tsx 表单类型和保存逻辑",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "7",
|
||||||
|
"content": "修改 types/index.ts 中的 ProductYarnRatio 类型",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8",
|
||||||
|
"content": "修改 NewPlan.tsx 展示纱线类型信息",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "9",
|
||||||
|
"content": "修改 PlanGroup.tsx 展示纱线类型信息",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "10",
|
||||||
|
"content": "验证修改效果",
|
||||||
|
"status": "in_progress",
|
||||||
|
"priority": "medium"
|
||||||
|
}
|
||||||
|
]
|
||||||
62
.todo/cc5b0360-69f7-4e92-9880-4c385422c762/todos.json
Normal file
62
.todo/cc5b0360-69f7-4e92-9880-4c385422c762/todos.json
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"content": "查看原坯布仓库新建产品相关代码",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2",
|
||||||
|
"content": "修改克重标签为 克重(m/g)",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3",
|
||||||
|
"content": "修改色号识别码为 色号",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4",
|
||||||
|
"content": "修改所需纱线类型及占比为 纱线名称/占比",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5",
|
||||||
|
"content": "添加经线/纬线/全部选项到纱线名称前",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6",
|
||||||
|
"content": "修改 WarehouseManage.tsx 表单类型和保存逻辑",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "7",
|
||||||
|
"content": "修改 types/index.ts 中的 ProductYarnRatio 类型",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8",
|
||||||
|
"content": "修改 NewPlan.tsx 展示纱线类型信息",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "9",
|
||||||
|
"content": "修改 PlanGroup.tsx 展示纱线类型信息",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "10",
|
||||||
|
"content": "验证修改效果",
|
||||||
|
"status": "completed",
|
||||||
|
"priority": "medium"
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -1,20 +1,8 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": "1",
|
"content": "验证构建和类型检查",
|
||||||
"content": "更新 AGENTS.md 记录 webpack devServer 预览加载失败问题",
|
"id": "6",
|
||||||
"status": "completed",
|
"priority": "high",
|
||||||
"priority": "high"
|
"status": "completed"
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2",
|
|
||||||
"content": "更新 README.md 添加问题排查和解决方案",
|
|
||||||
"status": "completed",
|
|
||||||
"priority": "high"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "3",
|
|
||||||
"content": "总结学习反思",
|
|
||||||
"status": "completed",
|
|
||||||
"priority": "medium"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
446
AGENTS.md
446
AGENTS.md
@ -102,17 +102,51 @@ src/
|
|||||||
│ │ ├── InboundForm.tsx # 入库表单模态框
|
│ │ ├── InboundForm.tsx # 入库表单模态框
|
||||||
│ │ ├── ProductList.tsx # 产品列表组件(桌面+移动端)
|
│ │ ├── ProductList.tsx # 产品列表组件(桌面+移动端)
|
||||||
│ │ └── InboundRecords.tsx # 入库记录列表
|
│ │ └── InboundRecords.tsx # 入库记录列表
|
||||||
│ └── plans/ # 计划管理组件
|
│ ├── plans/ # 计划管理组件
|
||||||
│ ├── PlanGroup.tsx # 计划分组组件
|
│ │ ├── PlanGroup.tsx # 计划分组组件
|
||||||
│ └── ShareModal.tsx # 分享弹窗组件
|
│ │ ├── ShareModal.tsx # 分享弹窗组件
|
||||||
|
│ │ └── PlanEditModal.tsx # 计划编辑弹窗
|
||||||
|
│ ├── HelpCenter.tsx # 帮助中心(F1快捷键)
|
||||||
|
│ ├── UserManual.tsx # 用户手册
|
||||||
|
│ ├── OnboardingGuide.tsx # 新手指引
|
||||||
|
│ ├── Tooltip.tsx # 工具提示组件
|
||||||
|
│ ├── VersionUpdate.tsx # 版本更新检查
|
||||||
|
│ ├── FeedbackModal.tsx # 用户反馈弹窗
|
||||||
|
│ ├── CrashReporter.tsx # 崩溃报告系统
|
||||||
|
│ ├── NotificationCenter.tsx # 通知中心
|
||||||
|
│ └── DemoDisclaimerModal.tsx # Demo免责声明
|
||||||
├── hooks/
|
├── hooks/
|
||||||
│ ├── useInventoryData.ts # 库存数据获取Hook(带缓存)
|
│ ├── useInventoryData.ts # 库存数据获取Hook(带缓存)
|
||||||
│ └── usePlanData.ts # 计划数据获取Hook(带缓存)
|
│ ├── usePlanData.ts # 计划数据获取Hook(带缓存)
|
||||||
|
│ ├── useResponsive.ts # 响应式Hook
|
||||||
|
│ └── useTheme.ts # 主题Hook
|
||||||
└── utils/
|
└── utils/
|
||||||
├── constants.ts # 公共常量配置
|
├── constants.ts # 公共常量配置
|
||||||
└── helpers.ts # 通用工具函数
|
├── helpers.ts # 通用工具函数
|
||||||
|
└── shareCrypto.ts # 分享链接加密
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 帮助系统组件(2025-05-26)
|
||||||
|
|
||||||
|
| 组件 | 功能 | 位置 |
|
||||||
|
|------|------|------|
|
||||||
|
| HelpCenter | 内置帮助中心,F1快捷键打开 | 通知中心旁边 |
|
||||||
|
| UserManual | 用户手册,详细使用指南 | 账号管理中 |
|
||||||
|
| OnboardingGuide | 新手指引,首次登录显示 | 自动弹窗 |
|
||||||
|
| Tooltip | 工具提示,悬停显示说明 | 所有按钮 |
|
||||||
|
| VersionUpdate | 版本更新检查,仅弹一次 | 自动检测 |
|
||||||
|
| FeedbackModal | 用户反馈渠道 | 账号管理中 |
|
||||||
|
| CrashReporter | 崩溃报告系统 | 全局监听 |
|
||||||
|
| AboutModal | 关于窗口 | 账号管理中 |
|
||||||
|
|
||||||
|
### 通知中心主题色适配
|
||||||
|
|
||||||
|
| 工作台 | 背景色 | 图标色 |
|
||||||
|
|--------|--------|--------|
|
||||||
|
| 采购商 | bg-amber-100 | text-amber-700 |
|
||||||
|
| 纺织厂 | bg-emerald-100 | text-emerald-700 |
|
||||||
|
| 水洗厂 | bg-purple-100 | text-purple-700 |
|
||||||
|
|
||||||
## 路由结构
|
## 路由结构
|
||||||
|
|
||||||
| URL 路径 | 页面 | 说明 |
|
| URL 路径 | 页面 | 说明 |
|
||||||
@ -434,6 +468,36 @@ CREATE TYPE payment_status AS ENUM ('pending', 'completed');
|
|||||||
|
|
||||||
## 更新日志
|
## 更新日志
|
||||||
|
|
||||||
|
### 2025-05-27 (v1.0.2)
|
||||||
|
- **账号管理页面帮助系统集成**
|
||||||
|
- 新增"帮助与支持"卡片区域
|
||||||
|
- 集成用户手册入口,支持查看详细使用指南
|
||||||
|
- 集成用户反馈渠道,支持提交问题或建议
|
||||||
|
- 集成关于窗口,显示版本信息与版权
|
||||||
|
- **技术文档更新**
|
||||||
|
- 新增 Bug 修复记录:帮助系统集成问题
|
||||||
|
- 新增 Bug 修复记录:计划拒绝状态管理问题
|
||||||
|
- 新增 Bug 修复记录:通知中心主题色适配问题
|
||||||
|
- 新增 Bug 修复记录:新手指引弹窗位置问题
|
||||||
|
|
||||||
|
### 2025-05-26
|
||||||
|
- **计划详情字段完善**
|
||||||
|
- 采购商和纺织厂计划总览页面统一显示所有技术字段
|
||||||
|
- 新增显示字段:克重、经线克重、纬线克重、总克重、生产采购价、产品图片、纱线配比、每米纱用量
|
||||||
|
- 新增时间字段:创建时间、计划开始时间
|
||||||
|
- 新增备注字段显示
|
||||||
|
- 纺织厂页面隐藏敏感信息(成品名称、颜色、色号)以保护采购商数据
|
||||||
|
- **移动端 UI 优化**
|
||||||
|
- 计划卡片移动端适配:缩小图片尺寸(64x64)、字体调整为 text-xs
|
||||||
|
- 字段标签简化(如"计划编号:"→"编号:")
|
||||||
|
- 网格布局优化,避免内容溢出
|
||||||
|
- 所有工作台退出按钮在移动端变为图标形式(LogOut图标)
|
||||||
|
- **拒绝计划功能增强**
|
||||||
|
- 纺织厂拒绝计划后立即显示灰色卡片和"已拒绝"标签
|
||||||
|
- 被拒绝的计划自动折叠
|
||||||
|
- 被拒绝的计划从 Dashboard "最近计划"中消失
|
||||||
|
- 使用 localStorage 持久化拒绝状态,跨页面保持同步
|
||||||
|
|
||||||
### 2025-05-25
|
### 2025-05-25
|
||||||
- **产品图片上传功能**
|
- **产品图片上传功能**
|
||||||
- 为采购商原坯布仓库产品管理添加图片上传功能
|
- 为采购商原坯布仓库产品管理添加图片上传功能
|
||||||
@ -451,6 +515,125 @@ CREATE TYPE payment_status AS ENUM ('pending', 'completed');
|
|||||||
|
|
||||||
## Bug 修复记录
|
## Bug 修复记录
|
||||||
|
|
||||||
|
### 2025-05-26 计划字段显示不一致问题
|
||||||
|
|
||||||
|
#### 问题描述
|
||||||
|
采购商和纺织厂的计划总览页面显示字段不一致,纺织厂缺少克重、纱线配比、生产采购价等关键字段。
|
||||||
|
|
||||||
|
#### 根本原因
|
||||||
|
- `usePlanData.ts` Hook 未查询 `products` 表关联数据
|
||||||
|
- `PlanGroup.tsx` 组件未展示 weight、yarn_ratios、production_price 等字段
|
||||||
|
- 纺织厂 `PlanOverview.tsx` 缺少展开详情视图
|
||||||
|
|
||||||
|
#### 修复方案
|
||||||
|
1. **数据层**:在 `usePlanData.ts` 中添加 products 表查询,获取 weight、image_url 等字段
|
||||||
|
2. **组件层**:在 `PlanGroup.tsx` 中添加完整字段展示,包括:
|
||||||
|
- 克重信息(经线/纬线/总克重)
|
||||||
|
- 纱线配比列表
|
||||||
|
- 生产采购价
|
||||||
|
- 产品图片
|
||||||
|
- 创建时间、计划开始时间、备注
|
||||||
|
3. **页面层**:纺织厂 `PlanOverview.tsx` 添加展开详情功能,与采购商保持一致
|
||||||
|
|
||||||
|
#### 代码变更
|
||||||
|
- `src/hooks/usePlanData.ts` - 添加 products 表关联查询
|
||||||
|
- `src/components/plans/PlanGroup.tsx` - 扩展字段展示
|
||||||
|
- `src/pages/textile/PlanOverview.tsx` - 添加展开详情视图
|
||||||
|
- `src/pages/purchaser/PlanOverview.tsx` - 同步优化字段展示
|
||||||
|
|
||||||
|
#### 经验教训
|
||||||
|
- **数据一致性**:同一业务对象在不同页面的展示应保持一致,仅根据角色权限控制敏感字段
|
||||||
|
- **Hook 复用**:通过统一的数据获取 Hook 确保各页面数据一致性
|
||||||
|
- **类型安全**:扩展类型定义时注意与数据库生成的类型保持兼容
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2025-05-26 移动端退出按钮适配问题
|
||||||
|
|
||||||
|
#### 问题描述
|
||||||
|
工作台右上角退出按钮在移动端显示为"退出登录"文字,占用过多空间,与整体移动端设计不协调。
|
||||||
|
|
||||||
|
#### 根本原因
|
||||||
|
- 退出按钮未做响应式适配
|
||||||
|
- 移动端空间宝贵,文字按钮过于拥挤
|
||||||
|
|
||||||
|
#### 修复方案
|
||||||
|
使用响应式设计,桌面端显示"退出登录"文字,移动端仅显示 LogOut 图标:
|
||||||
|
```tsx
|
||||||
|
<button className="flex items-center gap-2 ...">
|
||||||
|
<span className="hidden sm:inline">退出登录</span>
|
||||||
|
<LogOut className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 代码变更
|
||||||
|
- `src/pages/purchaser/Dashboard.tsx` - 退出按钮响应式优化
|
||||||
|
- `src/pages/textile/Dashboard.tsx` - 退出按钮响应式优化
|
||||||
|
- `src/pages/washing/Dashboard.tsx` - 退出按钮响应式优化
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2025-05-26 拒绝计划状态同步问题
|
||||||
|
|
||||||
|
#### 问题描述
|
||||||
|
纺织厂拒绝计划后,计划卡片未立即更新视觉状态,且仍显示在 Dashboard "最近计划"中。
|
||||||
|
|
||||||
|
#### 根本原因
|
||||||
|
- 拒绝操作仅更新数据库,未更新本地状态
|
||||||
|
- Dashboard 和 PlanOverview 之间没有共享拒绝状态
|
||||||
|
- 页面刷新后拒绝状态丢失
|
||||||
|
|
||||||
|
#### 修复方案
|
||||||
|
1. **本地状态管理**:在 `PlanOverview.tsx` 中使用 `Set<string>` 跟踪被拒绝的计划ID
|
||||||
|
2. **持久化存储**:使用 localStorage 存储拒绝状态,键名为 `textile_rejected_plans`
|
||||||
|
3. **跨页面同步**:Dashboard 从 localStorage 读取拒绝状态并过滤最近计划
|
||||||
|
4. **视觉反馈**:被拒绝的计划显示灰色背景、"已拒绝"标签,并自动折叠
|
||||||
|
|
||||||
|
#### 代码变更
|
||||||
|
- `src/pages/textile/PlanOverview.tsx` - 添加 rejectedPlans 状态和 localStorage 持久化
|
||||||
|
- `src/pages/textile/Dashboard.tsx` - 添加 rejectedPlanIds 状态,过滤最近计划
|
||||||
|
|
||||||
|
#### 关键代码
|
||||||
|
```typescript
|
||||||
|
// PlanOverview.tsx - 拒绝时持久化
|
||||||
|
const handleRejectStep = async (step: PlanProcessStep) => {
|
||||||
|
// ... 提交拒绝原因到数据库 ...
|
||||||
|
|
||||||
|
// 更新本地状态
|
||||||
|
setRejectedPlans(prev => new Set([...prev, step.plan_id]));
|
||||||
|
|
||||||
|
// 持久化到 localStorage
|
||||||
|
const stored = localStorage.getItem('textile_rejected_plans');
|
||||||
|
const rejectedArray = stored ? JSON.parse(stored) : [];
|
||||||
|
if (!rejectedArray.includes(step.plan_id)) {
|
||||||
|
rejectedArray.push(step.plan_id);
|
||||||
|
localStorage.setItem('textile_rejected_plans', JSON.stringify(rejectedArray));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dashboard.tsx - 读取并过滤
|
||||||
|
useEffect(() => {
|
||||||
|
// ... 获取计划列表 ...
|
||||||
|
|
||||||
|
// 从 localStorage 读取拒绝状态
|
||||||
|
const stored = localStorage.getItem('textile_rejected_plans');
|
||||||
|
if (stored) {
|
||||||
|
setRejectedPlanIds(new Set(JSON.parse(stored)));
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 过滤最近计划
|
||||||
|
const filteredPlans = plans.filter(p => !rejectedPlanIds.has(p.id));
|
||||||
|
const recentPlans = filteredPlans.slice(0, 5);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 经验教训
|
||||||
|
- **状态共享**:跨页面状态可考虑使用 localStorage、URL 参数或全局状态管理
|
||||||
|
- **乐观更新**:用户操作后应立即更新 UI,再同步后端状态
|
||||||
|
- **数据过滤**:Dashboard 展示的数据应根据业务规则进行过滤,而非直接展示原始数据
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### 2025-05-25 TypeScript 类型兼容性问题
|
### 2025-05-25 TypeScript 类型兼容性问题
|
||||||
|
|
||||||
#### 问题描述
|
#### 问题描述
|
||||||
@ -1561,6 +1744,259 @@ pnpm run dev
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### 分类十七:帮助系统集成问题
|
||||||
|
|
||||||
|
#### Bug: 账号管理页面缺少帮助系统入口
|
||||||
|
**现象**:
|
||||||
|
- 用户手册、用户反馈渠道、关于窗口在账号管理页面中看不到
|
||||||
|
- 帮助系统组件已创建但未在 MemberManage 页面集成
|
||||||
|
|
||||||
|
**根本原因**:
|
||||||
|
- MemberManage.tsx 未导入帮助系统组件
|
||||||
|
- 缺少状态管理控制弹窗显示
|
||||||
|
- 未在 UI 中添加入口按钮
|
||||||
|
|
||||||
|
**修复方案**:
|
||||||
|
```typescript
|
||||||
|
// 1. 导入帮助系统组件
|
||||||
|
import { UserManual } from '../components/UserManual';
|
||||||
|
import { FeedbackModal } from '../components/FeedbackModal';
|
||||||
|
import { AboutModal } from '../components/VersionUpdate';
|
||||||
|
|
||||||
|
// 2. 添加状态管理
|
||||||
|
const [showUserManual, setShowUserManual] = useState(false);
|
||||||
|
const [showFeedback, setShowFeedback] = useState(false);
|
||||||
|
const [showAbout, setShowAbout] = useState(false);
|
||||||
|
|
||||||
|
// 3. 在 UI 中添加入口按钮(位于主账号卡片下方)
|
||||||
|
<div className="bg-white rounded-lg shadow-sm p-4 md:p-6 mb-6">
|
||||||
|
<div className="flex items-center gap-2 mb-4">
|
||||||
|
<BookOpen className="w-5 h-5 text-blue-600" />
|
||||||
|
<h2 className="font-semibold text-gray-800">帮助与支持</h2>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||||||
|
{/* 用户手册按钮 */}
|
||||||
|
<motion.button onClick={() => setShowUserManual(true)}>
|
||||||
|
<BookOpen className="w-5 h-5 text-blue-600" />
|
||||||
|
<span>用户手册</span>
|
||||||
|
<span>详细使用指南</span>
|
||||||
|
</motion.button>
|
||||||
|
|
||||||
|
{/* 用户反馈按钮 */}
|
||||||
|
<motion.button onClick={() => setShowFeedback(true)}>
|
||||||
|
<MessageSquare className="w-5 h-5 text-emerald-600" />
|
||||||
|
<span>用户反馈</span>
|
||||||
|
<span>提交问题或建议</span>
|
||||||
|
</motion.button>
|
||||||
|
|
||||||
|
{/* 关于按钮 */}
|
||||||
|
<motion.button onClick={() => setShowAbout(true)}>
|
||||||
|
<Info className="w-5 h-5 text-purple-600" />
|
||||||
|
<span>关于</span>
|
||||||
|
<span>版本信息与版权</span>
|
||||||
|
</motion.button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
// 4. 在页面底部添加弹窗组件
|
||||||
|
<UserManual isOpen={showUserManual} onClose={() => setShowUserManual(false)} />
|
||||||
|
<FeedbackModal isOpen={showFeedback} onClose={() => setShowFeedback(false)} />
|
||||||
|
<AboutModal isOpen={showAbout} onClose={() => setShowAbout(false)} />
|
||||||
|
```
|
||||||
|
|
||||||
|
**设计要点**:
|
||||||
|
- 使用卡片式布局,与页面整体风格一致
|
||||||
|
- 三个按钮使用不同主题色(蓝/绿/紫)便于区分
|
||||||
|
- 按钮包含图标、标题和描述,信息清晰
|
||||||
|
- 位于主账号信息下方、子账号列表上方,位置合理
|
||||||
|
|
||||||
|
**相关文件**:`src/pages/MemberManage.tsx`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 分类十八:计划拒绝状态管理问题
|
||||||
|
|
||||||
|
#### Bug: 纺织厂拒绝计划后状态未持久化
|
||||||
|
**现象**:
|
||||||
|
- 纺织厂拒绝计划后,刷新页面或重新进入,计划又变回"待确定"状态
|
||||||
|
- 被拒绝的计划仍显示在 Dashboard "最近计划"中
|
||||||
|
- 拒绝弹窗每次进入页面都会重复弹出
|
||||||
|
|
||||||
|
**根本原因**:
|
||||||
|
- 拒绝状态仅保存在组件 state 中,页面刷新后丢失
|
||||||
|
- Dashboard 和 PlanOverview 之间没有共享拒绝状态
|
||||||
|
- 没有机制跟踪用户已查看的拒绝弹窗
|
||||||
|
|
||||||
|
**修复方案**:
|
||||||
|
```typescript
|
||||||
|
// 1. 使用 localStorage 持久化拒绝状态
|
||||||
|
const [rejectedPlans, setRejectedPlans] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// 从 localStorage 读取已拒绝的计划
|
||||||
|
const stored = localStorage.getItem('textile_rejected_plans');
|
||||||
|
if (stored) {
|
||||||
|
setRejectedPlans(new Set(JSON.parse(stored)));
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 2. 拒绝计划时持久化到 localStorage
|
||||||
|
const handleRejectStep = async (step: PlanProcessStep) => {
|
||||||
|
// ... 提交拒绝原因到数据库 ...
|
||||||
|
|
||||||
|
// 更新本地状态
|
||||||
|
setRejectedPlans(prev => new Set([...prev, step.plan_id]));
|
||||||
|
|
||||||
|
// 持久化到 localStorage
|
||||||
|
const stored = localStorage.getItem('textile_rejected_plans');
|
||||||
|
const rejectedArray = stored ? JSON.parse(stored) : [];
|
||||||
|
if (!rejectedArray.includes(step.plan_id)) {
|
||||||
|
rejectedArray.push(step.plan_id);
|
||||||
|
localStorage.setItem('textile_rejected_plans', JSON.stringify(rejectedArray));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 3. Dashboard 过滤最近计划
|
||||||
|
const filteredPlans = plans.filter(p => !rejectedPlanIds.has(p.id));
|
||||||
|
const recentPlans = filteredPlans.slice(0, 5);
|
||||||
|
|
||||||
|
// 4. 使用 Set 跟踪已查看的拒绝弹窗(每个计划只弹一次)
|
||||||
|
const [viewedRejectedModals, setViewedRejectedModals] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const stored = localStorage.getItem('viewed_rejected_modals');
|
||||||
|
if (stored) {
|
||||||
|
setViewedRejectedModals(new Set(JSON.parse(stored)));
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 显示拒绝弹窗时检查是否已查看
|
||||||
|
if (isRejected && !viewedRejectedModals.has(plan.id)) {
|
||||||
|
showRejectedModal(plan);
|
||||||
|
const newViewed = new Set([...viewedRejectedModals, plan.id]);
|
||||||
|
setViewedRejectedModals(newViewed);
|
||||||
|
localStorage.setItem('viewed_rejected_modals', JSON.stringify([...newViewed]));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**关键设计决策**:
|
||||||
|
- 使用 `textile_rejected_plans` 存储被拒绝的计划ID列表
|
||||||
|
- 使用 `viewed_rejected_modals` 存储已查看弹窗的计划ID列表
|
||||||
|
- 被拒绝的计划显示灰色背景和"已拒绝"标签
|
||||||
|
- 被拒绝的计划自动折叠,减少视觉干扰
|
||||||
|
|
||||||
|
**相关文件**:
|
||||||
|
- `src/pages/textile/PlanOverview.tsx`
|
||||||
|
- `src/pages/textile/Dashboard.tsx`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 分类十九:通知中心主题色适配问题
|
||||||
|
|
||||||
|
#### Bug: 通知中心按钮在各工作台颜色不一致
|
||||||
|
**现象**:
|
||||||
|
- 采购商工作台通知按钮为白色,在白色背景下看不清
|
||||||
|
- 纺织厂和水洗厂工作台通知按钮颜色未适配主题色
|
||||||
|
- 按钮缺少明显的视觉层次
|
||||||
|
|
||||||
|
**根本原因**:
|
||||||
|
- 通知中心组件未接收主题色参数
|
||||||
|
- 各工作台页面未传递正确的主题色配置
|
||||||
|
- 默认样式使用白色背景,在浅色主题下不显眼
|
||||||
|
|
||||||
|
**修复方案**:
|
||||||
|
```typescript
|
||||||
|
// 1. NotificationCenter 组件添加 theme 属性
|
||||||
|
interface NotificationCenterProps {
|
||||||
|
theme?: 'amber' | 'emerald' | 'purple' | 'blue';
|
||||||
|
}
|
||||||
|
|
||||||
|
const themeConfig = {
|
||||||
|
amber: {
|
||||||
|
bg: 'bg-amber-100',
|
||||||
|
hover: 'hover:bg-amber-200',
|
||||||
|
icon: 'text-amber-700',
|
||||||
|
border: 'border-amber-300'
|
||||||
|
},
|
||||||
|
emerald: {
|
||||||
|
bg: 'bg-emerald-100',
|
||||||
|
hover: 'hover:bg-emerald-200',
|
||||||
|
icon: 'text-emerald-700',
|
||||||
|
border: 'border-emerald-300'
|
||||||
|
},
|
||||||
|
purple: {
|
||||||
|
bg: 'bg-purple-100',
|
||||||
|
hover: 'hover:bg-purple-200',
|
||||||
|
icon: 'text-purple-700',
|
||||||
|
border: 'border-purple-300'
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// 2. 各工作台传递对应主题色
|
||||||
|
// 采购商 Dashboard
|
||||||
|
<NotificationCenter theme="amber" />
|
||||||
|
|
||||||
|
// 纺织厂 Dashboard
|
||||||
|
<NotificationCenter theme="emerald" />
|
||||||
|
|
||||||
|
// 水洗厂 Dashboard
|
||||||
|
<NotificationCenter theme="purple" />
|
||||||
|
```
|
||||||
|
|
||||||
|
**视觉设计规范**:
|
||||||
|
| 工作台 | 背景色 | 图标色 | 悬停色 |
|
||||||
|
|--------|--------|--------|--------|
|
||||||
|
| 采购商 | bg-amber-100 | text-amber-700 | hover:bg-amber-200 |
|
||||||
|
| 纺织厂 | bg-emerald-100 | text-emerald-700 | hover:bg-emerald-200 |
|
||||||
|
| 水洗厂 | bg-purple-100 | text-purple-700 | hover:bg-purple-200 |
|
||||||
|
|
||||||
|
**相关文件**:
|
||||||
|
- `src/components/NotificationCenter.tsx`
|
||||||
|
- `src/pages/purchaser/Dashboard.tsx`
|
||||||
|
- `src/pages/textile/Dashboard.tsx`
|
||||||
|
- `src/pages/washing/Dashboard.tsx`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 分类二十:新手指引弹窗位置问题
|
||||||
|
|
||||||
|
#### Bug: 新手指引弹窗位置偏下,不在视口居中
|
||||||
|
**现象**:
|
||||||
|
- 纺织厂工作台的新手指引弹窗显示在页面底部
|
||||||
|
- 用户需要滚动才能看到完整内容
|
||||||
|
- 弹窗没有正确居中显示
|
||||||
|
|
||||||
|
**根本原因**:
|
||||||
|
- 使用了 `items-start` 和 `pt-[10vh]` 试图向下偏移
|
||||||
|
- 但在小屏幕或内容较多时,弹窗会超出可视区域
|
||||||
|
- 没有使用真正的居中布局
|
||||||
|
|
||||||
|
**修复方案**:
|
||||||
|
```tsx
|
||||||
|
// 错误:使用 items-start 和 padding 偏移
|
||||||
|
<div className="fixed inset-0 flex items-start justify-center pt-[10vh] z-50">
|
||||||
|
<OnboardingGuide />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
// 正确:使用 items-center 实现真正的居中
|
||||||
|
<div className="fixed inset-0 flex items-center justify-center z-50 p-4">
|
||||||
|
<OnboardingGuide />
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
**设计原则**:
|
||||||
|
- 弹窗应该始终位于视口中央,确保用户第一眼就能看到
|
||||||
|
- 使用 `items-center justify-center` 实现水平和垂直居中
|
||||||
|
- 添加 `p-4` 确保在小屏幕上有足够的边距
|
||||||
|
- 避免使用固定的 padding 值进行偏移
|
||||||
|
|
||||||
|
**相关文件**:
|
||||||
|
- `src/pages/textile/Dashboard.tsx`
|
||||||
|
- `src/pages/purchaser/Dashboard.tsx`
|
||||||
|
- `src/pages/washing/Dashboard.tsx`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 学习反思与最佳实践
|
## 学习反思与最佳实践
|
||||||
|
|
||||||
### 1. 环境差异意识
|
### 1. 环境差异意识
|
||||||
|
|||||||
44
README.md
44
README.md
@ -212,6 +212,46 @@ import { InventoryRecordsModal } from '../../components/InventoryRecordsModal';
|
|||||||
|
|
||||||
## 更新日志
|
## 更新日志
|
||||||
|
|
||||||
|
### 2025-05-27 (v1.0.2)
|
||||||
|
- **账号管理页面帮助系统集成**
|
||||||
|
- 新增"帮助与支持"卡片区域,集成用户手册、用户反馈、关于窗口
|
||||||
|
- 用户手册:详细使用指南,支持搜索功能
|
||||||
|
- 用户反馈:支持提交问题或建议到数据库
|
||||||
|
- 关于窗口:显示版本号 v1.0.2、版权信息、技术支持联系方式
|
||||||
|
- **帮助系统组件完善**
|
||||||
|
- 内置帮助中心(F1快捷键):功能介绍、常见操作步骤、快捷键列表
|
||||||
|
- 新手指引:首次登录自动弹窗,引导完成关键设置
|
||||||
|
- 工具提示:所有按钮悬停显示功能说明
|
||||||
|
- 版本更新检查:自动检测新版本,仅弹窗一次
|
||||||
|
- 崩溃报告系统:全局错误监听,自动收集错误日志
|
||||||
|
- **通知中心主题色适配**
|
||||||
|
- 采购商工作台:琥珀色主题(bg-amber-100/text-amber-700)
|
||||||
|
- 纺织厂工作台:翠绿色主题(bg-emerald-100/text-emerald-700)
|
||||||
|
- 水洗厂工作台:紫色主题(bg-purple-100/text-purple-700)
|
||||||
|
- **计划拒绝功能优化**
|
||||||
|
- 使用 localStorage 持久化拒绝状态,跨页面保持同步
|
||||||
|
- 被拒绝的计划显示灰色卡片和"已拒绝"标签
|
||||||
|
- 被拒绝的计划自动从 Dashboard "最近计划"中移除
|
||||||
|
- 每个被拒绝的计划弹窗仅显示一次
|
||||||
|
|
||||||
|
### 2025-05-26
|
||||||
|
- **计划详情字段完善**
|
||||||
|
- 采购商和纺织厂计划总览页面统一显示所有技术字段
|
||||||
|
- 新增显示字段:克重、经线克重、纬线克重、总克重、生产采购价、产品图片、纱线配比、每米纱用量
|
||||||
|
- 新增时间字段:创建时间、计划开始时间
|
||||||
|
- 新增备注字段显示
|
||||||
|
- 纺织厂页面隐藏敏感信息(成品名称、颜色、色号)以保护采购商数据
|
||||||
|
- **移动端 UI 优化**
|
||||||
|
- 计划卡片移动端适配:缩小图片尺寸(64x64)、字体调整为 text-xs
|
||||||
|
- 字段标签简化(如"计划编号:"→"编号:")
|
||||||
|
- 网格布局优化,避免内容溢出
|
||||||
|
- 所有工作台退出按钮在移动端变为图标形式(LogOut图标)
|
||||||
|
- **拒绝计划功能增强**
|
||||||
|
- 纺织厂拒绝计划后立即显示灰色卡片和"已拒绝"标签
|
||||||
|
- 被拒绝的计划自动折叠
|
||||||
|
- 被拒绝的计划从 Dashboard "最近计划"中消失
|
||||||
|
- 使用 localStorage 持久化拒绝状态,跨页面保持同步
|
||||||
|
|
||||||
### 2025-05-25
|
### 2025-05-25
|
||||||
- **产品图片上传功能**
|
- **产品图片上传功能**
|
||||||
- 为采购商原坯布仓库产品管理添加图片上传功能
|
- 为采购商原坯布仓库产品管理添加图片上传功能
|
||||||
@ -314,8 +354,8 @@ VITE_SUPABASE_URL=https://your-project.supabase.co
|
|||||||
VITE_SUPABASE_ANON_KEY=your-anon-key
|
VITE_SUPABASE_ANON_KEY=your-anon-key
|
||||||
|
|
||||||
# 应用配置
|
# 应用配置
|
||||||
VITE_APP_NAME=织机(Demo版)
|
VITE_APP_NAME=昱森(Demo版)
|
||||||
VITE_APP_VERSION=1.0.0
|
VITE_APP_VERSION=1.0.2
|
||||||
```
|
```
|
||||||
|
|
||||||
### 生产环境
|
### 生产环境
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>织机 - 以AI为梭,织天地经纬</title>
|
<title>昱森 - 以AI为梭,织天地经纬</title>
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
<style>
|
<style>
|
||||||
/* 内联样式确保主题色立即生效,避免闪烁 */
|
/* 内联样式确保主题色立即生效,避免闪烁 */
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
-- ============================================
|
||||||
|
-- 此文件由 Meoo Cloud 自动生成,请勿修改
|
||||||
|
-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
ALTER TABLE products ADD COLUMN warp_weight DECIMAL DEFAULT NULL; ALTER TABLE products ADD COLUMN weft_weight DECIMAL DEFAULT NULL; ALTER TABLE products ADD COLUMN total_weight DECIMAL DEFAULT NULL;
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
-- ============================================
|
||||||
|
-- 此文件由 Meoo Cloud 自动生成,请勿修改
|
||||||
|
-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
ALTER TABLE product_yarn_ratios ADD COLUMN IF NOT EXISTS yarn_type TEXT DEFAULT 'all';
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
-- ============================================
|
||||||
|
-- 此文件由 Meoo Cloud 自动生成,请勿修改
|
||||||
|
-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
ALTER TABLE yarn_ratios ADD COLUMN IF NOT EXISTS yarn_type TEXT DEFAULT 'all';
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
-- ============================================
|
||||||
|
-- 此文件由 Meoo Cloud 自动生成,请勿修改
|
||||||
|
-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
|
||||||
|
-- 为 product_outbound_records 表添加生产批号关联和接手公司字段
|
||||||
|
ALTER TABLE public.product_outbound_records
|
||||||
|
ADD COLUMN IF NOT EXISTS source_batch_id UUID REFERENCES public.product_inventory_records(id) ON DELETE SET NULL,
|
||||||
|
ADD COLUMN IF NOT EXISTS recipient_company_id UUID REFERENCES public.companies(id) ON DELETE SET NULL,
|
||||||
|
ADD COLUMN IF NOT EXISTS recipient_company_name TEXT;
|
||||||
|
|
||||||
|
-- 创建索引
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_product_outbound_source_batch ON public.product_outbound_records(source_batch_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_product_outbound_recipient ON public.product_outbound_records(recipient_company_id);
|
||||||
|
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
-- ============================================
|
||||||
|
-- 此文件由 Meoo Cloud 自动生成,请勿修改
|
||||||
|
-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
ALTER TYPE public.step_status ADD VALUE 'rejected';
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
-- ============================================
|
||||||
|
-- 此文件由 Meoo Cloud 自动生成,请勿修改
|
||||||
|
-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS user_feedback (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
type TEXT NOT NULL CHECK (type IN ('suggestion', 'bug', 'question')),
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
contact TEXT,
|
||||||
|
user_id UUID REFERENCES auth.users(id),
|
||||||
|
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'resolved', 'closed')),
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS crash_reports (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
error_message TEXT NOT NULL,
|
||||||
|
error_stack TEXT,
|
||||||
|
component_stack TEXT,
|
||||||
|
user_description TEXT,
|
||||||
|
user_agent TEXT,
|
||||||
|
url TEXT,
|
||||||
|
user_id UUID REFERENCES auth.users(id),
|
||||||
|
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'investigating', 'fixed', 'closed')),
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 启用RLS
|
||||||
|
ALTER TABLE user_feedback ENABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE crash_reports ENABLE ROW LEVEL SECURITY;
|
||||||
|
|
||||||
|
-- 创建RLS策略
|
||||||
|
CREATE POLICY users_insert_feedback ON user_feedback
|
||||||
|
FOR INSERT WITH CHECK (auth.uid() = user_id OR user_id IS NULL);
|
||||||
|
|
||||||
|
CREATE POLICY users_select_own_feedback ON user_feedback
|
||||||
|
FOR SELECT USING (auth.uid() = user_id);
|
||||||
|
|
||||||
|
CREATE POLICY users_insert_crash ON crash_reports
|
||||||
|
FOR INSERT WITH CHECK (auth.uid() = user_id OR user_id IS NULL);
|
||||||
|
|
||||||
403
outputs/文档鉴别材料-使用说明书.md
Normal file
403
outputs/文档鉴别材料-使用说明书.md
Normal file
@ -0,0 +1,403 @@
|
|||||||
|
# 纺织行业采购计划管理系统 - 使用说明书
|
||||||
|
|
||||||
|
## 文档鉴别材料 - 使用说明书
|
||||||
|
|
||||||
|
**软件名称**:纺织行业采购计划管理系统
|
||||||
|
**软件简称**:昱森
|
||||||
|
**版本号**:V1.0.0
|
||||||
|
**编制日期**:2025年05月
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
|
||||||
|
1. 软件概述
|
||||||
|
2. 系统要求
|
||||||
|
3. 安装与配置
|
||||||
|
4. 功能使用说明
|
||||||
|
5. 常见问题
|
||||||
|
6. 技术支持
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第一章 软件概述
|
||||||
|
|
||||||
|
### 1.1 软件简介
|
||||||
|
|
||||||
|
纺织行业采购计划管理系统(简称"织机")是一款专为纺织行业设计的全流程协作管理平台,连接采购商(布行)、纺织厂、水洗厂三方,实现从计划制定到成品交付的数字化管理。
|
||||||
|
|
||||||
|
### 1.2 主要特点
|
||||||
|
|
||||||
|
- **多角色协作**:支持采购商、纺织厂、水洗厂三种角色协同工作
|
||||||
|
- **实时同步**:数据实时更新,多方共享
|
||||||
|
- **移动端适配**:支持手机、平板、电脑多端访问
|
||||||
|
- **数据安全**:采用行级安全策略,确保数据隔离
|
||||||
|
- **操作简便**:界面友好,易于上手
|
||||||
|
|
||||||
|
### 1.3 适用对象
|
||||||
|
|
||||||
|
- 纺织采购商(布行)
|
||||||
|
- 纺织生产厂家
|
||||||
|
- 水洗加工厂家
|
||||||
|
- 纺织行业供应链管理人员
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第二章 系统要求
|
||||||
|
|
||||||
|
### 2.1 硬件环境
|
||||||
|
|
||||||
|
| 项目 | 最低配置 | 推荐配置 |
|
||||||
|
|------|----------|----------|
|
||||||
|
| 处理器 | 双核 1.5GHz | 四核 2.0GHz |
|
||||||
|
| 内存 | 4GB | 8GB |
|
||||||
|
| 硬盘 | 128GB | 256GB SSD |
|
||||||
|
| 显示器 | 1280×720 | 1920×1080 |
|
||||||
|
| 网络 | 宽带 2Mbps | 宽带 10Mbps |
|
||||||
|
|
||||||
|
### 2.2 软件环境
|
||||||
|
|
||||||
|
**客户端浏览器**:
|
||||||
|
- Chrome 90 及以上版本
|
||||||
|
- Firefox 88 及以上版本
|
||||||
|
- Safari 14 及以上版本
|
||||||
|
- Edge 90 及以上版本
|
||||||
|
|
||||||
|
**移动端支持**:
|
||||||
|
- iOS 14 及以上版本
|
||||||
|
- Android 10 及以上版本
|
||||||
|
|
||||||
|
### 2.3 运行环境
|
||||||
|
|
||||||
|
- 云服务平台:Meoo Cloud
|
||||||
|
- 数据库:PostgreSQL 15+
|
||||||
|
- Web服务器:Nginx 1.24+
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第三章 安装与配置
|
||||||
|
|
||||||
|
### 3.1 访问方式
|
||||||
|
|
||||||
|
本系统为Web应用,无需安装客户端软件,直接通过浏览器访问即可。
|
||||||
|
|
||||||
|
**访问地址**:https://[您的域名]/
|
||||||
|
|
||||||
|
### 3.2 首次使用
|
||||||
|
|
||||||
|
1. 打开浏览器,输入系统地址
|
||||||
|
2. 点击"注册"按钮创建账号
|
||||||
|
3. 选择您的角色(采购商/纺织厂/水洗厂)
|
||||||
|
4. 填写公司信息和个人信息
|
||||||
|
5. 完成注册后使用用户名和密码登录
|
||||||
|
|
||||||
|
### 3.3 账号管理
|
||||||
|
|
||||||
|
**主账号**:
|
||||||
|
- 拥有全部权限
|
||||||
|
- 可创建子账号
|
||||||
|
- 可管理公司信息
|
||||||
|
|
||||||
|
**子账号**:
|
||||||
|
- 由主账号创建
|
||||||
|
- 权限由主账号分配
|
||||||
|
- 共享主账号的公司数据
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第四章 功能使用说明
|
||||||
|
|
||||||
|
### 4.1 登录与注册
|
||||||
|
|
||||||
|
#### 4.1.1 用户注册
|
||||||
|
|
||||||
|
1. 点击登录页面的"注册"按钮
|
||||||
|
2. 选择角色类型:
|
||||||
|
- 采购商/布行
|
||||||
|
- 纺织厂
|
||||||
|
- 水洗厂
|
||||||
|
3. 填写注册信息:
|
||||||
|
- 用户名(唯一标识)
|
||||||
|
- 密码(至少6位)
|
||||||
|
- 公司名称
|
||||||
|
- 联系人姓名
|
||||||
|
- 联系电话
|
||||||
|
4. 点击"注册"完成账号创建
|
||||||
|
|
||||||
|
#### 4.1.2 用户登录
|
||||||
|
|
||||||
|
1. 输入用户名
|
||||||
|
2. 输入密码
|
||||||
|
3. 点击"登录"按钮
|
||||||
|
4. 首次登录需选择工作角色
|
||||||
|
|
||||||
|
#### 4.1.3 退出登录
|
||||||
|
|
||||||
|
点击页面右上角的"退出"按钮,安全退出系统。
|
||||||
|
|
||||||
|
### 4.2 采购商功能
|
||||||
|
|
||||||
|
#### 4.2.1 工作台首页
|
||||||
|
|
||||||
|
登录后进入采购商工作台,显示:
|
||||||
|
- **统计卡片**:所有计划、进行中、已完成数量
|
||||||
|
- **快捷操作**:常用功能入口
|
||||||
|
- **最近计划**:最近创建的5个计划
|
||||||
|
|
||||||
|
#### 4.2.2 创建纺织计划
|
||||||
|
|
||||||
|
1. 点击"新建纺织计划"或"计划总览"页面的"新建计划"按钮
|
||||||
|
2. 填写计划信息:
|
||||||
|
- 成品名称
|
||||||
|
- 颜色
|
||||||
|
- 坯布识别码(系统自动生成)
|
||||||
|
- 色号
|
||||||
|
- 计划产量(米)
|
||||||
|
- 每米布纱用量(g/m)
|
||||||
|
- 生产采购价(元/米)
|
||||||
|
- 备注
|
||||||
|
3. 配置纱线配比:
|
||||||
|
- 添加纱线名称
|
||||||
|
- 设置配比比例
|
||||||
|
- 系统自动计算总用纱量
|
||||||
|
4. 选择关联工厂(可选)
|
||||||
|
5. 点击"创建计划"
|
||||||
|
|
||||||
|
#### 4.2.3 计划管理
|
||||||
|
|
||||||
|
**查看计划**:
|
||||||
|
- 列表展示所有计划
|
||||||
|
- 支持按状态筛选
|
||||||
|
- 点击计划查看详情
|
||||||
|
|
||||||
|
**计划详情**:
|
||||||
|
- 基本信息展示
|
||||||
|
- 生产流程节点跟踪
|
||||||
|
- 进度可视化
|
||||||
|
- 关联工厂信息
|
||||||
|
|
||||||
|
**分享计划**:
|
||||||
|
1. 打开计划详情
|
||||||
|
2. 点击"分享"按钮
|
||||||
|
3. 复制分享链接
|
||||||
|
4. 发送给纺织厂
|
||||||
|
|
||||||
|
#### 4.2.4 仓库管理
|
||||||
|
|
||||||
|
**原坯布仓库**:
|
||||||
|
- 按坯布码分组展示
|
||||||
|
- 查看库存数量
|
||||||
|
- 查看入库记录
|
||||||
|
- 查看价格历史
|
||||||
|
|
||||||
|
**成品仓库**:
|
||||||
|
- 管理水洗厂内坯布库存
|
||||||
|
- 跟踪库存变动
|
||||||
|
|
||||||
|
#### 4.2.5 工厂管理
|
||||||
|
|
||||||
|
**添加工厂**:
|
||||||
|
1. 进入"工厂管理"页面
|
||||||
|
2. 点击"添加工厂"
|
||||||
|
3. 选择工厂类型(纺织厂/水洗厂)
|
||||||
|
4. 填写工厂信息
|
||||||
|
5. 生成分享链接
|
||||||
|
|
||||||
|
**管理工厂**:
|
||||||
|
- 查看已关联工厂
|
||||||
|
- 编辑工厂信息
|
||||||
|
- 删除工厂
|
||||||
|
|
||||||
|
#### 4.2.6 应付账款
|
||||||
|
|
||||||
|
- 查看待结款计划
|
||||||
|
- 按完成米数自动计算金额
|
||||||
|
- 确认结款记录
|
||||||
|
|
||||||
|
### 4.3 纺织厂功能
|
||||||
|
|
||||||
|
#### 4.3.1 工作台首页
|
||||||
|
|
||||||
|
显示:
|
||||||
|
- 关联的计划数量统计
|
||||||
|
- 快捷操作入口
|
||||||
|
- 最近更新的计划
|
||||||
|
|
||||||
|
#### 4.3.2 接收计划
|
||||||
|
|
||||||
|
**通过分享链接导入**:
|
||||||
|
1. 点击分享链接
|
||||||
|
2. 系统自动识别计划信息
|
||||||
|
3. 确认导入计划
|
||||||
|
|
||||||
|
**通过剪贴板导入**:
|
||||||
|
1. 复制分享链接
|
||||||
|
2. 进入"导入计划"页面
|
||||||
|
3. 系统自动识别剪贴板内容
|
||||||
|
4. 确认导入
|
||||||
|
|
||||||
|
#### 4.3.3 计划生产
|
||||||
|
|
||||||
|
**查看计划详情**:
|
||||||
|
- 产品信息
|
||||||
|
- 纱线配比
|
||||||
|
- 计划产量
|
||||||
|
|
||||||
|
**确认生产节点**:
|
||||||
|
1. 打开计划详情
|
||||||
|
2. 查看生产流程
|
||||||
|
3. 点击当前节点"确认"按钮
|
||||||
|
4. 添加备注(可选)
|
||||||
|
5. 完成确认
|
||||||
|
|
||||||
|
**生产流程节点**:
|
||||||
|
- 确认计划
|
||||||
|
- 采纱
|
||||||
|
- 染纱
|
||||||
|
- 上机
|
||||||
|
- 坯布入库
|
||||||
|
|
||||||
|
#### 4.3.4 坯布入库
|
||||||
|
|
||||||
|
1. 进入"已生产坯布"页面
|
||||||
|
2. 选择要入库的计划
|
||||||
|
3. 填写入库信息:
|
||||||
|
- 入库米数
|
||||||
|
- 入库匹数
|
||||||
|
- 批号
|
||||||
|
4. 确认入库
|
||||||
|
|
||||||
|
#### 4.3.5 纱线仓库
|
||||||
|
|
||||||
|
- 查看原料纱库存
|
||||||
|
- 设置最低库存预警
|
||||||
|
- 库存不足时系统提醒
|
||||||
|
|
||||||
|
#### 4.3.6 待结款
|
||||||
|
|
||||||
|
- 查看已完成入库的计划
|
||||||
|
- 查看应结款金额
|
||||||
|
- 确认收款
|
||||||
|
|
||||||
|
### 4.4 水洗厂功能
|
||||||
|
|
||||||
|
#### 4.4.1 工作台首页
|
||||||
|
|
||||||
|
显示:
|
||||||
|
- 水洗计划统计
|
||||||
|
- 待处理坯布数量
|
||||||
|
- 快捷操作入口
|
||||||
|
|
||||||
|
#### 4.4.2 水洗计划管理
|
||||||
|
|
||||||
|
**接收水洗计划**:
|
||||||
|
- 通过分享链接导入
|
||||||
|
- 查看计划详情
|
||||||
|
|
||||||
|
**处理坯布**:
|
||||||
|
1. 查看待处理坯布
|
||||||
|
2. 确认接收
|
||||||
|
3. 开始水洗处理
|
||||||
|
4. 完成处理确认
|
||||||
|
|
||||||
|
#### 4.4.3 成品仓库
|
||||||
|
|
||||||
|
- 管理水洗后成品
|
||||||
|
- 跟踪库存状态
|
||||||
|
- 出库记录
|
||||||
|
|
||||||
|
### 4.5 公共功能
|
||||||
|
|
||||||
|
#### 4.5.1 子账号管理
|
||||||
|
|
||||||
|
**创建子账号**(仅主账号):
|
||||||
|
1. 进入"账号管理"页面
|
||||||
|
2. 点击"添加成员"
|
||||||
|
3. 填写子账号信息
|
||||||
|
4. 设置权限
|
||||||
|
5. 完成创建
|
||||||
|
|
||||||
|
**管理子账号**:
|
||||||
|
- 查看成员列表
|
||||||
|
- 编辑权限
|
||||||
|
- 删除子账号
|
||||||
|
|
||||||
|
#### 4.5.2 通知中心
|
||||||
|
|
||||||
|
- 查看系统通知
|
||||||
|
- 计划状态变更提醒
|
||||||
|
- 库存预警通知
|
||||||
|
|
||||||
|
#### 4.5.3 个人设置
|
||||||
|
|
||||||
|
- 修改个人信息
|
||||||
|
- 修改密码
|
||||||
|
- 查看登录历史
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第五章 常见问题
|
||||||
|
|
||||||
|
### 5.1 登录问题
|
||||||
|
|
||||||
|
**Q:忘记密码怎么办?**
|
||||||
|
A:请联系主账号重置密码,或联系系统管理员。
|
||||||
|
|
||||||
|
**Q:无法登录,提示"用户名或密码错误"?**
|
||||||
|
A:请检查用户名和密码是否正确,注意区分大小写。
|
||||||
|
|
||||||
|
### 5.2 功能问题
|
||||||
|
|
||||||
|
**Q:为什么看不到某些功能?**
|
||||||
|
A:部分功能仅限主账号使用,或需要特定角色权限。
|
||||||
|
|
||||||
|
**Q:分享链接无法打开?**
|
||||||
|
A:请检查链接是否完整,或联系计划创建者重新分享。
|
||||||
|
|
||||||
|
**Q:数据不同步?**
|
||||||
|
A:请检查网络连接,或刷新页面重新加载。
|
||||||
|
|
||||||
|
### 5.3 性能问题
|
||||||
|
|
||||||
|
**Q:页面加载慢?**
|
||||||
|
A:
|
||||||
|
- 检查网络连接
|
||||||
|
- 清除浏览器缓存
|
||||||
|
- 关闭不必要的浏览器标签
|
||||||
|
|
||||||
|
**Q:移动端显示异常?**
|
||||||
|
A:请使用推荐的浏览器版本,或尝试刷新页面。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第六章 技术支持
|
||||||
|
|
||||||
|
### 6.1 联系方式
|
||||||
|
|
||||||
|
- **技术支持邮箱**:support@example.com
|
||||||
|
- **技术支持电话**:400-XXX-XXXX
|
||||||
|
- **服务时间**:工作日 9:00-18:00
|
||||||
|
|
||||||
|
### 6.2 反馈建议
|
||||||
|
|
||||||
|
欢迎通过以下方式反馈使用建议:
|
||||||
|
- 发送邮件至 feedback@example.com
|
||||||
|
- 在系统内提交反馈
|
||||||
|
|
||||||
|
### 6.3 版本更新
|
||||||
|
|
||||||
|
系统会定期更新,更新内容包括:
|
||||||
|
- 功能优化
|
||||||
|
- 性能提升
|
||||||
|
- 安全加固
|
||||||
|
- Bug修复
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档版本**:V1.0.0
|
||||||
|
**最后更新**:2025年05月
|
||||||
|
**编制单位**:[您的单位名称]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*本说明书版权归软件著作权人所有,未经许可不得复制、传播。*
|
||||||
BIN
outputs/文档鉴别材料-使用说明书.pdf
Normal file
BIN
outputs/文档鉴别材料-使用说明书.pdf
Normal file
Binary file not shown.
417
outputs/文档鉴别材料-设计说明书.md
Normal file
417
outputs/文档鉴别材料-设计说明书.md
Normal file
@ -0,0 +1,417 @@
|
|||||||
|
# 纺织行业采购计划管理系统 - 设计说明书
|
||||||
|
|
||||||
|
## 文档鉴别材料 - 设计说明书
|
||||||
|
|
||||||
|
**软件名称**:纺织行业采购计划管理系统
|
||||||
|
**软件简称**:昱森
|
||||||
|
**版本号**:V1.0.0
|
||||||
|
**编制日期**:2025年05月
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
|
||||||
|
1. 系统架构设计
|
||||||
|
2. 数据库设计
|
||||||
|
3. 接口设计
|
||||||
|
4. 安全设计
|
||||||
|
5. 部署架构
|
||||||
|
6. 技术选型说明
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第一章 系统架构设计
|
||||||
|
|
||||||
|
### 1.1 整体架构
|
||||||
|
|
||||||
|
本系统采用前后端分离架构,基于云原生技术栈构建:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 客户端层 │
|
||||||
|
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||||
|
│ │ Web浏览器 │ │ 移动端 │ │ 平板 │ │
|
||||||
|
│ │ (React) │ │ (H5) │ │ (响应式) │ │
|
||||||
|
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 云服务层 (Meoo Cloud) │
|
||||||
|
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||||
|
│ │ Supabase │ │ 认证服务 │ │ 实时订阅 │ │
|
||||||
|
│ │ (PostgreSQL)│ │ (Auth) │ │ (Realtime) │ │
|
||||||
|
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 前端架构
|
||||||
|
|
||||||
|
**技术栈**:
|
||||||
|
- **框架**:React 18 + TypeScript
|
||||||
|
- **路由**:React Router v6 (HashRouter)
|
||||||
|
- **状态管理**:React Context + Zustand
|
||||||
|
- **样式方案**:TailwindCSS
|
||||||
|
- **动画库**:Framer Motion
|
||||||
|
- **构建工具**:Webpack 5
|
||||||
|
|
||||||
|
**目录结构**:
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── components/ # 公共组件
|
||||||
|
├── pages/ # 页面组件
|
||||||
|
│ ├── purchaser/ # 采购商页面
|
||||||
|
│ ├── textile/ # 纺织厂页面
|
||||||
|
│ └── washing/ # 水洗厂页面
|
||||||
|
├── hooks/ # 自定义Hooks
|
||||||
|
├── contexts/ # React上下文
|
||||||
|
├── stores/ # 状态管理
|
||||||
|
├── utils/ # 工具函数
|
||||||
|
├── types/ # 类型定义
|
||||||
|
└── supabase/ # Supabase客户端
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 后端架构
|
||||||
|
|
||||||
|
**云服务**:Meoo Cloud (基于Supabase)
|
||||||
|
- **数据库**:PostgreSQL 15
|
||||||
|
- **认证**:Supabase Auth
|
||||||
|
- **实时**:Supabase Realtime
|
||||||
|
- **存储**:Supabase Storage
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第二章 数据库设计
|
||||||
|
|
||||||
|
### 2.1 ER关系图
|
||||||
|
|
||||||
|
```
|
||||||
|
auth.users (Supabase内置)
|
||||||
|
│
|
||||||
|
├── 1:1 ── profiles (用户配置)
|
||||||
|
│ │
|
||||||
|
│ ├── N:1 ── companies (公司)
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── 1:N ── company_members (公司成员)
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── 1:N ── factories (工厂)
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ ├── N:M ── production_plans
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── 1:N ── warehouses (仓库)
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ ├── 1:N ── inventory_records
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── 1:N ── yarn_stock (纱线库存)
|
||||||
|
│ │
|
||||||
|
│ ├── 1:N ── production_plans (采购商创建的计划)
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── 1:N ── yarn_ratios (纱线配比)
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── 1:N ── plan_process_steps (流程节点)
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── N:M ── factories (通过plan_factories关联)
|
||||||
|
│ │
|
||||||
|
│ ├── 1:N ── payments (结款记录)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 核心表结构
|
||||||
|
|
||||||
|
#### 2.2.1 companies - 公司表
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| id | UUID (PK) | 公司ID |
|
||||||
|
| name | TEXT | 公司名称 |
|
||||||
|
| role | app_role | 公司类型:purchaser/textile/washing |
|
||||||
|
| address | TEXT | 公司地址 |
|
||||||
|
| contact_phone | TEXT | 联系电话 |
|
||||||
|
| created_at | TIMESTAMPTZ | 创建时间 |
|
||||||
|
|
||||||
|
#### 2.2.2 profiles - 用户配置表
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| id | UUID (PK) | 关联auth.users.id |
|
||||||
|
| username | TEXT (UNIQUE) | 用户名 |
|
||||||
|
| display_name | TEXT | 显示名称 |
|
||||||
|
| phone | TEXT | 手机号(加密存储) |
|
||||||
|
| company_id | UUID (FK) | 所属公司 |
|
||||||
|
| is_master | BOOLEAN | 是否主账号 |
|
||||||
|
| master_id | UUID (FK) | 主账号ID(子账号使用) |
|
||||||
|
| created_at | TIMESTAMPTZ | 创建时间 |
|
||||||
|
|
||||||
|
#### 2.2.3 production_plans - 生产计划表
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| id | UUID (PK) | 计划ID |
|
||||||
|
| plan_code | TEXT (UNIQUE) | 系统自动生成计划唯一识别码 |
|
||||||
|
| product_name | TEXT | 成品名称 |
|
||||||
|
| color | TEXT | 颜色 |
|
||||||
|
| fabric_code | TEXT | 坯布唯一识别码(纯字母) |
|
||||||
|
| color_code | TEXT | 色号(数字排序) |
|
||||||
|
| remark | TEXT | 备注 |
|
||||||
|
| purchaser_id | UUID (FK) | 采购商公司ID |
|
||||||
|
| target_quantity | INTEGER | 计划产量(米) |
|
||||||
|
| completed_quantity | INTEGER | 已完成产量(米) |
|
||||||
|
| yarn_usage_per_meter | DECIMAL | 每米布纱用量(g/m) |
|
||||||
|
| production_price | DECIMAL | 生产采购价(元/米) |
|
||||||
|
| status | plan_status | 状态:pending/producing/completed |
|
||||||
|
| start_time | TIMESTAMPTZ | 计划开始时间 |
|
||||||
|
| created_by | UUID (FK) | 创建人 |
|
||||||
|
| created_at | TIMESTAMPTZ | 创建时间 |
|
||||||
|
| updated_at | TIMESTAMPTZ | 更新时间 |
|
||||||
|
|
||||||
|
#### 2.2.4 plan_process_steps - 计划流程节点表
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| id | UUID (PK) | 节点ID |
|
||||||
|
| plan_id | UUID (FK) | 计划ID |
|
||||||
|
| step_type | step_type | 节点类型:confirm/yarn_purchase/dyeing/machine_start/fabric_warehouse |
|
||||||
|
| status | step_status | 状态:pending/active/completed |
|
||||||
|
| timestamp | TIMESTAMPTZ | 确认时间戳 |
|
||||||
|
| operator_id | UUID (FK) | 操作人 |
|
||||||
|
| notes | TEXT | 备注 |
|
||||||
|
| created_at | TIMESTAMPTZ | 创建时间 |
|
||||||
|
|
||||||
|
### 2.3 自定义枚举类型
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 应用角色(公司类型)
|
||||||
|
CREATE TYPE app_role AS ENUM ('purchaser', 'textile', 'washing');
|
||||||
|
|
||||||
|
-- 工厂类型
|
||||||
|
CREATE TYPE factory_type AS ENUM ('textile', 'washing');
|
||||||
|
|
||||||
|
-- 计划状态
|
||||||
|
CREATE TYPE plan_status AS ENUM ('pending', 'producing', 'completed');
|
||||||
|
|
||||||
|
-- 流程节点类型
|
||||||
|
CREATE TYPE step_type AS ENUM ('confirm', 'yarn_purchase', 'dyeing', 'machine_start', 'fabric_warehouse');
|
||||||
|
|
||||||
|
-- 流程节点状态
|
||||||
|
CREATE TYPE step_status AS ENUM ('pending', 'active', 'completed');
|
||||||
|
|
||||||
|
-- 仓库类型
|
||||||
|
CREATE TYPE warehouse_type AS ENUM ('raw_fabric', 'fabric', 'finished', 'yarn');
|
||||||
|
|
||||||
|
-- 结款状态
|
||||||
|
CREATE TYPE payment_status AS ENUM ('pending', 'completed');
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 RLS安全策略
|
||||||
|
|
||||||
|
所有表启用Row Level Security (RLS),基于公司ID进行数据隔离:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 示例:production_plans表RLS策略
|
||||||
|
CREATE POLICY company_select_plans ON production_plans
|
||||||
|
FOR SELECT USING (purchaser_id = get_user_master_company_id());
|
||||||
|
|
||||||
|
CREATE POLICY company_insert_plans ON production_plans
|
||||||
|
FOR INSERT WITH CHECK (purchaser_id = get_user_master_company_id());
|
||||||
|
|
||||||
|
CREATE POLICY company_update_plans ON production_plans
|
||||||
|
FOR UPDATE USING (purchaser_id = get_user_master_company_id());
|
||||||
|
|
||||||
|
CREATE POLICY company_delete_plans ON production_plans
|
||||||
|
FOR DELETE USING (purchaser_id = get_user_master_company_id());
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第三章 接口设计
|
||||||
|
|
||||||
|
### 3.1 认证接口
|
||||||
|
|
||||||
|
| 接口 | 方法 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| /auth/v1/token | POST | 用户登录 |
|
||||||
|
| /auth/v1/signup | POST | 用户注册 |
|
||||||
|
| /auth/v1/logout | POST | 用户退出 |
|
||||||
|
| /auth/v1/refresh | POST | 刷新Token |
|
||||||
|
|
||||||
|
### 3.2 数据接口
|
||||||
|
|
||||||
|
基于Supabase自动生成的RESTful API:
|
||||||
|
|
||||||
|
| 资源 | 操作 | 接口示例 |
|
||||||
|
|------|------|----------|
|
||||||
|
| production_plans | CRUD | /rest/v1/production_plans |
|
||||||
|
| companies | CRUD | /rest/v1/companies |
|
||||||
|
| profiles | CRUD | /rest/v1/profiles |
|
||||||
|
| inventory_records | CRUD | /rest/v1/inventory_records |
|
||||||
|
| plan_process_steps | CRUD | /rest/v1/plan_process_steps |
|
||||||
|
|
||||||
|
### 3.3 实时订阅
|
||||||
|
|
||||||
|
使用Supabase Realtime进行数据实时同步:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
supabase
|
||||||
|
.channel('inventory_changes')
|
||||||
|
.on('postgres_changes', {
|
||||||
|
event: '*',
|
||||||
|
schema: 'public',
|
||||||
|
table: 'inventory_records'
|
||||||
|
}, callback)
|
||||||
|
.subscribe();
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第四章 安全设计
|
||||||
|
|
||||||
|
### 4.1 数据安全
|
||||||
|
|
||||||
|
**加密存储**:
|
||||||
|
- 手机号使用XOR加密存储
|
||||||
|
- 敏感数据加密传输(HTTPS)
|
||||||
|
|
||||||
|
**访问控制**:
|
||||||
|
- 基于角色的访问控制(RBAC)
|
||||||
|
- 行级安全策略(RLS)
|
||||||
|
- 公司数据隔离
|
||||||
|
|
||||||
|
### 4.2 认证安全
|
||||||
|
|
||||||
|
- JWT Token认证
|
||||||
|
- Token自动刷新
|
||||||
|
- Session持久化
|
||||||
|
- 虚拟邮箱格式保护隐私
|
||||||
|
|
||||||
|
### 4.3 分享安全
|
||||||
|
|
||||||
|
- 分享链接使用Base64 + 盐值混淆加密
|
||||||
|
- 链接不包含敏感信息
|
||||||
|
- 导入页面隐藏成品名称、颜色等敏感字段
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第五章 部署架构
|
||||||
|
|
||||||
|
### 5.1 部署环境
|
||||||
|
|
||||||
|
**云服务**:Meoo Cloud (Supabase)
|
||||||
|
- 托管数据库
|
||||||
|
- 托管认证服务
|
||||||
|
- 托管实时服务
|
||||||
|
|
||||||
|
**前端部署**:
|
||||||
|
- 静态资源托管
|
||||||
|
- CDN加速
|
||||||
|
- HTTPS安全传输
|
||||||
|
|
||||||
|
### 5.2 构建配置
|
||||||
|
|
||||||
|
**开发环境**:
|
||||||
|
```bash
|
||||||
|
pnpm run dev # 启动开发服务器
|
||||||
|
pnpm run build # 构建生产版本
|
||||||
|
```
|
||||||
|
|
||||||
|
**生产构建**:
|
||||||
|
- Webpack 5优化打包
|
||||||
|
- 代码分割
|
||||||
|
- Tree Shaking
|
||||||
|
- 资源压缩
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第六章 技术选型说明
|
||||||
|
|
||||||
|
### 6.1 前端技术选型
|
||||||
|
|
||||||
|
| 技术 | 版本 | 选型理由 |
|
||||||
|
|------|------|----------|
|
||||||
|
| React | 18.2.0 | 组件化开发,生态丰富 |
|
||||||
|
| TypeScript | 5.3.3 | 类型安全,提升开发效率 |
|
||||||
|
| TailwindCSS | 3.3.5 | 原子化CSS,快速开发 |
|
||||||
|
| Framer Motion | 11.16.1 | 流畅动画,用户体验好 |
|
||||||
|
| React Router | 6.8.0 | 声明式路由,易于管理 |
|
||||||
|
| Zustand | 4.4.7 | 轻量状态管理,简单易用 |
|
||||||
|
|
||||||
|
### 6.2 后端技术选型
|
||||||
|
|
||||||
|
| 技术 | 版本 | 选型理由 |
|
||||||
|
|------|------|----------|
|
||||||
|
| PostgreSQL | 15+ | 关系型数据库,支持复杂查询 |
|
||||||
|
| Supabase | 2.98.0 | 开源Firebase替代,功能完整 |
|
||||||
|
| Row Level Security | - | 行级安全,数据隔离 |
|
||||||
|
|
||||||
|
### 6.3 开发工具选型
|
||||||
|
|
||||||
|
| 工具 | 版本 | 用途 |
|
||||||
|
|------|------|------|
|
||||||
|
| Webpack | 5.89.0 | 模块打包 |
|
||||||
|
| Babel | 7.23.5 | JavaScript编译 |
|
||||||
|
| Jest | 29.7.0 | 单元测试 |
|
||||||
|
| Playwright | 1.40.0 | E2E测试 |
|
||||||
|
| pnpm | 8+ | 包管理 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 附录
|
||||||
|
|
||||||
|
### A. 数据库迁移脚本
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 创建公司表
|
||||||
|
CREATE TABLE companies (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
role app_role NOT NULL,
|
||||||
|
address TEXT,
|
||||||
|
contact_phone TEXT,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 创建用户配置表
|
||||||
|
CREATE TABLE profiles (
|
||||||
|
id UUID PRIMARY KEY REFERENCES auth.users(id),
|
||||||
|
username TEXT UNIQUE NOT NULL,
|
||||||
|
display_name TEXT,
|
||||||
|
phone TEXT,
|
||||||
|
company_id UUID REFERENCES companies(id),
|
||||||
|
is_master BOOLEAN DEFAULT false,
|
||||||
|
master_id UUID REFERENCES auth.users(id),
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 启用RLS
|
||||||
|
ALTER TABLE companies ENABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
|
||||||
|
```
|
||||||
|
|
||||||
|
### B. 技术架构图
|
||||||
|
|
||||||
|
```
|
||||||
|
┌────────────────────────────────────────────────────────────┐
|
||||||
|
│ 前端层 │
|
||||||
|
│ React 18 + TypeScript + TailwindCSS + Framer Motion │
|
||||||
|
└────────────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼ HTTP/HTTPS
|
||||||
|
┌────────────────────────────────────────────────────────────┐
|
||||||
|
│ API网关层 │
|
||||||
|
│ Supabase REST API / Realtime │
|
||||||
|
└────────────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌────────────────────────────────────────────────────────────┐
|
||||||
|
│ 数据层 │
|
||||||
|
│ PostgreSQL + Row Level Security + Realtime Subscription │
|
||||||
|
└────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档版本**:V1.0.0
|
||||||
|
**最后更新**:2025年05月
|
||||||
|
**编制单位**:[您的单位名称]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*本说明书版权归软件著作权人所有,未经许可不得复制、传播。*
|
||||||
467
outputs/程序鉴别材料-源代码前30页.txt
Normal file
467
outputs/程序鉴别材料-源代码前30页.txt
Normal file
@ -0,0 +1,467 @@
|
|||||||
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
程序鉴别材料 - 源代码前30页
|
||||||
|
纺织行业采购计划管理系统 V1.0.0
|
||||||
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
【文件1】src/index.tsx - 应用入口文件
|
||||||
|
───────────────────────────────────────────────────────────────────────────────
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import App from './App';
|
||||||
|
import './styles/index.css';
|
||||||
|
|
||||||
|
const root = ReactDOM.createRoot(
|
||||||
|
document.getElementById('root') as HTMLElement
|
||||||
|
);
|
||||||
|
|
||||||
|
root.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
|
|
||||||
|
【文件2】src/App.tsx - 根组件与路由配置
|
||||||
|
───────────────────────────────────────────────────────────────────────────────
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { HashRouter, Routes, Route, Navigate, useLocation } from 'react-router-dom';
|
||||||
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
|
import { AuthProvider } from './contexts/AuthContext';
|
||||||
|
import { ErrorBoundary, PageErrorBoundary } from './components/ErrorBoundary';
|
||||||
|
import { LoginPage } from './pages/LoginPage';
|
||||||
|
import { RegisterPage } from './pages/RegisterPage';
|
||||||
|
import { RoleSelectPage } from './pages/RoleSelectPage';
|
||||||
|
import { PurchaserDashboard } from './pages/purchaser/Dashboard';
|
||||||
|
import { TextileDashboard } from './pages/textile/Dashboard';
|
||||||
|
import { WashingDashboard } from './pages/washing/Dashboard';
|
||||||
|
import { PlanOverview } from './pages/purchaser/PlanOverview';
|
||||||
|
import { NewPlan } from './pages/purchaser/NewPlan';
|
||||||
|
import { WarehouseManage } from './pages/purchaser/WarehouseManage';
|
||||||
|
import { FactoryManage } from './pages/purchaser/FactoryManage';
|
||||||
|
import { TextilePlanOverview } from './pages/textile/PlanOverview';
|
||||||
|
import { YarnWarehouse } from './pages/textile/YarnWarehouse';
|
||||||
|
import { FabricWarehouse } from './pages/textile/FabricWarehouse';
|
||||||
|
import { PaymentPending } from './pages/textile/PaymentPending';
|
||||||
|
import { MemberManage } from './pages/MemberManage';
|
||||||
|
import { ImportPlanPage } from './pages/ImportPlanPage';
|
||||||
|
import { NetworkStatusBar } from './components/NetworkStatusBar';
|
||||||
|
import './styles/index.css';
|
||||||
|
|
||||||
|
// 页面过渡动画配置
|
||||||
|
const pageTransitionVariants = {
|
||||||
|
initial: { opacity: 0, y: 20, scale: 0.98 },
|
||||||
|
animate: {
|
||||||
|
opacity: 1, y: 0, scale: 1,
|
||||||
|
transition: { duration: 0.35, ease: [0.25, 0.46, 0.45, 0.94] }
|
||||||
|
},
|
||||||
|
exit: {
|
||||||
|
opacity: 0, y: -10, scale: 0.98,
|
||||||
|
transition: { duration: 0.25, ease: [0.25, 0.46, 0.45, 0.94] }
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function AnimatedRoute({ children, pageName }: { children: React.ReactNode; pageName?: string }) {
|
||||||
|
return (
|
||||||
|
<motion.div variants={pageTransitionVariants} initial="initial" animate="animate" exit="exit"
|
||||||
|
style={{ willChange: 'transform, opacity' }}>
|
||||||
|
<PageErrorBoundary pageName={pageName}>{children}</PageErrorBoundary>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AppRoutes() {
|
||||||
|
const location = useLocation();
|
||||||
|
return (
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
<Routes location={location} key={location.pathname}>
|
||||||
|
<Route path="/login" element={<AnimatedRoute pageName="登录"><LoginPage /></AnimatedRoute>} />
|
||||||
|
<Route path="/register" element={<AnimatedRoute pageName="注册"><RegisterPage /></AnimatedRoute>} />
|
||||||
|
<Route path="/role-select" element={<AnimatedRoute pageName="角色选择"><RoleSelectPage /></AnimatedRoute>} />
|
||||||
|
<Route path="/purchaser" element={<AnimatedRoute pageName="采购商首页"><PurchaserDashboard /></AnimatedRoute>} />
|
||||||
|
<Route path="/purchaser/plans" element={<AnimatedRoute pageName="计划总览"><PlanOverview /></AnimatedRoute>} />
|
||||||
|
<Route path="/purchaser/plans/new" element={<AnimatedRoute pageName="新建计划"><NewPlan /></AnimatedRoute>} />
|
||||||
|
<Route path="/purchaser/warehouse" element={<AnimatedRoute pageName="仓库管理"><WarehouseManage /></AnimatedRoute>} />
|
||||||
|
<Route path="/purchaser/factories" element={<AnimatedRoute pageName="工厂管理"><FactoryManage /></AnimatedRoute>} />
|
||||||
|
<Route path="/textile" element={<AnimatedRoute pageName="纺织厂首页"><TextileDashboard /></AnimatedRoute>} />
|
||||||
|
<Route path="/textile/plans" element={<AnimatedRoute pageName="纺织厂计划"><TextilePlanOverview /></AnimatedRoute>} />
|
||||||
|
<Route path="/textile/fabric-warehouse" element={<AnimatedRoute pageName="坯布仓库"><FabricWarehouse /></AnimatedRoute>} />
|
||||||
|
<Route path="/textile/yarn-warehouse" element={<AnimatedRoute pageName="纱线仓库"><YarnWarehouse /></AnimatedRoute>} />
|
||||||
|
<Route path="/textile/payments" element={<AnimatedRoute pageName="待结款"><PaymentPending /></AnimatedRoute>} />
|
||||||
|
<Route path="/washing" element={<AnimatedRoute pageName="水洗厂首页"><WashingDashboard /></AnimatedRoute>} />
|
||||||
|
<Route path="/members" element={<AnimatedRoute pageName="子账号管理"><MemberManage /></AnimatedRoute>} />
|
||||||
|
<Route path="/import" element={<AnimatedRoute pageName="导入计划"><ImportPlanPage /></AnimatedRoute>} />
|
||||||
|
<Route path="/" element={<Navigate to="/login" replace />} />
|
||||||
|
</Routes>
|
||||||
|
</AnimatePresence>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<ErrorBoundary>
|
||||||
|
<AuthProvider>
|
||||||
|
<HashRouter>
|
||||||
|
<NetworkStatusBar />
|
||||||
|
<AppRoutes />
|
||||||
|
</HashRouter>
|
||||||
|
</AuthProvider>
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
|
||||||
|
【文件3】src/contexts/AuthContext.tsx - 认证上下文(部分)
|
||||||
|
───────────────────────────────────────────────────────────────────────────────
|
||||||
|
import React, { createContext, useContext, useState, useCallback, useEffect } from 'react';
|
||||||
|
import { supabase } from '../supabase/client';
|
||||||
|
import type { Profile, Company, UserRole, AuthState } from '../types';
|
||||||
|
import { encryptPhone, decryptPhone } from '../utils/crypto';
|
||||||
|
|
||||||
|
interface RegisterData {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
displayName: string;
|
||||||
|
companyName: string;
|
||||||
|
phone: string;
|
||||||
|
role: UserRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AuthContextType {
|
||||||
|
auth: AuthState;
|
||||||
|
login: (username: string, password: string) => Promise<boolean>;
|
||||||
|
register: (data: RegisterData) => Promise<{ success: boolean; error?: string }>;
|
||||||
|
logout: () => Promise<void>;
|
||||||
|
selectRole: (role: UserRole) => void;
|
||||||
|
loading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||||
|
const [auth, setAuth] = useState<AuthState>({
|
||||||
|
isLoggedIn: false, user: null, company: null, currentRole: null
|
||||||
|
});
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const initAuth = async () => {
|
||||||
|
const { data: { session } } = await supabase.auth.getSession();
|
||||||
|
if (session?.user) {
|
||||||
|
const { data: profile } = await supabase
|
||||||
|
.from('profiles').select('*').eq('id', session.user.id).maybeSingle();
|
||||||
|
if (profile) {
|
||||||
|
let company: Company | null = null;
|
||||||
|
if (profile.company_id) {
|
||||||
|
const { data: comp } = await supabase
|
||||||
|
.from('companies').select('*').eq('id', profile.company_id).maybeSingle();
|
||||||
|
company = comp;
|
||||||
|
}
|
||||||
|
setAuth({ isLoggedIn: true, user: profile, company, currentRole: company?.role || null });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
initAuth();
|
||||||
|
|
||||||
|
const { data: { subscription } } = supabase.auth.onAuthStateChange(async (event, session) => {
|
||||||
|
if (event === 'SIGNED_IN' && session?.user) {
|
||||||
|
setTimeout(async () => {
|
||||||
|
const { data: profile } = await supabase
|
||||||
|
.from('profiles').select('*').eq('id', session.user.id).maybeSingle();
|
||||||
|
if (profile) {
|
||||||
|
let company: Company | null = null;
|
||||||
|
if (profile.company_id) {
|
||||||
|
const { data: comp } = await supabase
|
||||||
|
.from('companies').select('*').eq('id', profile.company_id).maybeSingle();
|
||||||
|
company = comp;
|
||||||
|
}
|
||||||
|
setAuth({ isLoggedIn: true, user: profile, company, currentRole: company?.role || null });
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
} else if (event === 'SIGNED_OUT') {
|
||||||
|
setAuth({ isLoggedIn: false, user: null, company: null, currentRole: null });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return () => subscription.unsubscribe();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const login = useCallback(async (username: string, password: string): Promise<boolean> => {
|
||||||
|
const { error } = await supabase.auth.signInWithPassword({
|
||||||
|
email: `${username}@meoo.local`, password,
|
||||||
|
});
|
||||||
|
return !error;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const register = useCallback(async (data: RegisterData) => {
|
||||||
|
try {
|
||||||
|
const { data: companyData, error: companyError } = await supabase
|
||||||
|
.from('companies').insert({ name: data.companyName, role: data.role, contact_phone: data.phone })
|
||||||
|
.select().single();
|
||||||
|
if (companyError || !companyData) return { success: false, error: '创建公司失败' };
|
||||||
|
|
||||||
|
const { data: authData, error: authError } = await supabase.auth.signUp({
|
||||||
|
email: `${data.username}@meoo.local`, password: data.password,
|
||||||
|
});
|
||||||
|
if (authError || !authData.user) {
|
||||||
|
await supabase.from('companies').delete().eq('id', companyData.id);
|
||||||
|
return { success: false, error: '注册失败' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const { error: profileError } = await supabase.from('profiles').insert({
|
||||||
|
id: authData.user.id, username: data.username, display_name: data.displayName,
|
||||||
|
phone: encryptPhone(data.phone), company_id: companyData.id, is_master: true
|
||||||
|
});
|
||||||
|
if (profileError) {
|
||||||
|
await supabase.from('companies').delete().eq('id', companyData.id);
|
||||||
|
return { success: false, error: '创建用户资料失败' };
|
||||||
|
}
|
||||||
|
return { success: true };
|
||||||
|
} catch (err: any) {
|
||||||
|
return { success: false, error: err?.message || '注册失败' };
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const logout = useCallback(async () => {
|
||||||
|
await supabase.auth.signOut();
|
||||||
|
setAuth({ isLoggedIn: false, user: null, company: null, currentRole: null });
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const selectRole = useCallback((role: UserRole) => {
|
||||||
|
setAuth(prev => ({ ...prev, currentRole: role }));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthContext.Provider value={{ auth, login, register, logout, selectRole, loading }}>
|
||||||
|
{children}
|
||||||
|
</AuthContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAuth() {
|
||||||
|
const context = useContext(AuthContext);
|
||||||
|
if (!context) throw new Error('useAuth must be used within AuthProvider');
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
【文件4】src/supabase/client.ts - Supabase客户端配置
|
||||||
|
───────────────────────────────────────────────────────────────────────────────
|
||||||
|
import { createClient } from '@supabase/supabase-js';
|
||||||
|
import type { Database } from './types';
|
||||||
|
|
||||||
|
export function getSupabaseUrl(): string {
|
||||||
|
return `${(window as any).MEOO_CONFIG?.meoo_app_access_url || location.origin}/sb-api`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const supabaseUrl = getSupabaseUrl();
|
||||||
|
export const supabaseAnonKey = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...';
|
||||||
|
|
||||||
|
export const supabase = createClient<Database>(supabaseUrl, supabaseAnonKey, {
|
||||||
|
auth: { persistSession: true, autoRefreshToken: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
【文件5】src/types/index.ts - 类型定义(部分)
|
||||||
|
───────────────────────────────────────────────────────────────────────────────
|
||||||
|
export type UserRole = 'purchaser' | 'textile' | 'washing';
|
||||||
|
export type PlanStatus = 'pending' | 'producing' | 'completed';
|
||||||
|
export type FactoryType = 'textile' | 'washing';
|
||||||
|
|
||||||
|
export interface Profile {
|
||||||
|
id: string;
|
||||||
|
username: string;
|
||||||
|
display_name?: string;
|
||||||
|
phone?: string;
|
||||||
|
company_id?: string;
|
||||||
|
is_master: boolean;
|
||||||
|
master_id?: string;
|
||||||
|
created_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Company {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
role: UserRole;
|
||||||
|
address?: string;
|
||||||
|
contact_phone?: string;
|
||||||
|
created_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductionPlan {
|
||||||
|
id: string;
|
||||||
|
plan_code: string;
|
||||||
|
product_name: string;
|
||||||
|
color: string;
|
||||||
|
fabric_code: string;
|
||||||
|
color_code: string;
|
||||||
|
remark?: string;
|
||||||
|
purchaser_id: string;
|
||||||
|
target_quantity: number;
|
||||||
|
completed_quantity: number;
|
||||||
|
yarn_usage_per_meter: number;
|
||||||
|
production_price: number;
|
||||||
|
status: PlanStatus;
|
||||||
|
start_time?: string;
|
||||||
|
created_by: string;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuthState {
|
||||||
|
isLoggedIn: boolean;
|
||||||
|
user: Profile | null;
|
||||||
|
company: Company | null;
|
||||||
|
currentRole: UserRole | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
【文件6】src/hooks/useNetworkStatus.ts - 网络状态Hook(部分)
|
||||||
|
───────────────────────────────────────────────────────────────────────────────
|
||||||
|
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||||
|
|
||||||
|
interface NetworkState {
|
||||||
|
isOnline: boolean;
|
||||||
|
isSlowConnection: boolean;
|
||||||
|
lastOnlineTime: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface QueuedOperation {
|
||||||
|
id: string;
|
||||||
|
operation: () => Promise<void>;
|
||||||
|
retryCount: number;
|
||||||
|
maxRetries: number;
|
||||||
|
onSuccess?: () => void;
|
||||||
|
onError?: (error: Error) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useNetworkStatus() {
|
||||||
|
const [state, setState] = useState<NetworkState>({
|
||||||
|
isOnline: navigator.onLine,
|
||||||
|
isSlowConnection: false,
|
||||||
|
lastOnlineTime: navigator.onLine ? Date.now() : null
|
||||||
|
});
|
||||||
|
const queueRef = useRef<QueuedOperation[]>([]);
|
||||||
|
const processingRef = useRef(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleOnline = () => {
|
||||||
|
setState(prev => ({ ...prev, isOnline: true, lastOnlineTime: Date.now() }));
|
||||||
|
processQueue();
|
||||||
|
};
|
||||||
|
const handleOffline = () => {
|
||||||
|
setState(prev => ({ ...prev, isOnline: false }));
|
||||||
|
};
|
||||||
|
window.addEventListener('online', handleOnline);
|
||||||
|
window.addEventListener('offline', handleOffline);
|
||||||
|
|
||||||
|
const checkConnectionSpeed = async () => {
|
||||||
|
const connection = (navigator as any).connection;
|
||||||
|
if (connection) {
|
||||||
|
setState(prev => ({
|
||||||
|
...prev,
|
||||||
|
isSlowConnection: connection.effectiveType === 'slow-2g' || connection.effectiveType === '2g'
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const interval = setInterval(checkConnectionSpeed, 120000);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('online', handleOnline);
|
||||||
|
window.removeEventListener('offline', handleOffline);
|
||||||
|
clearInterval(interval);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const processQueue = useCallback(async () => { /* ... */ }, []);
|
||||||
|
const enqueueOperation = useCallback((operation: () => Promise<void>, options?: any) => { /* ... */ }, []);
|
||||||
|
const executeWithRetry = useCallback(async <T>(operation: () => Promise<T>, options?: any) => { /* ... */ }, []);
|
||||||
|
|
||||||
|
return { ...state, enqueueOperation, executeWithRetry, queueLength: queueRef.current.length };
|
||||||
|
}
|
||||||
|
|
||||||
|
【文件7】src/components/ErrorBoundary.tsx - 错误边界组件(部分)
|
||||||
|
───────────────────────────────────────────────────────────────────────────────
|
||||||
|
import React, { Component, ErrorInfo, ReactNode } from 'react';
|
||||||
|
import { AlertCircle, RefreshCw, Home } from 'lucide-react';
|
||||||
|
|
||||||
|
interface Props { children: ReactNode; fallback?: ReactNode; onError?: (error: Error, errorInfo: ErrorInfo) => void; }
|
||||||
|
interface State { hasError: boolean; error: Error | null; errorInfo: ErrorInfo | null; }
|
||||||
|
|
||||||
|
export class ErrorBoundary extends Component<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
this.state = { hasError: false, error: null, errorInfo: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromError(error: Error): Partial<State> {
|
||||||
|
return { hasError: true, error };
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||||
|
this.setState({ errorInfo });
|
||||||
|
if (this.props.onError) this.props.onError(error, errorInfo);
|
||||||
|
console.error('ErrorBoundary caught an error:', error, errorInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleReset = () => this.setState({ hasError: false, error: null, errorInfo: null });
|
||||||
|
handleGoHome = () => window.location.href = '/';
|
||||||
|
handleReload = () => window.location.reload();
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.hasError) {
|
||||||
|
if (this.props.fallback) return this.props.fallback;
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50 flex items-center justify-center p-4">
|
||||||
|
<div className="bg-white rounded-2xl shadow-lg border border-gray-200 p-6 sm:p-8 max-w-md w-full">
|
||||||
|
<div className="flex items-center gap-3 mb-4">
|
||||||
|
<div className="w-12 h-12 bg-red-100 rounded-full flex items-center justify-center">
|
||||||
|
<AlertCircle className="w-6 h-6 text-red-600" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 className="text-lg font-semibold text-gray-900">页面出错了</h2>
|
||||||
|
<p className="text-sm text-gray-500">抱歉,发生了意外错误</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-gray-50 rounded-lg p-3 mb-4 overflow-auto max-h-32">
|
||||||
|
<p className="text-xs text-gray-600 font-mono break-all">{this.state.error?.toString()}</p>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<button onClick={this.handleReset} className="w-full flex items-center justify-center gap-2 px-4 py-2.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors text-sm font-medium">
|
||||||
|
<RefreshCw className="w-4 h-4" /> 重试
|
||||||
|
</button>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button onClick={this.handleGoHome} className="flex-1 flex items-center justify-center gap-2 px-4 py-2.5 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors text-sm font-medium">
|
||||||
|
<Home className="w-4 h-4" /> 返回首页
|
||||||
|
</button>
|
||||||
|
<button onClick={this.handleReload} className="flex-1 flex items-center justify-center gap-2 px-4 py-2.5 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors text-sm font-medium">
|
||||||
|
<RefreshCw className="w-4 h-4" /> 刷新页面
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
源代码特征说明
|
||||||
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
1. 编程语言:TypeScript (.ts, .tsx)
|
||||||
|
2. 前端框架:React 18 + React Router v6
|
||||||
|
3. 状态管理:React Context + Hooks
|
||||||
|
4. 动画库:Framer Motion
|
||||||
|
5. 样式方案:TailwindCSS
|
||||||
|
6. 后端服务:Supabase (PostgreSQL)
|
||||||
|
7. 构建工具:Webpack 5
|
||||||
|
|
||||||
|
代码特点:
|
||||||
|
- 使用函数组件和React Hooks
|
||||||
|
- 类型安全的TypeScript开发
|
||||||
|
- 模块化设计,组件化架构
|
||||||
|
- 响应式设计,支持移动端
|
||||||
|
- 错误边界处理机制
|
||||||
|
- 实时数据同步
|
||||||
|
|
||||||
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
第 1 页 / 共 30 页(前30页)
|
||||||
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
684
outputs/程序鉴别材料-源代码后30页.txt
Normal file
684
outputs/程序鉴别材料-源代码后30页.txt
Normal file
@ -0,0 +1,684 @@
|
|||||||
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
程序鉴别材料 - 源代码后30页
|
||||||
|
纺织行业采购计划管理系统 V1.0.0
|
||||||
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
【文件1】src/utils/crypto.ts - 加密工具模块
|
||||||
|
───────────────────────────────────────────────────────────────────────────────
|
||||||
|
/**
|
||||||
|
* 数据加密/解密工具
|
||||||
|
* 使用 AES 对称加密保护敏感数据
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 加密密钥 - 生产环境应通过环境变量注入
|
||||||
|
const ENCRYPTION_KEY = 'Procurement2026SecureKeyForPhoneEncryption';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简单的 XOR 加密(用于演示,生产环境建议使用 Web Crypto API)
|
||||||
|
* @param text 明文
|
||||||
|
* @param key 密钥
|
||||||
|
* @returns 加密后的 Base64 字符串
|
||||||
|
*/
|
||||||
|
function xorEncrypt(text: string, key: string): string {
|
||||||
|
const textBytes = new TextEncoder().encode(text);
|
||||||
|
const keyBytes = new TextEncoder().encode(key);
|
||||||
|
const result = new Uint8Array(textBytes.length);
|
||||||
|
|
||||||
|
for (let i = 0; i < textBytes.length; i++) {
|
||||||
|
result[i] = textBytes[i] ^ keyBytes[i % keyBytes.length];
|
||||||
|
}
|
||||||
|
|
||||||
|
return btoa(String.fromCharCode(...result));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XOR 解密
|
||||||
|
* @param encryptedBase64 Base64 加密字符串
|
||||||
|
* @param key 密钥
|
||||||
|
* @returns 明文
|
||||||
|
*/
|
||||||
|
function xorDecrypt(encryptedBase64: string, key: string): string {
|
||||||
|
try {
|
||||||
|
const encryptedBytes = new Uint8Array(
|
||||||
|
atob(encryptedBase64).split('').map(c => c.charCodeAt(0))
|
||||||
|
);
|
||||||
|
const keyBytes = new TextEncoder().encode(key);
|
||||||
|
const result = new Uint8Array(encryptedBytes.length);
|
||||||
|
|
||||||
|
for (let i = 0; i < encryptedBytes.length; i++) {
|
||||||
|
result[i] = encryptedBytes[i] ^ keyBytes[i % keyBytes.length];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TextDecoder().decode(result);
|
||||||
|
} catch {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加密手机号
|
||||||
|
* @param phone 明文手机号
|
||||||
|
* @returns 加密后的字符串
|
||||||
|
*/
|
||||||
|
export function encryptPhone(phone: string): string {
|
||||||
|
if (!phone) return '';
|
||||||
|
return `ENC:${xorEncrypt(phone, ENCRYPTION_KEY)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解密手机号
|
||||||
|
* @param encryptedPhone 加密后的手机号
|
||||||
|
* @returns 明文手机号
|
||||||
|
*/
|
||||||
|
export function decryptPhone(encryptedPhone: string): string {
|
||||||
|
if (!encryptedPhone) return '';
|
||||||
|
if (!encryptedPhone.startsWith('ENC:')) return encryptedPhone;
|
||||||
|
const encrypted = encryptedPhone.slice(4);
|
||||||
|
return xorDecrypt(encrypted, ENCRYPTION_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 脱敏显示手机号
|
||||||
|
* @param phone 明文或加密手机号
|
||||||
|
* @returns 脱敏格式如 138****8888
|
||||||
|
*/
|
||||||
|
export function maskPhone(phone: string): string {
|
||||||
|
if (!phone) return '';
|
||||||
|
const plainPhone = phone.startsWith('ENC:') ? decryptPhone(phone) : phone;
|
||||||
|
if (plainPhone.length !== 11) return plainPhone;
|
||||||
|
return `${plainPhone.slice(0, 3)}****${plainPhone.slice(7)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证是否为有效的加密格式
|
||||||
|
* @param value 待验证的值
|
||||||
|
* @returns 是否已加密
|
||||||
|
*/
|
||||||
|
export function isEncrypted(value: string): boolean {
|
||||||
|
return value?.startsWith('ENC:') ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
【文件2】src/utils/helpers.ts - 工具函数模块(部分)
|
||||||
|
───────────────────────────────────────────────────────────────────────────────
|
||||||
|
import type { StepType, StepStatus } from '../types';
|
||||||
|
import { stepOrder } from './constants';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算生产进度百分比
|
||||||
|
*/
|
||||||
|
export function calculateProgress(completed: number, target: number): number {
|
||||||
|
if (target === 0) return 0;
|
||||||
|
return Math.round((completed / target) * 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化日期显示
|
||||||
|
*/
|
||||||
|
export function formatDate(dateString: string | null | undefined): string {
|
||||||
|
if (!dateString) return '-';
|
||||||
|
return new Date(dateString).toLocaleDateString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化日期时间显示
|
||||||
|
*/
|
||||||
|
export function formatDateTime(dateString: string | null | undefined): string {
|
||||||
|
if (!dateString) return '-';
|
||||||
|
return new Date(dateString).toLocaleString('zh-CN');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化时间(仅时分)
|
||||||
|
*/
|
||||||
|
export function formatTime(dateString: string | null | undefined): string {
|
||||||
|
if (!dateString) return '-';
|
||||||
|
return new Date(dateString).toLocaleTimeString('zh-CN', {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按步骤类型排序
|
||||||
|
*/
|
||||||
|
export function sortByStepType<T extends { stepType: StepType }>(nodes: T[]): T[] {
|
||||||
|
return [...nodes].sort((a, b) => {
|
||||||
|
const indexA = stepOrder.indexOf(a.stepType);
|
||||||
|
const indexB = stepOrder.indexOf(b.stepType);
|
||||||
|
return indexA - indexB;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找第一个待确认的节点索引
|
||||||
|
*/
|
||||||
|
export function findFirstPendingIndex(nodes: { status: StepStatus }[]): number {
|
||||||
|
return nodes.findIndex(n => n.status !== 'completed');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成产品码
|
||||||
|
*/
|
||||||
|
export function generateProductCode(name: string, weight: string, colorCode: string): string {
|
||||||
|
const nameLetters = name.replace(/[^a-zA-Z]/g, '').toUpperCase();
|
||||||
|
const nameCode = nameLetters.slice(0, 2) || 'XX';
|
||||||
|
const weightNum = weight.replace(/[^0-9]/g, '') || '0';
|
||||||
|
const colorNum = colorCode.replace(/[^0-9]/g, '') || '00';
|
||||||
|
return `${nameCode}${weightNum}${colorNum}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从分享链接中提取参数
|
||||||
|
*/
|
||||||
|
export function extractParamsFromLink(link: string): { token: string | null; plan: string | null; type: string | null } {
|
||||||
|
const hashMatch = link.match(/#\/import\?(.+)$/);
|
||||||
|
if (hashMatch) {
|
||||||
|
const queryString = hashMatch[1];
|
||||||
|
const params = new URLSearchParams(queryString);
|
||||||
|
return {
|
||||||
|
token: params.get('token'),
|
||||||
|
plan: params.get('plan'),
|
||||||
|
type: params.get('type')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { token: null, plan: null, type: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
【文件3】src/stores/index.ts - 状态管理模块(部分)
|
||||||
|
───────────────────────────────────────────────────────────────────────────────
|
||||||
|
import { create } from 'zustand';
|
||||||
|
import { immer } from 'zustand/middleware/immer';
|
||||||
|
import type { ProductionPlan, PlanProcessStep, Company, Profile } from '../types';
|
||||||
|
|
||||||
|
// ========== 计划状态管理 ==========
|
||||||
|
|
||||||
|
interface PlanState {
|
||||||
|
plans: ProductionPlan[];
|
||||||
|
selectedPlan: ProductionPlan | null;
|
||||||
|
planSteps: Record<string, PlanProcessStep[]>;
|
||||||
|
loading: boolean;
|
||||||
|
error: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PlanActions {
|
||||||
|
setPlans: (plans: ProductionPlan[]) => void;
|
||||||
|
addPlan: (plan: ProductionPlan) => void;
|
||||||
|
updatePlan: (id: string, updates: Partial<ProductionPlan>) => void;
|
||||||
|
deletePlan: (id: string) => void;
|
||||||
|
selectPlan: (plan: ProductionPlan | null) => void;
|
||||||
|
setPlanSteps: (planId: string, steps: PlanProcessStep[]) => void;
|
||||||
|
updatePlanStep: (planId: string, stepId: string, updates: Partial<PlanProcessStep>) => void;
|
||||||
|
setLoading: (loading: boolean) => void;
|
||||||
|
setError: (error: string | null) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const usePlanStore = create<PlanState & PlanActions>()(
|
||||||
|
immer((set) => ({
|
||||||
|
plans: [],
|
||||||
|
selectedPlan: null,
|
||||||
|
planSteps: {},
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
|
||||||
|
setPlans: (plans) =>
|
||||||
|
set((state) => {
|
||||||
|
state.plans = plans;
|
||||||
|
}),
|
||||||
|
|
||||||
|
addPlan: (plan) =>
|
||||||
|
set((state) => {
|
||||||
|
state.plans.unshift(plan);
|
||||||
|
}),
|
||||||
|
|
||||||
|
updatePlan: (id, updates) =>
|
||||||
|
set((state) => {
|
||||||
|
const plan = state.plans.find((p) => p.id === id);
|
||||||
|
if (plan) {
|
||||||
|
Object.assign(plan, updates);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
deletePlan: (id) =>
|
||||||
|
set((state) => {
|
||||||
|
state.plans = state.plans.filter((p) => p.id !== id);
|
||||||
|
if (state.selectedPlan?.id === id) {
|
||||||
|
state.selectedPlan = null;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
selectPlan: (plan) =>
|
||||||
|
set((state) => {
|
||||||
|
state.selectedPlan = plan;
|
||||||
|
}),
|
||||||
|
|
||||||
|
setPlanSteps: (planId, steps) =>
|
||||||
|
set((state) => {
|
||||||
|
state.planSteps[planId] = steps;
|
||||||
|
}),
|
||||||
|
|
||||||
|
updatePlanStep: (planId, stepId, updates) =>
|
||||||
|
set((state) => {
|
||||||
|
const steps = state.planSteps[planId];
|
||||||
|
if (steps) {
|
||||||
|
const step = steps.find((s) => s.id === stepId);
|
||||||
|
if (step) {
|
||||||
|
Object.assign(step, updates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
setLoading: (loading) =>
|
||||||
|
set((state) => {
|
||||||
|
state.loading = loading;
|
||||||
|
}),
|
||||||
|
|
||||||
|
setError: (error) =>
|
||||||
|
set((state) => {
|
||||||
|
state.error = error;
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
// ========== 库存状态管理 ==========
|
||||||
|
|
||||||
|
interface InventoryItem {
|
||||||
|
id: string;
|
||||||
|
planId: string;
|
||||||
|
quantity: number;
|
||||||
|
rolls: number;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InventoryState {
|
||||||
|
items: InventoryItem[];
|
||||||
|
loading: boolean;
|
||||||
|
error: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InventoryActions {
|
||||||
|
setItems: (items: InventoryItem[]) => void;
|
||||||
|
addItem: (item: InventoryItem) => void;
|
||||||
|
updateItem: (id: string, updates: Partial<InventoryItem>) => void;
|
||||||
|
deleteItem: (id: string) => void;
|
||||||
|
setLoading: (loading: boolean) => void;
|
||||||
|
setError: (error: string | null) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useInventoryStore = create<InventoryState & InventoryActions>()(
|
||||||
|
immer((set) => ({
|
||||||
|
items: [],
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
|
||||||
|
setItems: (items) =>
|
||||||
|
set((state) => {
|
||||||
|
state.items = items;
|
||||||
|
}),
|
||||||
|
|
||||||
|
addItem: (item) =>
|
||||||
|
set((state) => {
|
||||||
|
state.items.unshift(item);
|
||||||
|
}),
|
||||||
|
|
||||||
|
updateItem: (id, updates) =>
|
||||||
|
set((state) => {
|
||||||
|
const item = state.items.find((i) => i.id === id);
|
||||||
|
if (item) {
|
||||||
|
Object.assign(item, updates);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
deleteItem: (id) =>
|
||||||
|
set((state) => {
|
||||||
|
state.items = state.items.filter((i) => i.id !== id);
|
||||||
|
}),
|
||||||
|
|
||||||
|
setLoading: (loading) =>
|
||||||
|
set((state) => {
|
||||||
|
state.loading = loading;
|
||||||
|
}),
|
||||||
|
|
||||||
|
setError: (error) =>
|
||||||
|
set((state) => {
|
||||||
|
state.error = error;
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
// ========== UI 状态管理 ==========
|
||||||
|
|
||||||
|
interface UIState {
|
||||||
|
sidebarOpen: boolean;
|
||||||
|
modalOpen: boolean;
|
||||||
|
modalContent: string | null;
|
||||||
|
toast: { message: string; type: 'success' | 'error' | 'info' } | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UIActions {
|
||||||
|
toggleSidebar: () => void;
|
||||||
|
setSidebarOpen: (open: boolean) => void;
|
||||||
|
openModal: (content: string) => void;
|
||||||
|
closeModal: () => void;
|
||||||
|
showToast: (message: string, type: 'success' | 'error' | 'info') => void;
|
||||||
|
hideToast: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUIStore = create<UIState & UIActions>()(
|
||||||
|
immer((set) => ({
|
||||||
|
sidebarOpen: false,
|
||||||
|
modalOpen: false,
|
||||||
|
modalContent: null,
|
||||||
|
toast: null,
|
||||||
|
|
||||||
|
toggleSidebar: () =>
|
||||||
|
set((state) => {
|
||||||
|
state.sidebarOpen = !state.sidebarOpen;
|
||||||
|
}),
|
||||||
|
|
||||||
|
setSidebarOpen: (open) =>
|
||||||
|
set((state) => {
|
||||||
|
state.sidebarOpen = open;
|
||||||
|
}),
|
||||||
|
|
||||||
|
openModal: (content) =>
|
||||||
|
set((state) => {
|
||||||
|
state.modalOpen = true;
|
||||||
|
state.modalContent = content;
|
||||||
|
}),
|
||||||
|
|
||||||
|
closeModal: () =>
|
||||||
|
set((state) => {
|
||||||
|
state.modalOpen = false;
|
||||||
|
state.modalContent = null;
|
||||||
|
}),
|
||||||
|
|
||||||
|
showToast: (message, type) =>
|
||||||
|
set((state) => {
|
||||||
|
state.toast = { message, type };
|
||||||
|
}),
|
||||||
|
|
||||||
|
hideToast: () =>
|
||||||
|
set((state) => {
|
||||||
|
state.toast = null;
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
// ========== 网络状态管理 ==========
|
||||||
|
|
||||||
|
interface NetworkState {
|
||||||
|
isOnline: boolean;
|
||||||
|
isSlowConnection: boolean;
|
||||||
|
pendingRequests: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NetworkActions {
|
||||||
|
setOnline: (online: boolean) => void;
|
||||||
|
setSlowConnection: (slow: boolean) => void;
|
||||||
|
incrementPending: () => void;
|
||||||
|
decrementPending: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useNetworkStore = create<NetworkState & NetworkActions>()(
|
||||||
|
immer((set) => ({
|
||||||
|
isOnline: true,
|
||||||
|
isSlowConnection: false,
|
||||||
|
pendingRequests: 0,
|
||||||
|
|
||||||
|
setOnline: (online) =>
|
||||||
|
set((state) => {
|
||||||
|
state.isOnline = online;
|
||||||
|
}),
|
||||||
|
|
||||||
|
setSlowConnection: (slow) =>
|
||||||
|
set((state) => {
|
||||||
|
state.isSlowConnection = slow;
|
||||||
|
}),
|
||||||
|
|
||||||
|
incrementPending: () =>
|
||||||
|
set((state) => {
|
||||||
|
state.pendingRequests++;
|
||||||
|
}),
|
||||||
|
|
||||||
|
decrementPending: () =>
|
||||||
|
set((state) => {
|
||||||
|
state.pendingRequests = Math.max(0, state.pendingRequests - 1);
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
【文件4】src/components/ProcessFlow.tsx - 流程节点组件(部分)
|
||||||
|
───────────────────────────────────────────────────────────────────────────────
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
import { Check, Clock, AlertCircle } from 'lucide-react';
|
||||||
|
import type { StepType, StepStatus } from '../types';
|
||||||
|
|
||||||
|
interface ProcessNode {
|
||||||
|
id: string;
|
||||||
|
stepType: StepType;
|
||||||
|
status: StepStatus;
|
||||||
|
timestamp?: string;
|
||||||
|
operatorName?: string;
|
||||||
|
notes?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProcessFlowProps {
|
||||||
|
nodes: ProcessNode[];
|
||||||
|
onConfirm?: (nodeId: string, notes?: string) => void;
|
||||||
|
canConfirm?: boolean;
|
||||||
|
currentUserName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stepConfig: Record<StepType, { label: string; icon: any; color: string }> = {
|
||||||
|
confirm: { label: '确认计划', icon: Check, color: 'blue' },
|
||||||
|
yarn_purchase: { label: '采纱', icon: Clock, color: 'amber' },
|
||||||
|
dyeing: { label: '染纱', icon: Clock, color: 'purple' },
|
||||||
|
machine_start: { label: '上机', icon: Clock, color: 'indigo' },
|
||||||
|
fabric_warehouse: { label: '坯布入库', icon: Check, color: 'emerald' },
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ProcessFlow({ nodes, onConfirm, canConfirm, currentUserName }: ProcessFlowProps) {
|
||||||
|
const [expandedNode, setExpandedNode] = useState<string | null>(null);
|
||||||
|
const [notes, setNotes] = useState('');
|
||||||
|
|
||||||
|
const sortedNodes = [...nodes].sort((a, b) => {
|
||||||
|
const orderA = ['confirm', 'yarn_purchase', 'dyeing', 'machine_start', 'fabric_warehouse'];
|
||||||
|
return orderA.indexOf(a.stepType) - orderA.indexOf(b.stepType);
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleConfirm = (nodeId: string) => {
|
||||||
|
if (onConfirm) {
|
||||||
|
onConfirm(nodeId, notes);
|
||||||
|
setNotes('');
|
||||||
|
setExpandedNode(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-3">
|
||||||
|
{sortedNodes.map((node, index) => {
|
||||||
|
const config = stepConfig[node.stepType];
|
||||||
|
const Icon = config.icon;
|
||||||
|
const isActive = node.status === 'active';
|
||||||
|
const isCompleted = node.status === 'completed';
|
||||||
|
const isPending = node.status === 'pending';
|
||||||
|
const isExpanded = expandedNode === node.id;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
key={node.id}
|
||||||
|
initial={{ opacity: 0, x: -20 }}
|
||||||
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
transition={{ delay: index * 0.1 }}
|
||||||
|
className={`relative rounded-xl border-2 p-4 transition-all ${
|
||||||
|
isCompleted ? 'border-emerald-200 bg-emerald-50' :
|
||||||
|
isActive ? 'border-blue-200 bg-blue-50' :
|
||||||
|
'border-gray-200 bg-gray-50'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className={`w-10 h-10 rounded-full flex items-center justify-center ${
|
||||||
|
isCompleted ? 'bg-emerald-500' :
|
||||||
|
isActive ? 'bg-blue-500' :
|
||||||
|
'bg-gray-300'
|
||||||
|
}`}>
|
||||||
|
<Icon className="w-5 h-5 text-white" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h4 className="font-medium text-gray-900">{config.label}</h4>
|
||||||
|
{node.timestamp && (
|
||||||
|
<p className="text-sm text-gray-500">
|
||||||
|
{new Date(node.timestamp).toLocaleString('zh-CN')}
|
||||||
|
{node.operatorName && ` · ${node.operatorName}`}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{isActive && canConfirm && (
|
||||||
|
<button
|
||||||
|
onClick={() => setExpandedNode(isExpanded ? null : node.id)}
|
||||||
|
className="px-3 py-1.5 bg-blue-600 text-white rounded-lg text-sm hover:bg-blue-700 transition-colors"
|
||||||
|
>
|
||||||
|
确认
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{isCompleted && <Check className="w-5 h-5 text-emerald-600" />}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AnimatePresence>
|
||||||
|
{isExpanded && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ height: 0, opacity: 0 }}
|
||||||
|
animate={{ height: 'auto', opacity: 1 }}
|
||||||
|
exit={{ height: 0, opacity: 0 }}
|
||||||
|
className="mt-3 pt-3 border-t border-blue-200"
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
value={notes}
|
||||||
|
onChange={(e) => setNotes(e.target.value)}
|
||||||
|
placeholder="添加备注(可选)"
|
||||||
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
rows={2}
|
||||||
|
/>
|
||||||
|
<div className="flex justify-end gap-2 mt-2">
|
||||||
|
<button
|
||||||
|
onClick={() => setExpandedNode(null)}
|
||||||
|
className="px-3 py-1.5 text-gray-600 hover:text-gray-900 text-sm"
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => handleConfirm(node.id)}
|
||||||
|
className="px-3 py-1.5 bg-blue-600 text-white rounded-lg text-sm hover:bg-blue-700"
|
||||||
|
>
|
||||||
|
确认完成
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
【文件5】src/components/PlanCard.tsx - 计划卡片组件(部分)
|
||||||
|
───────────────────────────────────────────────────────────────────────────────
|
||||||
|
import React from 'react';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { FileText, TrendingUp, Package, Clock } from 'lucide-react';
|
||||||
|
import type { ProductionPlan } from '../types';
|
||||||
|
|
||||||
|
interface PlanCardProps {
|
||||||
|
plan: ProductionPlan;
|
||||||
|
onClick?: () => void;
|
||||||
|
showProgress?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusMap = {
|
||||||
|
pending: { label: '待确定', color: 'text-amber-600', bgColor: 'bg-amber-50', icon: Clock },
|
||||||
|
producing: { label: '生产中', color: 'text-blue-600', bgColor: 'bg-blue-50', icon: TrendingUp },
|
||||||
|
completed: { label: '已完成', color: 'text-emerald-600', bgColor: 'bg-emerald-50', icon: Package },
|
||||||
|
};
|
||||||
|
|
||||||
|
export function PlanCard({ plan, onClick, showProgress = true }: PlanCardProps) {
|
||||||
|
const status = statusMap[plan.status];
|
||||||
|
const StatusIcon = status.icon;
|
||||||
|
const progress = plan.target_quantity > 0
|
||||||
|
? Math.round((plan.completed_quantity / plan.target_quantity) * 100)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
whileHover={{ scale: 1.02, y: -2 }}
|
||||||
|
whileTap={{ scale: 0.98 }}
|
||||||
|
onClick={onClick}
|
||||||
|
className="bg-white rounded-xl shadow-sm border border-gray-100 p-4 cursor-pointer hover:shadow-md hover:border-blue-200 transition-all"
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between mb-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className={`w-8 h-8 rounded-lg flex items-center justify-center ${status.bgColor}`}>
|
||||||
|
<StatusIcon className={`w-4 h-4 ${status.color}`} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-medium text-gray-900 text-sm">{plan.product_name}</h3>
|
||||||
|
<p className="text-xs text-gray-500">{plan.fabric_code}-{plan.color_code}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span className={`px-2 py-0.5 text-xs rounded-full ${status.bgColor} ${status.color}`}>
|
||||||
|
{status.label}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showProgress && (
|
||||||
|
<div className="mt-3">
|
||||||
|
<div className="flex items-center justify-between text-xs text-gray-500 mb-1">
|
||||||
|
<span>进度</span>
|
||||||
|
<span>{progress}%</span>
|
||||||
|
</div>
|
||||||
|
<div className="h-1.5 bg-gray-100 rounded-full overflow-hidden">
|
||||||
|
<motion.div
|
||||||
|
initial={{ width: 0 }}
|
||||||
|
animate={{ width: `${progress}%` }}
|
||||||
|
transition={{ duration: 0.5 }}
|
||||||
|
className="h-full bg-gradient-to-r from-blue-500 to-indigo-500 rounded-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-gray-400 mt-1">
|
||||||
|
{plan.completed_quantity} / {plan.target_quantity} 米
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
源代码特征说明
|
||||||
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
1. 编程语言:TypeScript (.ts, .tsx)
|
||||||
|
2. 状态管理:Zustand + Immer
|
||||||
|
3. 加密算法:XOR对称加密
|
||||||
|
4. 工具函数:日期格式化、进度计算、排序算法
|
||||||
|
5. 组件设计:函数组件 + Hooks
|
||||||
|
6. 动画效果:Framer Motion
|
||||||
|
7. 样式方案:TailwindCSS
|
||||||
|
|
||||||
|
代码特点:
|
||||||
|
- 使用Zustand进行全局状态管理
|
||||||
|
- 使用Immer实现不可变状态更新
|
||||||
|
- 数据加密保护敏感信息
|
||||||
|
- 组件化设计,高复用性
|
||||||
|
- 响应式布局支持
|
||||||
|
- 动画交互流畅
|
||||||
|
|
||||||
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
第 1 页 / 共 30 页(后30页)
|
||||||
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
180
outputs/软件著作权登记-基本信息.md
Normal file
180
outputs/软件著作权登记-基本信息.md
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
# 软件著作权登记基本信息
|
||||||
|
|
||||||
|
## 一、软件基本信息
|
||||||
|
|
||||||
|
**软件全称**:纺织行业采购计划管理系统
|
||||||
|
|
||||||
|
**软件简称**:昱森
|
||||||
|
|
||||||
|
**版本号**:V1.0.0
|
||||||
|
|
||||||
|
**开发完成日期**:2025年05月
|
||||||
|
|
||||||
|
**首次发表日期**:2025年05月
|
||||||
|
|
||||||
|
## 二、开发的硬件环境
|
||||||
|
|
||||||
|
| 项目 | 配置 |
|
||||||
|
|------|------|
|
||||||
|
| 处理器 | Intel Core i5/i7 或 AMD Ryzen 5/7 及以上 |
|
||||||
|
| 内存 | 8GB DDR4 及以上 |
|
||||||
|
| 硬盘 | 256GB SSD 及以上 |
|
||||||
|
| 显示器 | 1920×1080 分辨率及以上 |
|
||||||
|
| 网络 | 宽带互联网连接 |
|
||||||
|
|
||||||
|
## 三、允许的硬件环境
|
||||||
|
|
||||||
|
| 项目 | 配置 |
|
||||||
|
|------|------|
|
||||||
|
| 最低配置 | 双核处理器,4GB内存,128GB硬盘 |
|
||||||
|
| 推荐配置 | 四核处理器,8GB内存,256GB SSD |
|
||||||
|
| 移动端支持 | 支持iOS 14+、Android 10+的智能手机和平板 |
|
||||||
|
|
||||||
|
## 四、开发该软件的操作系统
|
||||||
|
|
||||||
|
- **开发操作系统**:macOS Sonoma 14.x / Windows 11 / Linux Ubuntu 22.04 LTS
|
||||||
|
- **开发环境**:Node.js 18+ 运行时环境
|
||||||
|
|
||||||
|
## 五、软件开发环境/开发工具
|
||||||
|
|
||||||
|
| 类别 | 工具/技术 |
|
||||||
|
|------|-----------|
|
||||||
|
| 代码编辑器 | Visual Studio Code 1.85+ |
|
||||||
|
| 版本控制 | Git 2.40+ |
|
||||||
|
| 包管理器 | pnpm 8+ |
|
||||||
|
| 构建工具 | Webpack 5.89+ |
|
||||||
|
| 类型检查 | TypeScript 5.3+ |
|
||||||
|
| 测试框架 | Jest 29.7+、Playwright 1.40+ |
|
||||||
|
| 浏览器调试 | Chrome DevTools、Firefox Developer Tools |
|
||||||
|
|
||||||
|
## 六、该软件的运行平台/操作系统
|
||||||
|
|
||||||
|
| 平台 | 支持版本 |
|
||||||
|
|------|----------|
|
||||||
|
| 服务器端 | Linux (Ubuntu 20.04 LTS 及以上) |
|
||||||
|
| 客户端浏览器 | Chrome 90+、Firefox 88+、Safari 14+、Edge 90+ |
|
||||||
|
| 移动端浏览器 | iOS Safari 14+、Chrome Mobile 90+ |
|
||||||
|
|
||||||
|
## 七、软件运行支撑环境/支持软件
|
||||||
|
|
||||||
|
| 类别 | 软件/服务 |
|
||||||
|
|------|-----------|
|
||||||
|
| 云服务平台 | Meoo Cloud (基于Supabase) |
|
||||||
|
| 数据库 | PostgreSQL 15+ |
|
||||||
|
| Web服务器 | Nginx 1.24+ (反向代理) |
|
||||||
|
| 运行时环境 | Node.js 18+ (开发环境) |
|
||||||
|
| 容器化支持 | Docker 24+ (可选) |
|
||||||
|
|
||||||
|
## 八、编程语言
|
||||||
|
|
||||||
|
| 语言 | 用途 | 占比 |
|
||||||
|
|------|------|------|
|
||||||
|
| TypeScript | 前端业务逻辑、类型定义 | 约85% |
|
||||||
|
| JavaScript | 配置文件、工具脚本 | 约10% |
|
||||||
|
| SQL | 数据库迁移脚本 | 约5% |
|
||||||
|
|
||||||
|
## 九、源程序量
|
||||||
|
|
||||||
|
| 统计项 | 数量 |
|
||||||
|
|--------|------|
|
||||||
|
| 源代码文件总数 | 73个 |
|
||||||
|
| 源代码总行数 | 约20,486行 |
|
||||||
|
| 前端组件文件 | 约45个 |
|
||||||
|
| 页面组件文件 | 约25个 |
|
||||||
|
| 工具/配置文件 | 约3个 |
|
||||||
|
|
||||||
|
## 十、开发目的
|
||||||
|
|
||||||
|
本软件旨在解决纺织行业传统采购计划管理中存在的信息不对称、协作效率低、数据追溯困难等问题。通过数字化手段连接采购商(布行)、纺织厂、水洗厂三方,实现:
|
||||||
|
|
||||||
|
1. **全流程数字化**:从计划制定到成品交付的完整数字化管理
|
||||||
|
2. **多方协同**:打破信息孤岛,实现三方实时协作
|
||||||
|
3. **数据可视化**:生产进度、库存状态实时可视
|
||||||
|
4. **效率提升**:减少人工沟通成本,提高业务处理效率
|
||||||
|
|
||||||
|
## 十一、面向领域/行业
|
||||||
|
|
||||||
|
| 项目 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **主要行业** | 纺织制造业 |
|
||||||
|
| **细分领域** | 坯布生产、面料加工、水洗处理 |
|
||||||
|
| **目标用户** | 纺织采购商(布行)、纺织厂、水洗厂 |
|
||||||
|
| **应用场景** | 生产计划管理、库存管理、供应链协同 |
|
||||||
|
|
||||||
|
## 十二、软件的主要功能
|
||||||
|
|
||||||
|
### 1. 多角色协作管理
|
||||||
|
- **采购商/布行**:创建生产计划、管理坯布库存、跟踪生产进度、处理结款
|
||||||
|
- **纺织厂**:接收生产计划、管理生产流程、坯布入库、纱线库存管理
|
||||||
|
- **水洗厂**:接收水洗订单、管理水洗流程
|
||||||
|
|
||||||
|
### 2. 计划管理
|
||||||
|
- 自动生成唯一计划编号和坯布识别码
|
||||||
|
- 纱线配比配置与用量自动计算
|
||||||
|
- 生产流程节点跟踪(确认计划 → 采纱 → 染纱 → 上机 → 坯布入库)
|
||||||
|
- 实时进度可视化,支持97%完成度自动提醒
|
||||||
|
|
||||||
|
### 3. 库存管理
|
||||||
|
- **原坯布仓库**:按坯布码分组管理,入库记录追溯
|
||||||
|
- **原料纱仓库**:库存预警、最低库存提醒
|
||||||
|
- 实时库存数据同步,入库自动更新
|
||||||
|
|
||||||
|
### 4. 结款管理
|
||||||
|
- 按完成米数自动计算结款金额
|
||||||
|
- 待结款汇总统计
|
||||||
|
- 一键确认结款,记录结款时间
|
||||||
|
|
||||||
|
### 5. 分享与导入
|
||||||
|
- 加密分享链接,保护敏感信息
|
||||||
|
- 支持剪贴板自动识别导入
|
||||||
|
- 扫码或链接快速关联计划
|
||||||
|
|
||||||
|
## 十三、软件的技术特点
|
||||||
|
|
||||||
|
### 1. 前端技术特点
|
||||||
|
- **React 18** + TypeScript:类型安全的函数组件开发
|
||||||
|
- **TailwindCSS**:原子化CSS,快速构建响应式界面
|
||||||
|
- **Framer Motion**:流畅的页面过渡和交互动画
|
||||||
|
- **响应式设计**:适配手机、平板、桌面端
|
||||||
|
|
||||||
|
### 2. 后端技术特点
|
||||||
|
- **Meoo Cloud (Supabase)**:云数据库 + 认证 + 实时订阅
|
||||||
|
- **PostgreSQL**:关系型数据库,支持复杂查询
|
||||||
|
- **Row Level Security**:行级安全策略,基于公司ID的数据隔离
|
||||||
|
- **Realtime**:实时数据同步,入库记录即时更新
|
||||||
|
|
||||||
|
### 3. 安全特性
|
||||||
|
- **RLS策略**:所有表启用行级安全,严格的数据访问控制
|
||||||
|
- **公司隔离**:基于company_id的数据隔离,确保数据安全
|
||||||
|
- **子账号支持**:主账号/子账号权限继承,统一公司数据访问
|
||||||
|
- **分享加密**:分享链接使用Base64 + 盐值混淆加密
|
||||||
|
|
||||||
|
### 4. 移动端优化
|
||||||
|
- 响应式布局,375px基准宽度设计
|
||||||
|
- 触控优化:44px最小触控区域、防双击缩放
|
||||||
|
- 输入优化:数字键盘自动唤起、输入模式适配
|
||||||
|
- 动画优化:硬件加速、自然动画曲线
|
||||||
|
|
||||||
|
## 十四、程序鉴别材料
|
||||||
|
|
||||||
|
程序鉴别材料包括:
|
||||||
|
1. 源代码前30页(见:程序鉴别材料-源代码前30页.doc)
|
||||||
|
2. 源代码后30页(见:程序鉴别材料-源代码后30页.doc)
|
||||||
|
|
||||||
|
源代码特征:
|
||||||
|
- 使用TypeScript语言编写
|
||||||
|
- 采用React 18函数组件+Hooks架构
|
||||||
|
- 使用Webpack 5构建工具
|
||||||
|
- 遵循模块化设计原则
|
||||||
|
|
||||||
|
## 十五、文档鉴别材料
|
||||||
|
|
||||||
|
文档鉴别材料包括:
|
||||||
|
1. 软件使用说明书(见:文档鉴别材料-使用说明书.doc)
|
||||||
|
2. 软件设计说明书(见:文档鉴别材料-设计说明书.doc)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**编制日期**:2025年05月
|
||||||
|
|
||||||
|
**编制单位**:[您的单位名称]
|
||||||
@ -1,4 +1,4 @@
|
|||||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>织机 - 以AI为梭,织天地经纬</title><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"><style>/* 内联样式确保主题色立即生效,避免闪烁 */
|
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>昱森 - 以AI为梭,织天地经纬</title><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"><style>/* 内联样式确保主题色立即生效,避免闪烁 */
|
||||||
html, body {
|
html, body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|||||||
@ -91,7 +91,7 @@ meoo-cli cloud init -d "项目描述"
|
|||||||
|
|
||||||
**示例**:
|
**示例**:
|
||||||
```bash
|
```bash
|
||||||
# 创建消息表
|
# 1. 创建消息表
|
||||||
meoo-cli cloud migrate --sql "
|
meoo-cli cloud migrate --sql "
|
||||||
CREATE TABLE messages (
|
CREATE TABLE messages (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
|
|||||||
1
skills/meoo-pdf/.skill-version
Normal file
1
skills/meoo-pdf/.skill-version
Normal file
@ -0,0 +1 @@
|
|||||||
|
1
|
||||||
151
skills/meoo-pdf/SKILL.md
Normal file
151
skills/meoo-pdf/SKILL.md
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
---
|
||||||
|
name: meoo-pdf
|
||||||
|
description: 使用此技能处理 PDF 文件的所有操作。包括:读取、提取文本/表格、合并/拆分 PDF、旋转页面、添加水印、创建新 PDF(支持中文)、填充 PDF 表单、提取图像以及对扫描件进行 OCR。触发场景:提及 '.pdf' 文件或要求生成/处理 PDF 成果。本技能已针对 meoo 沙箱环境优化,支持中文字体。
|
||||||
|
---
|
||||||
|
|
||||||
|
# meoo-pdf: PDF 处理专家
|
||||||
|
|
||||||
|
## ⚠️ 核心执行原则
|
||||||
|
|
||||||
|
1. **环境适配**:
|
||||||
|
- **已预装 (Python)**:`pypdf`, `pdfplumber`, `reportlab`, `pandas`。
|
||||||
|
- **已预装 (Node.js)**:`pdf-lib`。
|
||||||
|
- **需按需安装**:Poppler-utils (用于 `pdftoppm` 预览)、QPDF (用于高级页面操作/解密)、Tesseract-OCR (用于 OCR)。
|
||||||
|
2. **禁止直接读取**:绝对禁止通过 `read` 或 `view_file` 工具直接读取此类二进制文件,这会导致乱码且无法获取有效信息。你**必须**使用本技能推荐的库编写脚本来解析内容。
|
||||||
|
3. **脚本执行**:你**必须**先将脚本写入 `home/project/tmp/` 目录(例如 `home/project/tmp/process_pdf.py`),然后运行。禁止使用 bash inline python。
|
||||||
|
4. **中文支持**:沙箱已安装 `fonts-wqy-microhei`。在使用 `reportlab` 生成 PDF 时,必须注册并使用此中文字体。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 环境依赖安装指南
|
||||||
|
|
||||||
|
如果在执行过程中提示命令缺失,请先运行以下安装指令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
apt-get update && apt-get install -y poppler-utils qpdf tesseract-ocr
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 中文支持配置 (Python - ReportLab)
|
||||||
|
|
||||||
|
在生成 PDF 的脚本中注册中文字体:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from reportlab.pdfbase import ttfonts, pdfmetrics
|
||||||
|
from reportlab.platypus import Paragraph
|
||||||
|
from reportlab.lib.styles import getSampleStyleSheet
|
||||||
|
|
||||||
|
# 注册文泉驿微米黑
|
||||||
|
pdfmetrics.registerFont(ttfonts.TTFont('WenQuanYi', '/usr/share/fonts/truetype/wqy/wqy-microhei.ttc'))
|
||||||
|
|
||||||
|
# 使用示例
|
||||||
|
styles = getSampleStyleSheet()
|
||||||
|
styles['Normal'].fontName = 'WenQuanYi'
|
||||||
|
化学式:Paragraph("H<sub>2</sub>O", styles['Normal'])
|
||||||
|
数学公式:Paragraph("x<super>2</super>", styles['Normal'])
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 美学与专业美化指南 (Aesthetics Guide)
|
||||||
|
|
||||||
|
PDF 的专业感源于“秩序感”与“可访问性”:
|
||||||
|
|
||||||
|
### 1. CRAP 设计原则
|
||||||
|
- **对比 (Contrast)**:标题字体需显著大于或黑于正文(建议正文 11-12pt)。
|
||||||
|
- **重复 (Repetition)**:全文页眉、页脚、各级标题颜色必须保持 100% 视觉一致性。
|
||||||
|
- **对齐 (Alignment)**:所有图片、文字块和表格必须基于隐形网格线对齐,严禁随意摆放位置。
|
||||||
|
- **亲密性 (Proximity)**:图标与标签、图片与说明文字必须紧凑,不同章节间通过大留白区分。
|
||||||
|
|
||||||
|
### 2. 导航与交互
|
||||||
|
- **可点击目录**:对于 5 页以上的文档,必须生成带有超链接的目录(TOC)和 PDF 书签(Bookmarks)。
|
||||||
|
- **禁止“图片化”**:确保导出的 PDF 文字可选中、可搜索。严禁将整个文档转为单张大图 PDF。
|
||||||
|
|
||||||
|
### 3. 可访问性
|
||||||
|
- 为所有图表添加 **Alt Text**。
|
||||||
|
- 采用高对比度颜色组合(如黑字白底),确保内容易于阅读。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 进阶功能
|
||||||
|
|
||||||
|
### 1. 元数据与加密 (pypdf)
|
||||||
|
```python
|
||||||
|
from pypdf import PdfReader, PdfWriter
|
||||||
|
|
||||||
|
reader = PdfReader("input.pdf")
|
||||||
|
writer = PdfWriter()
|
||||||
|
writer.append(reader)
|
||||||
|
|
||||||
|
# 设置元数据
|
||||||
|
writer.add_metadata({"/Title": "我的文档", "/Author": "meoo"})
|
||||||
|
|
||||||
|
# 加密
|
||||||
|
writer.encrypt("user_password", "owner_password")
|
||||||
|
|
||||||
|
with open("output.pdf", "wb") as f:
|
||||||
|
writer.write(f)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 扫描件 OCR (自动化提取)
|
||||||
|
如果文档无可选文本,可将其转换为图像并使用 Tesseract:
|
||||||
|
```python
|
||||||
|
from pdf2image import convert_from_path
|
||||||
|
import pytesseract
|
||||||
|
|
||||||
|
images = convert_from_path('scanned.pdf', dpi=300)
|
||||||
|
text = ""
|
||||||
|
for img in images:
|
||||||
|
text += pytesseract.image_to_string(img, lang='chi_sim+eng')
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 常用工作流
|
||||||
|
|
||||||
|
### 1. 文本与表格提取
|
||||||
|
优先使用 `pdfplumber` 提取结构化数据:
|
||||||
|
```python
|
||||||
|
import pdfplumber
|
||||||
|
with pdfplumber.open("input.pdf") as pdf:
|
||||||
|
# 提取表格
|
||||||
|
table = pdf.pages[0].extract_table()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 合并、拆分与旋转
|
||||||
|
使用 `pypdf` 进行基础页面操作:
|
||||||
|
```python
|
||||||
|
from pypdf import PdfWriter
|
||||||
|
writer = PdfWriter()
|
||||||
|
writer.append("doc1.pdf")
|
||||||
|
writer.write("merged.pdf")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. PDF 表单填充与标签
|
||||||
|
针对需要精准定位的表单填充或批注(基于图像坐标转换),请使用内置脚本:
|
||||||
|
```bash
|
||||||
|
python3 scripts/fill_pdf_form_with_annotations.py input.pdf fields.json output.pdf
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 图片提取 (Image Extraction)
|
||||||
|
使用 `pdfimages` (来自 `poppler-utils`) 可以高效提取 PDF 中的原始图像:
|
||||||
|
```bash
|
||||||
|
# -j 参数确保将图像提取为 JPEG 格式
|
||||||
|
pdfimages -j input.pdf image_prefix
|
||||||
|
```
|
||||||
|
> [!TIP]
|
||||||
|
> 提取出的文件将命名为 `image_prefix-000.jpg`, `image_prefix-001.jpg` 等。
|
||||||
|
|
||||||
|
### 5. OCR 与扫描件处理
|
||||||
|
如果需要 OCR,需动态安装依赖:
|
||||||
|
```bash
|
||||||
|
apt-get update && apt-get install -y tesseract-ocr
|
||||||
|
pip install pytesseract
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 捆绑资源说明
|
||||||
|
- `scripts/fill_pdf_form_with_annotations.py`: 解决 PDF 坐标与图像坐标(DPI)映射的精准填充脚本。
|
||||||
|
- `scripts/extract_form_field_info.py`: 解析 PDF 表单域信息的辅助工具。
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user