浏览代码

新增岗位申请页面

ZGC4846 1 周之前
父节点
当前提交
e7ab07790d
共有 7 个文件被更改,包括 1734 次插入212 次删除
  1. 9 2
      src/App.vue
  2. 119 0
      src/api/hr/index.js
  3. 2 1
      src/router/index.js
  4. 144 0
      src/styles/views/positionApplication/index.scss
  5. 1238 0
      src/views/positionApplication/index.vue
  6. 49 0
      src/views/positionApplication/mock.js
  7. 173 209
      yarn.lock

+ 9 - 2
src/App.vue

@@ -14,8 +14,15 @@ export default {
     return { PROJECT_NAME, sidebarCollapsed: false, menus: [
       { path: '/', meta: { title: '首页', icon: 'el-icon-house' } },
       { path: '/organization', meta: { title: '组织架构', icon: 'el-icon-office-building' } },
-      { path: '/position-management', meta: { title: '岗位管理', icon: 'el-icon-suitcase' } },
-      { path: '/position-sequence', meta: { title: '岗位序列', icon: 'el-icon-connection' } },
+      {
+        path: '/position',
+        meta: { title: '岗位管理', icon: 'el-icon-suitcase' },
+        children: [
+          { path: '/position-management', meta: { title: '岗位设定' } },
+          { path: '/position-sequence', meta: { title: '岗位序列' } },
+          { path: '/position-application', meta: { title: '岗位申请' } }
+        ]
+      },
       { path: '/employee-archive', meta: { title: '员工档案', icon: 'el-icon-folder-opened' } },
       { path: '/recruitment-plan', meta: { title: '招聘需求计划', icon: 'el-icon-suitcase' } },
       { path: '/talent-pool', meta: { title: '人才库', icon: 'el-icon-user' } },

+ 119 - 0
src/api/hr/index.js

@@ -597,3 +597,122 @@ export const getRecruitDemandById = async (id) =>
   unwrap(await request.get(`/recruit/demands/${id}`));
 export const changeRecruitDemandStatus = async (id, data) =>
   unwrap(await request.post(`/recruit/demands/${id}/action`, compact(data)));
+
+// 岗位申请
+export const createPositionApplication = async (data) =>
+  unwrap(await request.post("/hr/position/apply", data));
+export const getPositionApplicationPage = async (data = {}) =>
+  unwrap(await request.post("/hr/position/apply/page", compact(data)));
+export const getPositionApplicationById = async (id) =>
+  unwrap(await request.get(`/hr/position/apply/${id}`));
+export const updatePositionApplication = async (id, data) =>
+  unwrap(await request.put(`/hr/position/apply/${id}`, data));
+export const deletePositionApplication = async (id) =>
+  unwrap(await request.delete(`/hr/position/apply/${id}`));
+export const submitPositionApplication = async (id) =>
+  unwrap(await request.post(`/hr/position/apply/${id}/submit`));
+
+// 岗位申请列表项:后端列表字段 → 页面字段(列表不含职责/薪资/技能,由详情接口补齐)
+export function adaptPositionApplication(raw = {}) {
+  return {
+    ...raw,
+    id: raw.id,
+    code: raw.positionCode || "",
+    positionName: raw.positionName || "",
+    organizationId: raw.organizationId,
+    organizationName: raw.organizationName || "",
+    deptId: raw.deptId,
+    deptName: raw.deptName || "",
+    level: Number(raw.positionLevel) || "",
+    positionSequence: raw.positionSequence || "",
+    type: raw.positionType || "",
+    contractTemplateId: raw.contractTemplateId,
+    headcount: Number(raw.plannedRecruitmentCount ?? 0),
+    applicant: raw.applicantName || "",
+    status: raw.status || "DRAFT",
+    updatedAt: raw.updateTime || raw.createTime || "",
+    reviewer: raw.reviewerName || "",
+    reviewTime: raw.reviewTime || "",
+    effectivePositionId: raw.effectivePositionId,
+  };
+}
+
+// 岗位申请详情:在列表字段基础上补职责、薪资区间、技能要求与审批记录
+export function adaptPositionApplicationDetail(raw = {}) {
+  const salary = raw.salary || {};
+  const duties = splitText(salary.dutyContent);
+  return {
+    ...adaptPositionApplication(raw),
+    duties,
+    dutiesText: duties.join("\n"),
+    salaryMin: Number(salary.salaryMin ?? 0),
+    salaryMax: Number(salary.salaryMax ?? 0),
+    expYears: Number(salary.experienceYears ?? 0),
+    education: salary.educationRequirement || "",
+    industryExp: salary.industryExperience || "",
+    professionalAbility: salary.professionalAbility || "",
+    otherRequirement: salary.otherRequirement || "",
+    skills: (raw.skills || []).map((item) => ({
+      ...item,
+      name: item.skillName || "",
+      level: item.skillLevel || "",
+      required: String(item.essentialStatus) === "1",
+    })),
+    certificates: (raw.certificates || []).map((item) => ({
+      ...item,
+      name: item.certificateName || "",
+      category: item.certificateType || "",
+      validMonth: Number(item.certificateValidityPeriod ?? 0),
+    })),
+    reviewOpinion: raw.reviewOpinion || "",
+    operationLogs: raw.operationLogs || [],
+  };
+}
+
+// 岗位申请表单 → 提交参数:基本信息 + 定薪定责 + 技能/证书要求
+export function serializePositionApplication(form) {
+  const duties = (form.dutiesText || "")
+    .split("\n")
+    .map((item) => item.trim())
+    .filter(Boolean);
+  const salary = compact({
+    dutyContent: duties.join("\n"),
+    salaryMin: Number(form.salaryMin || 0),
+    salaryMax: Number(form.salaryMax || 0),
+    experienceYears: Number(form.expYears || 0),
+    educationRequirement: form.education || "",
+    industryExperience: form.industryExp || "",
+    professionalAbility: form.professionalAbility || "",
+    otherRequirement: form.otherRequirement || "",
+  });
+  const payload = {
+    organizationId: form.organizationId,
+    deptId: form.deptId,
+    positionName: form.positionName,
+    positionLevel: String(form.level ?? 1),
+    positionSequence: form.positionSequence || "",
+    positionType: form.type || "general_positions",
+    contractTemplateId: form.contractTemplateId,
+    plannedRecruitmentCount: Number(form.headcount || 0),
+  };
+  if (Object.keys(salary).length) payload.salary = salary;
+  const skills = (form.skills || [])
+    .filter((item) => item.name)
+    .map((item) => ({
+      skillName: item.name,
+      skillLevel: item.level,
+      essentialStatus: item.required ? "1" : "2",
+    }));
+  if (skills.length) payload.skills = skills;
+  const certificates = (form.certificates || [])
+    .filter((item) => item.name)
+    .map((item, index) => ({
+      certificateName: item.name,
+      certificateType: item.category,
+      certificateValidityPeriod: Number(item.validMonth || 0),
+      sortNo: index + 1,
+    }));
+  if (certificates.length) payload.certificates = certificates;
+  if (form.id) payload.id = form.id;
+  return compact(payload);
+}

+ 2 - 1
src/router/index.js

@@ -11,8 +11,9 @@ const routes = [
   { path: '/login', name: 'login', component: () => import('@/views/login/index.vue'), meta: { title: '登录', public: true, fullPage: true } },
   { path: '/', name: 'home', component: HomeView, meta: { title: '首页', public: dev } },
   { path: '/organization', name: 'organization', component: () => import('@/views/organization/index.vue'), meta: { title: '组织架构', public: dev } },
-  { path: '/position-management', name: 'positionManagement', component: () => import('@/views/positionManagement/index.vue'), meta: { title: '岗位管理', public: dev } },
+  { path: '/position-management', name: 'positionManagement', component: () => import('@/views/positionManagement/index.vue'), meta: { title: '岗位设定', public: dev } },
   { path: '/position-sequence', name: 'positionSequence', component: () => import('@/views/positionSequence/index.vue'), meta: { title: '岗位序列', public: dev } },
+  { path: '/position-application', name: 'positionApplication', component: () => import('@/views/positionApplication/index.vue'), meta: { title: '岗位申请', public: dev } },
   { path: '/employee-archive', name: 'employeeArchive', component: () => import('@/views/employeeArchive/index.vue'), meta: { title: '员工档案', public: dev } },
   { path: '/performance-scoring', name: 'performanceScoring', component: () => import('@/views/performanceScoring/index.vue'), meta: { title: '绩效评分', public: dev } },
   { path: '/performance-scheme', name: 'performanceScheme', component: () => import('@/views/performanceScheme/index.vue'), meta: { title: '绩效方案', public: dev } },

+ 144 - 0
src/styles/views/positionApplication/index.scss

@@ -0,0 +1,144 @@
+.application-page {
+  --primary: #1768e5;
+  --ink: #203047;
+  --muted: #7c899c;
+  min-height: 100%;
+  padding: 22px;
+  color: var(--ink);
+  background: radial-gradient(circle at 7% 0, rgba(32,183,216,.08), transparent 24%), radial-gradient(circle at 94% 3%, rgba(23,104,229,.08), transparent 22%), #f3f6fb;
+}
+.metric-grid { margin-bottom: 18px; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; }
+.metric-card { min-height: 104px; padding: 17px 18px; display: flex; align-items: center; gap: 14px; border: 1px solid rgba(211,221,235,.78); border-radius: 10px; background: rgba(255,255,255,.97); box-shadow: 0 8px 24px rgba(40,67,106,.065); transition: transform .2s ease, box-shadow .2s ease; }
+.metric-card:hover { transform: translateY(-2px); box-shadow: 0 13px 28px rgba(40,67,106,.1); }
+.metric-icon { width: 46px; height: 46px; flex: 0 0 46px; display: grid; place-items: center; border-radius: 9px; font-size: 20px; }
+.metric-card small { color: #718096; font-size: 11px; }
+.metric-card strong { margin-top: 3px; display: block; color: #1d2c42; font-size: 25px; line-height: 1.1; }
+.metric-card strong em { margin-left: 4px; color: #8390a2; font-size: 10px; font-style: normal; font-weight: 500; }
+.metric-card p { margin: 6px 0 0; color: #94a0af; font-size: 10px; }
+.metric-blue .metric-icon { color: #1768e5; background: #eaf2ff; }
+.metric-green .metric-icon { color: #168d69; background: #e7f7f1; }
+.metric-cyan .metric-icon { color: #168c98; background: #e7f7f7; }
+.metric-orange .metric-icon { color: #db7a25; background: #fff1e5; }
+.workspace-panel { overflow: hidden; border: 1px solid #e0e7f1; border-radius: 9px; background: #fff; box-shadow: 0 8px 24px rgba(40,67,106,.055); }
+.filter-bar { padding: 16px 18px; display: grid; grid-template-columns: minmax(240px, 1.4fr) minmax(220px, 1.1fr) minmax(160px, .8fr) auto; align-items: end; gap: 14px; background: #fafbfd; border-bottom: 1px solid #edf1f6; }
+.filter-field label { margin-bottom: 7px; display: block; color: #536177; font-size: 11px; font-weight: 600; }
+.filter-field ::v-deep .el-select { width: 100%; }
+.filter-actions { display: flex; gap: 8px; }
+.filter-actions ::v-deep .el-button + .el-button { margin-left: 0; }
+.active-filters { min-height: 45px; padding: 8px 18px; display: flex; align-items: center; flex-wrap: wrap; gap: 8px; border-bottom: 1px solid #edf1f6; background: #fff; }
+.active-filters > span { margin-right: 3px; color: #68778c; font-size: 10px; font-weight: 600; }
+.active-filters > button { margin-left: auto; border: 0; color: #1768e5; background: transparent; font-size: 10px; cursor: pointer; }
+.application-table { width: 100%; }
+.application-table::before { display: none; }
+.application-table ::v-deep .ele-table-tool { min-height: 68px; padding: 12px 18px; border-bottom: 1px solid #e8edf3; }
+.application-table ::v-deep .ele-table-tool-title-label h6 { margin: 0; color: #26364c; font-size: 16px; font-weight: 600; }
+.application-table ::v-deep .ele-table-tool-title-label > div { margin-top: 4px; color: #8d99a9; font-size: 10px; }
+.table-toolbar-actions { display: flex; gap: 8px; }
+.table-toolbar-actions ::v-deep .el-button + .el-button { margin-left: 0; }
+.application-table ::v-deep th.el-table__cell { height: 48px; padding: 0; color: #68778c; background: #f6f8fb; font-size: 11px; font-weight: 600; }
+.application-table ::v-deep td.el-table__cell { padding: 11px 0; border-bottom-color: #edf0f3; }
+.application-table ::v-deep .el-table__row:hover > td.el-table__cell { background: #f2f7ff !important; }
+.position-name { width: 100%; min-width: 0; padding: 0; display: flex; align-items: center; gap: 10px; border: 0; color: inherit; background: none; text-align: left; cursor: pointer; }
+.position-name > span { min-width: 0; }
+.position-name strong, .position-name small { display: block; }
+.position-name strong { overflow: hidden; color: #26364c; font-size: 12px; text-overflow: ellipsis; white-space: nowrap; }
+.position-name:hover strong { color: #1768e5; }
+.position-name small { margin-top: 4px; color: #929dac; font-size: 9px; }
+.code-cell { color: #5d6b80; font-size: 11px; font-family: Consolas, monospace; }
+.contract-tag { color: #4d6f9f !important; border-color: #d6e4f6 !important; background: #f7faff !important; }
+.duties-cell { display: inline-block; max-width: 100%; overflow: hidden; color: #59677b; font-size: 11px; text-overflow: ellipsis; white-space: nowrap; vertical-align: middle; }
+.status-pill { display: inline-flex; align-items: center; gap: 5px; padding: 4px 9px; border-radius: 12px; font-size: 11px; }
+.status-pill i { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
+.status-pill.is-active { color: #118969; background: #e8f8f3; }
+.status-pill.is-warning { color: #c46f11; background: #fff4e5; }
+.status-pill.is-inactive { color: #8b94a2; background: #f0f2f5; }
+.status-pill.is-danger { color: #c94242; background: #fff0f0; }
+.application-table ::v-deep .el-pagination { padding: 15px 18px; }
+.action-cell { display: flex; align-items: center; justify-content: center; gap: 6px; }
+.action-button { width: 30px; height: 30px; margin: 0 !important; padding: 0 !important; border: 1px solid #dfe6ef !important; border-radius: 6px !important; color: #62748d !important; background: #fff !important; }
+.action-button:hover { color: #1768e5 !important; border-color: #b9cff2 !important; background: #f1f6ff !important; }
+.action-button.is-edit:hover { color: #168d69 !important; border-color: #b8dfd2 !important; background: #edf8f4 !important; }
+.table-empty { min-height: 210px; display: grid; place-content: center; justify-items: center; color: #8a96a7; }
+.table-empty > span { width: 46px; height: 46px; display: grid; place-items: center; border-radius: 8px; color: #1768e5; background: #edf4ff; font-size: 20px; }
+.table-empty strong { margin-top: 11px; color: #415069; font-size: 12px; }
+.table-empty p { margin: 5px 0 11px; font-size: 10px; }
+
+.drawer-title { min-width: 0; display: flex; align-items: center; gap: 10px; }
+.drawer-title > span { width: 38px; height: 38px; flex: 0 0 38px; display: grid; place-items: center; border-radius: 8px; color: #1768e5; background: #eaf2ff; font-size: 17px; }
+.drawer-title > div { min-width: 0; }
+.drawer-title h2 { margin: 0; color: #1f2e43; font-size: 16px; }
+.drawer-title p { margin: 4px 0 0; color: #8b97a7; font-size: 9px; }
+.drawer-title > em { margin-left: 7px; display: inline-flex; align-items: center; gap: 5px; color: #168d69; font-size: 10px; font-style: normal; white-space: nowrap; }
+.drawer-title > em i { width: 6px; height: 6px; border-radius: 50%; background: #1da274; }
+.drawer-title > em.draft { color: #8793a3; }
+.drawer-title > em.draft i { background: #a7b1be; }
+.drawer-title > em.pending { color: #d87826; }
+.drawer-title > em.pending i { background: #e0a25a; }
+.drawer-title > em.rejected { color: #d85151; }
+.drawer-title > em.rejected i { background: #e88b8b; }
+.drawer-body { padding: 0 20px 92px; }
+.detail-overview { margin-bottom: 15px; padding: 16px; display: flex; align-items: center; gap: 12px; border: 1px solid #dce8f6; border-radius: 8px; background: linear-gradient(110deg, #f6f9ff, #fbfdff); }
+.detail-overview > span { width: 46px; height: 46px; flex: 0 0 46px; display: grid; place-items: center; border-radius: 9px; color: #1768e5; background: #eaf2ff; font-size: 12px; font-weight: 700; }
+.type-管理岗 { color: #7553be !important; background: #f0ebfb !important; }
+.type-技术岗 { color: #168c98 !important; background: #e7f7f7 !important; }
+.type-操作岗 { color: #db7a25 !important; background: #fff1e5 !important; }
+.detail-overview h3 { margin: 0; color: #26364c; font-size: 17px; }
+.detail-overview p { margin: 5px 0 0; color: #8290a3; font-size: 10px; }
+.detail-section { padding: 18px; border: 1px solid #e1e7ef; border-radius: 8px; background: #fff; }
+.detail-section + .detail-section { margin-top: 14px; }
+.detail-heading { margin-bottom: 17px; display: flex; align-items: center; gap: 10px; }
+.detail-heading > span { width: 33px; height: 33px; display: grid; place-items: center; border-radius: 7px; color: #1768e5; background: #edf4ff; }
+.detail-heading h3 { margin: 0; color: #2a384d; font-size: 14px; }
+.detail-heading p { margin: 4px 0 0; color: #8a96a7; font-size: 9px; }
+.detail-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px 24px; }
+.detail-grid span, .detail-block > span { display: block; color: #8a96a7; font-size: 10px; }
+.detail-grid strong { margin-top: 5px; display: block; color: #3d4d64; font-size: 12px; font-weight: 600; }
+.detail-block { margin-top: 2px; }
+.duty-list { margin: 8px 0 0; padding-left: 19px; color: #59677b; font-size: 11px; line-height: 1.8; }
+.review-opinion { margin: 7px 0 0; color: #59677b; font-size: 11px; line-height: 1.65; }
+.log-list { margin: 14px 0 0; padding: 12px 0 0; list-style: none; border-top: 1px solid #edf1f6; }
+.log-list li { padding: 8px 0; display: grid; grid-template-columns: 66px 1fr auto; align-items: center; gap: 10px; }
+.log-list li + li { border-top: 1px dashed #edf1f6; }
+.log-list li > span { padding: 3px 7px; border-radius: 5px; color: #58739b; background: #f0f5fc; font-size: 10px; text-align: center; }
+.log-list li p { margin: 0; color: #59677b; font-size: 11px; }
+.log-list li em { color: #9aa5b4; font-size: 10px; font-style: normal; white-space: nowrap; }
+.detail-audit { margin-top: 14px; padding: 11px 13px; display: flex; align-items: center; justify-content: space-between; gap: 12px; color: #8793a3; background: #f8fafd; font-size: 9px; }
+.detail-audit i { margin-right: 4px; color: #95a3b6; }
+
+.form-guide { margin-bottom: 15px; padding: 11px 13px; display: flex; align-items: flex-start; gap: 9px; border: 1px solid #d9e8fb; border-radius: 7px; color: #59718f; background: #f5f9ff; }
+.form-guide > i { margin-top: 1px; color: #1768e5; }
+.form-guide strong, .form-guide small { display: block; }
+.form-guide strong { color: #385b86; font-size: 11px; }
+.form-guide small { margin-top: 3px; color: #8290a3; font-size: 9px; }
+.form-guide b { color: #e05a50; }
+.form-section { padding: 18px; border: 1px solid #e1e7ef; border-radius: 8px; background: #fff; }
+.form-section + .form-section { margin-top: 16px; }
+.section-title { margin-bottom: 17px; display: flex; align-items: center; gap: 10px; }
+.section-title > span { width: 33px; height: 33px; flex: 0 0 33px; display: grid; place-items: center; border-radius: 7px; color: #1768e5; background: #edf4ff; }
+.section-title h3 { margin: 0; color: #2a384d; font-size: 14px; }
+.section-title p { margin: 4px 0 0; color: #8a96a7; font-size: 9px; }
+.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 15px; }
+.application-form ::v-deep .el-form-item__label { padding-bottom: 5px; color: #4a586d; font-size: 11px; line-height: 19px; }
+.application-form ::v-deep .el-select, .application-form ::v-deep .el-input-number { width: 100%; }
+.field-help { margin: 6px 0 0; color: #8a96a7; font-size: 9px; line-height: 1.5; }
+.field-help i { margin-right: 4px; color: #1768e5; }
+.form-subtitle { margin: 16px 0 10px; color: #2a384d; font-size: 12px; font-weight: 700; }
+.salary-range-field { width: 100%; display: flex; align-items: center; gap: 8px; }
+.salary-range-field ::v-deep .el-input-number { flex: 1 1 0; width: auto; }
+.salary-range-field span { color: #8a96a7; font-size: 10px; }
+.dynamic-row { margin-bottom: 10px; display: grid; grid-template-columns: minmax(0, 1fr) 130px auto auto; align-items: center; gap: 10px; }
+.dynamic-row.cert-row { grid-template-columns: minmax(0, 1fr) 120px 140px auto; }
+.dynamic-row ::v-deep .el-input-number { width: 100%; }
+.add-line { margin-top: 2px; }
+.detail-tags { margin-top: 8px; display: flex; flex-wrap: wrap; gap: 7px; }
+.detail-tags em { padding: 5px 9px; border: 1px solid #d9e5f4; border-radius: 5px; color: #58739b; background: #f7faff; font-size: 10px; font-style: normal; }
+.detail-muted { margin: 8px 0 0; color: #9aa5b4; font-size: 10px; }
+.drawer-footer { position: absolute; right: 0; bottom: 0; left: 0; z-index: 5; min-height: 68px; padding: 11px 20px; display: flex; align-items: center; justify-content: space-between; gap: 13px; border-top: 1px solid #dfe6ef; background: #fff; box-shadow: 0 -8px 20px rgba(40,67,106,.06); }
+.drawer-footer > span { color: #7e8a9a; font-size: 9px; }
+.drawer-footer > span i { margin-right: 4px; }
+.drawer-footer > div { display: flex; gap: 8px; }
+.drawer-footer ::v-deep .el-button + .el-button { margin-left: 0; }
+::v-deep .application-drawer { position: absolute; right: 0; left: auto; overflow-y: auto; }
+::v-deep .application-drawer .el-drawer__header { margin-bottom: 15px; padding: 17px 20px 14px; border-bottom: 1px solid #e4e9f0; }
+@media (max-width: 1180px) { .metric-grid { grid-template-columns: 1fr 1fr; }.filter-bar { grid-template-columns: 1fr 1fr; }.filter-actions { justify-content: flex-end; } }
+@media (max-width: 760px) { .application-page { padding: 14px; }.metric-grid, .filter-bar { grid-template-columns: 1fr; }.filter-actions .el-button { flex: 1; }.form-grid, .detail-grid { grid-template-columns: 1fr; }.drawer-footer > span { display: none; }.drawer-footer > div { margin-left: auto; } }

+ 1238 - 0
src/views/positionApplication/index.vue

@@ -0,0 +1,1238 @@
+<template>
+  <main class="application-page" v-loading="loading">
+    <section class="metric-grid" aria-label="岗位申请概览">
+      <article
+        v-for="item in metrics"
+        :key="item.label"
+        class="metric-card"
+        :class="'metric-' + item.tone"
+      >
+        <span class="metric-icon"><i :class="item.icon"></i></span>
+        <div>
+          <small>{{ item.label }}</small
+          ><strong>{{ item.value }}<em>{{ item.unit }}</em></strong>
+          <p>{{ item.note }}</p>
+        </div>
+      </article>
+    </section>
+
+    <section class="workspace-panel">
+      <div class="filter-bar">
+        <div class="filter-field keyword-field">
+          <label>岗位名称</label>
+          <el-input
+            v-model.trim="draftFilters.keyword"
+            clearable
+            prefix-icon="el-icon-search"
+            placeholder="输入岗位名称,支持模糊查询"
+            @keyup.enter.native="applyFilters"
+          />
+        </div>
+        <div class="filter-field">
+          <label>所属机构</label>
+          <el-select
+            v-model="draftFilters.organizationId"
+            clearable
+            filterable
+            placeholder="全部机构"
+          >
+            <el-option
+              v-for="item in legalEntities"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id"
+            />
+          </el-select>
+        </div>
+        <div class="filter-field">
+          <label>状态</label>
+          <el-select
+            v-model="draftFilters.status"
+            clearable
+            placeholder="全部状态"
+          >
+            <el-option
+              v-for="item in statusOptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </div>
+        <div class="filter-actions">
+          <el-button type="primary" icon="el-icon-search" @click="applyFilters"
+            >查询</el-button
+          >
+          <el-button icon="el-icon-refresh-left" @click="resetFilters"
+            >重置</el-button
+          >
+        </div>
+      </div>
+
+      <div v-if="hasFilters" class="active-filters">
+        <span><i class="el-icon-filter"></i> 筛选条件</span>
+        <el-tag
+          v-if="filters.keyword"
+          closable
+          size="small"
+          @close="clearFilter('keyword')"
+          >关键词:{{ filters.keyword }}</el-tag
+        >
+        <el-tag
+          v-if="filters.organizationId"
+          closable
+          size="small"
+          type="success"
+          @close="clearFilter('organizationId')"
+          >{{ orgName(filters.organizationId) }}</el-tag
+        >
+        <el-tag
+          v-if="filters.status"
+          closable
+          size="small"
+          :type="statusTagType(filters.status)"
+          @close="clearFilter('status')"
+          >{{ statusName(filters.status) }}</el-tag
+        >
+        <button type="button" @click="resetFilters">清空全部</button>
+      </div>
+
+      <ele-pro-table
+        ref="table"
+        row-key="id"
+        :columns="columns"
+        :datasource="fetchPage"
+        :need-page="true"
+        :page-size="8"
+        :cache-key="cacheKeyUrl"
+        title="岗位申请列表"
+        :sub-title="
+          '共 ' + totalCount + ' 条数据,可按岗位名称、所属机构与状态快速定位'
+        "
+        :toolkit="['size', 'columns', 'fullscreen']"
+        stripe
+        class="application-table"
+      >
+        <template slot="toolbar">
+          <div class="table-toolbar-actions">
+            <el-button size="small" icon="el-icon-refresh" @click="refreshData"
+              >刷新</el-button
+            >
+            <el-button
+              size="small"
+              type="primary"
+              icon="el-icon-plus"
+              @click="openCreate"
+              >岗位申请</el-button
+            >
+          </div>
+        </template>
+        <template v-slot:code="{ row }">
+          <span class="code-cell">{{ row.code }}</span>
+        </template>
+        <template v-slot:name="{ row }">
+          <button
+            type="button"
+            class="position-name"
+            @click.stop="openView(row)"
+          >
+            <span
+              ><strong>{{ row.positionName }}</strong
+              ><small>{{ typeName(row.type) }} · {{ row.level }}级</small></span
+            >
+          </button>
+        </template>
+        <template v-slot:contract="{ row }"
+          ><el-tag size="mini" effect="plain" class="contract-tag">{{
+            contractName(row.contractTemplateId)
+          }}</el-tag></template
+        >
+        <template v-slot:duties="{ row }">
+          <el-tooltip
+            :content="(row.duties || []).join(';')"
+            :disabled="(row.duties || []).length <= 1"
+            placement="top"
+          >
+            <span class="duties-cell">{{ (row.duties || [])[0] || "-" }}</span>
+          </el-tooltip>
+        </template>
+        <template v-slot:status="{ row }">
+          <span class="status-pill" :class="statusClass(row.status)"
+            ><i></i>{{ statusName(row.status) }}</span
+          >
+        </template>
+        <template v-slot:action="{ row }">
+          <div class="action-cell">
+            <el-tooltip content="查看岗位申请" placement="top"
+              ><el-button
+                class="action-button is-view"
+                icon="el-icon-view"
+                @click.stop="openView(row)"
+            /></el-tooltip>
+            <el-tooltip v-if="canEdit(row)" content="编辑岗位申请" placement="top"
+              ><el-button
+                class="action-button is-edit"
+                icon="el-icon-edit-outline"
+                @click.stop="openEdit(row)"
+            /></el-tooltip>
+          </div>
+        </template>
+        <template slot="empty"
+          ><div class="table-empty">
+            <span><i class="el-icon-search"></i></span
+            ><strong>没有找到匹配的岗位申请</strong>
+            <p>请调整筛选条件后再试</p>
+            <el-button size="small" @click="resetFilters">清除筛选</el-button>
+          </div></template
+        >
+      </ele-pro-table>
+    </section>
+
+    <el-drawer
+      :visible.sync="drawerVisible"
+      direction="rtl"
+      :size="drawerSize"
+      custom-class="application-drawer"
+      append-to-body
+      :before-close="closeDrawer"
+    >
+      <template slot="title">
+        <div class="drawer-title">
+          <span
+            ><i :class="readOnly ? 'el-icon-view' : 'el-icon-edit-outline'"></i
+          ></span>
+          <div>
+            <h2>{{ drawerTitle }}</h2>
+            <p>{{ form.code || "保存后自动生成岗位编码" }}</p>
+          </div>
+          <em v-if="mode !== 'create'" :class="statusTone(form.status)"
+            ><i></i>{{ statusName(form.status) }}</em
+          >
+        </div>
+      </template>
+      <div class="drawer-body">
+        <template v-if="readOnly">
+          <section class="detail-overview">
+            <span :class="'type-' + form.type">{{ typeName(form.type) }}</span>
+            <div>
+              <h3>{{ form.positionName }}</h3>
+              <p>
+                {{ form.code }} · {{ orgName(form.organizationId) }} ·
+                {{ form.level }}级 · 申请招聘 {{ form.headcount }} 人
+              </p>
+            </div>
+          </section>
+          <section class="detail-section">
+            <div class="detail-heading">
+              <span><i class="el-icon-document"></i></span>
+              <div>
+                <h3>申请信息</h3>
+                <p>岗位申请单据的关键信息</p>
+              </div>
+            </div>
+            <div class="detail-grid">
+              <div>
+                <span>岗位编码</span><strong>{{ form.code }}</strong>
+              </div>
+              <div>
+                <span>所属机构</span
+                ><strong>{{ orgName(form.organizationId) }}</strong>
+              </div>
+              <div>
+                <span>所属部门</span><strong>{{ form.deptName || "-" }}</strong>
+              </div>
+              <div>
+                <span>岗位层级</span><strong>{{ form.level }}级</strong>
+              </div>
+              <div>
+                <span>岗位类型</span><strong>{{ typeName(form.type) }}</strong>
+              </div>
+              <div>
+                <span>岗位序列</span
+                ><strong>{{ form.positionSequence || "-" }}</strong>
+              </div>
+              <div>
+                <span>岗位薪资区间</span><strong>{{ salaryText }}</strong>
+              </div>
+              <div>
+                <span>绑定合同模板</span
+                ><strong>{{ contractName(form.contractTemplateId) }}</strong>
+              </div>
+              <div>
+                <span>申请招聘人数</span
+                ><strong>{{ form.headcount }} 人</strong>
+              </div>
+              <div>
+                <span>申请人</span><strong>{{ form.applicant || "-" }}</strong>
+              </div>
+              <div>
+                <span>提交时间</span
+                ><strong>{{ form.updatedAt || "-" }}</strong>
+              </div>
+            </div>
+          </section>
+          <section class="detail-section" v-if="form.reviewer || form.reviewTime">
+            <div class="detail-heading">
+              <span><i class="el-icon-s-check"></i></span>
+              <div>
+                <h3>审批记录</h3>
+                <p>最终审批结果与意见</p>
+              </div>
+            </div>
+            <div class="detail-grid">
+              <div>
+                <span>审批人</span><strong>{{ form.reviewer || "-" }}</strong>
+              </div>
+              <div>
+                <span>审批时间</span
+                ><strong>{{ form.reviewTime || "-" }}</strong>
+              </div>
+            </div>
+            <div class="detail-block">
+              <span>审批意见</span>
+              <p class="review-opinion">{{ form.reviewOpinion || "暂无审批意见" }}</p>
+            </div>
+            <ol class="log-list" v-if="form.operationLogs && form.operationLogs.length">
+              <li v-for="log in form.operationLogs" :key="log.id">
+                <span>{{ operationName(log.operationType) }}</span>
+                <p>{{ log.operationRemark || "-" }}</p>
+                <em>{{ log.createTime }}</em>
+              </li>
+            </ol>
+          </section>
+          <section class="detail-section">
+            <div class="detail-heading">
+              <span><i class="el-icon-s-cooperation"></i></span>
+              <div>
+                <h3>岗位职责</h3>
+                <p>该岗位的核心工作职责</p>
+              </div>
+            </div>
+            <div class="detail-block">
+              <span>岗位职责</span>
+              <ol class="duty-list">
+                <li v-for="item in form.duties" :key="item">{{ item }}</li>
+              </ol>
+            </div>
+          </section>
+          <section class="detail-section">
+            <div class="detail-heading">
+              <span><i class="el-icon-user"></i></span>
+              <div>
+                <h3>技能要求</h3>
+                <p>岗位任职能力、学历经验与技能证书要求</p>
+              </div>
+            </div>
+            <div class="detail-grid">
+              <div>
+                <span>工作年限要求</span
+                ><strong>{{ form.expYears || 0 }} 年</strong>
+              </div>
+              <div>
+                <span>学历要求</span
+                ><strong>{{ form.education || "-" }}</strong>
+              </div>
+              <div>
+                <span>行业经验</span
+                ><strong>{{ form.industryExp || "-" }}</strong>
+              </div>
+              <div>
+                <span>专业能力</span
+                ><strong>{{ form.professionalAbility || "-" }}</strong>
+              </div>
+              <div>
+                <span>其他要求</span
+                ><strong>{{ form.otherRequirement || "-" }}</strong>
+              </div>
+            </div>
+            <div class="detail-block">
+              <span>技能要求</span>
+              <div class="detail-tags" v-if="form.skills && form.skills.length">
+                <em v-for="skill in form.skills" :key="skill.name"
+                  >{{ skill.name }} · {{ skill.level
+                  }}{{ skill.required ? "(必备)" : "" }}</em
+                >
+              </div>
+              <p v-else class="detail-muted">未配置技能要求</p>
+            </div>
+            <div class="detail-block">
+              <span>技能证书</span>
+              <div
+                class="detail-tags"
+                v-if="form.certificates && form.certificates.length"
+              >
+                <em v-for="cert in form.certificates" :key="cert.name"
+                  >{{ cert.name }} · {{ cert.category
+                  }}{{ cert.validMonth ? " · " + cert.validMonth + "个月" : "" }}</em
+                >
+              </div>
+              <p v-else class="detail-muted">未配置证书要求</p>
+            </div>
+          </section>
+          <section class="detail-audit">
+            <span
+              ><i class="el-icon-time"></i>
+              {{ form.updatedAt || "当前为模拟数据环境" }}</span
+            >
+          </section>
+        </template>
+        <el-form
+          v-else
+          ref="applicationForm"
+          :model="form"
+          :rules="rules"
+          label-position="top"
+          class="application-form"
+        >
+          <div class="form-guide">
+            <i class="el-icon-info"></i
+            ><span
+              ><strong>{{
+                mode === "create" ? "发起岗位申请" : "调整岗位申请配置"
+              }}</strong
+              ><small v-if="mode === 'create'"
+                >带 <b>*</b> 的项目为必填项,保存后进入部门负责人 / HR 审核。</small
+              ><small v-else
+                >带 <b>*</b> 的项目为必填项,保存草稿后可继续修改,提交后进入部门负责人 / HR 审核。</small
+              ></span
+            >
+          </div>
+          <section class="form-section">
+            <div class="section-title">
+              <span><i class="el-icon-suitcase"></i></span>
+              <div>
+                <h3>岗位基本信息</h3>
+                <p>填写申请岗位的基础信息与定薪区间</p>
+              </div>
+            </div>
+            <div class="form-grid">
+              <el-form-item label="岗位名称" prop="positionName"
+                ><el-input
+                  v-model.trim="form.positionName"
+                  maxlength="40"
+                  show-word-limit
+                  placeholder="如:数控车床操作工"
+              /></el-form-item>
+              <el-form-item label="机构归属" prop="organizationId"
+                ><el-select
+                  v-model="form.organizationId"
+                  filterable
+                  placeholder="请选择所属机构"
+                  @change="handleOrganizationChange"
+                  ><el-option
+                    v-for="item in legalEntities"
+                    :key="item.id"
+                    :label="item.name"
+                    :value="item.id" /></el-select
+              ></el-form-item>
+              <el-form-item label="所属部门" prop="deptId"
+                ><el-select
+                  v-model="form.deptId"
+                  filterable
+                  placeholder="请选择所属部门"
+                  ><el-option
+                    v-for="item in departmentOptions"
+                    :key="item.id"
+                    :label="item.name"
+                    :value="item.id" /></el-select
+              ></el-form-item>
+              <el-form-item label="岗位类型" prop="type"
+                ><el-select v-model="form.type" placeholder="请选择岗位类型"
+                  ><el-option
+                    v-for="item in typeOptions"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value" /></el-select
+              ></el-form-item>
+              <el-form-item label="岗位序列" prop="positionSequence"
+                ><el-select
+                  v-model="form.positionSequence"
+                  filterable
+                  placeholder="请选择岗位序列"
+                  ><el-option
+                    v-for="item in sequenceOptions"
+                    :key="item"
+                    :label="item"
+                    :value="item" /></el-select
+              ></el-form-item>
+              <el-form-item label="岗位层级" prop="level"
+                ><el-select v-model="form.level" placeholder="请选择岗位层级"
+                  ><el-option
+                    v-for="n in levelOptions"
+                    :key="n"
+                    :label="n + '级'"
+                    :value="n" /></el-select
+              ></el-form-item>
+              <el-form-item label="岗位绑定合同模板" prop="contractTemplateId"
+                ><el-select
+                  v-model="form.contractTemplateId"
+                  filterable
+                  placeholder="请选择合同模板"
+                  ><el-option
+                    v-for="item in contractTemplates"
+                    :key="item.id"
+                    :label="item.name"
+                    :value="item.id" /></el-select
+              ></el-form-item>
+              <el-form-item label="申请招聘人数" prop="headcount"
+                ><el-input-number
+                  v-model="form.headcount"
+                  :min="1"
+                  :max="999"
+                  controls-position="right"
+              /></el-form-item>
+              <el-form-item label="岗位薪资区间" prop="salaryMax">
+                <div class="salary-range-field">
+                  <el-input-number
+                    v-model="form.salaryMin"
+                    :min="0"
+                    :step="500"
+                    controls-position="right"
+                    placeholder="最低月薪"
+                  />
+                  <span>至</span>
+                  <el-input-number
+                    v-model="form.salaryMax"
+                    :min="0"
+                    :step="500"
+                    controls-position="right"
+                    placeholder="最高月薪"
+                  />
+                </div>
+              </el-form-item>
+              <el-form-item label="申请人" class="applicant-field"
+                ><el-input
+                  v-model="form.applicant"
+                  disabled
+                  placeholder="保存后自动记录当前登录用户"
+              /></el-form-item>
+            </div>
+            <el-form-item label="岗位职责" prop="dutiesText"
+              ><el-input
+                v-model.trim="form.dutiesText"
+                type="textarea"
+                :rows="5"
+                maxlength="300"
+                show-word-limit
+                placeholder="1. 负责……&#10;2. 落实……&#10;3. 协同……"
+              />
+              <p class="field-help">
+                <i class="el-icon-info"></i>
+                建议按职责重要性由高到低逐行填写。
+              </p></el-form-item
+            >
+          </section>
+          <section class="form-section">
+            <div class="section-title">
+              <span><i class="el-icon-user"></i></span>
+              <div>
+                <h3>技能要求</h3>
+                <p>定义岗位任职能力、学历经验与技能证书要求</p>
+              </div>
+            </div>
+            <div class="form-grid">
+              <el-form-item label="工作年限要求"
+                ><el-input-number
+                  v-model="form.expYears"
+                  :min="0"
+                  :max="40"
+                  controls-position="right"
+                  placeholder="单位:年"
+              /></el-form-item>
+              <el-form-item label="学历要求"
+                ><el-select v-model="form.education" placeholder="请选择学历要求"
+                  ><el-option
+                    v-for="item in educationOptions"
+                    :key="item"
+                    :label="item"
+                    :value="item" /></el-select
+              ></el-form-item>
+              <el-form-item label="行业经验"
+                ><el-input
+                  v-model.trim="form.industryExp"
+                  placeholder="如:3 年以上汽车零部件制造行业"
+              /></el-form-item>
+              <el-form-item label="专业能力"
+                ><el-input
+                  v-model.trim="form.professionalAbility"
+                  placeholder="如:熟悉 IATF16949、APQP 五大工具"
+              /></el-form-item>
+              <el-form-item label="其他要求"
+                ><el-input
+                  v-model.trim="form.otherRequirement"
+                  placeholder="补充能力、素质或工作制要求"
+              /></el-form-item>
+            </div>
+            <div class="form-subtitle">技能</div>
+            <div
+              v-for="(skill, index) in form.skills"
+              :key="index"
+              class="dynamic-row"
+            >
+              <el-input v-model.trim="skill.name" placeholder="技能名称" />
+              <el-select v-model="skill.level" placeholder="技能等级">
+                <el-option
+                  v-for="item in skillLevelOptions"
+                  :key="item"
+                  :label="item"
+                  :value="item"
+                />
+              </el-select>
+              <el-switch v-model="skill.required" active-text="必备" />
+              <el-button icon="el-icon-delete" @click="removeSkill(index)" />
+            </div>
+            <el-button class="add-line" icon="el-icon-plus" @click="addSkill"
+              >新增技能</el-button
+            >
+            <div class="form-subtitle">技能证书</div>
+            <div
+              v-for="(cert, index) in form.certificates"
+              :key="cert.name + index"
+              class="dynamic-row cert-row"
+            >
+              <el-input v-model.trim="cert.name" placeholder="证书名称" />
+              <el-select v-model="cert.category" placeholder="证书类别">
+                <el-option
+                  v-for="item in certificateTypes"
+                  :key="item"
+                  :label="item"
+                  :value="item"
+                />
+              </el-select>
+              <el-input-number
+                v-model="cert.validMonth"
+                :min="0"
+                :max="120"
+                controls-position="right"
+                placeholder="有效期(月)"
+              />
+              <el-button icon="el-icon-delete" @click="removeCert(index)" />
+            </div>
+            <el-button class="add-line" icon="el-icon-plus" @click="addCert"
+              >新增证书</el-button
+            >
+          </section>
+        </el-form>
+      </div>
+      <div class="drawer-footer">
+        <span
+          ><i class="el-icon-lock"></i>
+          岗位申请将用于招聘需求岗位基础数据</span
+        >
+        <div v-if="readOnly">
+          <el-button @click="drawerVisible = false">关闭</el-button
+          ><el-button
+            v-if="canEdit(form)"
+            type="primary"
+            icon="el-icon-edit"
+            @click="mode = 'edit'"
+            >编辑</el-button
+          >
+        </div>
+        <div v-else>
+          <el-button @click="closeDrawer()">取消</el-button>
+          <template v-if="mode === 'create'">
+            <el-button
+              type="primary"
+              icon="el-icon-position"
+              @click="saveApplication(true)"
+              >保存</el-button
+            >
+          </template>
+          <template v-else>
+            <el-button @click="saveApplication(false)">保存草稿</el-button>
+            <el-button
+              type="primary"
+              icon="el-icon-position"
+              @click="saveApplication(true)"
+              >提交审批</el-button
+            >
+          </template>
+        </div>
+      </div>
+    </el-drawer>
+  </main>
+</template>
+
+<script>
+import {
+  adaptPositionApplication,
+  adaptPositionApplicationDetail,
+  createPositionApplication,
+  getContractTemplatePage,
+  getPositionApplicationById,
+  getPositionApplicationPage,
+  getPositionSequenceNames,
+  serializePositionApplication,
+  submitPositionApplication,
+  updatePositionApplication,
+} from "@/api/hr";
+import { listOrganizations } from "@/api/organization";
+import {
+  buildOrganizationTree,
+  flattenOrganizationTree,
+} from "@/utils/organization-tree";
+import {
+  positionLevels,
+  positionTypes,
+  contractTemplates as fallbackContractTemplates,
+  statusOptions,
+  educationOptions,
+  skillLevelOptions,
+  certificateTypes,
+  sequenceFallback,
+} from "./mock";
+
+const TYPE_LABELS = {
+  core_positions: "核心岗",
+  key_positions: "关键岗",
+  general_positions: "一般岗",
+  auxiliary_positions: "辅助岗",
+};
+const STATUS_LABELS = {
+  DRAFT: "未提交",
+  PENDING: "待审核",
+  APPROVED: "已审核",
+  REJECTED: "已驳回",
+};
+const OPERATION_LABELS = {
+  CREATE: "创建",
+  UPDATE: "编辑",
+  DELETE: "删除",
+  SUBMIT: "提交审批",
+  APPROVE: "审批通过",
+  REJECT: "驳回",
+  EFFECTIVE: "生效",
+};
+
+const emptyFilters = () => ({
+  keyword: "",
+  organizationId: "",
+  status: "",
+});
+
+const emptyForm = () => ({
+  id: null,
+  code: "",
+  positionName: "",
+  organizationId: null,
+  deptId: null,
+  deptName: "",
+  level: 1,
+  positionSequence: "",
+  type: "general_positions",
+  contractTemplateId: null,
+  headcount: 1,
+  salaryMin: 0,
+  salaryMax: 0,
+  expYears: 0,
+  education: "",
+  industryExp: "",
+  professionalAbility: "",
+  otherRequirement: "",
+  skills: [],
+  certificates: [],
+  applicant: "",
+  dutiesText: "",
+  duties: [],
+  status: "DRAFT",
+  reviewer: "",
+  reviewTime: "",
+  reviewOpinion: "",
+  operationLogs: [],
+  updatedAt: "",
+});
+
+export default {
+  name: "PositionApplication",
+  data() {
+    return {
+      loading: false,
+      saving: false,
+      rows: [],
+      totalCount: 0,
+      overview: [],
+      dutiesCache: {},
+      legalEntities: [],
+      departments: [],
+      orgMap: {},
+      draftFilters: emptyFilters(),
+      filters: emptyFilters(),
+      levelOptions: positionLevels,
+      typeOptions: positionTypes,
+      sequenceOptions: sequenceFallback,
+      educationOptions,
+      skillLevelOptions,
+      certificateTypes,
+      contractTemplates: fallbackContractTemplates,
+      statusOptions,
+      cacheKeyUrl: "oa-pc-position-application-v4",
+      drawerVisible: false,
+      mode: "view",
+      form: emptyForm(),
+      rules: {
+        positionName: [
+          { required: true, message: "请输入岗位名称", trigger: "blur" },
+        ],
+        organizationId: [
+          { required: true, message: "请选择所属机构", trigger: "change" },
+        ],
+        deptId: [
+          { required: true, message: "请选择所属部门", trigger: "change" },
+        ],
+        level: [{ required: true, message: "请选择岗位层级", trigger: "change" }],
+        type: [{ required: true, message: "请选择岗位类型", trigger: "change" }],
+        positionSequence: [
+          { required: true, message: "请选择岗位序列", trigger: "change" },
+        ],
+        contractTemplateId: [
+          { required: true, message: "请选择绑定合同模板", trigger: "change" },
+        ],
+        headcount: [
+          { required: true, message: "请输入申请招聘人数", trigger: "change" },
+        ],
+        salaryMax: [{ validator: this.salaryRangeValidator, trigger: "change" }],
+        dutiesText: [
+          { required: true, message: "请输入岗位职责", trigger: "blur" },
+        ],
+      },
+      salaryRangeValidator: (_rule, _value, callback) => {
+        if (
+          Number(this.form.salaryMax) > 0 &&
+          Number(this.form.salaryMax) < Number(this.form.salaryMin)
+        ) {
+          callback(new Error("薪资上限不能低于下限"));
+        } else {
+          callback();
+        }
+      },
+    };
+  },
+  computed: {
+    columns() {
+      return [
+        { type: "index", label: "序号", width: 60, align: "center" },
+        {
+          columnKey: "code",
+          label: "岗位编码",
+          width: 150,
+          slot: "code",
+          align: "center",
+        },
+        {
+          columnKey: "name",
+          label: "岗位名称",
+          minWidth: 190,
+          slot: "name",
+        },
+        {
+          prop: "level",
+          label: "岗位层级",
+          width: 90,
+          align: "center",
+          formatter: (row) => `${row.level}级`,
+        },
+        {
+          prop: "type",
+          label: "岗位类型",
+          width: 100,
+          align: "center",
+          formatter: (row) => this.typeName(row.type),
+        },
+        {
+          columnKey: "contract",
+          label: "岗位绑定合同模板",
+          minWidth: 170,
+          slot: "contract",
+        },
+        {
+          prop: "headcount",
+          label: "申请招聘人数",
+          width: 110,
+          align: "center",
+          formatter: (row) => `${row.headcount} 人`,
+        },
+        { prop: "applicant", label: "申请人", width: 100, align: "center" },
+        {
+          columnKey: "duties",
+          label: "岗位职责",
+          minWidth: 230,
+          slot: "duties",
+        },
+        {
+          columnKey: "status",
+          label: "状态",
+          width: 100,
+          align: "center",
+          slot: "status",
+        },
+        {
+          columnKey: "action",
+          label: "操作",
+          width: 96,
+          align: "center",
+          resizable: false,
+          slot: "action",
+        },
+      ];
+    },
+    metrics() {
+      const total = this.overview.length;
+      const pending = this.overview.filter(
+        (item) => item.status === "PENDING",
+      ).length;
+      const approved = this.overview.filter(
+        (item) => item.status === "APPROVED",
+      ).length;
+      const headcount = this.overview.reduce(
+        (sum, item) => sum + Number(item.headcount || 0),
+        0,
+      );
+      return [
+        {
+          label: "申请总数",
+          value: total,
+          unit: "单",
+          note: "岗位申请单据合计",
+          icon: "el-icon-document-copy",
+          tone: "blue",
+        },
+        {
+          label: "待审核",
+          value: pending,
+          unit: "单",
+          note: "需部门负责人 / HR 处理",
+          icon: "el-icon-time",
+          tone: "orange",
+        },
+        {
+          label: "已审核",
+          value: approved,
+          unit: "单",
+          note: "可进入招聘需求岗位库",
+          icon: "el-icon-circle-check",
+          tone: "green",
+        },
+        {
+          label: "招聘人数",
+          value: headcount,
+          unit: "人",
+          note: "申请编制招聘人数合计",
+          icon: "el-icon-user",
+          tone: "cyan",
+        },
+      ];
+    },
+    departmentOptions() {
+      return this.departments.filter(
+        (item) => item.legalId === this.form.organizationId,
+      );
+    },
+    salaryText() {
+      const min = Number(this.form.salaryMin || 0);
+      const max = Number(this.form.salaryMax || 0);
+      if (!min && !max) return "-";
+      return `¥${min.toLocaleString("zh-CN")} - ¥${max.toLocaleString("zh-CN")}`;
+    },
+    hasFilters() {
+      return Boolean(
+        this.filters.keyword ||
+          this.filters.organizationId ||
+          this.filters.status,
+      );
+    },
+    readOnly() {
+      return this.mode === "view";
+    },
+    drawerTitle() {
+      return this.mode === "create"
+        ? "新增岗位申请"
+        : this.mode === "edit"
+        ? "编辑岗位申请"
+        : "岗位申请详情";
+    },
+    drawerSize() {
+      return window.innerWidth < 900 ? "96%" : "760px";
+    },
+  },
+  created() {
+    this.loadOrganizations();
+    this.loadContractTemplates();
+    this.loadSequences();
+    this.loadOverview();
+  },
+  methods: {
+    // ele-pro-table 服务端分页数据源:列表接口 + 按行详情补全岗位职责
+    async fetchPage({ page, limit }) {
+      const data = await getPositionApplicationPage({
+        pageNum: page,
+        size: limit,
+        keyword: this.filters.keyword || undefined,
+        organizationId: this.filters.organizationId || undefined,
+        status: this.filters.status || undefined,
+      });
+      const list = (data?.list || []).map(adaptPositionApplication);
+      this.rows = list;
+      this.totalCount = Number(data?.count ?? 0);
+      const enriched = await Promise.all(list.map((item) => this.fillDuties(item)));
+      return { list: enriched, count: this.totalCount };
+    },
+    // 列表接口不返回岗位职责,按行拉详情补齐并缓存
+    async fillDuties(item) {
+      if (this.dutiesCache[item.id]) {
+        return { ...item, duties: this.dutiesCache[item.id] };
+      }
+      try {
+        const detail = await getPositionApplicationById(item.id);
+        const duties = adaptPositionApplicationDetail(detail).duties;
+        this.$set(this.dutiesCache, item.id, duties);
+        return { ...item, duties };
+      } catch (error) {
+        return { ...item, duties: [] };
+      }
+    },
+    // 加载组织架构:顶层机构 + 部门(部门归属到顶层机构)
+    async loadOrganizations() {
+      try {
+        const list = await listOrganizations();
+        const records = Array.isArray(list) ? list : [];
+        const tree = buildOrganizationTree(records);
+        const flat = flattenOrganizationTree(tree);
+        const rootOf = new Map();
+        const walk = (nodes, rootId) => {
+          nodes.forEach((node) => {
+            rootOf.set(String(node.id), rootId);
+            walk(node.children || [], rootId);
+          });
+        };
+        tree.forEach((node) => walk([node], node.id));
+        this.legalEntities = tree.map((node) => ({
+          id: node.id,
+          name: node.name,
+        }));
+        this.departments = flat.map((node) => ({
+          id: node.id,
+          name: node.name,
+          legalId: rootOf.get(String(node.id)),
+        }));
+        this.orgMap = Object.fromEntries(
+          flat.map((node) => [String(node.id), node.name]),
+        );
+      } catch (error) {
+        this.legalEntities = [];
+        this.departments = [];
+      }
+    },
+    // 加载劳动合同模板(启用中)
+    async loadContractTemplates() {
+      try {
+        const data = await getContractTemplatePage({
+          pageNum: 1,
+          size: 1000,
+          templateStatus: "ENABLED",
+        });
+        const list = (data?.list || [])
+          .map((item) => ({ id: item.id, name: item.templateName || "" }))
+          .filter((item) => item.name);
+        if (list.length) this.contractTemplates = list;
+      } catch (error) {
+        // 接口不可用时保留静态兜底模板
+      }
+    },
+    // 加载岗位序列名称(仅启用配置)
+    async loadSequences() {
+      try {
+        const list = await getPositionSequenceNames();
+        if (Array.isArray(list) && list.length) {
+          this.sequenceOptions = list.filter(Boolean);
+        }
+      } catch (error) {
+        // 接口不可用时保留静态兜底序列
+      }
+    },
+    // 概览数据:一次拉全量用于顶部指标卡
+    async loadOverview() {
+      try {
+        const data = await getPositionApplicationPage({ pageNum: 1, size: 1000 });
+        this.overview = (data?.list || []).map(adaptPositionApplication);
+      } catch (error) {
+        this.overview = [];
+      }
+    },
+    refreshData() {
+      this.loadOverview();
+      this.reloadTable();
+    },
+    applyFilters() {
+      this.filters = { ...this.draftFilters };
+      this.reloadTable();
+    },
+    resetFilters() {
+      this.draftFilters = emptyFilters();
+      this.filters = emptyFilters();
+      this.reloadTable();
+    },
+    clearFilter(key) {
+      this.draftFilters[key] = "";
+      this.filters[key] = "";
+      this.reloadTable();
+    },
+    reloadTable() {
+      this.$nextTick(() => this.$refs.table?.reload({ page: 1 }));
+    },
+    openCreate() {
+      this.mode = "create";
+      this.form = emptyForm();
+      this.drawerVisible = true;
+      this.$nextTick(() => this.$refs.applicationForm?.clearValidate());
+    },
+    async openEdit(row) {
+      if (!this.canEdit(row)) return;
+      this.mode = "edit";
+      this.drawerVisible = true;
+      try {
+        const detail = await getPositionApplicationById(row.id);
+        this.form = this.detailToForm(detail);
+      } catch (error) {
+        this.form = this.rowToForm(row);
+      }
+      this.$nextTick(() => this.$refs.applicationForm?.clearValidate());
+    },
+    async openView(row) {
+      this.mode = "view";
+      this.drawerVisible = true;
+      try {
+        const detail = await getPositionApplicationById(row.id);
+        this.form = this.detailToForm(detail);
+      } catch (error) {
+        this.form = this.rowToForm(row);
+      }
+    },
+    // 详情接口返回 → 表单/详情页字段
+    detailToForm(detail) {
+      const adapted = adaptPositionApplicationDetail(detail);
+      return {
+        ...adapted,
+        deptName: adapted.deptName || "",
+        level: adapted.level || 1,
+        headcount: adapted.headcount || 1,
+      };
+    },
+    // 列表行 → 表单/详情页字段(详情接口失败兜底)
+    rowToForm(row) {
+      return {
+        ...emptyForm(),
+        ...row,
+        deptName: row.deptName || "",
+        duties: row.duties || [],
+        dutiesText: (row.duties || []).join("\n"),
+      };
+    },
+    handleOrganizationChange() {
+      this.form.deptId = null;
+    },
+    addSkill() {
+      this.form.skills.push({ name: "", level: "中级", required: true });
+    },
+    removeSkill(index) {
+      this.form.skills.splice(index, 1);
+    },
+    addCert() {
+      this.form.certificates.push({
+        name: "",
+        category: "准入类",
+        validMonth: 36,
+      });
+    },
+    removeCert(index) {
+      this.form.certificates.splice(index, 1);
+    },
+    closeDrawer(done) {
+      this.drawerVisible = false;
+      if (typeof done === "function") done();
+    },
+    saveApplication(submit) {
+      const persist = async () => {
+        const payload = serializePositionApplication(this.form);
+        this.saving = true;
+        try {
+          let id = this.form.id;
+          if (id) {
+            await updatePositionApplication(id, payload);
+          } else {
+            id = await createPositionApplication(payload);
+          }
+          if (submit) await submitPositionApplication(id);
+          this.drawerVisible = false;
+          this.$message.success(
+            submit ? "岗位申请已提交审批" : "岗位申请草稿已保存",
+          );
+          this.loadOverview();
+          this.reloadTable();
+        } catch (error) {
+          this.$message.error(error.message || "岗位申请保存失败");
+        } finally {
+          this.saving = false;
+        }
+      };
+      if (submit)
+        this.$refs.applicationForm.validate((valid) => valid && persist());
+      else persist();
+    },
+    orgName(id) {
+      if (id === null || id === undefined || id === "") return "-";
+      return this.orgMap[String(id)] || "-";
+    },
+    contractName(id) {
+      if (id === null || id === undefined || id === "") return "-";
+      return (
+        this.contractTemplates.find((item) => item.id === id)?.name ||
+        `模板${id}`
+      );
+    },
+    typeName(value) {
+      return TYPE_LABELS[value] || value || "-";
+    },
+    statusName(value) {
+      return STATUS_LABELS[value] || value || "-";
+    },
+    statusClass(value) {
+      return {
+        DRAFT: "is-inactive",
+        PENDING: "is-warning",
+        APPROVED: "is-active",
+        REJECTED: "is-danger",
+      }[value];
+    },
+    statusTone(value) {
+      return {
+        DRAFT: "draft",
+        PENDING: "pending",
+        APPROVED: "approved",
+        REJECTED: "rejected",
+      }[value];
+    },
+    statusTagType(value) {
+      return {
+        DRAFT: "info",
+        PENDING: "warning",
+        APPROVED: "success",
+        REJECTED: "danger",
+      }[value];
+    },
+    operationName(value) {
+      return OPERATION_LABELS[value] || value || "";
+    },
+    canEdit(row) {
+      // 未提交与已驳回均可编辑后重新提交
+      return ["DRAFT", "REJECTED"].includes(row.status);
+    },
+  },
+};
+</script>
+
+<style
+  lang="scss"
+  scoped
+  src="../../styles/views/positionApplication/index.scss"
+></style>

+ 49 - 0
src/views/positionApplication/mock.js

@@ -0,0 +1,49 @@
+// 岗位申请 - 枚举与静态兜底数据
+// 页面主数据来自 /hr/position/apply 系列接口,此处仅保留选项枚举与合同模板兜底。
+
+// 岗位层级:1-5(后端 positionLevel 取值范围)
+export const positionLevels = [1, 2, 3, 4, 5];
+
+// 岗位类型:对应后端 positionType 枚举
+export const positionTypes = [
+  { value: "core_positions", label: "核心岗" },
+  { value: "key_positions", label: "关键岗" },
+  { value: "general_positions", label: "一般岗" },
+  { value: "auxiliary_positions", label: "辅助岗" },
+];
+
+// 申请状态:对应后端 status 枚举
+export const statusOptions = [
+  { value: "DRAFT", label: "未提交" },
+  { value: "PENDING", label: "待审核" },
+  { value: "APPROVED", label: "已审核" },
+  { value: "REJECTED", label: "已驳回" },
+];
+
+// 岗位绑定合同模板兜底(优先从 /hr/contracts/templates 接口加载)
+export const contractTemplates = [
+  { id: 1, name: "固定期限劳动合同", type: "FIXED" },
+  { id: 2, name: "无固定期限劳动合同", type: "NON_FIXED" },
+  { id: 3, name: "劳务派遣合同", type: "DISPATCH" },
+  { id: 4, name: "实习协议", type: "INTERN" },
+  { id: 5, name: "竞业限制协议", type: "NON_COMPETE" },
+];
+
+// 学历要求
+export const educationOptions = [
+  "不限",
+  "初中及以上",
+  "高中/中专及以上",
+  "大专及以上",
+  "本科及以上",
+  "硕士及以上",
+];
+
+// 技能等级
+export const skillLevelOptions = ["初级", "中级", "高级", "技师", "高级技师"];
+
+// 资质证书类型
+export const certificateTypes = ["准入类", "评价类", "管理类"];
+
+// 岗位序列兜底(优先从 /hr/positionSequence/sequences 接口加载)
+export const sequenceFallback = ["管理", "专业", "技术", "操作"];

+ 173 - 209
yarn.lock

@@ -25,7 +25,7 @@
   resolved "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.7.tgz"
   integrity sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==
 
-"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.0.0-0 || ^8.0.0-0 <8.0.0", "@babel/core@^7.12.0", "@babel/core@^7.12.16", "@babel/core@^7.13.0", "@babel/core@^7.4.0 || ^8.0.0-0 <8.0.0":
+"@babel/core@^7.12.16":
   version "7.29.7"
   resolved "https://registry.npmmirror.com/@babel/core/-/core-7.29.7.tgz"
   integrity sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==
@@ -936,7 +936,7 @@
     "@nodelib/fs.stat" "2.0.5"
     run-parallel "^1.1.9"
 
-"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5":
+"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
   version "2.0.5"
   resolved "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
   integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
@@ -949,6 +949,61 @@
     "@nodelib/fs.scandir" "2.1.5"
     fastq "^1.6.0"
 
+"@parcel/watcher-android-arm64@2.6.0":
+  version "2.6.0"
+  resolved "https://registry.npmmirror.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.6.0.tgz#99aaa3223d43807c9340af439cad7e9b6d26ada6"
+  integrity sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==
+
+"@parcel/watcher-darwin-arm64@2.6.0":
+  version "2.6.0"
+  resolved "https://registry.npmmirror.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.6.0.tgz#024496e586b4744f09ce532bbe89fe38ef02a64e"
+  integrity sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==
+
+"@parcel/watcher-darwin-x64@2.6.0":
+  version "2.6.0"
+  resolved "https://registry.npmmirror.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.6.0.tgz#a4621df1359a93d39a332d9bab5ff09016a0608f"
+  integrity sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==
+
+"@parcel/watcher-freebsd-x64@2.6.0":
+  version "2.6.0"
+  resolved "https://registry.npmmirror.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.6.0.tgz#7f565ed1a5b3a5e604e6a4799121518265d62a3d"
+  integrity sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==
+
+"@parcel/watcher-linux-arm-glibc@2.6.0":
+  version "2.6.0"
+  resolved "https://registry.npmmirror.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.6.0.tgz#ad7d3825e67b81999165da42593022045abc0889"
+  integrity sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==
+
+"@parcel/watcher-linux-arm-musl@2.6.0":
+  version "2.6.0"
+  resolved "https://registry.npmmirror.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.6.0.tgz#fe7d1cccb2c483215c090e938cf5cf404d2f9a8c"
+  integrity sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==
+
+"@parcel/watcher-linux-arm64-glibc@2.6.0":
+  version "2.6.0"
+  resolved "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.6.0.tgz#7e239dcb4646c4c79f006a7131a48238249530da"
+  integrity sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==
+
+"@parcel/watcher-linux-arm64-musl@2.6.0":
+  version "2.6.0"
+  resolved "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.6.0.tgz#c58b8d9c6d8d81594be00dd83aab741c1aaf7e0e"
+  integrity sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==
+
+"@parcel/watcher-linux-x64-glibc@2.6.0":
+  version "2.6.0"
+  resolved "https://registry.npmmirror.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.6.0.tgz#5184fa9a770478d86e56875f4ee163a0abdc8791"
+  integrity sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==
+
+"@parcel/watcher-linux-x64-musl@2.6.0":
+  version "2.6.0"
+  resolved "https://registry.npmmirror.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.6.0.tgz#2d1c55aa7246cbc7670e2612058a8a542c9cf246"
+  integrity sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==
+
+"@parcel/watcher-win32-arm64@2.6.0":
+  version "2.6.0"
+  resolved "https://registry.npmmirror.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.6.0.tgz#15e09432040fee9e2213aa9c10ed589012526def"
+  integrity sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==
+
 "@parcel/watcher-win32-x64@2.6.0":
   version "2.6.0"
   resolved "https://registry.npmmirror.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.6.0.tgz"
@@ -1357,7 +1412,7 @@
   resolved "https://registry.npmmirror.com/@vue/cli-plugin-vuex/-/cli-plugin-vuex-5.0.9.tgz"
   integrity sha512-AQhgGNFVd4Pu2crvS0a+hRckgrJv07gzOASdbLd3I72wkT43dd01MLRp8IBRRsu92t3MXenW86AZUCbQBz3//A==
 
-"@vue/cli-service@^3.0.0 || ^4.0.0 || ^5.0.0-0", "@vue/cli-service@^5.0.8":
+"@vue/cli-service@^5.0.8":
   version "5.0.9"
   resolved "https://registry.npmmirror.com/@vue/cli-service/-/cli-service-5.0.9.tgz"
   integrity sha512-yTX7GVyM19tEbd+y5/gA6MkVKA6K61nVYHYAivD61Hx6odVFmQsaC3/R3cWAHM1P5oVKCevBbumPljbT+tFG2w==
@@ -1455,6 +1510,17 @@
     "@vue/compiler-core" "3.5.41"
     "@vue/shared" "3.5.41"
 
+"@vue/compiler-sfc@2.7.16":
+  version "2.7.16"
+  resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-2.7.16.tgz"
+  integrity sha512-KWhJ9k5nXuNtygPU7+t1rX6baZeqOYLEforUPjgNDBnLicfHCoi48H87Q8XyLZOrNNsmhuwKqtpDQWjEFe6Ekg==
+  dependencies:
+    "@babel/parser" "^7.23.5"
+    postcss "^8.4.14"
+    source-map "^0.6.1"
+  optionalDependencies:
+    prettier "^1.18.2 || ^2.0.0"
+
 "@vue/compiler-sfc@^3.5.18":
   version "3.5.41"
   resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.41.tgz"
@@ -1470,17 +1536,6 @@
     postcss "^8.5.19"
     source-map-js "^1.2.1"
 
-"@vue/compiler-sfc@2.7.16":
-  version "2.7.16"
-  resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-2.7.16.tgz"
-  integrity sha512-KWhJ9k5nXuNtygPU7+t1rX6baZeqOYLEforUPjgNDBnLicfHCoi48H87Q8XyLZOrNNsmhuwKqtpDQWjEFe6Ekg==
-  dependencies:
-    "@babel/parser" "^7.23.5"
-    postcss "^8.4.14"
-    source-map "^0.6.1"
-  optionalDependencies:
-    prettier "^1.18.2 || ^2.0.0"
-
 "@vue/compiler-ssr@3.5.41":
   version "3.5.41"
   resolved "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.41.tgz"
@@ -1505,7 +1560,7 @@
   optionalDependencies:
     prettier "^1.18.2 || ^2.0.0"
 
-"@vue/shared@^3.5.18", "@vue/shared@3.5.41":
+"@vue/shared@3.5.41", "@vue/shared@^3.5.18":
   version "3.5.41"
   resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.41.tgz"
   integrity sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA==
@@ -1526,7 +1581,7 @@
   resolved "https://registry.npmmirror.com/@vue/web-component-wrapper/-/web-component-wrapper-1.3.0.tgz"
   integrity sha512-Iu8Tbg3f+emIIMmI2ycSI8QcEuAUgPTgHwesDU1eKMLE4YC/c/sFbGc70QgMq31ijRftV0R7vCm9co6rldCeOA==
 
-"@webassemblyjs/ast@^1.14.1", "@webassemblyjs/ast@1.14.1":
+"@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.14.1":
   version "1.14.1"
   resolved "https://registry.npmmirror.com/@webassemblyjs/ast/-/ast-1.14.1.tgz"
   integrity sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==
@@ -1627,7 +1682,7 @@
     "@webassemblyjs/wasm-gen" "1.14.1"
     "@webassemblyjs/wasm-parser" "1.14.1"
 
-"@webassemblyjs/wasm-parser@^1.14.1", "@webassemblyjs/wasm-parser@1.14.1":
+"@webassemblyjs/wasm-parser@1.14.1", "@webassemblyjs/wasm-parser@^1.14.1":
   version "1.14.1"
   resolved "https://registry.npmmirror.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz"
   integrity sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==
@@ -1701,7 +1756,7 @@ ajv-keywords@^5.1.0:
   dependencies:
     fast-deep-equal "^3.1.3"
 
-ajv@^6.12.4, ajv@^6.12.5, ajv@^6.9.1:
+ajv@^6.12.4, ajv@^6.12.5:
   version "6.15.0"
   resolved "https://registry.npmmirror.com/ajv/-/ajv-6.15.0.tgz"
   integrity sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==
@@ -1711,17 +1766,7 @@ ajv@^6.12.4, ajv@^6.12.5, ajv@^6.9.1:
     json-schema-traverse "^0.4.1"
     uri-js "^4.2.2"
 
-ajv@^8.0.0:
-  version "8.20.0"
-  resolved "https://registry.npmmirror.com/ajv/-/ajv-8.20.0.tgz"
-  integrity sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==
-  dependencies:
-    fast-deep-equal "^3.1.3"
-    fast-uri "^3.0.1"
-    json-schema-traverse "^1.0.0"
-    require-from-string "^2.0.2"
-
-ajv@^8.8.2, ajv@^8.9.0:
+ajv@^8.0.0, ajv@^8.9.0:
   version "8.20.0"
   resolved "https://registry.npmmirror.com/ajv/-/ajv-8.20.0.tgz"
   integrity sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==
@@ -1991,7 +2036,7 @@ braces@^3.0.3, braces@~3.0.2:
   dependencies:
     fill-range "^7.1.1"
 
-browserslist@^4.0.0, browserslist@^4.16.3, browserslist@^4.21.4, browserslist@^4.24.0, browserslist@^4.28.1, browserslist@^4.28.6, "browserslist@>= 4.21.0":
+browserslist@^4.0.0, browserslist@^4.16.3, browserslist@^4.21.4, browserslist@^4.24.0, browserslist@^4.28.1, browserslist@^4.28.6:
   version "4.28.7"
   resolved "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.7.tgz"
   integrity sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==
@@ -2015,7 +2060,7 @@ buffer@^5.5.0:
     base64-js "^1.3.1"
     ieee754 "^1.1.13"
 
-bytes@~3.1.2, bytes@3.1.2:
+bytes@3.1.2, bytes@~3.1.2:
   version "3.1.2"
   resolved "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz"
   integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
@@ -2101,23 +2146,7 @@ chalk@^3.0.0:
     ansi-styles "^4.1.0"
     supports-color "^7.1.0"
 
-chalk@^4.0.0:
-  version "4.1.2"
-  resolved "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz"
-  integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
-  dependencies:
-    ansi-styles "^4.1.0"
-    supports-color "^7.1.0"
-
-chalk@^4.1.0:
-  version "4.1.2"
-  resolved "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz"
-  integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
-  dependencies:
-    ansi-styles "^4.1.0"
-    supports-color "^7.1.0"
-
-chalk@^4.1.2:
+chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2:
   version "4.1.2"
   resolved "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz"
   integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
@@ -2245,16 +2274,16 @@ color-convert@^2.0.1:
   dependencies:
     color-name "~1.1.4"
 
-color-name@~1.1.4:
-  version "1.1.4"
-  resolved "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz"
-  integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
-
 color-name@1.1.3:
   version "1.1.3"
   resolved "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz"
   integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
 
+color-name@~1.1.4:
+  version "1.1.4"
+  resolved "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz"
+  integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+
 colord@^2.9.1:
   version "2.9.3"
   resolved "https://registry.npmmirror.com/colord/-/colord-2.9.3.tgz"
@@ -2444,7 +2473,7 @@ css-declaration-sorter@^6.3.1:
   resolved "https://registry.npmmirror.com/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz"
   integrity sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==
 
-css-loader@*, css-loader@^6.5.0:
+css-loader@^6.5.0:
   version "6.11.0"
   resolved "https://registry.npmmirror.com/css-loader/-/css-loader-6.11.0.tgz"
   integrity sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==
@@ -2575,13 +2604,6 @@ debounce@^1.2.1:
   resolved "https://registry.npmmirror.com/debounce/-/debounce-1.2.1.tgz"
   integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==
 
-debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.6, debug@^4.4.3:
-  version "4.4.3"
-  resolved "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz"
-  integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
-  dependencies:
-    ms "^2.1.3"
-
 debug@2.6.9:
   version "2.6.9"
   resolved "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz"
@@ -2589,6 +2611,13 @@ debug@2.6.9:
   dependencies:
     ms "2.0.0"
 
+debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.6, debug@^4.4.3:
+  version "4.4.3"
+  resolved "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz"
+  integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
+  dependencies:
+    ms "^2.1.3"
+
 deepmerge@^1.2.0, deepmerge@^1.5.2:
   version "1.5.2"
   resolved "https://registry.npmmirror.com/deepmerge/-/deepmerge-1.5.2.tgz"
@@ -2646,17 +2675,17 @@ delegate@^3.1.2:
   resolved "https://registry.npmmirror.com/delegate/-/delegate-3.2.0.tgz"
   integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==
 
+depd@2.0.0, depd@~2.0.0:
+  version "2.0.0"
+  resolved "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz"
+  integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
+
 depd@~1.1.2:
   version "1.1.2"
   resolved "https://registry.npmmirror.com/depd/-/depd-1.1.2.tgz"
   integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==
 
-depd@~2.0.0, depd@2.0.0:
-  version "2.0.0"
-  resolved "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz"
-  integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
-
-destroy@~1.2.0, destroy@1.2.0:
+destroy@1.2.0, destroy@~1.2.0:
   version "1.2.0"
   resolved "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz"
   integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
@@ -2782,7 +2811,7 @@ electron-to-chromium@^1.5.393:
   resolved "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.401.tgz"
   integrity sha512-H6ViHN68nGYlChEvlIU67fn8O2/tpbWQPwck98yaJmh+08LSvHiydzDQ6oXNccLU3kNRVIRS9A4mA7CG+i6fLQ==
 
-element-ui@>=2.15.4, element-ui@2.15.7:
+element-ui@2.15.7:
   version "2.15.7"
   resolved "https://registry.npmmirror.com/element-ui/-/element-ui-2.15.7.tgz"
   integrity sha512-+J6rnXajxzLwV6w8Q6bf7Yqzk1FO1ewbIrCy/4B5alnd7tj8WEpfQoAvISirVaUGVGy77d9Ji3o2bF4f0AsJLQ==
@@ -3160,6 +3189,11 @@ fs.realpath@^1.0.0:
   resolved "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz"
   integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
 
+fsevents@~2.3.2:
+  version "2.3.3"
+  resolved "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
+  integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
+
 function-bind@^1.1.2:
   version "1.1.2"
   resolved "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz"
@@ -3211,7 +3245,7 @@ get-stream@^6.0.0:
   resolved "https://registry.npmmirror.com/get-stream/-/get-stream-6.0.1.tgz"
   integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
 
-glob-parent@^5.1.2:
+glob-parent@^5.1.2, glob-parent@~5.1.2:
   version "5.1.2"
   resolved "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz"
   integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
@@ -3225,13 +3259,6 @@ glob-parent@^6.0.1:
   dependencies:
     is-glob "^4.0.3"
 
-glob-parent@~5.1.2:
-  version "5.1.2"
-  resolved "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz"
-  integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
-  dependencies:
-    is-glob "^4.0.1"
-
 glob@^7.1.3:
   version "7.2.3"
   resolved "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz"
@@ -3505,21 +3532,21 @@ inflight@^1.0.4:
     once "^1.3.0"
     wrappy "1"
 
-inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3, inherits@~2.0.4, inherits@2, inherits@2.0.4:
+inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3, inherits@~2.0.4:
   version "2.0.4"
   resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz"
   integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
 
-ipaddr.js@^2.0.1:
-  version "2.5.0"
-  resolved "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-2.5.0.tgz"
-  integrity sha512-aq+t5NAc+cS6rZQQVWC2x98CPqGtKKTMDd4Gaodv0wShnItdKg/51djkGJ1hqH+Oy0ivDftCbSLCQob8zso01w==
-
 ipaddr.js@1.9.1:
   version "1.9.1"
   resolved "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz"
   integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
 
+ipaddr.js@^2.0.1:
+  version "2.5.0"
+  resolved "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-2.5.0.tgz"
+  integrity sha512-aq+t5NAc+cS6rZQQVWC2x98CPqGtKKTMDd4Gaodv0wShnItdKg/51djkGJ1hqH+Oy0ivDftCbSLCQob8zso01w==
+
 is-arrayish@^0.2.1:
   version "0.2.1"
   resolved "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz"
@@ -3769,16 +3796,7 @@ loader-utils@^1.0.2, loader-utils@^1.1.0:
     emojis-list "^3.0.0"
     json5 "^1.0.1"
 
-loader-utils@^2.0.0:
-  version "2.0.4"
-  resolved "https://registry.npmmirror.com/loader-utils/-/loader-utils-2.0.4.tgz"
-  integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
-  dependencies:
-    big.js "^5.2.2"
-    emojis-list "^3.0.0"
-    json5 "^2.1.2"
-
-loader-utils@^2.0.4:
+loader-utils@^2.0.0, loader-utils@^2.0.4:
   version "2.0.4"
   resolved "https://registry.npmmirror.com/loader-utils/-/loader-utils-2.0.4.tgz"
   integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
@@ -3946,16 +3964,16 @@ micromatch@^4.0.2, micromatch@^4.0.8:
     braces "^3.0.3"
     picomatch "^2.3.1"
 
-mime-db@^1.54.0, "mime-db@>= 1.43.0 < 2":
-  version "1.54.0"
-  resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz"
-  integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==
-
 mime-db@1.52.0:
   version "1.52.0"
   resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz"
   integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
 
+"mime-db@>= 1.43.0 < 2", mime-db@^1.54.0:
+  version "1.54.0"
+  resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz"
+  integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==
+
 mime-types@^2.1.31, mime-types@^2.1.35, mime-types@~2.1.24, mime-types@~2.1.34, mime-types@~2.1.35:
   version "2.1.35"
   resolved "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz"
@@ -4030,16 +4048,16 @@ mrmime@^2.0.0:
   resolved "https://registry.npmmirror.com/mrmime/-/mrmime-2.0.1.tgz"
   integrity sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==
 
-ms@^2.1.3, ms@2.1.3:
-  version "2.1.3"
-  resolved "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz"
-  integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
-
 ms@2.0.0:
   version "2.0.0"
   resolved "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz"
   integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
 
+ms@2.1.3, ms@^2.1.3:
+  version "2.1.3"
+  resolved "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz"
+  integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+
 multicast-dns@^7.2.5:
   version "7.2.5"
   resolved "https://registry.npmmirror.com/multicast-dns/-/multicast-dns-7.2.5.tgz"
@@ -4062,16 +4080,16 @@ nanoid@^3.3.16:
   resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.17.tgz"
   integrity sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==
 
-negotiator@~0.6.4:
-  version "0.6.4"
-  resolved "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.4.tgz"
-  integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==
-
 negotiator@0.6.3:
   version "0.6.3"
   resolved "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz"
   integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
 
+negotiator@~0.6.4:
+  version "0.6.4"
+  resolved "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.4.tgz"
+  integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==
+
 neo-async@^2.6.2:
   version "2.6.2"
   resolved "https://registry.npmmirror.com/neo-async/-/neo-async-2.6.2.tgz"
@@ -4669,15 +4687,6 @@ postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
   resolved "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz"
   integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
 
-"postcss@^7.0.0 || ^8.0.1", postcss@^8.0.9, postcss@^8.1.0, postcss@^8.2.15, postcss@^8.2.2, postcss@^8.2.6, postcss@^8.3.5, postcss@^8.4.14, postcss@^8.4.33, postcss@^8.5.19:
-  version "8.5.25"
-  resolved "https://registry.npmmirror.com/postcss/-/postcss-8.5.25.tgz"
-  integrity sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==
-  dependencies:
-    nanoid "^3.3.16"
-    picocolors "^1.1.1"
-    source-map-js "^1.2.1"
-
 postcss@^7.0.36:
   version "7.0.39"
   resolved "https://registry.npmmirror.com/postcss/-/postcss-7.0.39.tgz"
@@ -4686,6 +4695,15 @@ postcss@^7.0.36:
     picocolors "^0.2.1"
     source-map "^0.6.1"
 
+postcss@^8.2.6, postcss@^8.3.5, postcss@^8.4.14, postcss@^8.4.33, postcss@^8.5.19:
+  version "8.5.25"
+  resolved "https://registry.npmmirror.com/postcss/-/postcss-8.5.25.tgz"
+  integrity sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==
+  dependencies:
+    nanoid "^3.3.16"
+    picocolors "^1.1.1"
+    source-map-js "^1.2.1"
+
 "prettier@^1.18.2 || ^2.0.0":
   version "2.8.8"
   resolved "https://registry.npmmirror.com/prettier/-/prettier-2.8.8.tgz"
@@ -4959,7 +4977,7 @@ run-parallel@^1.1.9:
   dependencies:
     queue-microtask "^1.2.2"
 
-safe-buffer@^5.1.0, safe-buffer@>=5.1.0, safe-buffer@~5.2.0, safe-buffer@5.2.1:
+safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0:
   version "5.2.1"
   resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz"
   integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
@@ -4981,7 +4999,7 @@ sass-loader@^13.0.2:
   dependencies:
     neo-async "^2.6.2"
 
-sass@^1.3.0, sass@^1.54.8:
+sass@^1.54.8:
   version "1.102.0"
   resolved "https://registry.npmmirror.com/sass/-/sass-1.102.0.tgz"
   integrity sha512-NSOyTnaQF7rTAEOtI2fwb386vL+akyiQLBZu8Na7hXCb+umJy0GAqlcMIaqACZ6Z1VgTBS4K9PG6B3IdjHGJsw==
@@ -5006,7 +5024,7 @@ schema-utils@^2.6.5:
     ajv "^6.12.4"
     ajv-keywords "^3.5.2"
 
-schema-utils@^3.0.0:
+schema-utils@^3.0.0, schema-utils@^3.1.1:
   version "3.3.0"
   resolved "https://registry.npmmirror.com/schema-utils/-/schema-utils-3.3.0.tgz"
   integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==
@@ -5015,46 +5033,7 @@ schema-utils@^3.0.0:
     ajv "^6.12.5"
     ajv-keywords "^3.5.2"
 
-schema-utils@^3.1.1:
-  version "3.3.0"
-  resolved "https://registry.npmmirror.com/schema-utils/-/schema-utils-3.3.0.tgz"
-  integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==
-  dependencies:
-    "@types/json-schema" "^7.0.8"
-    ajv "^6.12.5"
-    ajv-keywords "^3.5.2"
-
-schema-utils@^4.0.0:
-  version "4.3.3"
-  resolved "https://registry.npmmirror.com/schema-utils/-/schema-utils-4.3.3.tgz"
-  integrity sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==
-  dependencies:
-    "@types/json-schema" "^7.0.9"
-    ajv "^8.9.0"
-    ajv-formats "^2.1.1"
-    ajv-keywords "^5.1.0"
-
-schema-utils@^4.2.0:
-  version "4.3.3"
-  resolved "https://registry.npmmirror.com/schema-utils/-/schema-utils-4.3.3.tgz"
-  integrity sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==
-  dependencies:
-    "@types/json-schema" "^7.0.9"
-    ajv "^8.9.0"
-    ajv-formats "^2.1.1"
-    ajv-keywords "^5.1.0"
-
-schema-utils@^4.3.0:
-  version "4.3.3"
-  resolved "https://registry.npmmirror.com/schema-utils/-/schema-utils-4.3.3.tgz"
-  integrity sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==
-  dependencies:
-    "@types/json-schema" "^7.0.9"
-    ajv "^8.9.0"
-    ajv-formats "^2.1.1"
-    ajv-keywords "^5.1.0"
-
-schema-utils@^4.3.3:
+schema-utils@^4.0.0, schema-utils@^4.2.0, schema-utils@^4.3.0, schema-utils@^4.3.3:
   version "4.3.3"
   resolved "https://registry.npmmirror.com/schema-utils/-/schema-utils-4.3.3.tgz"
   integrity sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==
@@ -5082,7 +5061,7 @@ selfsigned@^2.1.1:
     "@types/node-forge" "^1.3.0"
     node-forge "^1"
 
-semver@^5.5.0:
+"semver@2 || 3 || 4 || 5", semver@^5.5.0:
   version "5.7.2"
   resolved "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz"
   integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==
@@ -5092,26 +5071,11 @@ semver@^6.0.0, semver@^6.3.1:
   resolved "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz"
   integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
 
-semver@^7.3.4:
-  version "7.8.5"
-  resolved "https://registry.npmmirror.com/semver/-/semver-7.8.5.tgz"
-  integrity sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==
-
-semver@^7.3.5:
+semver@^7.3.4, semver@^7.3.5, semver@^7.5.4:
   version "7.8.5"
   resolved "https://registry.npmmirror.com/semver/-/semver-7.8.5.tgz"
   integrity sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==
 
-semver@^7.5.4:
-  version "7.8.5"
-  resolved "https://registry.npmmirror.com/semver/-/semver-7.8.5.tgz"
-  integrity sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==
-
-"semver@2 || 3 || 4 || 5":
-  version "5.7.2"
-  resolved "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz"
-  integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==
-
 send@~0.19.0, send@~0.19.1:
   version "0.19.2"
   resolved "https://registry.npmmirror.com/send/-/send-0.19.2.tgz"
@@ -5178,7 +5142,7 @@ set-function-length@^1.2.2:
     gopd "^1.0.1"
     has-property-descriptors "^1.0.2"
 
-setprototypeof@~1.2.0, setprototypeof@1.2.0:
+setprototypeof@1.2.0, setprototypeof@~1.2.0:
   version "1.2.0"
   resolved "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz"
   integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
@@ -5292,17 +5256,17 @@ sockjs@^0.3.24:
     uuid "^8.3.2"
     websocket-driver "^0.7.4"
 
-sortablejs@^1.15.0:
-  version "1.15.7"
-  resolved "https://registry.npmmirror.com/sortablejs/-/sortablejs-1.15.7.tgz"
-  integrity sha512-Kk8wLQPlS+yi1ZEf48a4+fzHa4yxjC30M/Sr2AnQu+f/MPwvvX9XjZ6OWejiz8crBsLwSq8GHqaxaET7u6ux0A==
-
 sortablejs@1.10.2:
   version "1.10.2"
   resolved "https://registry.npmmirror.com/sortablejs/-/sortablejs-1.10.2.tgz"
   integrity sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A==
 
-source-map-js@^1.2.1, "source-map-js@>=0.6.2 <2.0.0":
+sortablejs@^1.15.0:
+  version "1.15.7"
+  resolved "https://registry.npmmirror.com/sortablejs/-/sortablejs-1.15.7.tgz"
+  integrity sha512-Kk8wLQPlS+yi1ZEf48a4+fzHa4yxjC30M/Sr2AnQu+f/MPwvvX9XjZ6OWejiz8crBsLwSq8GHqaxaET7u6ux0A==
+
+"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.1:
   version "1.2.1"
   resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz"
   integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
@@ -5396,20 +5360,6 @@ statuses@~2.0.1, statuses@~2.0.2:
   resolved "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz"
   integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==
 
-string_decoder@^1.1.1:
-  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==
-  dependencies:
-    safe-buffer "~5.2.0"
-
-string_decoder@~1.1.1:
-  version "1.1.1"
-  resolved "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz"
-  integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
-  dependencies:
-    safe-buffer "~5.1.0"
-
 string-width@^2.1.1:
   version "2.1.1"
   resolved "https://registry.npmmirror.com/string-width/-/string-width-2.1.1.tgz"
@@ -5427,6 +5377,20 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
     is-fullwidth-code-point "^3.0.0"
     strip-ansi "^6.0.1"
 
+string_decoder@^1.1.1:
+  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==
+  dependencies:
+    safe-buffer "~5.2.0"
+
+string_decoder@~1.1.1:
+  version "1.1.1"
+  resolved "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz"
+  integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
+  dependencies:
+    safe-buffer "~5.1.0"
+
 strip-ansi@^4.0.0:
   version "4.0.0"
   resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-4.0.0.tgz"
@@ -5575,7 +5539,7 @@ to-regex-range@^5.0.1:
   dependencies:
     is-number "^7.0.0"
 
-toidentifier@~1.0.1, toidentifier@1.0.1:
+toidentifier@1.0.1, toidentifier@~1.0.1:
   version "1.0.1"
   resolved "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz"
   integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
@@ -5590,16 +5554,16 @@ tr46@~0.0.3:
   resolved "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz"
   integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
 
-tslib@^2.0.3:
-  version "2.8.1"
-  resolved "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz"
-  integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
-
 tslib@2.3.0:
   version "2.3.0"
   resolved "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz"
   integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
 
+tslib@^2.0.3:
+  version "2.8.1"
+  resolved "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz"
+  integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
+
 type-fest@^0.6.0:
   version "0.6.0"
   resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.6.0.tgz"
@@ -5743,7 +5707,7 @@ vue-style-loader@^4.1.0, vue-style-loader@^4.1.3:
     hash-sum "^1.0.2"
     loader-utils "^1.0.2"
 
-vue-template-compiler@^2.0.0, vue-template-compiler@^2.7.10:
+vue-template-compiler@^2.7.10:
   version "2.7.16"
   resolved "https://registry.npmmirror.com/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz"
   integrity sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==
@@ -5756,7 +5720,7 @@ vue-template-es2015-compiler@^1.9.0:
   resolved "https://registry.npmmirror.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz"
   integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
 
-vue@*, "vue@^2 || ^3.2.13", vue@^2.0.0, vue@^2.5.17, vue@^2.7.10, "vue@>=2.6.0 <3.0.0":
+vue@^2.7.10:
   version "2.7.16"
   resolved "https://registry.npmmirror.com/vue/-/vue-2.7.16.tgz"
   integrity sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw==
@@ -5779,7 +5743,7 @@ vuex-persistedstate@^4.1.0:
     deepmerge "^4.2.2"
     shvl "^2.0.3"
 
-"vuex@^3.0 || ^4.0.0-rc", vuex@^3.6.2:
+vuex@^3.6.2:
   version "3.6.2"
   resolved "https://registry.npmmirror.com/vuex/-/vuex-3.6.2.tgz"
   integrity sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw==
@@ -5892,7 +5856,7 @@ webpack-merge@^5.7.3:
     flat "^5.0.2"
     wildcard "^2.0.0"
 
-webpack-sources@*, webpack-sources@^3.5.1:
+webpack-sources@^3.5.1:
   version "3.5.1"
   resolved "https://registry.npmmirror.com/webpack-sources/-/webpack-sources-3.5.1.tgz"
   integrity sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw==
@@ -5902,7 +5866,7 @@ webpack-virtual-modules@^0.4.2:
   resolved "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.4.6.tgz"
   integrity sha512-5tyDlKLqPfMqjT3Q9TAqf2YqjwmnUleZwzJi1A5qXnlBCdj2AtOJ6wAWdglTIDOPgOiOrXeBeFcsQ8+aGQ6QbA==
 
-"webpack@^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "webpack@^3.0.0 || ^4.1.0 || ^5.0.0-0", "webpack@^4.0.0 || ^5.0.0", "webpack@^4.1.0 || ^5.0.0-0", "webpack@^4.27.0 || ^5.0.0", "webpack@^4.37.0 || ^5.0.0", webpack@^5.0.0, webpack@^5.1.0, webpack@^5.20.0, webpack@^5.54.0, webpack@^5.74.0, webpack@>=2:
+webpack@^5.54.0, webpack@^5.74.0:
   version "5.109.2"
   resolved "https://registry.npmmirror.com/webpack/-/webpack-5.109.2.tgz"
   integrity sha512-U9/cvLzxObKNEZ9+TtdqrHM5/9z3lgl2c+c4BzbqGxFQvQvBAq87yql5A8pQ+rrMbS496MZJeF5enVBndIy2hw==
@@ -5928,7 +5892,7 @@ webpack-virtual-modules@^0.4.2:
     watchpack "^2.5.2"
     webpack-sources "^3.5.1"
 
-websocket-driver@^0.7.4, websocket-driver@>=0.5.1:
+websocket-driver@>=0.5.1, websocket-driver@^0.7.4:
   version "0.7.5"
   resolved "https://registry.npmmirror.com/websocket-driver/-/websocket-driver-0.7.5.tgz"
   integrity sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA==