feat: 完善前端功能,添加爬虫页面和项目分页
- 新增 CrawlerView 爬虫页面 - 完善 HomeView 分页展示(9个/页) - 更新 ProjectCard 组件图标 - 优化 API 客户端和类型定义 - 重构样式文件结构到独立目录 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
246
frontend/package-lock.json
generated
246
frontend/package-lock.json
generated
@@ -10,6 +10,7 @@
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.0",
|
||||
"@vueuse/core": "^11.0.0",
|
||||
"ant-design-vue": "^4.2.6",
|
||||
"axios": "^1.7.0",
|
||||
"element-plus": "^2.8.0",
|
||||
"pinia": "^2.2.0",
|
||||
@@ -26,6 +27,43 @@
|
||||
"vue-tsc": "^3.2.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@ant-design/colors": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/@ant-design/colors/-/colors-6.0.0.tgz",
|
||||
"integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ctrl/tinycolor": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ant-design/colors/node_modules/@ctrl/tinycolor": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
|
||||
"integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@ant-design/icons-svg": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmmirror.com/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz",
|
||||
"integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@ant-design/icons-vue": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/@ant-design/icons-vue/-/icons-vue-7.0.1.tgz",
|
||||
"integrity": "sha512-eCqY2unfZK6Fe02AwFlDHLfoyEFreP6rBwAZMIJ1LugmfMiVgwWDYlp1YsRugaPtICYOabV1iWxXdP12u9U43Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ant-design/colors": "^6.0.0",
|
||||
"@ant-design/icons-svg": "^4.2.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": ">=3.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||
@@ -59,6 +97,15 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.29.2",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.29.2.tgz",
|
||||
"integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz",
|
||||
@@ -97,6 +144,18 @@
|
||||
"vue": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/hash": {
|
||||
"version": "0.9.2",
|
||||
"resolved": "https://registry.npmmirror.com/@emotion/hash/-/hash-0.9.2.tgz",
|
||||
"integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@emotion/unitless": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.8.1.tgz",
|
||||
"integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
|
||||
@@ -1241,6 +1300,16 @@
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@simonwep/pickr": {
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmmirror.com/@simonwep/pickr/-/pickr-1.8.2.tgz",
|
||||
"integrity": "sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"core-js": "^3.15.1",
|
||||
"nanopop": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz",
|
||||
@@ -1486,6 +1555,61 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ant-design-vue": {
|
||||
"version": "4.2.6",
|
||||
"resolved": "https://registry.npmmirror.com/ant-design-vue/-/ant-design-vue-4.2.6.tgz",
|
||||
"integrity": "sha512-t7eX13Yj3i9+i5g9lqFyYneoIb3OzTvQjq9Tts1i+eiOd3Eva/6GagxBSXM1fOCjqemIu0FYVE1ByZ/38epR3Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ant-design/colors": "^6.0.0",
|
||||
"@ant-design/icons-vue": "^7.0.0",
|
||||
"@babel/runtime": "^7.10.5",
|
||||
"@ctrl/tinycolor": "^3.5.0",
|
||||
"@emotion/hash": "^0.9.0",
|
||||
"@emotion/unitless": "^0.8.0",
|
||||
"@simonwep/pickr": "~1.8.0",
|
||||
"array-tree-filter": "^2.1.0",
|
||||
"async-validator": "^4.0.0",
|
||||
"csstype": "^3.1.1",
|
||||
"dayjs": "^1.10.5",
|
||||
"dom-align": "^1.12.1",
|
||||
"dom-scroll-into-view": "^2.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.15",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"scroll-into-view-if-needed": "^2.2.25",
|
||||
"shallow-equal": "^1.0.0",
|
||||
"stylis": "^4.1.3",
|
||||
"throttle-debounce": "^5.0.0",
|
||||
"vue-types": "^3.0.0",
|
||||
"warning": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.22.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/ant-design-vue"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ant-design-vue/node_modules/@ctrl/tinycolor": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
|
||||
"integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/array-tree-filter": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/array-tree-filter/-/array-tree-filter-2.1.0.tgz",
|
||||
"integrity": "sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/async-validator": {
|
||||
"version": "4.2.5",
|
||||
"resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz",
|
||||
@@ -1579,6 +1703,23 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/compute-scroll-into-view": {
|
||||
"version": "1.0.20",
|
||||
"resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz",
|
||||
"integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/core-js": {
|
||||
"version": "3.49.0",
|
||||
"resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.49.0.tgz",
|
||||
"integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/core-js"
|
||||
}
|
||||
},
|
||||
"node_modules/crc-32": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz",
|
||||
@@ -1623,6 +1764,18 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-align": {
|
||||
"version": "1.12.4",
|
||||
"resolved": "https://registry.npmmirror.com/dom-align/-/dom-align-1.12.4.tgz",
|
||||
"integrity": "sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dom-scroll-into-view": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/dom-scroll-into-view/-/dom-scroll-into-view-2.0.1.tgz",
|
||||
"integrity": "sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
@@ -2020,6 +2173,21 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-plain-object": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-3.0.1.tgz",
|
||||
"integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.23.tgz",
|
||||
@@ -2043,6 +2211,18 @@
|
||||
"lodash-es": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"loose-envify": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.21",
|
||||
"resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz",
|
||||
@@ -2113,6 +2293,12 @@
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/nanopop": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmmirror.com/nanopop/-/nanopop-2.4.2.tgz",
|
||||
"integrity": "sha512-NzOgmMQ+elxxHeIha+OG/Pv3Oc3p4RU2aBhwWwAqDpXrdTbtRylbRLQztLy8dMMwfl6pclznBdfUhccEn9ZIzw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||
@@ -2223,6 +2409,12 @@
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/resize-observer-polyfill": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
|
||||
"integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.59.0.tgz",
|
||||
@@ -2647,6 +2839,21 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/scroll-into-view-if-needed": {
|
||||
"version": "2.2.31",
|
||||
"resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz",
|
||||
"integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"compute-scroll-into-view": "^1.0.20"
|
||||
}
|
||||
},
|
||||
"node_modules/shallow-equal": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/shallow-equal/-/shallow-equal-1.2.1.tgz",
|
||||
"integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
@@ -2668,6 +2875,12 @@
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/stylis": {
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.3.6.tgz",
|
||||
"integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz",
|
||||
@@ -2707,6 +2920,15 @@
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/throttle-debounce": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-5.0.2.tgz",
|
||||
"integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.22"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.15",
|
||||
"resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||
@@ -2913,6 +3135,30 @@
|
||||
"typescript": ">=5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-types": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/vue-types/-/vue-types-3.0.2.tgz",
|
||||
"integrity": "sha512-IwUC0Aq2zwaXqy74h4WCvFCUtoV0iSWr0snWnE9TnU18S66GAQyqQbRf2qfJtUuiFsBf6qp0MEwdonlwznlcrw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-plain-object": "3.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.15.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/warning": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/warning/-/warning-4.0.3.tgz",
|
||||
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wmf": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/wmf/-/wmf-1.0.2.tgz",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.0",
|
||||
"@vueuse/core": "^11.0.0",
|
||||
"ant-design-vue": "^4.2.6",
|
||||
"axios": "^1.7.0",
|
||||
"element-plus": "^2.8.0",
|
||||
"pinia": "^2.2.0",
|
||||
|
||||
@@ -248,6 +248,36 @@ html, body, #app {
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
/* Ant Design Vue Select Dropdown */
|
||||
.ant-select-dropdown {
|
||||
background: var(--bg-elevated) !important;
|
||||
border: 1px solid var(--border-subtle) !important;
|
||||
border-radius: 8px !important;
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.3) !important;
|
||||
}
|
||||
|
||||
.ant-select-item {
|
||||
color: var(--text-primary) !important;
|
||||
border-radius: 6px !important;
|
||||
}
|
||||
|
||||
.ant-select-item-option-active:not(.ant-select-item-option-disabled) {
|
||||
background: var(--bg-hover) !important;
|
||||
}
|
||||
|
||||
.ant-select-item-option-selected:not(.ant-select-item-option-disabled) {
|
||||
background: var(--accent-primary-muted) !important;
|
||||
color: var(--accent-primary) !important;
|
||||
}
|
||||
|
||||
.ant-select-selection-item {
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
.ant-select-selection-placeholder {
|
||||
color: var(--text-secondary) !important;
|
||||
}
|
||||
|
||||
.el-textarea__inner {
|
||||
background: var(--bg-tertiary) !important;
|
||||
border: 1px solid var(--border-subtle) !important;
|
||||
|
||||
@@ -26,6 +26,17 @@ request.interceptors.response.use(
|
||||
// Handle new ApiResponse format
|
||||
if (data.success !== undefined) {
|
||||
if (data.success) {
|
||||
// Check if this is a paginated response by checking for pagination field
|
||||
if (data.pagination) {
|
||||
// Return full response with pagination for paginated endpoints
|
||||
return {
|
||||
items: data.data,
|
||||
total: data.pagination.total,
|
||||
page: data.pagination.page,
|
||||
page_size: data.pagination.page_size,
|
||||
total_pages: data.pagination.total_pages
|
||||
}
|
||||
}
|
||||
return data.data // Return the actual data
|
||||
} else {
|
||||
return Promise.reject(new Error(data.message || data.error || '请求失败'))
|
||||
@@ -41,9 +52,10 @@ request.interceptors.response.use(
|
||||
)
|
||||
|
||||
export const projectApi = {
|
||||
list: () => request.get<Project[]>('/projects/'),
|
||||
list: (params?: { page?: number; page_size?: number }) =>
|
||||
request.get<{ items: Project[]; pagination: { total: number } }>('/projects', { params }),
|
||||
get: (id: string) => request.get<Project>(`/projects/${id}`),
|
||||
create: (data: ProjectCreate) => request.post<{ id: string }>('/projects/', data),
|
||||
create: (data: ProjectCreate) => request.post<{ id: string }>('/projects', data),
|
||||
update: (id: string, data: ProjectUpdate) => request.put<Project>(`/projects/${id}`, data),
|
||||
delete: (id: string) => request.delete(`/projects/${id}`)
|
||||
}
|
||||
@@ -53,14 +65,14 @@ export const fileApi = {
|
||||
request.post(`/projects/${projectId}/files/upload`, formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
}),
|
||||
list: (projectId: string) => request.get(`/projects/${projectId}/files/`),
|
||||
list: (projectId: string) => request.get(`/projects/${projectId}/files`),
|
||||
get: (projectId: string, fileId: string) => request.get(`/projects/${projectId}/files/${fileId}`),
|
||||
delete: (projectId: string, fileId: string) => request.delete(`/projects/${projectId}/files/${fileId}`)
|
||||
}
|
||||
|
||||
export const chunkApi = {
|
||||
split: (projectId: string, data: any) => request.post(`/projects/${projectId}/chunks/split`, data),
|
||||
list: (projectId: string, params?: any) => request.get(`/projects/${projectId}/chunks/`, { params }),
|
||||
list: (projectId: string, params?: any) => request.get(`/projects/${projectId}/chunks`, { params }),
|
||||
get: (projectId: string, chunkId: string) => request.get(`/projects/${projectId}/chunks/${chunkId}`),
|
||||
update: (projectId: string, chunkId: string, data: any) => request.put(`/projects/${projectId}/chunks/${chunkId}`, data),
|
||||
delete: (projectId: string, chunkId: string) => request.delete(`/projects/${projectId}/chunks/${chunkId}`)
|
||||
@@ -74,8 +86,8 @@ export const questionApi = {
|
||||
}
|
||||
|
||||
export const datasetApi = {
|
||||
list: (projectId: string) => request.get(`/projects/${projectId}/datasets/`),
|
||||
create: (projectId: string, data: any) => request.post(`/projects/${projectId}/datasets/`, data),
|
||||
list: (projectId: string) => request.get(`/projects/${projectId}/datasets`),
|
||||
create: (projectId: string, data: any) => request.post(`/projects/${projectId}/datasets`, data),
|
||||
get: (projectId: string, datasetId: string) => request.get(`/projects/${projectId}/datasets/${datasetId}`),
|
||||
delete: (projectId: string, datasetId: string) => request.delete(`/projects/${projectId}/datasets/${datasetId}`),
|
||||
export: (projectId: string, datasetId: string, data: any) =>
|
||||
|
||||
@@ -58,24 +58,35 @@
|
||||
<div class="templates-section">
|
||||
<span class="templates-label">快速开始模板</span>
|
||||
<div class="templates-grid">
|
||||
<div class="template-card" @click="useTemplate('qa')">
|
||||
<div
|
||||
class="template-card"
|
||||
:class="{ active: formData.type === 'qa' }"
|
||||
@click="useTemplate('qa')"
|
||||
>
|
||||
<el-icon><ChatDotRound /></el-icon>
|
||||
<span>问答对</span>
|
||||
</div>
|
||||
<div class="template-card" @click="useTemplate('conversation')">
|
||||
<el-icon><ChatLineRound /></el-icon>
|
||||
<span>对话</span>
|
||||
<div
|
||||
class="template-card"
|
||||
:class="{ active: formData.type === 'table' }"
|
||||
@click="useTemplate('table')"
|
||||
>
|
||||
<el-icon><Document /></el-icon>
|
||||
<span>表格</span>
|
||||
</div>
|
||||
<div class="template-card" @click="useTemplate('instruction')">
|
||||
<el-icon><Promotion /></el-icon>
|
||||
<span>指令</span>
|
||||
<div
|
||||
class="template-card"
|
||||
:class="{ active: formData.type === 'database' }"
|
||||
@click="useTemplate('database')"
|
||||
>
|
||||
<el-icon><Connection /></el-icon>
|
||||
<span>数据库</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose" class="btn-cancel">取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
@@ -108,19 +119,18 @@ const emit = defineEmits(['update:visible', 'submit'])
|
||||
|
||||
const formData = reactive({
|
||||
name: '',
|
||||
description: ''
|
||||
description: '',
|
||||
type: ''
|
||||
})
|
||||
|
||||
const templates = {
|
||||
qa: { name: '问答数据集', description: '基于文档生成问答对训练数据' },
|
||||
conversation: { name: '对话数据集', description: '创建多轮对话训练数据' },
|
||||
instruction: { name: '指令数据集', description: '构建指令跟随训练数据' }
|
||||
table: { name: '表格数据集', description: '从表格数据生成结构化训练数据' },
|
||||
database: { name: '数据库数据集', description: '从数据库导出数据生成训练数据' }
|
||||
}
|
||||
|
||||
const useTemplate = (type) => {
|
||||
const t = templates[type]
|
||||
formData.name = t.name
|
||||
formData.description = t.description
|
||||
formData.type = type
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
@@ -136,6 +146,7 @@ watch(() => props.visible, (newVal) => {
|
||||
if (newVal) {
|
||||
formData.name = ''
|
||||
formData.description = ''
|
||||
formData.type = ''
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -319,6 +330,10 @@ watch(() => props.visible, (newVal) => {
|
||||
transition: all 0.25s ease;
|
||||
}
|
||||
|
||||
.custom-input :deep(.el-textarea__inner::placeholder) {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.custom-input :deep(.el-textarea__inner:hover) {
|
||||
border-color: rgba(0, 212, 255, 0.3);
|
||||
}
|
||||
@@ -368,6 +383,12 @@ watch(() => props.visible, (newVal) => {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.template-card.active {
|
||||
background: rgba(0, 212, 255, 0.12);
|
||||
border-color: var(--accent-primary);
|
||||
box-shadow: 0 0 15px rgba(0, 212, 255, 0.2);
|
||||
}
|
||||
|
||||
.template-card .el-icon {
|
||||
font-size: 22px;
|
||||
color: var(--accent-primary);
|
||||
@@ -385,8 +406,7 @@ watch(() => props.visible, (newVal) => {
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
padding: 20px 28px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.05);
|
||||
@@ -408,11 +428,13 @@ watch(() => props.visible, (newVal) => {
|
||||
}
|
||||
|
||||
.btn-create {
|
||||
padding: 10px 24px;
|
||||
width: 100%;
|
||||
padding: 14px 32px;
|
||||
background: linear-gradient(135deg, var(--accent-primary) 0%, #0891b2 100%);
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-weight: 500;
|
||||
font-size: 15px;
|
||||
transition: all 0.25s ease;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
</button>
|
||||
<div class="card-header">
|
||||
<div class="card-avatar">
|
||||
<el-icon><Folder /></el-icon>
|
||||
<el-icon><component :is="projectIcon" /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="card-title">{{ project.name }}</h3>
|
||||
@@ -34,6 +34,7 @@
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { Folder, ChatDotRound, Document, Connection } from '@element-plus/icons-vue'
|
||||
|
||||
const props = defineProps({
|
||||
project: {
|
||||
@@ -58,6 +59,14 @@ defineEmits(['click', 'delete'])
|
||||
|
||||
const delay = computed(() => `${props.index * 0.1}s`)
|
||||
|
||||
const projectIcon = computed(() => {
|
||||
const type = props.project.type
|
||||
if (type === 'qa') return ChatDotRound
|
||||
if (type === 'table') return Document
|
||||
if (type === 'database') return Connection
|
||||
return Folder
|
||||
})
|
||||
|
||||
const formattedDate = computed(() => {
|
||||
if (!props.project.created_at) return ''
|
||||
const d = new Date(props.project.created_at)
|
||||
|
||||
@@ -2,6 +2,8 @@ import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import Antd from 'ant-design-vue'
|
||||
import 'ant-design-vue/dist/reset.css'
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
|
||||
import App from './App.vue'
|
||||
@@ -17,5 +19,6 @@ for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
app.use(ElementPlus)
|
||||
app.use(Antd)
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
@@ -51,6 +51,11 @@ const routes = [
|
||||
path: '/models',
|
||||
name: 'ModelSettings',
|
||||
component: () => import('@/views/ModelSettingsView.vue')
|
||||
},
|
||||
{
|
||||
path: '/crawler',
|
||||
name: 'Crawler',
|
||||
component: () => import('@/views/CrawlerView.vue')
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
5
frontend/src/types/project.d.ts
vendored
5
frontend/src/types/project.d.ts
vendored
@@ -6,16 +6,19 @@ export interface Project {
|
||||
id: string
|
||||
name: string
|
||||
description?: string
|
||||
type: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface ProjectCreate {
|
||||
name: string
|
||||
description?: string
|
||||
description: string
|
||||
type: string
|
||||
}
|
||||
|
||||
export interface ProjectUpdate {
|
||||
name?: string
|
||||
description?: string
|
||||
type?: string
|
||||
}
|
||||
|
||||
@@ -47,6 +47,10 @@
|
||||
<el-icon><Plus /></el-icon>
|
||||
创建项目
|
||||
</el-button>
|
||||
<el-button size="large" @click="goToCrawler" class="btn-secondary">
|
||||
<el-icon><Connection /></el-icon>
|
||||
数据爬虫
|
||||
</el-button>
|
||||
<el-button size="large" @click="goToModels" class="btn-secondary">
|
||||
<el-icon><Cpu /></el-icon>
|
||||
模型管理
|
||||
@@ -56,6 +60,47 @@
|
||||
|
||||
<!-- Hero Visual - Modern Abstract Composition -->
|
||||
<div class="hero-visual">
|
||||
<!-- Galaxy Background -->
|
||||
<div class="galaxy-bg">
|
||||
<!-- Nebula clouds -->
|
||||
<div class="nebula-cloud nebula-1"></div>
|
||||
<div class="nebula-cloud nebula-2"></div>
|
||||
<div class="nebula-cloud nebula-3"></div>
|
||||
|
||||
<!-- Galaxy core -->
|
||||
<div class="galaxy-core"></div>
|
||||
|
||||
<!-- Spiral arms -->
|
||||
<div class="galaxy-spiral">
|
||||
<div class="spiral-arm spiral-arm-1"></div>
|
||||
<div class="spiral-arm spiral-arm-2"></div>
|
||||
<div class="spiral-arm spiral-arm-3"></div>
|
||||
</div>
|
||||
|
||||
<!-- Orbit rings with stars -->
|
||||
<div class="orbit-ring orbit-ring-1">
|
||||
<span class="orbit-star"></span>
|
||||
<span class="orbit-star"></span>
|
||||
<span class="orbit-star"></span>
|
||||
<span class="orbit-star"></span>
|
||||
</div>
|
||||
<div class="orbit-ring orbit-ring-2">
|
||||
<span class="orbit-star"></span>
|
||||
<span class="orbit-star"></span>
|
||||
<span class="orbit-star"></span>
|
||||
<span class="orbit-star"></span>
|
||||
</div>
|
||||
<div class="orbit-ring orbit-ring-3">
|
||||
<span class="orbit-star"></span>
|
||||
<span class="orbit-star"></span>
|
||||
<span class="orbit-star"></span>
|
||||
</div>
|
||||
<div class="orbit-ring orbit-ring-4">
|
||||
<span class="orbit-star"></span>
|
||||
<span class="orbit-star"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Light rays -->
|
||||
<div class="light-rays">
|
||||
<div class="ray"></div>
|
||||
@@ -134,7 +179,7 @@
|
||||
<div class="section-header">
|
||||
<div class="section-title">
|
||||
<h2>我的项目</h2>
|
||||
<p>{{ projects.length }} 个项目</p>
|
||||
<p>{{ total }} 个项目</p>
|
||||
</div>
|
||||
<el-button type="primary" @click="createProject" class="add-btn">
|
||||
<el-icon><Plus /></el-icon>
|
||||
@@ -165,6 +210,29 @@
|
||||
@delete="confirmDelete"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="pagination-wrapper" v-if="needPagination">
|
||||
<div class="pagination-minimal">
|
||||
<span class="page-info">第 {{ currentPage }} / {{ totalPages }} 页</span>
|
||||
<div class="page-arrows">
|
||||
<button
|
||||
class="arrow-btn"
|
||||
:disabled="currentPage === 1"
|
||||
@click="handlePageChange(currentPage - 1)"
|
||||
>
|
||||
<el-icon><ArrowLeft /></el-icon>
|
||||
</button>
|
||||
<button
|
||||
class="arrow-btn"
|
||||
:disabled="currentPage === totalPages"
|
||||
@click="handlePageChange(currentPage + 1)"
|
||||
>
|
||||
<el-icon><ArrowRight /></el-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Create Dialog -->
|
||||
@@ -185,10 +253,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { FolderAdd, Check, Connection, Clock, Lock, TrendCharts } from '@element-plus/icons-vue'
|
||||
import { FolderAdd, Check, Connection, Clock, Lock, TrendCharts, ArrowLeft, ArrowRight } from '@element-plus/icons-vue'
|
||||
import { projectApi } from '@/api'
|
||||
import type { Project, ProjectCreate } from '@/types'
|
||||
|
||||
@@ -208,29 +276,62 @@ const projectToDelete = ref(null)
|
||||
const submitting = ref(false)
|
||||
const deleting = ref(false)
|
||||
|
||||
// Pagination
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(9)
|
||||
const total = ref(0)
|
||||
|
||||
const fetchProjects = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await projectApi.list()
|
||||
// New paginated format: {items: [...], total, page, page_size}
|
||||
projects.value = res.items || res || []
|
||||
const res = await projectApi.list({ page: currentPage.value, page_size: pageSize.value })
|
||||
// API returns: { items: [], total, page, page_size, total_pages }
|
||||
if (res && typeof res === 'object' && 'items' in res) {
|
||||
projects.value = res.items || []
|
||||
total.value = res.total || 0
|
||||
} else if (Array.isArray(res)) {
|
||||
projects.value = res
|
||||
total.value = res.length
|
||||
} else {
|
||||
projects.value = []
|
||||
total.value = 0
|
||||
}
|
||||
} catch (error) {
|
||||
projects.value = []
|
||||
total.value = 0
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
currentPage.value = page
|
||||
fetchProjects()
|
||||
}
|
||||
|
||||
const needPagination = computed(() => total.value > pageSize.value || projects.value.length === pageSize.value)
|
||||
|
||||
const totalPages = computed(() => Math.ceil(total.value / pageSize.value))
|
||||
|
||||
|
||||
const createProject = () => {
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleCreateSubmit = async (formData) => {
|
||||
// Simple validation
|
||||
// Validation - name, description and type are required
|
||||
if (!formData.name || formData.name.trim() === '') {
|
||||
ElMessage.warning('请输入项目名称')
|
||||
return
|
||||
}
|
||||
if (!formData.description || formData.description.trim() === '') {
|
||||
ElMessage.warning('请输入项目描述')
|
||||
return
|
||||
}
|
||||
if (!formData.type) {
|
||||
ElMessage.warning('请选择项目类型')
|
||||
return
|
||||
}
|
||||
|
||||
console.log('Creating project with form:', formData)
|
||||
submitting.value = true
|
||||
@@ -278,6 +379,7 @@ const handleDelete = async () => {
|
||||
}
|
||||
|
||||
const goToDataSquare = () => router.push('/data-square')
|
||||
const goToCrawler = () => router.push('/crawler')
|
||||
const goToModels = () => router.push('/models')
|
||||
|
||||
onMounted(() => fetchProjects())
|
||||
|
||||
@@ -205,7 +205,8 @@ const fetchFiles = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await fileApi.list(projectId.value)
|
||||
files.value = res.data.files || []
|
||||
// API returns array directly via interceptor
|
||||
files.value = res || []
|
||||
} catch (error) {
|
||||
files.value = []
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user