Explorar o código

feat(job管理): 添加岗位暂停、恢复和删除功能

- 在jobStore中添加pauseJob、resumeJob和deleteJob方法
- 为JobCard组件添加操作下拉菜单和模态框
- 新增JobDetailModal和JobEditModal组件
- 调整JobCreatorModal的样式和类型定义
- 添加Parse SDK初始化配置和依赖
yangjinhaowife hai 5 días
pai
achega
a96125fd92

+ 0 - 3
.get/config.json

@@ -1,3 +0,0 @@
-{
-  "template": "vite-vue-ts"
-}

+ 462 - 3
package-lock.json

@@ -9,8 +9,11 @@
       "version": "0.0.0",
       "dependencies": {
         "@vueuse/motion": "^2.0.0",
+        "events": "^3.3.0",
         "lucide-vue-next": "^0.290.0",
         "pinia": "^2.1.7",
+        "stream-browserify": "^3.0.0",
+        "util": "^0.12.5",
         "vue": "^3.4.38"
       },
       "devDependencies": {
@@ -1270,6 +1273,21 @@
         "postcss": "^8.1.0"
       }
     },
+    "node_modules/available-typed-arrays": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmmirror.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
+      "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
+      "license": "MIT",
+      "dependencies": {
+        "possible-typed-array-names": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/balanced-match": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -1375,6 +1393,53 @@
         }
       }
     },
+    "node_modules/call-bind": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.8.tgz",
+      "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.0",
+        "es-define-property": "^1.0.0",
+        "get-intrinsic": "^1.2.4",
+        "set-function-length": "^1.2.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/call-bind-apply-helpers": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+      "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/call-bound": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz",
+      "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "get-intrinsic": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/camelcase-css": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
@@ -1520,6 +1585,23 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/define-data-property": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz",
+      "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+      "license": "MIT",
+      "dependencies": {
+        "es-define-property": "^1.0.0",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/defu": {
       "version": "6.1.4",
       "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
@@ -1561,6 +1643,20 @@
         "url": "https://dotenvx.com"
       }
     },
+    "node_modules/dunder-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz",
+      "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.2.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/eastasianwidth": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@@ -1601,6 +1697,36 @@
       "license": "MIT",
       "optional": true
     },
+    "node_modules/es-define-property": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
+      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-errors": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz",
+      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-object-atoms": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+      "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/esbuild": {
       "version": "0.21.5",
       "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
@@ -1672,6 +1798,15 @@
         "@types/estree": "^1.0.0"
       }
     },
+    "node_modules/events": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmmirror.com/events/-/events-3.3.0.tgz",
+      "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.8.x"
+      }
+    },
     "node_modules/exsolve": {
       "version": "1.0.7",
       "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz",
@@ -1732,6 +1867,21 @@
         "node": ">=8"
       }
     },
+    "node_modules/for-each": {
+      "version": "0.3.5",
+      "resolved": "https://registry.npmmirror.com/for-each/-/for-each-0.3.5.tgz",
+      "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==",
+      "license": "MIT",
+      "dependencies": {
+        "is-callable": "^1.2.7"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/foreground-child": {
       "version": "3.3.1",
       "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
@@ -1791,12 +1941,48 @@
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
       "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
-      "dev": true,
       "license": "MIT",
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/get-intrinsic": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+      "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "es-define-property": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "es-object-atoms": "^1.1.1",
+        "function-bind": "^1.1.2",
+        "get-proto": "^1.0.1",
+        "gopd": "^1.2.0",
+        "has-symbols": "^1.1.0",
+        "hasown": "^2.0.2",
+        "math-intrinsics": "^1.1.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz",
+      "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+      "license": "MIT",
+      "dependencies": {
+        "dunder-proto": "^1.0.1",
+        "es-object-atoms": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/giget": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz",
@@ -1849,11 +2035,61 @@
         "node": ">=10.13.0"
       }
     },
+    "node_modules/gopd": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
+      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-property-descriptors": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+      "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+      "license": "MIT",
+      "dependencies": {
+        "es-define-property": "^1.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-symbols": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
+      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-tostringtag": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+      "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+      "license": "MIT",
+      "dependencies": {
+        "has-symbols": "^1.0.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/hasown": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
       "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "function-bind": "^1.1.2"
@@ -1888,6 +2124,28 @@
         "node": ">= 4"
       }
     },
+    "node_modules/inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "license": "ISC"
+    },
+    "node_modules/is-arguments": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/is-arguments/-/is-arguments-1.2.0.tgz",
+      "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bound": "^1.0.2",
+        "has-tostringtag": "^1.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/is-binary-path": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -1901,6 +2159,18 @@
         "node": ">=8"
       }
     },
+    "node_modules/is-callable": {
+      "version": "1.2.7",
+      "resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.7.tgz",
+      "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/is-core-module": {
       "version": "2.16.1",
       "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
@@ -1937,6 +2207,24 @@
         "node": ">=8"
       }
     },
+    "node_modules/is-generator-function": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/is-generator-function/-/is-generator-function-1.1.0.tgz",
+      "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bound": "^1.0.3",
+        "get-proto": "^1.0.0",
+        "has-tostringtag": "^1.0.2",
+        "safe-regex-test": "^1.1.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/is-glob": {
       "version": "4.0.3",
       "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@@ -1960,6 +2248,39 @@
         "node": ">=0.12.0"
       }
     },
+    "node_modules/is-regex": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.2.1.tgz",
+      "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bound": "^1.0.2",
+        "gopd": "^1.2.0",
+        "has-tostringtag": "^1.0.2",
+        "hasown": "^2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/is-typed-array": {
+      "version": "1.1.15",
+      "resolved": "https://registry.npmmirror.com/is-typed-array/-/is-typed-array-1.1.15.tgz",
+      "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==",
+      "license": "MIT",
+      "dependencies": {
+        "which-typed-array": "^1.1.16"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/isexe": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -2080,6 +2401,15 @@
         "@jridgewell/sourcemap-codec": "^1.5.0"
       }
     },
+    "node_modules/math-intrinsics": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+      "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/merge2": {
       "version": "1.4.1",
       "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -2427,6 +2757,15 @@
         "tslib": "2.4.0"
       }
     },
+    "node_modules/possible-typed-array-names": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
+      "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/postcss": {
       "version": "8.5.6",
       "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
@@ -2635,6 +2974,20 @@
         "pify": "^2.3.0"
       }
     },
+    "node_modules/readable-stream": {
+      "version": "3.6.2",
+      "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz",
+      "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+      "license": "MIT",
+      "dependencies": {
+        "inherits": "^2.0.3",
+        "string_decoder": "^1.1.1",
+        "util-deprecate": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/readdirp": {
       "version": "4.1.2",
       "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
@@ -2745,6 +3098,43 @@
         "queue-microtask": "^1.2.2"
       }
     },
+    "node_modules/safe-buffer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/safe-regex-test": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
+      "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bound": "^1.0.2",
+        "es-errors": "^1.3.0",
+        "is-regex": "^1.2.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/scule": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz",
@@ -2765,6 +3155,23 @@
         "node": ">=10"
       }
     },
+    "node_modules/set-function-length": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz",
+      "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
+      "license": "MIT",
+      "dependencies": {
+        "define-data-property": "^1.1.4",
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2",
+        "get-intrinsic": "^1.2.4",
+        "gopd": "^1.0.1",
+        "has-property-descriptors": "^1.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/shebang-command": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -2817,6 +3224,25 @@
       "license": "MIT",
       "optional": true
     },
+    "node_modules/stream-browserify": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/stream-browserify/-/stream-browserify-3.0.0.tgz",
+      "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==",
+      "license": "MIT",
+      "dependencies": {
+        "inherits": "~2.0.4",
+        "readable-stream": "^3.5.0"
+      }
+    },
+    "node_modules/string_decoder": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz",
+      "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "~5.2.0"
+      }
+    },
     "node_modules/string-width": {
       "version": "5.1.2",
       "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
@@ -3359,11 +3785,23 @@
         "browserslist": ">= 4.21.0"
       }
     },
+    "node_modules/util": {
+      "version": "0.12.5",
+      "resolved": "https://registry.npmmirror.com/util/-/util-0.12.5.tgz",
+      "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==",
+      "license": "MIT",
+      "dependencies": {
+        "inherits": "^2.0.3",
+        "is-arguments": "^1.0.4",
+        "is-generator-function": "^1.0.7",
+        "is-typed-array": "^1.1.3",
+        "which-typed-array": "^1.1.2"
+      }
+    },
     "node_modules/util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
       "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/vite": {
@@ -3520,6 +3958,27 @@
         "node": ">= 8"
       }
     },
+    "node_modules/which-typed-array": {
+      "version": "1.1.19",
+      "resolved": "https://registry.npmmirror.com/which-typed-array/-/which-typed-array-1.1.19.tgz",
+      "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==",
+      "license": "MIT",
+      "dependencies": {
+        "available-typed-arrays": "^1.0.7",
+        "call-bind": "^1.0.8",
+        "call-bound": "^1.0.4",
+        "for-each": "^0.3.5",
+        "get-proto": "^1.0.1",
+        "gopd": "^1.2.0",
+        "has-tostringtag": "^1.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/wrap-ansi": {
       "version": "8.1.0",
       "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",

+ 3 - 0
package.json

@@ -10,8 +10,11 @@
   },
   "dependencies": {
     "@vueuse/motion": "^2.0.0",
+    "events": "^3.3.0",
     "lucide-vue-next": "^0.290.0",
     "pinia": "^2.1.7",
+    "stream-browserify": "^3.0.0",
+    "util": "^0.12.5",
     "vue": "^3.4.38"
   },
   "devDependencies": {

+ 1 - 1
src/App.vue

@@ -35,7 +35,7 @@ onMounted(() => {
         appear
       >
         <component 
-          :is="views[currentView]" 
+          :is="views[currentView as keyof typeof views]"
           :key="currentView"
           class="min-h-full"
         />

+ 109 - 15
src/components/JobCard.vue

@@ -1,7 +1,9 @@
 <script setup lang="ts">
-import { ref } from 'vue'
+import { computed, ref, onMounted, onUnmounted } from 'vue'
 import { useJobStore, type Job } from '../stores/jobStore'
-import { Briefcase, MapPin, Clock, Users, Zap, MoreHorizontal } from 'lucide-vue-next'
+import { Briefcase, MapPin, Clock, Users, Zap, MoreHorizontal, Eye, Edit, Pause, Play, Trash2 } from 'lucide-vue-next'
+import JobDetailModal from './JobDetailModal.vue'
+import JobEditModal from './JobEditModal.vue'
 
 interface Props {
   job: Job
@@ -11,19 +13,22 @@ const props = defineProps<Props>()
 const jobStore = useJobStore()
 
 const isScreening = ref(false)
+const showDetailModal = ref(false)
+const showEditModal = ref(false)
+const showDropdown = ref(false)
 
-const getStatusConfig = (status: Job['status']) => {
-  switch (status) {
+const statusConfig = computed(() => {
+  switch (props.job.status) {
     case 'active':
       return { label: '招聘中', class: 'bg-success-100 text-success-700' }
     case 'paused':
-      return { label: '已暂停', class: 'bg-gray-100 text-gray-700' }
+      return { label: '已暂停', class: 'bg-warning-100 text-warning-700' }
     case 'draft':
       return { label: '草稿', class: 'bg-warning-100 text-warning-700' }
     default:
       return { label: '未知', class: 'bg-gray-100 text-gray-500' }
   }
-}
+})
 
 const handleScreening = async () => {
   if (props.job.pendingResumes === 0) return
@@ -44,7 +49,52 @@ const handleScreening = async () => {
   }
 }
 
-const statusConfig = getStatusConfig(props.job.status)
+const handleViewDetails = () => {
+  showDetailModal.value = true
+}
+
+const handleEditJob = () => {
+  showEditModal.value = true
+}
+
+const handlePauseJob = () => {
+  if (props.job.status === 'active') {
+    jobStore.pauseJob(props.job.id)
+  } else if (props.job.status === 'paused') {
+    jobStore.resumeJob(props.job.id)
+  }
+}
+
+const handleEditSave = (updatedJob: Job) => {
+  showEditModal.value = false
+}
+
+const toggleDropdown = () => {
+  showDropdown.value = !showDropdown.value
+}
+
+const handleDelete = () => {
+  if (confirm('确定要删除这个岗位吗?此操作不可撤销。')) {
+    jobStore.deleteJob(props.job.id)
+  }
+  showDropdown.value = false
+}
+
+// 点击外部关闭下拉菜单
+const handleClickOutside = (event: MouseEvent) => {
+  const target = event.target as HTMLElement
+  if (!target.closest('.relative')) {
+    showDropdown.value = false
+  }
+}
+
+onMounted(() => {
+  document.addEventListener('click', handleClickOutside)
+})
+
+onUnmounted(() => {
+  document.removeEventListener('click', handleClickOutside)
+})
 </script>
 
 <template>
@@ -74,9 +124,28 @@ const statusConfig = getStatusConfig(props.job.status)
         <span :class="['px-3 py-1 rounded-full text-xs font-medium', statusConfig.class]">
           {{ statusConfig.label }}
         </span>
-        <button class="p-2 rounded-lg hover:bg-gray-100 transition-colors">
-          <MoreHorizontal :size="16" class="text-gray-400" />
-        </button>
+        <div class="relative">
+          <button 
+            @click="toggleDropdown"
+            class="p-2 rounded-lg hover:bg-gray-100 transition-colors"
+          >
+            <MoreHorizontal :size="16" class="text-gray-400" />
+          </button>
+          
+          <!-- 下拉菜单 -->
+          <div 
+            v-if="showDropdown"
+            class="absolute right-0 top-full mt-1 w-32 bg-white rounded-lg shadow-lg border border-gray-200 z-50"
+          >
+            <button 
+              @click="handleDelete"
+              class="w-full px-3 py-2 text-left text-sm text-red-600 hover:bg-red-50 rounded-lg flex items-center gap-2 transition-colors"
+            >
+              <Trash2 :size="14" />
+              删除岗位
+            </button>
+          </div>
+        </div>
       </div>
     </div>
 
@@ -122,17 +191,28 @@ const statusConfig = getStatusConfig(props.job.status)
 
       <!-- 次要操作按钮 -->
       <div class="flex gap-2">
-        <button class="flex-1 py-2.5 px-4 text-sm font-medium text-gray-600 hover:text-gray-800 transition-colors">
+        <button 
+          @click="handleViewDetails"
+          class="flex-1 py-2.5 px-4 text-sm font-medium text-gray-600 hover:text-gray-800 transition-colors flex items-center justify-center gap-1"
+        >
+          <Eye :size="14" />
           查看详情
         </button>
-        <button class="flex-1 py-2.5 px-4 text-sm font-medium text-gray-600 hover:text-gray-800 transition-colors">
+        <button 
+          @click="handleEditJob"
+          class="flex-1 py-2.5 px-4 text-sm font-medium text-gray-600 hover:text-gray-800 transition-colors flex items-center justify-center gap-1"
+        >
+          <Edit :size="14" />
           编辑岗位
         </button>
         <button 
-          v-if="job.status === 'active'"
-          class="flex-1 py-2.5 px-4 text-sm font-medium text-gray-600 hover:text-gray-800 transition-colors"
+          v-if="job.status === 'active' || job.status === 'paused'"
+          @click="handlePauseJob"
+          class="flex-1 py-2.5 px-4 text-sm font-medium text-gray-600 hover:text-gray-800 transition-colors flex items-center justify-center gap-1"
         >
-          暂停招聘
+          <Pause v-if="job.status === 'active'" :size="14" />
+          <Play v-else :size="14" />
+          {{ job.status === 'active' ? '暂停招聘' : '恢复招聘' }}
         </button>
       </div>
     </div>
@@ -143,6 +223,20 @@ const statusConfig = getStatusConfig(props.job.status)
       最后更新: {{ new Date(job.updatedAt).toLocaleDateString() }}
     </div>
   </div>
+
+  <!-- 模态框 -->
+  <JobDetailModal 
+    :is-open="showDetailModal"
+    :job="job"
+    @close="showDetailModal = false"
+  />
+  
+  <JobEditModal 
+    :is-open="showEditModal"
+    :job="job"
+    @close="showEditModal = false"
+    @save="handleEditSave"
+  />
 </template>
 
 <style scoped>

+ 7 - 7
src/components/JobCreatorModal.vue

@@ -104,7 +104,7 @@ const handleSubmit = async () => {
   }
 }
 
-const adjustWeight = (key: string, delta: number) => {
+const adjustWeight = (key: keyof typeof formData.value.aiCriteria, delta: number) => {
   const currentWeight = formData.value.aiCriteria[key].weight
   const newWeight = Math.max(0, Math.min(100, currentWeight + delta))
   formData.value.aiCriteria[key].weight = newWeight
@@ -124,7 +124,7 @@ const adjustWeight = (key: string, delta: number) => {
         
         <!-- 模态框内容 -->
         <div 
-          class="relative w-full max-w-2xl max-h-[90vh] bg-white rounded-t-3xl sm:rounded-3xl shadow-2xl overflow-hidden"
+          class="relative w-full max-w-2xl max-h-[90vh] bg-white rounded-t-3xl sm:rounded-3xl shadow-2xl overflow-hidden flex flex-col"
           v-motion-slide-bottom
         >
           <!-- 模态框头部 -->
@@ -140,7 +140,7 @@ const adjustWeight = (key: string, delta: number) => {
           </div>
 
           <!-- 模态框内容 -->
-          <div class="p-6 overflow-y-auto max-h-[calc(90vh-140px)]">
+          <div class="flex-1 p-6 overflow-y-auto" style="max-height: calc(90vh - 200px);">
             <form @submit.prevent="handleSubmit" class="space-y-8">
               <!-- 基础信息 -->
               <section>
@@ -373,20 +373,20 @@ const adjustWeight = (key: string, delta: number) => {
           </div>
 
           <!-- 模态框底部 -->
-          <div class="flex gap-3 p-6 border-t border-gray-100 bg-gray-50">
+          <div class="flex justify-center gap-4 p-6 border-t border-gray-100 bg-gray-50">
             <button
-              type="button"
               @click="handleClose"
-              class="flex-1 btn-secondary"
+              class="btn-secondary px-8"
               :disabled="isSubmitting"
             >
               取消
             </button>
+            
             <button
               @click="handleSubmit"
               :disabled="!isFormValid || totalWeight !== 100 || isSubmitting"
               :class="[
-                'flex-1 btn-primary flex items-center justify-center gap-2',
+                'btn-primary flex items-center justify-center gap-2 px-8',
                 (!isFormValid || totalWeight !== 100 || isSubmitting) && 'opacity-50 cursor-not-allowed'
               ]"
             >

+ 236 - 0
src/components/JobDetailModal.vue

@@ -0,0 +1,236 @@
+<script setup lang="ts">
+import { computed } from 'vue'
+import { useJobStore, type Job } from '../stores/jobStore'
+import { X, Briefcase, MapPin, Users, Calendar, Star } from 'lucide-vue-next'
+
+interface Props {
+  isOpen: boolean
+  job: Job | null
+}
+
+interface Emits {
+  (e: 'close'): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+const jobStore = useJobStore()
+
+const handleClose = () => {
+  emit('close')
+}
+
+const getStatusConfig = (status: Job['status']) => {
+  switch (status) {
+    case 'active':
+      return { label: '招聘中', class: 'bg-success-100 text-success-700' }
+    case 'paused':
+      return { label: '已暂停', class: 'bg-gray-100 text-gray-700' }
+    case 'draft':
+      return { label: '草稿', class: 'bg-warning-100 text-warning-700' }
+    default:
+      return { label: '未知', class: 'bg-gray-100 text-gray-500' }
+  }
+}
+
+const statusConfig = computed(() => 
+  props.job ? getStatusConfig(props.job.status) : { label: '', class: '' }
+)
+</script>
+
+<template>
+  <Teleport to="body">
+    <Transition name="modal" appear>
+      <div 
+        v-if="isOpen && job"
+        class="fixed inset-0 z-50 flex items-end sm:items-center justify-center p-4"
+        @click.self="handleClose"
+      >
+        <!-- 背景遮罩 -->
+        <div class="absolute inset-0 bg-black/50 backdrop-blur-sm"></div>
+        
+        <!-- 模态框内容 -->
+        <div 
+          class="relative w-full max-w-2xl max-h-[90vh] bg-white rounded-t-3xl sm:rounded-3xl shadow-2xl overflow-hidden"
+          v-motion-slide-bottom
+        >
+          <!-- 模态框头部 -->
+          <div class="flex items-center justify-between p-6 border-b border-gray-100">
+            <div class="flex items-center gap-3">
+              <h2 class="text-2xl font-bold text-gray-900">岗位详情</h2>
+              <span :class="['px-3 py-1 rounded-full text-xs font-medium', statusConfig.class]">
+                {{ statusConfig.label }}
+              </span>
+            </div>
+            <button
+              @click="handleClose"
+              class="p-2 rounded-full hover:bg-gray-100 transition-colors"
+            >
+              <X :size="20" class="text-gray-500" />
+            </button>
+          </div>
+
+          <!-- 模态框内容 -->
+          <div class="p-6 overflow-y-auto max-h-[calc(90vh-140px)]">
+            <div class="space-y-8">
+              <!-- 基础信息 -->
+              <section>
+                <h3 class="text-lg font-semibold text-gray-900 mb-4">基础信息</h3>
+                <div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
+                  <div class="bg-gray-50 rounded-xl p-4">
+                    <div class="flex items-center gap-2 mb-2">
+                      <Briefcase :size="16" class="text-primary-600" />
+                      <span class="text-sm font-medium text-gray-700">岗位名称</span>
+                    </div>
+                    <p class="text-gray-900 font-semibold">{{ job.title }}</p>
+                  </div>
+                  
+                  <div class="bg-gray-50 rounded-xl p-4">
+                    <div class="flex items-center gap-2 mb-2">
+                      <Users :size="16" class="text-primary-600" />
+                      <span class="text-sm font-medium text-gray-700">所属部门</span>
+                    </div>
+                    <p class="text-gray-900 font-semibold">{{ job.department }}</p>
+                  </div>
+                  
+                  <div class="bg-gray-50 rounded-xl p-4">
+                    <div class="flex items-center gap-2 mb-2">
+                      <MapPin :size="16" class="text-primary-600" />
+                      <span class="text-sm font-medium text-gray-700">工作地点</span>
+                    </div>
+                    <p class="text-gray-900 font-semibold">{{ job.location }}</p>
+                  </div>
+                  
+                  <div class="bg-gray-50 rounded-xl p-4">
+                    <div class="flex items-center gap-2 mb-2">
+                      <Calendar :size="16" class="text-primary-600" />
+                      <span class="text-sm font-medium text-gray-700">创建时间</span>
+                    </div>
+                    <p class="text-gray-900 font-semibold">{{ new Date(job.createdAt).toLocaleDateString() }}</p>
+                  </div>
+                </div>
+              </section>
+
+              <!-- 岗位描述 -->
+              <section>
+                <h3 class="text-lg font-semibold text-gray-900 mb-4">岗位描述 (JD)</h3>
+                <div class="bg-gray-50 rounded-xl p-6">
+                  <pre class="whitespace-pre-wrap text-gray-700 leading-relaxed font-sans">{{ job.description }}</pre>
+                </div>
+              </section>
+
+              <!-- 招聘统计 -->
+              <section>
+                <h3 class="text-lg font-semibold text-gray-900 mb-4">招聘统计</h3>
+                <div class="grid grid-cols-2 gap-4">
+                  <div class="text-center p-4 bg-gradient-to-br from-warning-50 to-orange-50 rounded-xl border border-warning-100">
+                    <div class="text-2xl font-bold text-warning-600 mb-1">
+                      {{ job.pendingResumes }}
+                    </div>
+                    <div class="text-xs text-warning-700 font-medium">待处理简历</div>
+                  </div>
+                  
+                  <div class="text-center p-4 bg-gradient-to-br from-success-50 to-green-50 rounded-xl border border-success-100">
+                    <div class="text-2xl font-bold text-success-600 mb-1">
+                      {{ job.passedResumes }}
+                    </div>
+                    <div class="text-xs text-success-700 font-medium">已通过初筛</div>
+                  </div>
+                </div>
+              </section>
+
+              <!-- AI筛选评分标准 -->
+              <section>
+                <h3 class="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
+                  <Star :size="20" class="text-primary-600" />
+                  AI筛选评分标准
+                </h3>
+                
+                <div class="space-y-4">
+                  <!-- 学历要求 -->
+                  <div class="p-4 border border-gray-200 rounded-xl">
+                    <div class="flex items-center justify-between mb-2">
+                      <h4 class="font-medium text-gray-900">学历要求</h4>
+                      <span class="text-sm font-medium text-primary-600">
+                        {{ job.aiCriteria.education.weight }}%
+                      </span>
+                    </div>
+                    <p class="text-gray-700">
+                      {{ job.aiCriteria.education.condition === '>=' ? '大于等于' : 
+                         job.aiCriteria.education.condition === '=' ? '等于' : '包含' }} 
+                      {{ job.aiCriteria.education.value }}
+                    </p>
+                  </div>
+
+                  <!-- 工作经验 -->
+                  <div class="p-4 border border-gray-200 rounded-xl">
+                    <div class="flex items-center justify-between mb-2">
+                      <h4 class="font-medium text-gray-900">工作经验</h4>
+                      <span class="text-sm font-medium text-primary-600">
+                        {{ job.aiCriteria.experience.weight }}%
+                      </span>
+                    </div>
+                    <p class="text-gray-700">
+                      {{ job.aiCriteria.experience.condition === '>=' ? '大于等于' : 
+                         job.aiCriteria.experience.condition === '=' ? '等于' : '包含' }} 
+                      {{ job.aiCriteria.experience.value }}
+                    </p>
+                  </div>
+
+                  <!-- 技术能力 -->
+                  <div class="p-4 border border-gray-200 rounded-xl">
+                    <div class="flex items-center justify-between mb-2">
+                      <h4 class="font-medium text-gray-900">技术能力</h4>
+                      <span class="text-sm font-medium text-primary-600">
+                        {{ job.aiCriteria.skills.weight }}%
+                      </span>
+                    </div>
+                    <p class="text-gray-700">
+                      {{ job.aiCriteria.skills.condition === '>=' ? '大于等于' : 
+                         job.aiCriteria.skills.condition === '=' ? '等于' : '包含' }} 
+                      {{ job.aiCriteria.skills.value || '无特殊要求' }}
+                    </p>
+                  </div>
+
+                  <!-- 语言要求 -->
+                  <div class="p-4 border border-gray-200 rounded-xl">
+                    <div class="flex items-center justify-between mb-2">
+                      <h4 class="font-medium text-gray-900">语言要求</h4>
+                      <span class="text-sm font-medium text-primary-600">
+                        {{ job.aiCriteria.language.weight }}%
+                      </span>
+                    </div>
+                    <p class="text-gray-700">
+                      {{ job.aiCriteria.language.condition === '>=' ? '大于等于' : 
+                         job.aiCriteria.language.condition === '=' ? '等于' : '包含' }} 
+                      {{ job.aiCriteria.language.value }}
+                    </p>
+                  </div>
+                </div>
+              </section>
+            </div>
+          </div>
+        </div>
+      </div>
+    </Transition>
+  </Teleport>
+</template>
+
+<style scoped>
+/* 模态框动画 */
+.modal-enter-active,
+.modal-leave-active {
+  transition: all 0.3s ease-out;
+}
+
+.modal-enter-from,
+.modal-leave-to {
+  opacity: 0;
+}
+
+.modal-enter-from .relative,
+.modal-leave-to .relative {
+  transform: translateY(100px) scale(0.95);
+}
+</style>

+ 431 - 0
src/components/JobEditModal.vue

@@ -0,0 +1,431 @@
+<script setup lang="ts">
+import { ref, computed, watch } from 'vue'
+import { useJobStore, type Job } from '../stores/jobStore'
+import { X, Save } from 'lucide-vue-next'
+
+interface Props {
+  isOpen: boolean
+  job: Job | null
+}
+
+interface Emits {
+  (e: 'close'): void
+  (e: 'save', job: Job): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+const jobStore = useJobStore()
+
+const formData = ref({
+  title: '',
+  department: '',
+  location: '',
+  description: '',
+  aiCriteria: {
+    education: { condition: '>=', value: '本科', weight: 20 },
+    experience: { condition: '>=', value: '3年', weight: 30 },
+    skills: { condition: 'includes', value: '', weight: 35 },
+    language: { condition: '>=', value: '英语四级', weight: 15 }
+  }
+})
+
+const isSubmitting = ref(false)
+
+const departments = [
+  '技术部', '产品部', '设计部', '市场部', '运营部', '销售部', '人事部', '财务部'
+]
+
+const locations = [
+  '北京', '上海', '广州', '深圳', '杭州', '成都', '武汉', '西安', '南京', '苏州'
+]
+
+const educationOptions = [
+  '不限', '高中', '专科', '本科', '硕士', '博士'
+]
+
+const experienceOptions = [
+  '不限', '1年', '2年', '3年', '5年', '8年', '10年以上'
+]
+
+const languageOptions = [
+  '无要求', '英语四级', '英语六级', '英语专业八级', '托福', '雅思'
+]
+
+const conditionOptions = [
+  { value: '>=', label: '大于等于' },
+  { value: '=', label: '等于' },
+  { value: 'includes', label: '包含' }
+]
+
+const isFormValid = computed(() => {
+  return formData.value.title.trim() && 
+         formData.value.department && 
+         formData.value.location && 
+         formData.value.description.trim()
+})
+
+const totalWeight = computed(() => {
+  return Object.values(formData.value.aiCriteria).reduce((sum, criteria) => sum + criteria.weight, 0)
+})
+
+// 监听job变化,初始化表单数据
+watch(() => props.job, (newJob) => {
+  if (newJob) {
+    formData.value = {
+      title: newJob.title,
+      department: newJob.department,
+      location: newJob.location,
+      description: newJob.description,
+      aiCriteria: {
+        education: { ...newJob.aiCriteria.education },
+        experience: { ...newJob.aiCriteria.experience },
+        skills: { ...newJob.aiCriteria.skills },
+        language: { ...newJob.aiCriteria.language }
+      }
+    }
+  }
+}, { immediate: true })
+
+const handleClose = () => {
+  if (!isSubmitting.value) {
+    emit('close')
+  }
+}
+
+const handleSubmit = async () => {
+  if (!isFormValid.value || totalWeight.value !== 100 || !props.job) return
+  
+  isSubmitting.value = true
+  
+  try {
+    // 模拟API调用
+    await new Promise(resolve => setTimeout(resolve, 1000))
+    
+    // 更新岗位信息
+    jobStore.updateJob(props.job.id, {
+      ...formData.value
+    })
+    
+    emit('save', { ...props.job, ...formData.value })
+    alert('岗位信息更新成功!')
+    handleClose()
+  } catch (error) {
+    alert('更新失败,请稍后重试')
+  } finally {
+    isSubmitting.value = false
+  }
+}
+
+const adjustWeight = (key: keyof typeof formData.value.aiCriteria, delta: number) => {
+  const currentWeight = formData.value.aiCriteria[key].weight
+  const newWeight = Math.max(0, Math.min(100, currentWeight + delta))
+  formData.value.aiCriteria[key].weight = newWeight
+}
+</script>
+
+<template>
+  <Teleport to="body">
+    <Transition name="modal" appear>
+      <div 
+        v-if="isOpen && job"
+        class="fixed inset-0 z-50 flex items-end sm:items-center justify-center p-4"
+        @click.self="handleClose"
+      >
+        <!-- 背景遮罩 -->
+        <div class="absolute inset-0 bg-black/50 backdrop-blur-sm"></div>
+        
+        <!-- 模态框内容 -->
+        <div 
+          class="relative w-full max-w-2xl max-h-[90vh] bg-white rounded-t-3xl sm:rounded-3xl shadow-2xl overflow-hidden flex flex-col"
+          v-motion-slide-bottom
+        >
+          <!-- 模态框头部 -->
+          <div class="flex items-center justify-between p-6 border-b border-gray-100">
+            <h2 class="text-2xl font-bold text-gray-900">编辑岗位信息</h2>
+            <button
+              @click="handleClose"
+              class="p-2 rounded-full hover:bg-gray-100 transition-colors"
+              :disabled="isSubmitting"
+            >
+              <X :size="20" class="text-gray-500" />
+            </button>
+          </div>
+
+          <!-- 模态框内容 -->
+          <div class="flex-1 p-6 overflow-y-auto" style="max-height: calc(90vh - 200px);">
+            <form @submit.prevent="handleSubmit" class="space-y-8">
+              <!-- 基础信息 -->
+              <section>
+                <h3 class="text-lg font-semibold text-gray-900 mb-4">基础信息</h3>
+                <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
+                  <div>
+                    <label class="block text-sm font-medium text-gray-700 mb-2">
+                      岗位名称 *
+                    </label>
+                    <input
+                      v-model="formData.title"
+                      type="text"
+                      placeholder="请输入岗位名称"
+                      class="input-field"
+                      required
+                    >
+                  </div>
+                  
+                  <div>
+                    <label class="block text-sm font-medium text-gray-700 mb-2">
+                      所属部门 *
+                    </label>
+                    <select v-model="formData.department" class="input-field" required>
+                      <option value="">请选择部门</option>
+                      <option v-for="dept in departments" :key="dept" :value="dept">
+                        {{ dept }}
+                      </option>
+                    </select>
+                  </div>
+                  
+                  <div class="sm:col-span-2">
+                    <label class="block text-sm font-medium text-gray-700 mb-2">
+                      工作地点 *
+                    </label>
+                    <select v-model="formData.location" class="input-field" required>
+                      <option value="">请选择城市</option>
+                      <option v-for="city in locations" :key="city" :value="city">
+                        {{ city }}
+                      </option>
+                    </select>
+                  </div>
+                </div>
+              </section>
+
+              <!-- 岗位描述 -->
+              <section>
+                <h3 class="text-lg font-semibold text-gray-900 mb-4">岗位描述 (JD)</h3>
+                <textarea
+                  v-model="formData.description"
+                  placeholder="请详细描述岗位职责、任职要求等信息..."
+                  class="input-field min-h-[120px] resize-none"
+                  required
+                ></textarea>
+              </section>
+
+              <!-- AI筛选硬性指标 -->
+              <section>
+                <div class="flex items-center justify-between mb-4">
+                  <h3 class="text-lg font-semibold text-gray-900">AI筛选评分标准</h3>
+                  <div class="text-sm text-gray-500">
+                    权重总计: 
+                    <span :class="totalWeight === 100 ? 'text-success-600' : 'text-error-600'">
+                      {{ totalWeight }}%
+                    </span>
+                  </div>
+                </div>
+                
+                <div class="space-y-6">
+                  <!-- 学历要求 -->
+                  <div class="p-4 border border-gray-200 rounded-xl">
+                    <div class="flex items-center justify-between mb-3">
+                      <h4 class="font-medium text-gray-900">学历要求</h4>
+                      <div class="flex items-center gap-2">
+                        <button
+                          type="button"
+                          @click="adjustWeight('education', -5)"
+                          class="w-6 h-6 rounded-full bg-gray-100 hover:bg-gray-200 flex items-center justify-center text-xs"
+                        >
+                          -
+                        </button>
+                        <span class="text-sm font-medium w-8 text-center">
+                          {{ formData.aiCriteria.education.weight }}%
+                        </span>
+                        <button
+                          type="button"
+                          @click="adjustWeight('education', 5)"
+                          class="w-6 h-6 rounded-full bg-gray-100 hover:bg-gray-200 flex items-center justify-center text-xs"
+                        >
+                          +
+                        </button>
+                      </div>
+                    </div>
+                    <div class="grid grid-cols-2 gap-3">
+                      <select v-model="formData.aiCriteria.education.condition" class="input-field">
+                        <option v-for="opt in conditionOptions" :key="opt.value" :value="opt.value">
+                          {{ opt.label }}
+                        </option>
+                      </select>
+                      <select v-model="formData.aiCriteria.education.value" class="input-field">
+                        <option v-for="edu in educationOptions" :key="edu" :value="edu">
+                          {{ edu }}
+                        </option>
+                      </select>
+                    </div>
+                  </div>
+
+                  <!-- 工作经验 -->
+                  <div class="p-4 border border-gray-200 rounded-xl">
+                    <div class="flex items-center justify-between mb-3">
+                      <h4 class="font-medium text-gray-900">工作经验</h4>
+                      <div class="flex items-center gap-2">
+                        <button
+                          type="button"
+                          @click="adjustWeight('experience', -5)"
+                          class="w-6 h-6 rounded-full bg-gray-100 hover:bg-gray-200 flex items-center justify-center text-xs"
+                        >
+                          -
+                        </button>
+                        <span class="text-sm font-medium w-8 text-center">
+                          {{ formData.aiCriteria.experience.weight }}%
+                        </span>
+                        <button
+                          type="button"
+                          @click="adjustWeight('experience', 5)"
+                          class="w-6 h-6 rounded-full bg-gray-100 hover:bg-gray-200 flex items-center justify-center text-xs"
+                        >
+                          +
+                        </button>
+                      </div>
+                    </div>
+                    <div class="grid grid-cols-2 gap-3">
+                      <select v-model="formData.aiCriteria.experience.condition" class="input-field">
+                        <option v-for="opt in conditionOptions" :key="opt.value" :value="opt.value">
+                          {{ opt.label }}
+                        </option>
+                      </select>
+                      <select v-model="formData.aiCriteria.experience.value" class="input-field">
+                        <option v-for="exp in experienceOptions" :key="exp" :value="exp">
+                          {{ exp }}
+                        </option>
+                      </select>
+                    </div>
+                  </div>
+
+                  <!-- 技术能力 -->
+                  <div class="p-4 border border-gray-200 rounded-xl">
+                    <div class="flex items-center justify-between mb-3">
+                      <h4 class="font-medium text-gray-900">技术能力</h4>
+                      <div class="flex items-center gap-2">
+                        <button
+                          type="button"
+                          @click="adjustWeight('skills', -5)"
+                          class="w-6 h-6 rounded-full bg-gray-100 hover:bg-gray-200 flex items-center justify-center text-xs"
+                        >
+                          -
+                        </button>
+                        <span class="text-sm font-medium w-8 text-center">
+                          {{ formData.aiCriteria.skills.weight }}%
+                        </span>
+                        <button
+                          type="button"
+                          @click="adjustWeight('skills', 5)"
+                          class="w-6 h-6 rounded-full bg-gray-100 hover:bg-gray-200 flex items-center justify-center text-xs"
+                        >
+                          +
+                        </button>
+                      </div>
+                    </div>
+                    <div class="grid grid-cols-2 gap-3">
+                      <select v-model="formData.aiCriteria.skills.condition" class="input-field">
+                        <option v-for="opt in conditionOptions" :key="opt.value" :value="opt.value">
+                          {{ opt.label }}
+                        </option>
+                      </select>
+                      <input
+                        v-model="formData.aiCriteria.skills.value"
+                        type="text"
+                        placeholder="例:Vue.js,TypeScript,Node.js"
+                        class="input-field"
+                      >
+                    </div>
+                  </div>
+
+                  <!-- 语言要求 -->
+                  <div class="p-4 border border-gray-200 rounded-xl">
+                    <div class="flex items-center justify-between mb-3">
+                      <h4 class="font-medium text-gray-900">语言要求</h4>
+                      <div class="flex items-center gap-2">
+                        <button
+                          type="button"
+                          @click="adjustWeight('language', -5)"
+                          class="w-6 h-6 rounded-full bg-gray-100 hover:bg-gray-200 flex items-center justify-center text-xs"
+                        >
+                          -
+                        </button>
+                        <span class="text-sm font-medium w-8 text-center">
+                          {{ formData.aiCriteria.language.weight }}%
+                        </span>
+                        <button
+                          type="button"
+                          @click="adjustWeight('language', 5)"
+                          class="w-6 h-6 rounded-full bg-gray-100 hover:bg-gray-200 flex items-center justify-center text-xs"
+                        >
+                          +
+                        </button>
+                      </div>
+                    </div>
+                    <div class="grid grid-cols-2 gap-3">
+                      <select v-model="formData.aiCriteria.language.condition" class="input-field">
+                        <option v-for="opt in conditionOptions" :key="opt.value" :value="opt.value">
+                          {{ opt.label }}
+                        </option>
+                      </select>
+                      <select v-model="formData.aiCriteria.language.value" class="input-field">
+                        <option v-for="lang in languageOptions" :key="lang" :value="lang">
+                          {{ lang }}
+                        </option>
+                      </select>
+                    </div>
+                  </div>
+                </div>
+
+                <div v-if="totalWeight !== 100" class="mt-4 p-3 bg-warning-50 border border-warning-200 rounded-lg">
+                  <p class="text-sm text-warning-700">
+                    权重总计必须为100%,当前为{{ totalWeight }}%
+                  </p>
+                </div>
+              </section>
+            </form>
+          </div>
+
+          <!-- 模态框底部 -->
+          <div class="flex justify-end gap-3 p-6 border-t border-gray-100 bg-gray-50">
+            <button
+              @click="handleSubmit"
+              :disabled="!isFormValid || totalWeight !== 100 || isSubmitting"
+              :class="[
+                'btn-primary flex items-center justify-center gap-2 px-8',
+                (!isFormValid || totalWeight !== 100 || isSubmitting) && 'opacity-50 cursor-not-allowed'
+              ]"
+            >
+              <div v-if="isSubmitting" class="flex items-center gap-2">
+                <div class="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
+                保存中...
+              </div>
+              <div v-else class="flex items-center gap-2">
+                <Save :size="16" />
+                保存
+              </div>
+            </button>
+          </div>
+        </div>
+      </div>
+    </Transition>
+  </Teleport>
+</template>
+
+<style scoped>
+/* 模态框动画 */
+.modal-enter-active,
+.modal-leave-active {
+  transition: all 0.3s ease-out;
+}
+
+.modal-enter-from,
+.modal-leave-to {
+  opacity: 0;
+}
+
+.modal-enter-from .relative,
+.modal-leave-to .relative {
+  transform: translateY(100px) scale(0.95);
+}
+</style>

+ 27 - 1
src/main.ts

@@ -1,8 +1,34 @@
 import { createApp } from 'vue'
 import { createPinia } from 'pinia'
 import { MotionPlugin } from '@vueuse/motion'
-import './style.css'
 import App from './App.vue'
+import './style.css'
+
+// --- Parse 初始化配置 ---
+// 动态导入Parse以避免LiveQuery初始化问题
+let Parse: any;
+
+async function initializeParse() {
+  try {
+    // 设置环境变量禁用LiveQuery
+    (window as any).process = { env: { PARSE_DISABLE_LIVEQUERY: 'true' } };
+    
+    // 动态导入Parse
+    const ParseModule = await import('parse');
+    Parse = ParseModule.default;
+    
+    // 初始化Parse
+    Parse.initialize("myAppId", "myJavascriptKey");
+    Parse.serverURL = "http://localhost:1337/parse";
+    
+    console.log("Parse SDK 成功初始化!");
+  } catch (error) {
+    console.error("Parse SDK 初始化失败:", error);
+  }
+}
+
+// 异步初始化Parse
+initializeParse();
 
 const app = createApp(App)
 const pinia = createPinia()

+ 31 - 1
src/stores/jobStore.ts

@@ -375,6 +375,33 @@ export const useJobStore = defineStore('job', () => {
     interviews.value.push(newInterview)
   }
 
+  const pauseJob = (jobId: string) => {
+    const job = jobs.value.find(j => j.id === jobId)
+    if (job) {
+      job.status = 'paused'
+      job.updatedAt = new Date()
+    }
+  }
+
+  const resumeJob = (jobId: string) => {
+    const job = jobs.value.find(j => j.id === jobId)
+    if (job) {
+      job.status = 'active'
+      job.updatedAt = new Date()
+    }
+  }
+
+  const deleteJob = (jobId: string) => {
+    const index = jobs.value.findIndex(j => j.id === jobId)
+    if (index !== -1) {
+      jobs.value.splice(index, 1)
+      // 同时删除相关的候选人数据
+      candidates.value = candidates.value.filter(c => c.jobId !== jobId)
+      // 删除相关的面试数据
+      //interviews.value = interviews.value.filter(i => i.jobId !== jobId)
+    }
+  }
+
   return {
     // 状态
     jobs,
@@ -408,6 +435,9 @@ export const useJobStore = defineStore('job', () => {
     closeModal,
     setCurrentJob,
     setCurrentCandidate,
-    addInterview
+    addInterview,
+    pauseJob,
+    resumeJob,
+    deleteJob
   }
 })

+ 22 - 0
vite.config.ts

@@ -4,4 +4,26 @@ import vue from '@vitejs/plugin-vue'
 // https://vitejs.dev/config/
 export default defineConfig({
   plugins: [vue()],
+  define: {
+    global: 'globalThis',
+    'process.env': '{}',
+    'process.env.NODE_ENV': '"development"',
+    'process.env.PARSE_DISABLE_LIVEQUERY': '"true"',
+  },
+  optimizeDeps: {
+    include: ['parse', 'events'],
+    exclude: ['parse/lib/browser/ParseLiveQuery'],
+  },
+  resolve: {
+    alias: {
+      events: 'events',
+      util: 'util',
+      stream: 'stream-browserify',
+    },
+  },
+  build: {
+    rollupOptions: {
+      external: [],
+    },
+  },
 })