chore: 代码格式化和依赖更新
- 设计规范文档格式化 - 求职者布局样式微调 Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
536be6c1a1
commit
ed3b29aafe
|
|
@ -21,7 +21,7 @@
|
||||||
└─────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
整体高度撑满视口(`height: calc(100vh - navbar高度)`),各栏独立滚动。
|
**高度设置:** PortalLayout 包含 `el-header`(60px)+ `el-main`(默认 padding 20px)+ `el-footer`(60px)。三栏容器根元素设置 `height: calc(100vh - 200px)`(60 header + 60 footer + 40 el-main padding + 40 buffer),`overflow: hidden`,各栏内部 `overflow-y: auto` 独立滚动。此高度值在主流桌面分辨率下提供良好体验,无需精确到像素。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -31,53 +31,106 @@
|
||||||
|
|
||||||
- 顶部标题「全部企业」+ 公司总数 badge
|
- 顶部标题「全部企业」+ 公司总数 badge
|
||||||
- 每个公司卡片包含:
|
- 每个公司卡片包含:
|
||||||
- 首字 Logo(无图片时用公司名首字 + 蓝色背景)
|
- Logo:如有 `org.logo`,显示 `<img>`;否则显示公司名首字 + 蓝色背景 div
|
||||||
- 公司名称、行业、城市
|
- 公司名称(`org.name`)
|
||||||
- 在招岗位数
|
- 简介摘要(`org.description` 截取前 20 字,为空则不显示)
|
||||||
|
- **不显示在招岗位数**(Organization API 无此字段)
|
||||||
- 选中状态:左边框 `3px solid #409EFF`,背景白色
|
- 选中状态:左边框 `3px solid #409EFF`,背景白色
|
||||||
- 未选中:左边框透明,背景 `#f5f7fa`
|
- 未选中:左边框透明,背景 `#f5f7fa`
|
||||||
- 数据来源:`getOrganizations()` API
|
- **加载状态**:`orgsLoading` 为 true 时显示 `el-skeleton`(3 行)
|
||||||
|
- **错误状态**:`orgsError` 为 true 时显示「加载失败,请刷新重试」居中提示
|
||||||
|
- **空状态**:`orgs` 为空数组时显示「暂无公司数据」居中提示
|
||||||
|
- 数据来源:`getOrganizations()` → `GET /organizations/public/`,取 `data.results`
|
||||||
|
|
||||||
### 中栏 — 岗位列表
|
### 中栏 — 岗位列表
|
||||||
|
|
||||||
- 标题显示「{公司名} · 职位列表」
|
- 标题:「{org.name} · 职位列表」
|
||||||
- 每条岗位显示:岗位名称 + 城市/薪资/类别标签
|
- 每条岗位显示:
|
||||||
|
- 岗位名称(`job.title`)
|
||||||
|
- 标签行:`job.location`(城市)、`job.salary`(薪资)、`job.category`(类别)
|
||||||
- 选中状态:左边框 `3px solid #409EFF`,背景 `#ecf5ff`
|
- 选中状态:左边框 `3px solid #409EFF`,背景 `#ecf5ff`
|
||||||
- 数据来源:`getJobs({ organization: org.id })` API(已有 organization 过滤器)
|
- **空状态**:该公司无已发布岗位时,显示「暂无在招职位」居中提示
|
||||||
- 加载中显示 skeleton
|
- **加载状态**:`jobsLoading` 为 true 时显示 `el-skeleton`(3 行)
|
||||||
|
- **错误状态**:API 失败时显示「加载失败,请刷新重试」
|
||||||
|
- 数据来源:`getJobs({ organization: org.id })` → 取 `data.results`
|
||||||
|
|
||||||
### 右栏 — 岗位详情
|
### 右栏 — 岗位详情
|
||||||
|
|
||||||
- 顶部:公司 Logo + 岗位名称 + 城市副标题 + 「立即申请」按钮
|
**右栏渲染优先级(严格按顺序):**
|
||||||
- 标签行:城市、薪资、类别、经验要求
|
1. `detailLoading === true` → 显示 `el-skeleton`
|
||||||
- 正文:职位描述、任职要求(`job.description` 字段)
|
2. `detailError === true` → 显示「加载失败,请刷新重试」
|
||||||
- 未选中岗位时显示空状态提示
|
3. `selectedJob !== null` → 显示岗位详情
|
||||||
- 数据来源:`getJob(job.id)` API
|
4. 以上均不满足(`selectedJob === null` 且无加载/错误)→ 显示空状态
|
||||||
|
|
||||||
|
**各状态内容:**
|
||||||
|
- **空状态**:居中空状态图标 + 「← 请选择左侧职位查看详情」
|
||||||
|
- **岗位详情**:
|
||||||
|
- 顶部:公司首字 Logo + 岗位名称(`job.title`)+ 公司/城市副标题 + 「立即申请」按钮
|
||||||
|
- 标签行:`job.location`、`job.salary`、`job.category`(无经验字段,不显示)
|
||||||
|
- 正文:`job.description`(用 `white-space: pre-wrap` 保留换行)
|
||||||
|
- **加载状态**:`el-skeleton`(5 行,含头部和正文)
|
||||||
|
- **错误状态**:「加载失败,请刷新重试」居中提示
|
||||||
|
- **「立即申请」按钮行为**:
|
||||||
|
- 已登录(seeker):`router.push({ name: 'JobDetail', params: { id: job.id } })`(跳转现有详情页完成申请)
|
||||||
|
- 未登录:`router.push({ name: 'Login', query: { redirect: '/jobs/' + job.id } })`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 数据结构
|
||||||
|
|
||||||
|
`getOrganizations()` 返回的 Organization 对象字段:`id, name, logo (string|null), description, email, is_active`
|
||||||
|
|
||||||
|
`getJobs()` 使用 `JobListSerializer`,返回字段:`id, title, category, location, salary, organization (id only), organization_name, status, created_at`。**不含 `description`。**
|
||||||
|
|
||||||
|
`getJob(id)` 使用 `JobDetailSerializer`,返回字段:`id, title, category, location, salary, description, organization: {id, name, logo, ...}, status, created_at`。**含完整 `description`,因此点击岗位时必须调用此接口获取详情。**
|
||||||
|
|
||||||
|
`/jobs/public/` 后端已在 queryset 层过滤 `status='published'`,无需前端额外传 status 参数。
|
||||||
|
|
||||||
|
**Logo URL 处理**:`org.logo` 为相对路径(如 `/media/org_logos/foo.png`),需拼接后端地址。通过 Vite proxy,开发环境直接使用原始路径即可(proxy 会转发到 `http://127.0.0.1:8000`)。渲染时用 `org.logo ? org.logo : null` 判断是否显示图片。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 状态管理
|
## 状态管理
|
||||||
|
|
||||||
组件内使用三个 ref:
|
组件内使用以下 ref:
|
||||||
|
|
||||||
| 变量 | 类型 | 说明 |
|
| 变量 | 类型 | 说明 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| `orgs` | `Ref<Organization[]>` | 所有公司列表 |
|
| `orgs` | `Ref<Organization[]>` | 所有公司列表 |
|
||||||
|
| `orgsLoading` | `Ref<boolean>` | 左栏加载状态 |
|
||||||
|
| `orgsError` | `Ref<boolean>` | 左栏错误状态 |
|
||||||
| `selectedOrg` | `Ref<Organization \| null>` | 当前选中公司 |
|
| `selectedOrg` | `Ref<Organization \| null>` | 当前选中公司 |
|
||||||
| `jobs` | `Ref<Job[]>` | 当前公司的岗位列表 |
|
| `jobs` | `Ref<Job[]>` | 当前公司的岗位列表 |
|
||||||
| `selectedJob` | `Ref<JobDetail \| null>` | 当前选中岗位详情 |
|
| `selectedJob` | `Ref<Job \| null>` | 当前选中岗位(getJob 返回的完整 Job 对象) |
|
||||||
| `jobsLoading` | `Ref<boolean>` | 中栏加载状态 |
|
| `jobsLoading` | `Ref<boolean>` | 中栏加载状态 |
|
||||||
| `detailLoading` | `Ref<boolean>` | 右栏加载状态 |
|
| `detailLoading` | `Ref<boolean>` | 右栏加载状态 |
|
||||||
|
| `jobsError` | `Ref<boolean>` | 中栏错误状态 |
|
||||||
|
| `detailError` | `Ref<boolean>` | 右栏错误状态 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 交互流程
|
## 交互流程
|
||||||
|
|
||||||
1. `onMounted` → 调用 `getOrganizations()`,加载公司列表,自动选中第一个
|
1. `onMounted` → 设置 `orgsLoading = true`,调用 `getOrganizations()`
|
||||||
2. 自动选中第一个公司 → 触发 `selectOrg(org)` → 调用 `getJobs({ organization: org.id })`
|
- 成功且列表非空:`orgsLoading = false`,渲染左栏,自动调用 `selectOrg(orgs.value[0])`
|
||||||
3. 点击公司 → `selectOrg(org)` → 更新岗位列表,清空 `selectedJob`
|
- 成功但列表为空:`orgsLoading = false`,左栏显示「暂无公司数据」
|
||||||
4. 点击岗位 → `selectJob(job)` → 调用 `getJob(job.id)` → 更新右栏详情
|
- 失败:`orgsLoading = false`,`orgsError = true`,左栏显示「加载失败,请刷新重试」
|
||||||
5. 点击「立即申请」→ 跳转到 `/jobs/:id`(保留现有申请流程)
|
|
||||||
|
2. `selectOrg(org)` →
|
||||||
|
- 重置 `jobsError = false`,清空 `selectedJob`,`jobs = []`
|
||||||
|
- 设置 `selectedOrg = org`,`jobsLoading = true`
|
||||||
|
- 调用 `getJobs({ organization: org.id })`,取 `data.results` 更新 `jobs`
|
||||||
|
- 完成后 `jobsLoading = false`;失败则 `jobsError = true`
|
||||||
|
|
||||||
|
3. `selectJob(job)` →
|
||||||
|
- 重置 `detailError = false`
|
||||||
|
- 设置 `detailLoading = true`,`selectedJob = null`
|
||||||
|
- 调用 `getJob(job.id)` 更新 `selectedJob`
|
||||||
|
- 完成后 `detailLoading = false`;失败则 `detailError = true`
|
||||||
|
|
||||||
|
4. 「立即申请」按钮点击 →
|
||||||
|
- 若 `authStore.isSeeker`:`router.push({ name: 'JobDetail', params: { id: selectedJob.id } })`
|
||||||
|
- 否则(未登录或非 seeker 角色):`router.push({ name: 'Login', query: { redirect: '/jobs/' + selectedJob.id } })`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -87,12 +140,13 @@
|
||||||
|------|---------|
|
|------|---------|
|
||||||
| `offer_frontend/src/views/portal/CompanyListView.vue` | 完全重写 |
|
| `offer_frontend/src/views/portal/CompanyListView.vue` | 完全重写 |
|
||||||
|
|
||||||
无需改动后端(organization 过滤已支持)、无需新增路由、无需新增组件。
|
无需改动后端、无需新增路由、无需新增组件。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 不在范围内
|
## 不在范围内
|
||||||
|
|
||||||
- 搜索/筛选公司
|
- 搜索/筛选公司或岗位
|
||||||
- 分页(公司数量有限,一次性加载)
|
- 分页(公司数量有限,一次性加载)
|
||||||
- CompanyDetailView(`/companies/:id`)不变
|
- CompanyDetailView(`/companies/:id`)不变
|
||||||
|
- 在招岗位数统计(Organization API 无此字段)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@
|
||||||
<el-menu router :default-active="$route.path">
|
<el-menu router :default-active="$route.path">
|
||||||
<el-menu-item index="/seeker/resume">我的简历</el-menu-item>
|
<el-menu-item index="/seeker/resume">我的简历</el-menu-item>
|
||||||
<el-menu-item index="/seeker/applications">我的投递</el-menu-item>
|
<el-menu-item index="/seeker/applications">我的投递</el-menu-item>
|
||||||
|
<el-menu-item index="/seeker/favorites">关注职位</el-menu-item>
|
||||||
<el-menu-item index="/seeker/profile">账号设置</el-menu-item>
|
<el-menu-item index="/seeker/profile">账号设置</el-menu-item>
|
||||||
|
<el-menu-item index="/home">返回主页</el-menu-item>
|
||||||
</el-menu>
|
</el-menu>
|
||||||
</el-aside>
|
</el-aside>
|
||||||
<el-main><router-view /></el-main>
|
<el-main><router-view /></el-main>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue