feat: add public portal pages (home, job list, job detail, companies)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
734c83b7fe
commit
564fb06e3b
|
|
@ -1,3 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<div>CompanyCard - 开发中</div>
|
<el-card shadow="hover" @click="$router.push(`/companies/${org.id}`)" style="cursor:pointer;margin-bottom:16px">
|
||||||
|
<el-avatar :src="org.logo" size="large" />
|
||||||
|
<div style="margin-top:8px;font-weight:600">{{ org.name }}</div>
|
||||||
|
<div style="color:#666;font-size:13px">{{ org.description?.slice(0,60) }}</div>
|
||||||
|
</el-card>
|
||||||
</template>
|
</template>
|
||||||
|
<script setup>
|
||||||
|
defineProps({ org: Object })
|
||||||
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,22 @@
|
||||||
<template>
|
<template>
|
||||||
<div>JobCard - 开发中</div>
|
<el-card class="job-card" shadow="hover" @click="$router.push(`/jobs/${job.id}`)">
|
||||||
|
<div class="job-title">{{ job.title }}</div>
|
||||||
|
<div class="job-meta">
|
||||||
|
<span>{{ job.company_name || job.organization_name }}</span>
|
||||||
|
<el-divider direction="vertical" />
|
||||||
|
<span>{{ job.location }}</span>
|
||||||
|
<el-divider direction="vertical" />
|
||||||
|
<span class="salary">{{ job.salary }}</span>
|
||||||
|
</div>
|
||||||
|
<el-tag size="small">{{ job.category }}</el-tag>
|
||||||
|
</el-card>
|
||||||
</template>
|
</template>
|
||||||
|
<script setup>
|
||||||
|
defineProps({ job: Object })
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.job-card { cursor: pointer; margin-bottom: 12px; }
|
||||||
|
.job-title { font-size: 16px; font-weight: 600; margin-bottom: 8px; }
|
||||||
|
.job-meta { color: #666; font-size: 13px; margin-bottom: 8px; }
|
||||||
|
.salary { color: #f56c6c; font-weight: 500; }
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,31 @@
|
||||||
<template>
|
<template>
|
||||||
<div>公司详情 - 开发中</div>
|
<div v-if="org" style="max-width:900px;margin:0 auto">
|
||||||
|
<el-card>
|
||||||
|
<template #header><h2>{{ org.name }}</h2></template>
|
||||||
|
<p>{{ org.description }}</p>
|
||||||
|
<p>联系邮箱:{{ org.email }}</p>
|
||||||
|
</el-card>
|
||||||
|
<h3 style="margin-top:24px">在招职位</h3>
|
||||||
|
<JobCard v-for="job in jobs" :key="job.id" :job="job" />
|
||||||
|
<el-empty v-if="jobs.length === 0" description="暂无在招职位" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { getOrganization } from '@/api/organizations'
|
||||||
|
import { getJobs } from '@/api/jobs'
|
||||||
|
import JobCard from '@/components/JobCard.vue'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const org = ref(null)
|
||||||
|
const jobs = ref([])
|
||||||
|
onMounted(async () => {
|
||||||
|
const [orgRes, jobRes] = await Promise.all([
|
||||||
|
getOrganization(route.params.id),
|
||||||
|
getJobs({ organization: route.params.id })
|
||||||
|
])
|
||||||
|
org.value = orgRes.data
|
||||||
|
jobs.value = jobRes.data.results
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<div>公司列表 - 开发中</div>
|
<div style="max-width:1000px;margin:0 auto">
|
||||||
|
<h2>公司列表</h2>
|
||||||
|
<el-row :gutter="16">
|
||||||
|
<el-col v-for="org in orgs" :key="org.id" :span="8">
|
||||||
|
<CompanyCard :org="org" />
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import CompanyCard from '@/components/CompanyCard.vue'
|
||||||
|
import { getOrganizations } from '@/api/organizations'
|
||||||
|
const orgs = ref([])
|
||||||
|
onMounted(async () => {
|
||||||
|
const { data } = await getOrganizations()
|
||||||
|
orgs.value = data.results
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,37 @@
|
||||||
<template>
|
<template>
|
||||||
<div>首页 - 开发中</div>
|
<div class="home">
|
||||||
|
<div class="hero">
|
||||||
|
<h1>发现你的理想职位</h1>
|
||||||
|
<el-input v-model="keyword" placeholder="搜索职位..." size="large" @keyup.enter="search">
|
||||||
|
<template #append><el-button @click="search">搜索</el-button></template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
<div class="latest-jobs">
|
||||||
|
<h3>最新职位</h3>
|
||||||
|
<JobCard v-for="job in latestJobs" :key="job.id" :job="job" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import JobCard from '@/components/JobCard.vue'
|
||||||
|
import { getJobs } from '@/api/jobs'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const keyword = ref('')
|
||||||
|
const latestJobs = ref([])
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const { data } = await getJobs({ page: 1 })
|
||||||
|
latestJobs.value = data.results.slice(0, 6)
|
||||||
|
})
|
||||||
|
|
||||||
|
const search = () => router.push({ name: 'JobList', query: { search: keyword.value } })
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.hero { text-align: center; padding: 60px 20px; background: linear-gradient(135deg, #409eff22, #fff); }
|
||||||
|
.hero h1 { font-size: 36px; margin-bottom: 24px; }
|
||||||
|
.el-input { max-width: 500px; }
|
||||||
|
.latest-jobs { max-width: 900px; margin: 40px auto; }
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,65 @@
|
||||||
<template>
|
<template>
|
||||||
<div>职位详情 - 开发中</div>
|
<el-row :gutter="24" v-loading="loading" style="max-width:1000px;margin:0 auto">
|
||||||
|
<el-col :span="16">
|
||||||
|
<el-card v-if="job">
|
||||||
|
<template #header>
|
||||||
|
<h2>{{ job.title }}</h2>
|
||||||
|
<div class="meta">
|
||||||
|
{{ job.organization?.name }} · {{ job.location }} · {{ job.salary }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-html="job.description" class="description"></div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-card>
|
||||||
|
<el-button type="primary" size="large" style="width:100%" @click="handleApply">
|
||||||
|
立即投递
|
||||||
|
</el-button>
|
||||||
|
<p class="hint" v-if="!auth.isLoggedIn">登录后才能投递</p>
|
||||||
|
<p class="hint success" v-if="applied">已投递,可在"我的投递"查看进度</p>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
</template>
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import { getJob } from '@/api/jobs'
|
||||||
|
import { applyJob } from '@/api/applications'
|
||||||
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
const auth = useAuthStore()
|
||||||
|
const job = ref(null)
|
||||||
|
const loading = ref(false)
|
||||||
|
const applied = ref(false)
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
loading.value = true
|
||||||
|
const { data } = await getJob(route.params.id)
|
||||||
|
job.value = data
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
|
||||||
|
async function handleApply() {
|
||||||
|
if (!auth.isLoggedIn) return router.push({ name: 'Login', query: { redirect: route.fullPath } })
|
||||||
|
if (!auth.isSeeker) return ElMessage.warning('管理员账号无法投递职位')
|
||||||
|
try {
|
||||||
|
await applyJob(job.value.id)
|
||||||
|
applied.value = true
|
||||||
|
ElMessage.success('投递成功!')
|
||||||
|
} catch (e) {
|
||||||
|
if (e.response?.status === 400) ElMessage.warning('您已投递过该职位')
|
||||||
|
else ElMessage.error('投递失败,请先完善简历')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.meta { color: #666; margin-top: 8px; }
|
||||||
|
.description { line-height: 1.8; }
|
||||||
|
.hint { color: #999; font-size: 13px; margin-top: 12px; }
|
||||||
|
.success { color: #67c23a; }
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,52 @@
|
||||||
<template>
|
<template>
|
||||||
<div>职位列表 - 开发中</div>
|
<div class="job-list-page">
|
||||||
|
<el-card class="search-bar">
|
||||||
|
<el-form :model="filters" inline>
|
||||||
|
<el-form-item><el-input v-model="filters.search" placeholder="职位名称/关键词" clearable @change="fetchJobs" /></el-form-item>
|
||||||
|
<el-form-item><el-input v-model="filters.location" placeholder="城市" clearable @change="fetchJobs" /></el-form-item>
|
||||||
|
<el-form-item><el-input v-model="filters.category" placeholder="职位类别" clearable @change="fetchJobs" /></el-form-item>
|
||||||
|
<el-form-item><el-button type="primary" @click="fetchJobs">搜索</el-button></el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<div v-loading="loading" class="job-results">
|
||||||
|
<div class="result-count">共 {{ total }} 个职位</div>
|
||||||
|
<JobCard v-for="job in jobs" :key="job.id" :job="job" />
|
||||||
|
<el-pagination
|
||||||
|
v-if="total > pageSize"
|
||||||
|
layout="prev, pager, next"
|
||||||
|
:total="total"
|
||||||
|
:page-size="pageSize"
|
||||||
|
v-model:current-page="page"
|
||||||
|
@current-change="fetchJobs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
|
import JobCard from '@/components/JobCard.vue'
|
||||||
|
import { getJobs } from '@/api/jobs'
|
||||||
|
|
||||||
|
const jobs = ref([])
|
||||||
|
const total = ref(0)
|
||||||
|
const loading = ref(false)
|
||||||
|
const page = ref(1)
|
||||||
|
const pageSize = 20
|
||||||
|
const filters = reactive({ search: '', location: '', category: '' })
|
||||||
|
|
||||||
|
async function fetchJobs() {
|
||||||
|
loading.value = true
|
||||||
|
const { data } = await getJobs({ ...filters, page: page.value })
|
||||||
|
jobs.value = data.results
|
||||||
|
total.value = data.count
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(fetchJobs)
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.job-list-page { max-width: 900px; margin: 0 auto; }
|
||||||
|
.search-bar { margin-bottom: 20px; }
|
||||||
|
.result-count { color: #666; margin-bottom: 12px; }
|
||||||
|
</style>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue