Files
jczxw-pc/app/pages/expert/apply.vue
T
2026-09-11 12:25:38 +08:00

618 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="expert-apply-page">
<div class="page-header">
<h1 class="page-title">专家申请备案</h1>
<p class="page-desc">请按备案表要求填写个人信息并上传证明材料</p>
</div>
<div class="expert-apply-layout">
<ExpertNavSidebar />
<div class="apply-form-wrap">
<a-steps :current="currentStep" class="steps-wrap">
<a-step title="填写信息" />
<a-step title="上传材料" />
<a-step title="确认提交" />
</a-steps>
<a-form :model="formData" layout="vertical" class="apply-form">
<div v-show="currentStep === 0">
<h3 class="section-title">备案信息</h3>
<a-row :gutter="24">
<a-col :span="8">
<a-form-item label="姓名" required>
<a-input v-model:value="formData.name" placeholder="请输入姓名" />
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="出生日期">
<a-date-picker v-model:value="formData.birthDate" style="width: 100%" />
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="性别">
<a-select v-model:value="formData.gender" placeholder="请选择">
<a-select-option value="男"></a-select-option>
<a-select-option value="女"></a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="8">
<a-form-item label="民族">
<a-select v-model:value="formData.ethnicity" placeholder="请选择民族">
<a-select-option v-for="item in ethnicityOptions" :key="item" :value="item">
{{ item }}
</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="政治面貌">
<a-select v-model:value="formData.politicalStatus" placeholder="请选择政治面貌">
<a-select-option v-for="item in politicalStatusOptions" :key="item" :value="item">
{{ item }}
</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="国籍">
<a-select v-model:value="formData.nationality" placeholder="请选择国籍">
<a-select-option v-for="item in nationalityOptions" :key="item" :value="item">
{{ item }}
</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="8">
<a-form-item label="文化程度">
<a-select v-model:value="formData.education" placeholder="请选择文化程度">
<a-select-option v-for="item in educationOptions" :key="item" :value="item">
{{ item }}
</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="社团职务">
<a-input v-model:value="formData.societyPosition" placeholder="请输入社团职务" />
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="社团专兼职">
<a-radio-group v-model:value="formData.societyEmploymentType">
<a-radio :value="0">专职</a-radio>
<a-radio :value="1">兼职</a-radio>
</a-radio-group>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="8">
<a-form-item label="职称">
<a-input v-model:value="formData.title" placeholder="请输入职称" />
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="身份证号">
<a-input v-model:value="formData.idCardNumber" placeholder="请输入身份证号" />
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="联系电话" required>
<a-input v-model:value="formData.phone" placeholder="请输入联系电话" />
</a-form-item>
</a-col>
</a-row>
<a-form-item label="住址">
<a-input v-model:value="formData.address" placeholder="请输入住址" />
</a-form-item>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item label="工作单位">
<a-input v-model:value="formData.organization" placeholder="请输入工作单位" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="工作职务">
<a-input v-model:value="formData.workDuty" placeholder="请输入工作职务" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item label="其他社会职务">
<a-input v-model:value="formData.otherSocialPosition" placeholder="请输入其他社会职务" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="研究领域">
<a-input v-model:value="formData.researchArea" placeholder="请输入研究领域" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="8">
<a-form-item label="担任其他社团职务">
<a-radio-group v-model:value="formData.hasOtherSocietyPosition">
<a-radio :value="0"></a-radio>
<a-radio :value="1"></a-radio>
</a-radio-group>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="在职国家工作人员">
<a-radio-group v-model:value="formData.isPublicServant">
<a-radio :value="0"></a-radio>
<a-radio :value="1"></a-radio>
</a-radio-group>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="离退休干部">
<a-radio-group v-model:value="formData.isRetired">
<a-radio :value="0"></a-radio>
<a-radio :value="1"></a-radio>
</a-radio-group>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item label="工作单位类型">
<a-select v-model:value="formData.unitType" placeholder="请选择">
<a-select-option value="行政机关">行政机关</a-select-option>
<a-select-option value="事业单位">事业单位</a-select-option>
<a-select-option value="国有企业">国有企业</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="工作单位级别">
<a-select v-model:value="formData.unitLevel" placeholder="请选择">
<a-select-option value="省部级及以上">省部级及以上</a-select-option>
<a-select-option value="地厅级">地厅级</a-select-option>
<a-select-option value="县处级">县处级</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-form-item label="邮箱">
<a-input v-model:value="formData.email" placeholder="选填,便于联系" />
</a-form-item>
<a-form-item label="主要工作简历">
<div class="experience-list">
<div v-for="(item, index) in formData.workExperience" :key="index" class="experience-row">
<a-row :gutter="12">
<a-col :span="8">
<a-input v-model:value="item.period" placeholder="何年月至何年月" />
</a-col>
<a-col :span="14">
<a-input v-model:value="item.organization" placeholder="在何地何单位" />
</a-col>
<a-col :span="2">
<a-button danger @click="removeWorkExperience(index)">删除</a-button>
</a-col>
</a-row>
</div>
<a-button block @click="addWorkExperience">新增一行</a-button>
</div>
</a-form-item>
<a-form-item label="主要研究成果">
<a-textarea
v-model:value="formData.mainResearchAchievements"
:rows="6"
placeholder="请输入主要研究成果"
/>
</a-form-item>
<a-form-item label="个人简介">
<a-textarea v-model:value="formData.bio" :rows="4" placeholder="可补充个人简介" />
</a-form-item>
</div>
<div v-show="currentStep === 1">
<h3 class="section-title">附件材料</h3>
<p class="section-desc">请按备案表要求上传申请表及相关证明材料</p>
<div class="materials-tip">
<div>附件身份证学位证职称证书研究成果证明课题合同结题证书获奖证书其他证明)。</div>
<a-button type="link" @click="navigateTo('/expert/downloads')">前往资料下载</a-button>
</div>
<a-form-item label="专家申请备案表">
<a-upload :before-upload="beforeUpload" :custom-request="handleUpload('applicationForm')">
<a-button><UploadOutlined /> 上传备案表</a-button>
</a-upload>
</a-form-item>
<a-form-item label="身份证">
<a-upload :before-upload="beforeUpload" :custom-request="handleUpload('idCard')">
<a-button><UploadOutlined /> 上传身份证</a-button>
</a-upload>
</a-form-item>
<a-form-item label="学位证">
<a-upload :before-upload="beforeUpload" :custom-request="handleUpload('degreeCertificate')">
<a-button><UploadOutlined /> 上传学位证</a-button>
</a-upload>
</a-form-item>
<a-form-item label="职称证书">
<a-upload :before-upload="beforeUpload" :custom-request="handleUpload('titleCertificate')">
<a-button><UploadOutlined /> 上传职称证书</a-button>
</a-upload>
</a-form-item>
<a-form-item label="研究成果证明">
<a-upload multiple :before-upload="beforeUpload" :custom-request="handleUpload('achievements')">
<a-button><UploadOutlined /> 上传证明材料</a-button>
</a-upload>
<div class="upload-hint">可上传多份材料支持 PDFWord图片格式不超过 10MB</div>
</a-form-item>
</div>
<div v-show="currentStep === 2" class="confirm-section">
<a-result title="确认提交申请" sub-title="请确认信息和材料准确无误">
<template #icon>
<CheckCircleOutlined style="font-size: 80px; color: #52c41a" />
</template>
<template #extra>
<a-button type="primary" size="large" @click="handleSubmit" :loading="submitting">
确认提交
</a-button>
</template>
</a-result>
</div>
<div class="step-actions">
<a-button v-if="currentStep > 0" @click="currentStep--">上一步</a-button>
<a-button v-if="currentStep < 2" type="primary" @click="handleNext">下一步</a-button>
</div>
</a-form>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import dayjs, { type Dayjs } from 'dayjs'
import { message } from 'ant-design-vue'
import { CheckCircleOutlined, UploadOutlined } from '@ant-design/icons-vue'
import ExpertNavSidebar from '@/components/ExpertNavSidebar.vue'
import { submitSiteExpertApplication, uploadSiteExpertFile } from '@/api/site'
useHead({ title: '专家申请备案 - 决策咨询网' })
type UploadRequestOption = {
file: File
onSuccess: (response?: unknown) => void
onError: (error?: unknown) => void
}
type WorkExperienceItem = {
period: string
organization: string
}
const currentStep = ref(0)
const submitting = ref(false)
const ethnicityOptions = [
'汉族',
'壮族',
'满族',
'回族',
'苗族',
'维吾尔族',
'土家族',
'彝族',
'蒙古族',
'藏族',
'布依族',
'侗族',
'瑶族',
'朝鲜族',
'白族',
'哈尼族',
'哈萨克族',
'黎族',
'傣族',
'畲族',
'傈僳族',
'仡佬族',
'东乡族',
'高山族',
'拉祜族',
'水族',
'佤族',
'纳西族',
'羌族',
'土族',
'仫佬族',
'锡伯族',
'柯尔克孜族',
'达斡尔族',
'景颇族',
'毛南族',
'撒拉族',
'布朗族',
'塔吉克族',
'阿昌族',
'普米族',
'鄂温克族',
'怒族',
'京族',
'基诺族',
'德昂族',
'保安族',
'俄罗斯族',
'裕固族',
'乌孜别克族',
'门巴族',
'鄂伦春族',
'独龙族',
'塔塔尔族',
'赫哲族',
'珞巴族'
]
const politicalStatusOptions = [
'中共党员',
'中共预备党员',
'共青团员',
'民革党员',
'民盟盟员',
'民建会员',
'民进会员',
'农工党党员',
'致公党党员',
'九三学社社员',
'台盟盟员',
'无党派人士',
'群众'
]
const educationOptions = [
'高中及以下',
'中专',
'大专',
'本科',
'硕士研究生',
'博士研究生',
'博士后'
]
const nationalityOptions = [
'中国',
'中国香港',
'中国澳门',
'中国台湾',
'其他'
]
const formData = reactive({
name: '',
birthDate: undefined as Dayjs | undefined,
gender: '',
ethnicity: ethnicityOptions[0],
politicalStatus: '',
nationality: nationalityOptions[0],
education: '',
societyPosition: '',
societyEmploymentType: 0,
title: '',
idCardNumber: '',
address: '',
phone: '',
organization: '',
workDuty: '',
otherSocialPosition: '',
hasOtherSocietyPosition: 0,
isPublicServant: 0,
unitType: '',
unitLevel: '',
isRetired: 0,
researchArea: '',
email: '',
bio: '',
workExperience: [{ period: '', organization: '' }] as WorkExperienceItem[],
mainResearchAchievements: '',
applicationForm: '',
idCard: '',
degreeCertificate: '',
titleCertificate: '',
achievements: [] as string[],
})
function beforeUpload(file: File) {
const isLt10M = file.size / 1024 / 1024 < 10
if (!isLt10M) {
message.error('文件大小不能超过 10MB')
return false
}
return true
}
function addWorkExperience() {
formData.workExperience.push({ period: '', organization: '' })
}
function removeWorkExperience(index: number) {
if (formData.workExperience.length === 1) {
formData.workExperience[0] = { period: '', organization: '' }
return
}
formData.workExperience.splice(index, 1)
}
function handleUpload(type: string) {
return async (option: UploadRequestOption) => {
try {
const form = new FormData()
form.append('file', option.file)
const data = await uploadSiteExpertFile(form)
if (type === 'achievements') {
formData.achievements.push(data.path)
} else {
formData[type as Exclude<keyof typeof formData, 'achievements' | 'workExperience' | 'birthDate'>] = data.path as never
}
option.onSuccess(data)
message.success('上传成功')
} catch (e: unknown) {
option.onError(e)
message.error(e instanceof Error ? e.message : '上传失败')
}
}
}
function handleNext() {
if (currentStep.value === 0) {
if (!formData.name.trim() || !formData.phone.trim()) {
message.warning('请至少填写姓名和联系电话')
return
}
}
currentStep.value++
}
async function handleSubmit() {
submitting.value = true
try {
await submitSiteExpertApplication({
...formData,
birthDate: formData.birthDate ? dayjs(formData.birthDate).format('YYYY-MM-DD') : '',
workExperience: formData.workExperience.filter((item) => item.period || item.organization),
})
message.success('提交成功,请等待审核')
navigateTo('/expert')
} catch (e: unknown) {
message.error(e instanceof Error ? e.message : '提交失败')
} finally {
submitting.value = false
}
}
</script>
<style scoped>
.expert-apply-page {
max-width: 1200px;
margin: 0 auto;
padding: 40px 20px;
}
.page-header {
text-align: center;
margin-bottom: 40px;
}
.page-title {
font-size: 32px;
font-weight: 700;
color: #1f2937;
margin: 0 0 12px;
}
.page-desc {
font-size: 16px;
color: #6b7280;
margin: 0;
}
.expert-apply-layout {
display: grid;
grid-template-columns: 220px minmax(0, 1fr);
gap: 28px;
align-items: start;
}
.apply-form-wrap {
min-width: 0;
background: #fff;
border-radius: 16px;
padding: 40px;
box-shadow: 0 4px 16px rgba(0,0,0,0.08);
}
.steps-wrap {
margin-bottom: 40px;
}
.section-title {
font-size: 18px;
font-weight: 600;
color: #1f2937;
margin: 0 0 20px;
}
.section-desc {
font-size: 14px;
color: #6b7280;
margin: -10px 0 20px;
}
.materials-tip {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-bottom: 20px;
padding: 14px 16px;
background: #f8fafc;
border: 1px solid #dbeafe;
border-radius: 12px;
color: #475569;
}
.experience-list {
display: grid;
gap: 12px;
}
.experience-row {
padding: 12px 14px;
background: #f8fafc;
border-radius: 12px;
}
.upload-hint {
font-size: 12px;
color: #9ca3af;
margin-top: 8px;
}
.confirm-section {
padding: 40px 0;
}
.step-actions {
display: flex;
justify-content: center;
gap: 16px;
margin-top: 32px;
padding-top: 24px;
border-top: 1px solid #f0f0f0;
}
@media (max-width: 1024px) {
.expert-apply-layout {
grid-template-columns: 1fr;
}
}
</style>