Files
number/frontend/src/views/join-us/index.vue
Developer e7d9f47c61 feat: 促销系统完善、发票页重构、前端中文化、订单表迁移及多项优化
前端:

- 发票页完整重构:智能按钮状态、防重复开票、空态引导、订单多选

- 全局中文化:Ant Design Vue locale配置、清除残留英文UI文本

- 新增关于我们、联系我们页面

- 首页活动专区、搜索页、Skill详情等多处优化

后端:

- 订单模块:新增original_amount/promotion_deduct_amount字段及DB迁移

- 促销系统:完善促销规则、过期任务、批量查询等

- 新增RateLimit注解及拦截器、CORS过滤器、Health检查接口

- Logback日志配置、points枚举修复等
2026-03-23 16:38:13 +08:00

921 lines
27 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="join-us-page">
<div class="page-container">
<div class="page-header">
<h1 class="page-title">加入我们</h1>
<p class="page-desc">成为Skill开发者开启自由职业新篇章</p>
</div>
<div class="hero-section">
<div class="hero-content">
<h2>招募Skill开发者</h2>
<p>移动办公 · 时间自由 · 收入可观</p>
<div class="hero-tags">
<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>
<div class="benefits-section">
<h2 class="section-title">开发者权益</h2>
<div class="benefits-grid">
<div class="benefit-card">
<div class="benefit-icon">
<wallet-outlined :style="{ fontSize: '36px', color: '#409eff' }" />
</div>
<h3>丰厚收益</h3>
<p>每次下载获得收益分成<br/>优秀开发者月入过万</p>
</div>
<div class="benefit-card">
<div class="benefit-icon">
<environment-outlined :style="{ fontSize: '36px', color: '#67c23a' }" />
</div>
<h3>移动办公</h3>
<p>无需坐班随时随地<br/>在家也能轻松赚钱</p>
</div>
<div class="benefit-card">
<div class="benefit-icon">
<clock-circle-outlined :style="{ fontSize: '36px', color: '#e6a23c' }" />
</div>
<h3>时间自由</h3>
<p>自己安排开发时间<br/>工作生活完美平衡</p>
</div>
<div class="benefit-card">
<div class="benefit-icon">
<rise-outlined :style="{ fontSize: '36px', color: '#f56c6c' }" />
</div>
<h3>技术成长</h3>
<p>实战项目经验积累<br/>提升技术能力</p>
</div>
<div class="benefit-card">
<div class="benefit-icon">
<trophy-outlined :style="{ fontSize: '36px', color: '#909399' }" />
</div>
<h3>官方认证</h3>
<p>获得平台官方认证<br/>提升个人品牌影响力</p>
</div>
<div class="benefit-card">
<div class="benefit-icon">
<team-outlined :style="{ fontSize: '36px', color: '#9b59b6' }" />
</div>
<h3>社区资源</h3>
<p>加入开发者社区<br/>与同行交流学习</p>
</div>
</div>
</div>
<div class="requirements-section">
<h2 class="section-title">申请要求</h2>
<div class="requirements-list">
<div class="requirement-item">
<check-circle-outlined :style="{ fontSize: '24px', color: '#67c23a' }" />
<div class="requirement-content">
<h4>技术能力</h4>
<p>熟练掌握至少一门编程语言有实际项目开发经验</p>
</div>
</div>
<div class="requirement-item">
<check-circle-outlined :style="{ fontSize: '24px', color: '#67c23a' }" />
<div class="requirement-content">
<h4>作品展示</h4>
<p>需提供至少一个可演示的Skill作品或相关项目经验</p>
</div>
</div>
<div class="requirement-item">
<check-circle-outlined :style="{ fontSize: '24px', color: '#67c23a' }" />
<div class="requirement-content">
<h4>责任心</h4>
<p>对产品质量负责能够及时响应和修复问题</p>
</div>
</div>
<div class="requirement-item">
<check-circle-outlined :style="{ fontSize: '24px', color: '#67c23a' }" />
<div class="requirement-content">
<h4>持续维护</h4>
<p>愿意持续维护和更新已发布的Skill产品</p>
</div>
</div>
</div>
</div>
<div class="form-section">
<h2 class="section-title">申请成为开发者</h2>
<a-form
ref="formRef"
:model="form"
:rules="rules"
layout="horizontal"
:label-col="{ span: 5 }"
:wrapper-col="{ span: 19 }"
class="apply-form"
>
<a-divider orientation="left">基本信息</a-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>
<a-divider orientation="left">专业信息</a-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>
<a-divider orientation="left">简历与作品</a-divider>
<a-form-item label="个人简介" name="bio">
<a-textarea
v-model:value="form.bio"
:rows="4"
placeholder="请简单介绍一下自己,包括教育背景、工作经历等"
/>
</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">
<div class="upload-area">
<a-upload
:file-list="portfolioFileList"
:before-upload="beforePortfolioUpload"
:custom-request="handlePortfolioUpload"
:max-count="5"
multiple
accept=".zip,.rar,.7z,.tar.gz,.pdf,.doc,.docx,.ppt,.pptx,.png,.jpg,.jpeg,.gif"
@remove="handlePortfolioRemove"
>
<a-button :loading="portfolioUploading">
<template #icon><upload-outlined /></template>
{{ portfolioUploading ? '上传中...' : '选择作品文件' }}
</a-button>
</a-upload>
<div class="upload-or"></div>
<a-input
v-model:value="form.portfolioUrl"
placeholder="粘贴作品集链接GitHub、网盘等"
:disabled="!!portfolioFileList.length"
style="flex: 1"
>
<template #prefix><link-outlined /></template>
</a-input>
</div>
<div class="form-tip">支持 ZIP/RAR/PDF/图片等格式单文件最大 50MB最多 5 也可粘贴链接</div>
<a-progress v-if="portfolioUploading" :percent="portfolioProgress" size="small" style="margin-top: 4px" />
</a-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>
<a-form-item :wrapper-col="{ offset: 5, span: 19 }">
<a-checkbox v-model:checked="form.agreement">
我已阅读并同意
<a-button type="link">开发者协议</a-button>
</a-checkbox>
</a-form-item>
<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">
<h2 class="section-title">申请流程</h2>
<div class="process-steps">
<div class="process-step">
<div class="step-number">1</div>
<div class="step-content">
<h4>提交申请</h4>
<p>填写申请表单<br/>上传简历和作品</p>
</div>
</div>
<div class="step-arrow">
<right-outlined :style="{ fontSize: '24px' }" />
</div>
<div class="process-step">
<div class="step-number">2</div>
<div class="step-content">
<h4>资质审核</h4>
<p>平台审核团队<br/>评估技术能力</p>
</div>
</div>
<div class="step-arrow">
<right-outlined :style="{ fontSize: '24px' }" />
</div>
<div class="process-step">
<div class="step-number">3</div>
<div class="step-content">
<h4>技能测试</h4>
<p>完成测试任务<br/>展示开发能力</p>
</div>
</div>
<div class="step-arrow">
<right-outlined :style="{ fontSize: '24px' }" />
</div>
<div class="process-step">
<div class="step-number">4</div>
<div class="step-content">
<h4>正式入驻</h4>
<p>签署合作协议<br/>开始发布Skill</p>
</div>
</div>
</div>
</div>
</div>
<a-modal
v-model:open="successDialogVisible"
title="申请提交成功"
:width="500"
:closable="true"
:mask-closable="false"
>
<div class="success-content">
<div class="success-icon">
<check-circle-outlined :style="{ fontSize: '64px', color: '#67c23a' }" />
</div>
<h3>您的申请已提交成功</h3>
<p class="success-desc">我们将在3-5个工作日内完成审核审核结果将通过邮件通知您</p>
<div class="contact-info">
<p>如有疑问请联系</p>
<p>邮箱developer@openclaw.com</p>
<p>微信OpenClawDev</p>
</div>
</div>
<template #footer>
<a-button type="primary" @click="successDialogVisible = false">我知道了</a-button>
</template>
</a-modal>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { message } from 'ant-design-vue'
import { developerApi, uploadApi } from '@/service/apiService'
import {
UserOutlined,
LockOutlined,
MailOutlined,
EnvironmentOutlined,
CodeOutlined,
ClockCircleOutlined,
FileTextOutlined,
PlayCircleOutlined,
ProjectOutlined,
DollarCircleOutlined,
CheckCircleFilled,
CheckCircleOutlined,
TrophyOutlined,
StarOutlined,
UploadOutlined,
VideoCameraOutlined,
LinkOutlined,
DesktopOutlined,
WalletOutlined,
RiseOutlined,
TeamOutlined,
RightOutlined
} 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 portfolioFileList = ref([])
const portfolioUploading = ref(false)
const portfolioProgress = ref(0)
const form = reactive({
realName: '',
phone: '',
email: '',
city: '',
techStack: [],
experience: undefined,
expertise: [],
bio: '',
resumeUrl: '',
demoVideoUrl: '',
portfolioUrl: '',
expectedIncome: '',
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 beforePortfolioUpload = (file) => {
if (file.size > 50 * 1024 * 1024) {
message.error('单个文件大小不能超过 50MB')
return false
}
return true
}
// 作品集上传处理
const handlePortfolioUpload = async ({ file, onSuccess, onError }) => {
portfolioUploading.value = true
portfolioProgress.value = 0
const progressTimer = setInterval(() => {
if (portfolioProgress.value < 90) portfolioProgress.value += 10
}, 200)
try {
const res = await uploadApi.uploadFile('portfolio', file)
clearInterval(progressTimer)
portfolioProgress.value = 100
const url = res.data?.url || res.data?.fileUrl
portfolioFileList.value = [...portfolioFileList.value, { uid: file.uid, name: file.name, status: 'done', url }]
// 将所有已上传文件URL用逗号拼接存入portfolioUrl
form.portfolioUrl = portfolioFileList.value.map(f => f.url).join(',')
onSuccess(res, file)
message.success('文件上传成功')
} catch (err) {
clearInterval(progressTimer)
onError(err)
message.error('文件上传失败:' + (err.response?.data?.message || err.message))
} finally {
portfolioUploading.value = false
portfolioProgress.value = 0
}
}
// 作品集文件删除
const handlePortfolioRemove = (file) => {
portfolioFileList.value = portfolioFileList.value.filter(f => f.uid !== file.uid)
form.portfolioUrl = portfolioFileList.value.length
? portfolioFileList.value.map(f => f.url).join(',')
: ''
}
const rules = {
realName: [{ required: true, message: '请输入真实姓名', trigger: 'blur' }],
phone: [
{ required: true, message: '请输入手机号码', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
],
email: [
{ required: true, message: '请输入电子邮箱', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
],
city: [{ required: true, message: '请输入所在城市', trigger: 'blur' }],
techStack: [{ required: true, message: '请选择技术栈', trigger: 'change' }],
experience: [{ required: true, message: '请选择工作年限', trigger: 'change' }],
expertise: [{ required: true, message: '请选择擅长领域', trigger: 'change' }],
bio: [
{ required: true, message: '请输入个人简介', trigger: 'blur' },
{ min: 50, message: '个人简介至少50个字符', 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
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
portfolioFileList.value = []
portfolioProgress.value = 0
}
</script>
<style lang="scss" scoped>
.join-us-page {
padding: 20px 0 60px;
.page-header {
text-align: center;
margin-bottom: 30px;
.page-title {
font-size: 32px;
color: #303133;
margin-bottom: 12px;
}
.page-desc {
font-size: 16px;
color: #909399;
}
}
.hero-section {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
padding: 50px 40px;
margin-bottom: 40px;
text-align: center;
color: #fff;
h2 {
font-size: 28px;
margin-bottom: 12px;
}
p {
font-size: 18px;
opacity: 0.9;
margin-bottom: 20px;
}
.hero-tags {
display: flex;
justify-content: center;
gap: 12px;
flex-wrap: wrap;
}
}
.section-title {
font-size: 24px;
color: #303133;
text-align: center;
margin-bottom: 30px;
}
.benefits-section {
margin-bottom: 50px;
.benefits-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
.benefit-card {
background: #fff;
border-radius: 12px;
padding: 30px 20px;
text-align: center;
transition: all 0.3s;
&:hover {
transform: translateY(-4px);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1);
}
.benefit-icon {
width: 64px;
height: 64px;
background: #f5f7fa;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 16px;
}
h3 {
font-size: 18px;
color: #303133;
margin-bottom: 8px;
}
p {
font-size: 14px;
color: #909399;
line-height: 1.6;
}
}
}
}
.requirements-section {
background: #fff;
border-radius: 12px;
padding: 40px;
margin-bottom: 50px;
.requirements-list {
max-width: 700px;
margin: 0 auto;
.requirement-item {
display: flex;
gap: 16px;
padding: 20px 0;
border-bottom: 1px solid #ebeef5;
&:last-child {
border-bottom: none;
}
.requirement-content {
h4 {
font-size: 16px;
color: #303133;
margin-bottom: 8px;
}
p {
color: #909399;
font-size: 14px;
}
}
}
}
}
.form-section {
background: #fff;
border-radius: 12px;
padding: 40px;
margin-bottom: 50px;
.apply-form {
max-width: 700px;
margin: 0 auto;
.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;
}
}
}
}
.process-section {
background: #fff;
border-radius: 12px;
padding: 40px;
.process-steps {
display: flex;
align-items: flex-start;
justify-content: center;
gap: 16px;
.process-step {
text-align: center;
flex: 1;
max-width: 180px;
.step-number {
width: 48px;
height: 48px;
background: linear-gradient(135deg, #409eff, #67c23a);
border-radius: 50%;
color: #fff;
font-size: 20px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 16px;
}
.step-content {
h4 {
font-size: 16px;
color: #303133;
margin-bottom: 8px;
}
p {
font-size: 13px;
color: #909399;
line-height: 1.6;
}
}
}
.step-arrow {
padding-top: 12px;
color: #c0c4cc;
}
}
}
.success-content {
text-align: center;
.success-icon {
margin-bottom: 16px;
}
h3 {
font-size: 20px;
color: #303133;
margin-bottom: 8px;
}
.success-desc {
color: #909399;
margin-bottom: 20px;
}
.contact-info {
background: #f5f7fa;
border-radius: 8px;
padding: 16px;
text-align: left;
p {
color: #606266;
font-size: 14px;
line-height: 1.8;
}
}
}
}
@media (max-width: 992px) {
.join-us-page {
.benefits-grid {
grid-template-columns: repeat(2, 1fr) !important;
}
.process-steps {
flex-wrap: wrap;
.step-arrow {
display: none;
}
.process-step {
flex-basis: calc(50% - 12px);
}
}
}
}
@media (max-width: 576px) {
.join-us-page {
.benefits-grid {
grid-template-columns: 1fr !important;
}
.process-steps {
.process-step {
flex-basis: 100%;
}
}
.form-section,
.requirements-section,
.process-section {
padding: 24px 16px;
}
}
}
</style>