feat: 全量更新前后端代码及文档 - 社区/定制/优惠券/活动/会员等模块

This commit is contained in:
Developer
2026-03-21 18:35:41 +08:00
parent a8aaf15bfb
commit 942465b758
590 changed files with 27840 additions and 14720 deletions

View File

@@ -11,10 +11,10 @@
<h2>招募Skill开发者</h2>
<p>移动办公 · 时间自由 · 收入可观</p>
<div class="hero-tags">
<el-tag effect="dark" size="large">远程办公</el-tag>
<el-tag effect="dark" size="large" type="success">时间自由</el-tag>
<el-tag effect="dark" size="large" type="warning">收入可观</el-tag>
<el-tag effect="dark" size="large" type="danger">技术成长</el-tag>
<a-tag color="blue">远程办公</a-tag>
<a-tag color="green">时间自由</a-tag>
<a-tag color="orange">收入可观</a-tag>
<a-tag color="red">技术成长</a-tag>
</div>
</div>
</div>
@@ -24,42 +24,42 @@
<div class="benefits-grid">
<div class="benefit-card">
<div class="benefit-icon">
<el-icon :size="36" color="#409eff"><Wallet /></el-icon>
<wallet-outlined :style="{ fontSize: '36px', color: '#409eff' }" />
</div>
<h3>丰厚收益</h3>
<p>每次下载获得收益分成<br/>优秀开发者月入过万</p>
</div>
<div class="benefit-card">
<div class="benefit-icon">
<el-icon :size="36" color="#67c23a"><Location /></el-icon>
<environment-outlined :style="{ fontSize: '36px', color: '#67c23a' }" />
</div>
<h3>移动办公</h3>
<p>无需坐班随时随地<br/>在家也能轻松赚钱</p>
</div>
<div class="benefit-card">
<div class="benefit-icon">
<el-icon :size="36" color="#e6a23c"><Clock /></el-icon>
<clock-circle-outlined :style="{ fontSize: '36px', color: '#e6a23c' }" />
</div>
<h3>时间自由</h3>
<p>自己安排开发时间<br/>工作生活完美平衡</p>
</div>
<div class="benefit-card">
<div class="benefit-icon">
<el-icon :size="36" color="#f56c6c"><TrendCharts /></el-icon>
<rise-outlined :style="{ fontSize: '36px', color: '#f56c6c' }" />
</div>
<h3>技术成长</h3>
<p>实战项目经验积累<br/>提升技术能力</p>
</div>
<div class="benefit-card">
<div class="benefit-icon">
<el-icon :size="36" color="#909399"><Medal /></el-icon>
<trophy-outlined :style="{ fontSize: '36px', color: '#909399' }" />
</div>
<h3>官方认证</h3>
<p>获得平台官方认证<br/>提升个人品牌影响力</p>
</div>
<div class="benefit-card">
<div class="benefit-icon">
<el-icon :size="36" color="#9b59b6"><Connection /></el-icon>
<team-outlined :style="{ fontSize: '36px', color: '#9b59b6' }" />
</div>
<h3>社区资源</h3>
<p>加入开发者社区<br/>与同行交流学习</p>
@@ -71,28 +71,28 @@
<h2 class="section-title">申请要求</h2>
<div class="requirements-list">
<div class="requirement-item">
<el-icon :size="24" color="#67c23a"><Check /></el-icon>
<check-circle-outlined :style="{ fontSize: '24px', color: '#67c23a' }" />
<div class="requirement-content">
<h4>技术能力</h4>
<p>熟练掌握至少一门编程语言有实际项目开发经验</p>
</div>
</div>
<div class="requirement-item">
<el-icon :size="24" color="#67c23a"><Check /></el-icon>
<check-circle-outlined :style="{ fontSize: '24px', color: '#67c23a' }" />
<div class="requirement-content">
<h4>作品展示</h4>
<p>需提供至少一个可演示的Skill作品或相关项目经验</p>
</div>
</div>
<div class="requirement-item">
<el-icon :size="24" color="#67c23a"><Check /></el-icon>
<check-circle-outlined :style="{ fontSize: '24px', color: '#67c23a' }" />
<div class="requirement-content">
<h4>责任心</h4>
<p>对产品质量负责能够及时响应和修复问题</p>
</div>
</div>
<div class="requirement-item">
<el-icon :size="24" color="#67c23a"><Check /></el-icon>
<check-circle-outlined :style="{ fontSize: '24px', color: '#67c23a' }" />
<div class="requirement-content">
<h4>持续维护</h4>
<p>愿意持续维护和更新已发布的Skill产品</p>
@@ -103,121 +103,140 @@
<div class="form-section">
<h2 class="section-title">申请成为开发者</h2>
<el-form
<a-form
ref="formRef"
:model="form"
:rules="rules"
label-width="120px"
layout="horizontal"
:label-col="{ span: 5 }"
:wrapper-col="{ span: 19 }"
class="apply-form"
>
<el-divider content-position="left">基本信息</el-divider>
<el-form-item label="真实姓名" prop="realName">
<el-input v-model="form.realName" placeholder="请输入真实姓名" />
</el-form-item>
<el-form-item label="手机号码" prop="phone">
<el-input v-model="form.phone" placeholder="请输入手机号码" />
</el-form-item>
<el-form-item label="电子邮箱" prop="email">
<el-input v-model="form.email" placeholder="请输入电子邮箱" />
</el-form-item>
<el-form-item label="所在城市" prop="city">
<el-input v-model="form.city" placeholder="请输入所在城市" />
</el-form-item>
<a-divider orientation="left">基本信息</a-divider>
<el-divider content-position="left">专业信息</el-divider>
<a-form-item label="真实姓名" name="realName">
<a-input v-model:value="form.realName" placeholder="请输入真实姓名" />
</a-form-item>
<a-form-item label="手机号码" name="phone">
<a-input v-model:value="form.phone" placeholder="请输入手机号码" />
</a-form-item>
<a-form-item label="电子邮箱" name="email">
<a-input v-model:value="form.email" placeholder="请输入电子邮箱" />
</a-form-item>
<a-form-item label="所在城市" name="city">
<a-input v-model:value="form.city" placeholder="请输入所在城市" />
</a-form-item>
<el-form-item label="技术栈" prop="techStack">
<el-checkbox-group v-model="form.techStack">
<el-checkbox label="Python">Python</el-checkbox>
<el-checkbox label="JavaScript">JavaScript</el-checkbox>
<el-checkbox label="Java">Java</el-checkbox>
<el-checkbox label="Go">Go</el-checkbox>
<el-checkbox label="C/C++">C/C++</el-checkbox>
<el-checkbox label="Rust">Rust</el-checkbox>
<el-checkbox label="TypeScript">TypeScript</el-checkbox>
<el-checkbox label="其他">其他</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="工作年限" prop="experience">
<el-select v-model="form.experience" placeholder="请选择工作年限" style="width: 100%">
<el-option label="1年以下" value="0-1" />
<el-option label="1-3年" value="1-3" />
<el-option label="3-5年" value="3-5" />
<el-option label="5-10年" value="5-10" />
<el-option label="10年以上" value="10+" />
</el-select>
</el-form-item>
<el-form-item label="擅长领域" prop="expertise">
<el-select v-model="form.expertise" multiple placeholder="请选择擅长领域" style="width: 100%">
<el-option label="办公自动化" value="office" />
<el-option label="数据处理" value="data" />
<el-option label="AI/机器学习" value="ai" />
<el-option label="Web开发" value="web" />
<el-option label="移动端开发" value="mobile" />
<el-option label="爬虫/采集" value="crawler" />
<el-option label="自动化测试" value="testing" />
<el-option label="其他" value="other" />
</el-select>
</el-form-item>
<a-divider orientation="left">专业信息</a-divider>
<el-divider content-position="left">简历与作品</el-divider>
<a-form-item label="技术栈" name="techStack">
<a-checkbox-group v-model:value="form.techStack" :options="techStackOptions" />
</a-form-item>
<a-form-item label="工作年限" name="experience">
<a-select v-model:value="form.experience" placeholder="请选择工作年限" :options="experienceOptions" />
</a-form-item>
<a-form-item label="擅长领域" name="expertise">
<a-select v-model:value="form.expertise" mode="multiple" placeholder="请选择擅长领域" :options="expertiseOptions" />
</a-form-item>
<el-form-item label="个人简介" prop="bio">
<el-input
v-model="form.bio"
type="textarea"
<a-divider orientation="left">简历与作品</a-divider>
<a-form-item label="个人简介" name="bio">
<a-textarea
v-model:value="form.bio"
:rows="4"
placeholder="请简单介绍一下自己,包括教育背景、工作经历等"
/>
</el-form-item>
<el-form-item label="简历上传" prop="resumeUrl">
<el-input v-model="form.resumeUrl" placeholder="请输入简历网盘链接(支持百度网盘、腾讯微云等)">
<template #prepend>
<el-icon><Link /></el-icon>
</a-form-item>
<a-form-item label="简历上传" name="resumeUrl">
<div class="upload-area">
<a-upload
:file-list="resumeFileList"
:before-upload="beforeResumeUpload"
:custom-request="handleResumeUpload"
:max-count="1"
accept=".pdf,.doc,.docx"
@remove="handleResumeRemove"
>
<a-button :loading="resumeUploading">
<template #icon><upload-outlined /></template>
{{ resumeUploading ? '上传中...' : '选择简历文件' }}
</a-button>
</a-upload>
<div class="upload-or"></div>
<a-input
v-model:value="form.resumeUrl"
placeholder="粘贴简历网盘链接(百度网盘、腾讯微云等)"
:disabled="!!resumeFileList.length"
style="flex: 1"
>
<template #prefix><link-outlined /></template>
</a-input>
</div>
<div class="form-tip">支持 PDF/DOC/DOCX 格式最大 5MB也可粘贴网盘链接</div>
<a-progress v-if="resumeUploading" :percent="resumeProgress" size="small" style="margin-top: 4px" />
</a-form-item>
<a-form-item label="Skill演示视频" name="demoVideoUrl">
<div class="upload-area">
<a-upload
:file-list="videoFileList"
:before-upload="beforeVideoUpload"
:custom-request="handleVideoUpload"
:max-count="1"
accept=".mp4,.mov,.avi,.webm,.mkv"
@remove="handleVideoRemove"
>
<a-button :loading="videoUploading">
<template #icon><upload-outlined /></template>
{{ videoUploading ? '上传中...' : '选择视频文件' }}
</a-button>
</a-upload>
<div class="upload-or"></div>
<a-input
v-model:value="form.demoVideoUrl"
placeholder="粘贴视频网盘链接"
:disabled="!!videoFileList.length"
style="flex: 1"
>
<template #prefix><video-camera-outlined /></template>
</a-input>
</div>
<div class="form-tip">支持 MP4/MOV/AVI/WEBM 格式最大 100MB也可粘贴网盘链接</div>
<a-progress v-if="videoUploading" :percent="videoProgress" size="small" style="margin-top: 4px" />
</a-form-item>
<a-form-item label="作品链接" name="portfolioUrl">
<a-input v-model:value="form.portfolioUrl" placeholder="请输入个人作品集或GitHub链接选填">
<template #prefix>
<desktop-outlined />
</template>
</el-input>
<div class="form-tip">请将简历上传至网盘后粘贴分享链接</div>
</el-form-item>
<el-form-item label="Skill演示视频" prop="demoVideoUrl">
<el-input v-model="form.demoVideoUrl" placeholder="请输入Skill演示视频网盘链接">
<template #prepend>
<el-icon><VideoCamera /></el-icon>
</template>
</el-input>
<div class="form-tip">请录制Skill演示视频并上传至网盘展示您的作品功能</div>
</el-form-item>
<el-form-item label="作品链接" prop="portfolioUrl">
<el-input v-model="form.portfolioUrl" placeholder="请输入个人作品集或GitHub链接选填">
<template #prepend>
<el-icon><Monitor /></el-icon>
</template>
</el-input>
</el-form-item>
</a-input>
</a-form-item>
<el-form-item label="期望收益" prop="expectedIncome">
<el-radio-group v-model="form.expectedIncome">
<el-radio label="1000-3000">1,000-3,000/</el-radio>
<el-radio label="3000-5000">3,000-5,000/</el-radio>
<el-radio label="5000-10000">5,000-10,000/</el-radio>
<el-radio label="10000+">10,000元以上/</el-radio>
</el-radio-group>
</el-form-item>
<a-form-item label="期望收益" name="expectedIncome">
<a-radio-group v-model:value="form.expectedIncome">
<a-radio value="1000-3000">1,000-3,000/</a-radio>
<a-radio value="3000-5000">3,000-5,000/</a-radio>
<a-radio value="5000-10000">5,000-10,000/</a-radio>
<a-radio value="10000+">10,000元以上/</a-radio>
</a-radio-group>
</a-form-item>
<el-form-item>
<el-checkbox v-model="form.agreement">
<a-form-item :wrapper-col="{ offset: 5, span: 19 }">
<a-checkbox v-model:checked="form.agreement">
我已阅读并同意
<el-button type="primary" text>开发者协议</el-button>
</el-checkbox>
</el-form-item>
<a-button type="link">开发者协议</a-button>
</a-checkbox>
</a-form-item>
<el-form-item>
<el-button type="primary" size="large" :loading="submitting" @click="handleSubmit">
提交申请
</el-button>
<el-button size="large" @click="resetForm">重置</el-button>
</el-form-item>
</el-form>
<a-form-item :wrapper-col="{ offset: 5, span: 19 }">
<a-space>
<a-button type="primary" size="large" :loading="submitting" @click="handleSubmit">
提交申请
</a-button>
<a-button size="large" @click="resetForm">重置</a-button>
</a-space>
</a-form-item>
</a-form>
</div>
<div class="process-section">
@@ -231,7 +250,7 @@
</div>
</div>
<div class="step-arrow">
<el-icon :size="24"><ArrowRight /></el-icon>
<right-outlined :style="{ fontSize: '24px' }" />
</div>
<div class="process-step">
<div class="step-number">2</div>
@@ -241,7 +260,7 @@
</div>
</div>
<div class="step-arrow">
<el-icon :size="24"><ArrowRight /></el-icon>
<right-outlined :style="{ fontSize: '24px' }" />
</div>
<div class="process-step">
<div class="step-number">3</div>
@@ -251,7 +270,7 @@
</div>
</div>
<div class="step-arrow">
<el-icon :size="24"><ArrowRight /></el-icon>
<right-outlined :style="{ fontSize: '24px' }" />
</div>
<div class="process-step">
<div class="step-number">4</div>
@@ -264,15 +283,16 @@
</div>
</div>
<el-dialog
v-model="successDialogVisible"
<a-modal
v-model:open="successDialogVisible"
title="申请提交成功"
width="500px"
:close-on-click-modal="false"
:width="500"
:closable="true"
:mask-closable="false"
>
<div class="success-content">
<div class="success-icon">
<el-icon :size="64" color="#67c23a"><CircleCheckFilled /></el-icon>
<check-circle-outlined :style="{ fontSize: '64px', color: '#67c23a' }" />
</div>
<h3>您的申请已提交成功</h3>
<p class="success-desc">我们将在3-5个工作日内完成审核审核结果将通过邮件通知您</p>
@@ -283,27 +303,57 @@
</div>
</div>
<template #footer>
<el-button type="primary" @click="successDialogVisible = false">我知道了</el-button>
<a-button type="primary" @click="successDialogVisible = false">我知道了</a-button>
</template>
</el-dialog>
</a-modal>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { ElMessage } from 'element-plus'
import { message } from 'ant-design-vue'
import { developerApi, uploadApi } from '@/service/apiService'
import {
UserOutlined,
LockOutlined,
MailOutlined,
EnvironmentOutlined,
CodeOutlined,
ClockCircleOutlined,
FileTextOutlined,
PlayCircleOutlined,
ProjectOutlined,
DollarCircleOutlined,
CheckCircleFilled,
TrophyOutlined,
StarOutlined,
UploadOutlined,
VideoCameraOutlined,
LinkOutlined,
DesktopOutlined
} from '@ant-design/icons-vue'
const formRef = ref(null)
const submitting = ref(false)
const successDialogVisible = ref(false)
// 简历上传状态
const resumeFileList = ref([])
const resumeUploading = ref(false)
const resumeProgress = ref(0)
// 视频上传状态
const videoFileList = ref([])
const videoUploading = ref(false)
const videoProgress = ref(0)
const form = reactive({
realName: '',
phone: '',
email: '',
city: '',
techStack: [],
experience: '',
experience: undefined,
expertise: [],
bio: '',
resumeUrl: '',
@@ -313,6 +363,131 @@ const form = reactive({
agreement: false
})
const techStackOptions = [
{ label: 'Python', value: 'Python' },
{ label: 'JavaScript', value: 'JavaScript' },
{ label: 'Java', value: 'Java' },
{ label: 'Go', value: 'Go' },
{ label: 'C/C++', value: 'C/C++' },
{ label: 'Rust', value: 'Rust' },
{ label: 'TypeScript', value: 'TypeScript' },
{ label: '其他', value: '其他' }
]
const experienceOptions = [
{ label: '1年以下', value: '0-1' },
{ label: '1-3年', value: '1-3' },
{ label: '3-5年', value: '3-5' },
{ label: '5-10年', value: '5-10' },
{ label: '10年以上', value: '10+' }
]
const expertiseOptions = [
{ label: '办公自动化', value: 'office' },
{ label: '数据处理', value: 'data' },
{ label: 'AI/机器学习', value: 'ai' },
{ label: 'Web开发', value: 'web' },
{ label: '移动端开发', value: 'mobile' },
{ label: '爬虫/采集', value: 'crawler' },
{ label: '自动化测试', value: 'testing' },
{ label: '其他', value: 'other' }
]
// 简历上传前校验
const beforeResumeUpload = (file) => {
const allowedExts = ['.pdf', '.doc', '.docx']
const ext = file.name.substring(file.name.lastIndexOf('.')).toLowerCase()
if (!allowedExts.includes(ext)) {
message.error('简历仅支持 PDF/DOC/DOCX 格式')
return false
}
if (file.size > 5 * 1024 * 1024) {
message.error('简历文件大小不能超过 5MB')
return false
}
return true
}
// 简历上传处理
const handleResumeUpload = async ({ file, onSuccess, onError }) => {
resumeUploading.value = true
resumeProgress.value = 0
// 模拟进度
const progressTimer = setInterval(() => {
if (resumeProgress.value < 90) resumeProgress.value += 10
}, 200)
try {
const res = await uploadApi.uploadFile('resume', file)
clearInterval(progressTimer)
resumeProgress.value = 100
const url = res.data?.url || res.data?.fileUrl
form.resumeUrl = url
resumeFileList.value = [{ uid: file.uid, name: file.name, status: 'done', url }]
onSuccess(res, file)
message.success('简历上传成功')
} catch (err) {
clearInterval(progressTimer)
onError(err)
message.error('简历上传失败:' + (err.response?.data?.message || err.message))
} finally {
resumeUploading.value = false
}
}
// 简历删除
const handleResumeRemove = () => {
resumeFileList.value = []
form.resumeUrl = ''
resumeProgress.value = 0
}
// 视频上传前校验
const beforeVideoUpload = (file) => {
const allowedExts = ['.mp4', '.mov', '.avi', '.webm', '.mkv']
const ext = file.name.substring(file.name.lastIndexOf('.')).toLowerCase()
if (!allowedExts.includes(ext)) {
message.error('视频仅支持 MP4/MOV/AVI/WEBM/MKV 格式')
return false
}
if (file.size > 100 * 1024 * 1024) {
message.error('视频文件大小不能超过 100MB')
return false
}
return true
}
// 视频上传处理
const handleVideoUpload = async ({ file, onSuccess, onError }) => {
videoUploading.value = true
videoProgress.value = 0
const progressTimer = setInterval(() => {
if (videoProgress.value < 90) videoProgress.value += 5
}, 500)
try {
const res = await uploadApi.uploadFile('video', file)
clearInterval(progressTimer)
videoProgress.value = 100
const url = res.data?.url || res.data?.fileUrl
form.demoVideoUrl = url
videoFileList.value = [{ uid: file.uid, name: file.name, status: 'done', url }]
onSuccess(res, file)
message.success('视频上传成功')
} catch (err) {
clearInterval(progressTimer)
onError(err)
message.error('视频上传失败:' + (err.response?.data?.message || err.message))
} finally {
videoUploading.value = false
}
}
// 视频删除
const handleVideoRemove = () => {
videoFileList.value = []
form.demoVideoUrl = ''
videoProgress.value = 0
}
const rules = {
realName: [{ required: true, message: '请输入真实姓名', trigger: 'blur' }],
phone: [
@@ -331,32 +506,44 @@ const rules = {
{ required: true, message: '请输入个人简介', trigger: 'blur' },
{ min: 50, message: '个人简介至少50个字符', trigger: 'blur' }
],
resumeUrl: [{ required: true, message: '请输入简历网盘链接', trigger: 'blur' }],
demoVideoUrl: [{ required: true, message: '请输入Skill演示视频链接', trigger: 'blur' }],
resumeUrl: [{ required: true, message: '请上传简历或粘贴网盘链接', trigger: 'blur' }],
demoVideoUrl: [{ required: true, message: '请上传视频或粘贴网盘链接', trigger: 'blur' }],
expectedIncome: [{ required: true, message: '请选择期望收益', trigger: 'change' }]
}
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate((valid) => {
if (valid) {
if (!form.agreement) {
ElMessage.warning('请阅读并同意开发者协议')
return
}
submitting.value = true
setTimeout(() => {
submitting.value = false
successDialogVisible.value = true
ElMessage.success('申请提交成功')
}, 1500)
}
})
try {
await formRef.value.validate()
} catch {
return
}
if (!form.agreement) {
message.warning('请阅读并同意开发者协议')
return
}
submitting.value = true
try {
await developerApi.submitApplication(form)
message.success('申请提交成功')
successDialogVisible.value = true
} catch (error) {
console.error('提交失败:', error)
message.error(error.response?.data?.message || '提交失败,请稍后重试')
} finally {
submitting.value = false
}
}
const resetForm = () => {
formRef.value?.resetFields()
resumeFileList.value = []
resumeProgress.value = 0
videoFileList.value = []
videoProgress.value = 0
}
</script>
@@ -506,23 +693,25 @@ const resetForm = () => {
max-width: 700px;
margin: 0 auto;
:deep(.el-checkbox-group) {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
:deep(.el-radio-group) {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.form-tip {
font-size: 12px;
color: #909399;
margin-top: 4px;
}
.upload-area {
display: flex;
align-items: flex-start;
gap: 12px;
flex-wrap: wrap;
.upload-or {
color: #c0c4cc;
font-size: 13px;
line-height: 32px;
flex-shrink: 0;
}
}
}
}