pet/pages/adoption/publish.vue

1083 lines
24 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="publish-container page-container-with-bg">
<!-- 表单内容 -->
<view class="form-container">
<!-- 宠物照片上传 -->
<view class="photo-section">
<view class="section-header">
<text class="section-title">宠物照片</text>
<text class="section-desc">最多上传6张照片第一张为封面</text>
</view>
<view class="photo-grid">
<view
class="photo-item"
v-for="(photo, index) in formData.photos"
:key="index"
@click="previewPhoto(index)"
>
<image class="photo-image" :src="photo" mode="aspectFill" />
<view class="photo-delete" @click.stop="deletePhoto(index)">
<text class="delete-icon">×</text>
</view>
<view class="photo-cover" v-if="index === 0">
<text class="cover-text">封面</text>
</view>
</view>
<view
class="photo-add"
v-if="formData.photos.length < 6"
@click="addPhoto"
>
<text class="add-icon">+</text>
<text class="add-text">添加照片</text>
</view>
</view>
</view>
<!-- 基本信息 -->
<view class="info-section">
<view class="section-header">
<text class="section-title">基本信息</text>
</view>
<view class="form-group">
<text class="form-label">宠物名称 *</text>
<u-input
v-model="formData.name"
placeholder="请输入宠物名称"
border="none"
:custom-style="inputStyle"
></u-input>
</view>
<view class="form-group">
<text class="form-label">品种 *</text>
<view class="picker-field" @click="showBreedPicker">
<text class="picker-text" :class="{ placeholder: !formData.breed }">
{{ formData.breed || '请选择品种' }}
</text>
<text class="picker-arrow">→</text>
</view>
</view>
<view class="form-group">
<text class="form-label">性别 *</text>
<view class="radio-group">
<view
class="radio-item"
:class="{ active: formData.gender === '公' }"
@click="setGender('公')"
>
<text class="radio-text">公</text>
</view>
<view
class="radio-item"
:class="{ active: formData.gender === '母' }"
@click="setGender('母')"
>
<text class="radio-text">母</text>
</view>
</view>
</view>
<view class="form-group">
<text class="form-label">年龄 *</text>
<view class="picker-field" @click="showAgePicker">
<text class="picker-text" :class="{ placeholder: !formData.age }">
{{ formData.age || '请选择年龄' }}
</text>
<text class="picker-arrow">→</text>
</view>
</view>
<view class="form-group">
<text class="form-label">体重</text>
<u-input
v-model="formData.weight"
placeholder="请输入体重3.5kg"
border="none"
:custom-style="inputStyle"
></u-input>
</view>
</view>
<!-- 健康状况 -->
<view class="health-section">
<view class="section-header">
<text class="section-title">健康状况</text>
</view>
<view class="checkbox-group">
<view
class="checkbox-item"
:class="{ active: formData.vaccinated }"
@click="toggleVaccinated"
>
<view class="checkbox">
<text class="check-icon" v-if="formData.vaccinated">✓</text>
</view>
<text class="checkbox-text">已接种疫苗</text>
</view>
<view
class="checkbox-item"
:class="{ active: formData.sterilized }"
@click="toggleSterilized"
>
<view class="checkbox">
<text class="check-icon" v-if="formData.sterilized">✓</text>
</view>
<text class="checkbox-text">已绝育</text>
</view>
<view
class="checkbox-item"
:class="{ active: formData.dewormed }"
@click="toggleDewormed"
>
<view class="checkbox">
<text class="check-icon" v-if="formData.dewormed">✓</text>
</view>
<text class="checkbox-text">已驱虫</text>
</view>
</view>
<view class="form-group">
<text class="form-label">健康说明</text>
<u-textarea
v-model="formData.healthNote"
placeholder="请描述宠物的健康状况、疾病史等"
:maxlength="200"
count
:custom-style="textareaStyle"
></u-textarea>
</view>
</view>
<!-- 性格特点 -->
<view class="personality-section">
<view class="section-header">
<text class="section-title">性格特点</text>
</view>
<view class="tag-group">
<view
class="tag-item"
v-for="tag in personalityTags"
:key="tag"
:class="{ active: formData.personality.includes(tag) }"
@click="togglePersonality(tag)"
>
<text class="tag-text">{{ tag }}</text>
</view>
</view>
<view class="form-group">
<text class="form-label">性格描述</text>
<u-textarea
v-model="formData.personalityNote"
placeholder="详细描述宠物的性格特点、生活习惯等"
:maxlength="300"
count
:custom-style="textareaStyle"
></u-textarea>
</view>
</view>
<!-- 领养要求 -->
<view class="requirements-section">
<view class="section-header">
<text class="section-title">领养要求</text>
</view>
<view class="form-group">
<text class="form-label">领养条件</text>
<u-textarea
v-model="formData.requirements"
placeholder="请描述对领养人的要求,如:有养宠经验、有固定住所、经济稳定等"
:maxlength="300"
count
:custom-style="textareaStyle"
></u-textarea>
</view>
<view class="checkbox-group">
<view
class="checkbox-item"
:class="{ active: formData.needVisit }"
@click="toggleNeedVisit"
>
<view class="checkbox">
<text class="check-icon" v-if="formData.needVisit">✓</text>
</view>
<text class="checkbox-text">需要上门家访</text>
</view>
<view
class="checkbox-item"
:class="{ active: formData.needContract }"
@click="toggleNeedContract"
>
<view class="checkbox">
<text class="check-icon" v-if="formData.needContract">✓</text>
</view>
<text class="checkbox-text">需要签署领养协议</text>
</view>
</view>
</view>
<!-- 联系方式 -->
<view class="contact-section">
<view class="section-header">
<text class="section-title">联系方式</text>
</view>
<view class="form-group">
<text class="form-label">联系人 *</text>
<u-input
v-model="formData.contactName"
placeholder="请输入联系人姓名"
border="none"
:custom-style="inputStyle"
></u-input>
</view>
<view class="form-group">
<text class="form-label">联系电话 *</text>
<u-input
v-model="formData.contactPhone"
placeholder="请输入联系电话"
border="none"
:custom-style="inputStyle"
></u-input>
</view>
<view class="form-group">
<text class="form-label">微信号</text>
<u-input
v-model="formData.contactWechat"
placeholder="请输入微信号(可选)"
border="none"
:custom-style="inputStyle"
></u-input>
</view>
<view class="form-group">
<text class="form-label">所在地区 *</text>
<view class="picker-field" @click="showLocationPicker">
<text class="picker-text" :class="{ placeholder: !formData.location }">
{{ formData.location || '请选择所在地区' }}
</text>
<text class="picker-arrow">→</text>
</view>
</view>
</view>
<!-- 其他说明 -->
<view class="note-section">
<view class="section-header">
<text class="section-title">其他说明</text>
</view>
<view class="form-group">
<u-textarea
v-model="formData.note"
placeholder="其他需要说明的情况(可选)"
:maxlength="500"
count
:custom-style="textareaStyle"
></u-textarea>
</view>
</view>
</view>
<!-- 提交按钮 -->
<view class="submit-section">
<u-button
type="primary"
:custom-style="buttonStyle"
@click="submitForm"
:loading="submitting"
>
{{ submitting ? '发布中...' : (isEdit ? '保存修改' : '发布领养信息') }}
</u-button>
</view>
<!-- 选择器 -->
<u-picker
:show="showBreed"
:columns="breedColumns"
@confirm="onBreedConfirm"
@cancel="showBreed = false"
></u-picker>
<u-picker
:show="showAge"
:columns="ageColumns"
@confirm="onAgeConfirm"
@cancel="showAge = false"
></u-picker>
<u-picker
:show="showLocation"
:columns="locationColumns"
@confirm="onLocationConfirm"
@cancel="showLocation = false"
></u-picker>
</view>
</template>
<script>
import { reactive, ref, onMounted } from 'vue'
export default {
name: 'PublishPage',
setup() {
// 响应式数据
const formData = reactive({
photos: [],
name: '',
breed: '',
gender: '',
age: '',
weight: '',
vaccinated: false,
sterilized: false,
dewormed: false,
healthNote: '',
personality: [],
personalityNote: '',
requirements: '',
needVisit: false,
needContract: false,
contactName: '',
contactPhone: '',
contactWechat: '',
location: '',
note: ''
})
const submitting = ref(false)
const isEdit = ref(false)
const editId = ref('')
// 选择器状态
const showBreed = ref(false)
const showAge = ref(false)
const showLocation = ref(false)
// 选择器数据
const breedColumns = ref([
['橘猫', '英短', '美短', '布偶', '暹罗', '波斯', '缅因', '折耳', '田园猫', '其他猫咪', '金毛', '拉布拉多', '哈士奇', '萨摩耶', '柯基', '泰迪', '比熊', '博美', '田园犬', '其他犬类']
])
const ageColumns = ref([
['2个月', '3个月', '4个月', '5个月', '6个月', '7个月', '8个月', '9个月', '10个月', '11个月', '1岁', '2岁', '3岁', '4岁', '5岁', '6岁', '7岁', '8岁', '9岁', '10岁以上']
])
const locationColumns = ref([
['北京市', '上海市', '广州市', '深圳市', '杭州市', '成都市', '武汉市', '西安市', '南京市', '重庆市', '天津市', '苏州市', '长沙市', '郑州市', '青岛市', '大连市', '宁波市', '厦门市']
])
const personalityTags = ref([
'温顺', '活泼', '安静', '粘人', '独立', '聪明', '胆小', '勇敢', '爱玩', '懒散', '好奇', '友善'
])
// 样式配置
const inputStyle = {
fontSize: '28rpx',
color: '#333333',
backgroundColor: 'rgba(255, 138, 128, 0.05)',
borderRadius: '12rpx',
padding: '20rpx'
}
const textareaStyle = {
fontSize: '26rpx',
color: '#333333',
backgroundColor: 'rgba(255, 138, 128, 0.05)',
borderRadius: '12rpx',
padding: '20rpx'
}
const buttonStyle = {
background: 'linear-gradient(135deg, #FF8A80, #FFB6C1)',
borderRadius: '24rpx',
height: '88rpx',
fontSize: '32rpx'
}
// 生命周期
onMounted(() => {
checkEditMode()
loadUserInfo()
})
// 方法定义
const checkEditMode = () => {
// 检查是否为编辑模式
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
const options = currentPage.options
if (options.id && options.mode === 'edit') {
isEdit.value = true
editId.value = options.id
loadEditData(options.id)
}
}
const loadEditData = (id) => {
try {
const publishedList = uni.getStorageSync('myPublishedAdoptions') || []
const item = publishedList.find(p => p.id === id)
if (item) {
Object.assign(formData, {
photos: item.photos || [],
name: item.name || '',
breed: item.breed || '',
gender: item.gender || '',
age: item.age || '',
weight: item.weight || '',
vaccinated: item.vaccinated || false,
sterilized: item.sterilized || false,
dewormed: item.dewormed || false,
healthNote: item.healthNote || '',
personality: item.personality || [],
personalityNote: item.personalityNote || '',
requirements: item.requirements || '',
needVisit: item.needVisit || false,
needContract: item.needContract || false,
contactName: item.contactName || '',
contactPhone: item.contactPhone || '',
contactWechat: item.contactWechat || '',
location: item.location || '',
note: item.note || ''
})
}
} catch (error) {
console.error('加载编辑数据失败:', error)
}
}
const loadUserInfo = () => {
try {
const userInfo = uni.getStorageSync('userInfo') || {}
if (!formData.contactName && userInfo.nickName) {
formData.contactName = userInfo.nickName
}
} catch (error) {
console.error('加载用户信息失败:', error)
}
}
const addPhoto = () => {
uni.chooseImage({
count: 6 - formData.photos.length,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
formData.photos.push(...res.tempFilePaths)
},
fail: (err) => {
console.error('选择照片失败:', err)
uni.showToast({
title: '选择照片失败',
icon: 'none'
})
}
})
}
const deletePhoto = (index) => {
formData.photos.splice(index, 1)
}
const previewPhoto = (index) => {
uni.previewImage({
urls: formData.photos,
current: index
})
}
const setGender = (gender) => {
formData.gender = gender
}
const toggleVaccinated = () => {
formData.vaccinated = !formData.vaccinated
}
const toggleSterilized = () => {
formData.sterilized = !formData.sterilized
}
const toggleDewormed = () => {
formData.dewormed = !formData.dewormed
}
const togglePersonality = (tag) => {
const index = formData.personality.indexOf(tag)
if (index > -1) {
formData.personality.splice(index, 1)
} else {
formData.personality.push(tag)
}
}
const toggleNeedVisit = () => {
formData.needVisit = !formData.needVisit
}
const toggleNeedContract = () => {
formData.needContract = !formData.needContract
}
const showBreedPicker = () => {
showBreed.value = true
}
const showAgePicker = () => {
showAge.value = true
}
const showLocationPicker = () => {
showLocation.value = true
}
const onBreedConfirm = (e) => {
formData.breed = e.value[0]
showBreed.value = false
}
const onAgeConfirm = (e) => {
formData.age = e.value[0]
showAge.value = false
}
const onLocationConfirm = (e) => {
formData.location = e.value[0]
showLocation.value = false
}
const validateForm = () => {
if (formData.photos.length === 0) {
uni.showToast({
title: '请至少上传一张照片',
icon: 'none'
})
return false
}
if (!formData.name.trim()) {
uni.showToast({
title: '请输入宠物名称',
icon: 'none'
})
return false
}
if (!formData.breed) {
uni.showToast({
title: '请选择宠物品种',
icon: 'none'
})
return false
}
if (!formData.gender) {
uni.showToast({
title: '请选择宠物性别',
icon: 'none'
})
return false
}
if (!formData.age) {
uni.showToast({
title: '请选择宠物年龄',
icon: 'none'
})
return false
}
if (!formData.contactName.trim()) {
uni.showToast({
title: '请输入联系人姓名',
icon: 'none'
})
return false
}
if (!formData.contactPhone.trim()) {
uni.showToast({
title: '请输入联系电话',
icon: 'none'
})
return false
}
if (!formData.location) {
uni.showToast({
title: '请选择所在地区',
icon: 'none'
})
return false
}
return true
}
const submitForm = async () => {
if (!validateForm()) {
return
}
submitting.value = true
try {
// 模拟网络请求延迟
await new Promise(resolve => setTimeout(resolve, 2000))
const publishData = {
...formData,
id: isEdit.value ? editId.value : 'pub_' + Date.now(),
status: 'active',
publishTime: isEdit.value ? undefined : new Date().toISOString(),
updateTime: new Date().toISOString(),
applicationCount: 0,
viewCount: 0
}
// 保存到本地存储
let publishedList = uni.getStorageSync('myPublishedAdoptions') || []
if (isEdit.value) {
const index = publishedList.findIndex(p => p.id === editId.value)
if (index > -1) {
publishedList[index] = { ...publishedList[index], ...publishData }
}
} else {
publishedList.unshift(publishData)
}
uni.setStorageSync('myPublishedAdoptions', publishedList)
uni.showToast({
title: isEdit.value ? '修改成功' : '发布成功',
icon: 'success'
})
// 返回上一页
setTimeout(() => {
uni.navigateBack()
}, 1500)
} catch (error) {
console.error('提交失败:', error)
uni.showToast({
title: '提交失败',
icon: 'none'
})
} finally {
submitting.value = false
}
}
return {
formData,
submitting,
isEdit,
showBreed,
showAge,
showLocation,
breedColumns,
ageColumns,
locationColumns,
personalityTags,
inputStyle,
textareaStyle,
buttonStyle,
addPhoto,
deletePhoto,
previewPhoto,
setGender,
toggleVaccinated,
toggleSterilized,
toggleDewormed,
togglePersonality,
toggleNeedVisit,
toggleNeedContract,
showBreedPicker,
showAgePicker,
showLocationPicker,
onBreedConfirm,
onAgeConfirm,
onLocationConfirm,
submitForm
}
}
}
</script>
<style lang="scss" scoped>
.publish-container {
background: linear-gradient(135deg, #FF8A80 0%, #FFB6C1 25%, #FECFEF 50%, #F8BBD9 100%);
min-height: 100vh;
padding-bottom: 120rpx;
}
/* 表单容器 */
.form-container {
margin: 0 30rpx;
}
/* 通用区块样式 */
.photo-section,
.info-section,
.health-section,
.personality-section,
.requirements-section,
.contact-section,
.note-section {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
border-radius: 24rpx;
padding: 32rpx;
margin-bottom: 24rpx;
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
border: 1rpx solid rgba(255, 255, 255, 0.3);
.section-header {
margin-bottom: 24rpx;
.section-title {
font-size: 32rpx;
font-weight: 600;
color: #333333;
margin-bottom: 8rpx;
}
.section-desc {
font-size: 22rpx;
color: #666666;
}
}
}
/* 照片上传区域 */
.photo-section {
.photo-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 16rpx;
.photo-item {
position: relative;
aspect-ratio: 1;
border-radius: 16rpx;
overflow: hidden;
.photo-image {
width: 100%;
height: 100%;
}
.photo-delete {
position: absolute;
top: 8rpx;
right: 8rpx;
width: 32rpx;
height: 32rpx;
background: rgba(0, 0, 0, 0.6);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
.delete-icon {
color: white;
font-size: 24rpx;
font-weight: bold;
}
}
.photo-cover {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(135deg, #FF8A80, #FFB6C1);
padding: 6rpx 0;
text-align: center;
.cover-text {
color: white;
font-size: 20rpx;
font-weight: 500;
}
}
}
.photo-add {
aspect-ratio: 1;
border: 2rpx dashed #FF8A80;
border-radius: 16rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: rgba(255, 138, 128, 0.05);
transition: all 0.3s ease;
&:active {
background: rgba(255, 138, 128, 0.1);
transform: scale(0.98);
}
.add-icon {
font-size: 48rpx;
color: #FF8A80;
margin-bottom: 8rpx;
}
.add-text {
font-size: 22rpx;
color: #FF8A80;
}
}
}
}
/* 表单组件 */
.form-group {
margin-bottom: 32rpx;
&:last-child {
margin-bottom: 0;
}
.form-label {
display: block;
font-size: 28rpx;
font-weight: 500;
color: #333333;
margin-bottom: 16rpx;
}
}
/* 选择器字段 */
.picker-field {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
background: rgba(255, 138, 128, 0.05);
border-radius: 12rpx;
transition: all 0.3s ease;
&:active {
background: rgba(255, 138, 128, 0.1);
}
.picker-text {
font-size: 28rpx;
color: #333333;
&.placeholder {
color: #CCCCCC;
}
}
.picker-arrow {
font-size: 24rpx;
color: #FF8A80;
}
}
/* 单选组 */
.radio-group {
display: flex;
gap: 20rpx;
.radio-item {
flex: 1;
padding: 20rpx;
border: 2rpx solid rgba(255, 138, 128, 0.2);
border-radius: 12rpx;
text-align: center;
transition: all 0.3s ease;
&.active {
border-color: #FF8A80;
background: rgba(255, 138, 128, 0.1);
.radio-text {
color: #FF8A80;
font-weight: 600;
}
}
.radio-text {
font-size: 28rpx;
color: #666666;
}
}
}
/* 复选框组 */
.checkbox-group {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
margin-bottom: 24rpx;
.checkbox-item {
display: flex;
align-items: center;
padding: 16rpx 20rpx;
border: 2rpx solid rgba(255, 138, 128, 0.2);
border-radius: 12rpx;
transition: all 0.3s ease;
&.active {
border-color: #FF8A80;
background: rgba(255, 138, 128, 0.1);
.checkbox-text {
color: #FF8A80;
}
}
.checkbox {
width: 32rpx;
height: 32rpx;
border: 2rpx solid #CCCCCC;
border-radius: 6rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12rpx;
transition: all 0.3s ease;
}
&.active .checkbox {
border-color: #FF8A80;
background: #FF8A80;
.check-icon {
color: white;
font-size: 20rpx;
font-weight: bold;
}
}
.checkbox-text {
font-size: 26rpx;
color: #666666;
}
}
}
/* 标签组 */
.tag-group {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
margin-bottom: 24rpx;
.tag-item {
padding: 12rpx 20rpx;
border: 2rpx solid rgba(255, 138, 128, 0.2);
border-radius: 20rpx;
transition: all 0.3s ease;
&.active {
border-color: #FF8A80;
background: linear-gradient(135deg, #FF8A80, #FFB6C1);
.tag-text {
color: white;
}
}
.tag-text {
font-size: 24rpx;
color: #666666;
}
}
}
/* 提交按钮 */
.submit-section {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
padding: 20rpx 30rpx;
border-top: 1rpx solid rgba(255, 255, 255, 0.3);
z-index: 100;
}
/* 响应式设计 */
@media (max-width: 375px) {
.publish-container {
.form-container {
margin: 0 20rpx;
}
.photo-section,
.info-section,
.health-section,
.personality-section,
.requirements-section,
.contact-section,
.note-section {
padding: 24rpx;
margin-bottom: 20rpx;
}
.photo-grid {
gap: 12rpx;
}
.submit-section {
padding: 16rpx 20rpx;
}
}
}
/* 动画效果 */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.form-container > view {
animation: fadeIn 0.5s ease-out;
}
.form-container > view:nth-child(1) { animation-delay: 0.1s; }
.form-container > view:nth-child(2) { animation-delay: 0.2s; }
.form-container > view:nth-child(3) { animation-delay: 0.3s; }
.form-container > view:nth-child(4) { animation-delay: 0.4s; }
.form-container > view:nth-child(5) { animation-delay: 0.5s; }
.form-container > view:nth-child(6) { animation-delay: 0.6s; }
.form-container > view:nth-child(7) { animation-delay: 0.7s; }
/* 交互反馈 */
.photo-add:active,
.picker-field:active,
.radio-item:active,
.checkbox-item:active,
.tag-item:active {
transform: scale(0.98);
}
/* 表单验证状态 */
.form-group.error {
.form-label {
color: #FF5722;
}
.picker-field {
border: 2rpx solid #FF5722;
}
}
/* 加载状态 */
.submit-section .u-button[loading] {
opacity: 0.7;
}
</style>