Files
zhiyin/zhiyin-app/src/pages/contribute/contribute.vue
T
2026-06-16 13:18:36 +08:00

199 lines
7.7 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>
<view class="page fade-in">
<view class="hero">
<text class="hero-title">贡献面经</text>
<text class="hero-sub">分享你的面试经验帮助更多同学</text>
</view>
<view class="form card">
<view class="form-group">
<text class="form-label">公司名称 <text class="required">*</text></text>
<input class="form-input" v-model="form.company" placeholder="如:腾讯、字节跳动、阿里巴巴" />
</view>
<view class="form-group">
<text class="form-label">面试岗位 <text class="required">*</text></text>
<input class="form-input" v-model="form.position" placeholder="如:前端工程师、产品经理" />
</view>
<view class="form-group">
<text class="form-label">面试轮次</text>
<input class="form-input" v-model="form.rounds" placeholder="如:一面(技术面)" />
</view>
<view class="form-group">
<text class="form-label">遇到的面试题每行一题</text>
<textarea
class="form-textarea"
v-model="questionsText"
placeholder="把你记得的面试题写下来,帮大家提前准备:&#10;1. 请介绍一下你最熟悉的项目&#10;2. 解释一下闭包的原理&#10;..."
:maxlength="2000"
></textarea>
<text class="form-hint">{{ questionsText.length }}/2000</text>
</view>
<view class="form-group">
<text class="form-label">面试感受/经验</text>
<textarea
class="form-textarea"
v-model="form.experience"
placeholder="分享一下整体感受、面试官风格、需要特别注意的地方..."
:maxlength="1000"
></textarea>
<text class="form-hint">{{ form.experience.length }}/1000</text>
</view>
<view class="form-group">
<text class="form-label">标签可选</text>
<view class="tag-input-area">
<view class="tag-list">
<view
v-for="tag in presetTags"
:key="tag"
class="tag-item"
:class="{ selected: form.tags.includes(tag) }"
@click="toggleTag(tag)"
>{{ tag }}</view>
</view>
<input class="form-input" v-model="customTag" placeholder="自定义标签,回车添加" @confirm="addCustomTag" />
</view>
</view>
<button class="btn-submit" @click="submit" :disabled="submitting">
{{ submitting ? '提交中...' : '提交面经' }}
</button>
<view class="success-box" v-if="submitted">
<text class="success-icon">🎉</text>
<text class="success-text">感谢你的分享你的面经将帮助更多同学准备面试</text>
<text class="success-action" @click="goBack">返回</text>
</view>
</view>
<view class="bottom-spacer"></view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { api } from '../../config'
const interviewId = ref('')
const urlPosition = ref('')
const form = ref({ company: '', position: '', rounds: '', experience: '', tags: [] })
const questionsText = ref('')
const customTag = ref('')
const submitting = ref(false)
const submitted = ref(false)
const presetTags = ['算法题多', '重视项目经历', '面试官nice', '压力面', '手撕代码', '系统设计', '行为面试', '八股文']
const token = () => uni.getStorageSync('token') || ''
onLoad((options) => {
if (options?.position) {
urlPosition.value = decodeURIComponent(options.position)
form.value.position = urlPosition.value
}
if (options?.interviewId) {
interviewId.value = options.interviewId
}
})
const toggleTag = (tag) => {
const idx = form.value.tags.indexOf(tag)
if (idx > -1) form.value.tags.splice(idx, 1)
else form.value.tags.push(tag)
}
const addCustomTag = () => {
const t = customTag.value.trim()
if (t && !form.value.tags.includes(t) && form.value.tags.length < 10) {
form.value.tags.push(t)
customTag.value = ''
}
}
const submit = async () => {
if (!form.value.company.trim()) { uni.showToast({ title: '请填写公司名称', icon: 'none' }); return }
if (!form.value.position.trim()) { uni.showToast({ title: '请填写面试岗位', icon: 'none' }); return }
submitting.value = true
try {
const questions = questionsText.value
.split('\n')
.map(q => q.replace(/^\d+[\.\、\s]+/, '').trim())
.filter(q => q.length > 0)
const res = await uni.request({
url: api('/contribution'), method: 'POST',
header: { 'Authorization': `Bearer ${token()}`, 'Content-Type': 'application/json' },
data: {
interviewId: interviewId.value || '',
company: form.value.company.trim(),
position: form.value.position.trim(),
rounds: form.value.rounds.trim(),
questions,
experience: form.value.experience.trim(),
tags: form.value.tags,
},
})
if (res.statusCode >= 200 && res.statusCode < 300) {
submitted.value = true
uni.showToast({ title: '提交成功!', icon: 'success' })
} else {
uni.showToast({ title: '提交失败,请重试', icon: 'none' })
}
} catch (e) {
uni.showToast({ title: '网络错误', icon: 'none' })
} finally {
submitting.value = false
}
}
const goBack = () => uni.navigateBack()
</script>
<style scoped>
.page { min-height: 100vh; background: var(--color-bg); }
.hero { background: linear-gradient(135deg, #10B981, #34D399, #6EE7B7); padding: 48rpx 32rpx 72rpx; border-radius: 0 0 48rpx 48rpx; }
.hero-title { font-size: 40rpx; font-weight: 700; color: #FFF; display: block; }
.hero-sub { font-size: 22rpx; color: rgba(255,255,255,0.8); margin-top: 8rpx; display: block; }
.form { margin: -40rpx 32rpx 0; border-radius: var(--radius-xl); padding: 32rpx; }
.form-group { margin-bottom: 28rpx; }
.form-label { font-size: 26rpx; font-weight: 600; color: var(--color-text); display: block; margin-bottom: 10rpx; }
.required { color: #EF4444; }
.form-input {
width: 100%; height: 72rpx; background: #F9FAFB; border-radius: var(--radius-md);
padding: 0 20rpx; font-size: 26rpx; box-sizing: border-box; border: 1rpx solid var(--color-border);
}
.form-textarea {
width: 100%; min-height: 180rpx; background: #F9FAFB; border-radius: var(--radius-md);
padding: 16rpx 20rpx; font-size: 26rpx; box-sizing: border-box; border: 1rpx solid var(--color-border);
}
.form-hint { font-size: 20rpx; color: var(--color-text-tertiary); text-align: right; display: block; margin-top: 6rpx; }
.tag-input-area { display: flex; flex-direction: column; gap: 12rpx; }
.tag-list { display: flex; flex-wrap: wrap; gap: 10rpx; }
.tag-item {
padding: 6rpx 18rpx; border-radius: var(--radius-round); font-size: 22rpx;
background: #F3F4F6; color: var(--color-text-secondary); border: 1rpx solid #E5E7EB;
transition: all 0.2s;
}
.tag-item.selected { background: linear-gradient(135deg, #D1FAE5, #A7F3D0); color: #065F46; border-color: #10B981; }
.btn-submit {
width: 100%; height: 88rpx; border-radius: var(--radius-lg);
background: linear-gradient(135deg, #10B981, #34D399); color: #FFF;
font-size: 30rpx; font-weight: 700; border: none; margin-top: 8rpx;
}
.btn-submit[disabled] { opacity: 0.6; }
.success-box { display: flex; flex-direction: column; align-items: center; padding: 40rpx 0; gap: 12rpx; background: #ECFDF5; border-radius: var(--radius-lg); margin-top: 24rpx; }
.success-icon { font-size: 56rpx; }
.success-text { font-size: 26rpx; color: #065F46; font-weight: 600; text-align: center; line-height: 1.5; }
.success-action { font-size: 28rpx; color: var(--color-primary); font-weight: 600; padding: 10rpx 40rpx; }
.bottom-spacer { height: 40rpx; }
</style>