Recruitment_site/docs/superpowers/specs/2026-03-25-company-jobs-thr...

7.1 KiB
Raw Blame History

公司列表三栏布局设计文档

日期: 2026-03-25 状态: 已批准 影响页面: /companiesCompanyListView.vue


目标

将现有 /companies 页面从网格卡片布局改造为三栏联动布局,用户可在同一页面浏览公司、查看岗位列表、阅读岗位详情,无需跳转。


布局结构

┌─────────────────────────────────────────────────────┐
│  左栏 220px  │  中栏 260px  │  右栏 flex:1           │
│  公司列表    │  岗位列表    │  岗位详情              │
└─────────────────────────────────────────────────────┘

高度设置: PortalLayout 包含 el-header60px+ el-main(默认 padding 20px+ el-footer60px。三栏容器根元素设置 height: calc(100vh - 200px)60 header + 60 footer + 40 el-main padding + 40 bufferoverflow: hidden,各栏内部 overflow-y: auto 独立滚动。此高度值在主流桌面分辨率下提供良好体验,无需精确到像素。


各栏设计

左栏 — 公司列表

  • 顶部标题「全部企业」+ 公司总数 badge
  • 每个公司卡片包含:
    • Logo如有 org.logo,显示 <img>;否则显示公司名首字 + 蓝色背景 div
    • 公司名称(org.name
    • 简介摘要(org.description 截取前 20 字,为空则不显示)
  • 不显示在招岗位数Organization API 无此字段)
  • 选中状态:左边框 3px solid #409EFF,背景白色
  • 未选中:左边框透明,背景 #f5f7fa
  • 加载状态orgsLoading 为 true 时显示 el-skeleton3 行)
  • 错误状态orgsError 为 true 时显示「加载失败,请刷新重试」居中提示
  • 空状态orgs 为空数组时显示「暂无公司数据」居中提示
  • 数据来源:getOrganizations()GET /organizations/public/,取 data.results

中栏 — 岗位列表

  • 标题:「{org.name} · 职位列表」
  • 每条岗位显示:
    • 岗位名称(job.title
    • 标签行:job.location(城市)、job.salary(薪资)、job.category(类别)
  • 选中状态:左边框 3px solid #409EFF,背景 #ecf5ff
  • 空状态:该公司无已发布岗位时,显示「暂无在招职位」居中提示
  • 加载状态jobsLoading 为 true 时显示 el-skeleton3 行)
  • 错误状态API 失败时显示「加载失败,请刷新重试」
  • 数据来源:getJobs({ organization: org.id }) → 取 data.results

右栏 — 岗位详情

右栏渲染优先级(严格按顺序):

  1. detailLoading === true → 显示 el-skeleton
  2. detailError === true → 显示「加载失败,请刷新重试」
  3. selectedJob !== null → 显示岗位详情
  4. 以上均不满足(selectedJob === null 且无加载/错误)→ 显示空状态

各状态内容:

  • 空状态:居中空状态图标 + 「← 请选择左侧职位查看详情」
  • 岗位详情
    • 顶部:公司首字 Logo + 岗位名称(job.title+ 公司/城市副标题 + 「立即申请」按钮
    • 标签行:job.locationjob.salaryjob.category(无经验字段,不显示)
    • 正文:job.description(用 white-space: pre-wrap 保留换行)
  • 加载状态el-skeleton5 行,含头部和正文)
  • 错误状态:「加载失败,请刷新重试」居中提示
  • 「立即申请」按钮行为
    • 已登录seekerrouter.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

变量 类型 说明
orgs Ref<Organization[]> 所有公司列表
orgsLoading Ref<boolean> 左栏加载状态
orgsError Ref<boolean> 左栏错误状态
selectedOrg Ref<Organization | null> 当前选中公司
jobs Ref<Job[]> 当前公司的岗位列表
selectedJob Ref<Job | null> 当前选中岗位getJob 返回的完整 Job 对象)
jobsLoading Ref<boolean> 中栏加载状态
detailLoading Ref<boolean> 右栏加载状态
jobsError Ref<boolean> 中栏错误状态
detailError Ref<boolean> 右栏错误状态

交互流程

  1. onMounted → 设置 orgsLoading = true,调用 getOrganizations()

    • 成功且列表非空:orgsLoading = false,渲染左栏,自动调用 selectOrg(orgs.value[0])
    • 成功但列表为空:orgsLoading = false,左栏显示「暂无公司数据」
    • 失败:orgsLoading = falseorgsError = true,左栏显示「加载失败,请刷新重试」
  2. selectOrg(org)

    • 重置 jobsError = false,清空 selectedJobjobs = []
    • 设置 selectedOrg = orgjobsLoading = true
    • 调用 getJobs({ organization: org.id }),取 data.results 更新 jobs
    • 完成后 jobsLoading = false;失败则 jobsError = true
  3. selectJob(job)

    • 重置 detailError = false
    • 设置 detailLoading = trueselectedJob = null
    • 调用 getJob(job.id) 更新 selectedJob
    • 完成后 detailLoading = false;失败则 detailError = true
  4. 「立即申请」按钮点击 →

    • authStore.isSeekerrouter.push({ name: 'JobDetail', params: { id: selectedJob.id } })
    • 否则(未登录或非 seeker 角色):router.push({ name: 'Login', query: { redirect: '/jobs/' + selectedJob.id } })

文件变更范围

文件 变更类型
offer_frontend/src/views/portal/CompanyListView.vue 完全重写

无需改动后端、无需新增路由、无需新增组件。


不在范围内

  • 搜索/筛选公司或岗位
  • 分页(公司数量有限,一次性加载)
  • CompanyDetailView/companies/:id)不变
  • 在招岗位数统计Organization API 无此字段)