528 lines
14 KiB
Vue
528 lines
14 KiB
Vue
<template>
|
|
<view class="publish-page">
|
|
<view class="publish-header" :style="{ paddingTop: statusBarHeight + 'px' }">
|
|
<view class="publish-header__left" @tap="handleCancel">取消</view>
|
|
<view class="publish-header__title">发帖</view>
|
|
<view class="publish-header__right">
|
|
<view class="publish-btn" :class="{ 'publish-btn--disabled': !canPublish || publishing }"
|
|
@tap="handlePublish">
|
|
<text>{{ publishing ? '发布中' : '发布' }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<scroll-view scroll-y class="publish-scroll" :style="{ height: scrollHeight + 'px' }">
|
|
<view class="publish-body">
|
|
<textarea v-model="form.content" class="publish-textarea" placeholder="分享你的体育见解..." :maxlength="2000"
|
|
auto-height />
|
|
|
|
<view class="publish-images">
|
|
<view v-for="(img, idx) in form.images.slice(0, form.images.length >= 9 ? 8 : form.images.length)"
|
|
:key="idx" class="publish-images__item">
|
|
<image :src="img" mode="aspectFill" class="publish-images__img" />
|
|
<view class="publish-images__del" @tap="removeImage(idx)">
|
|
<u-icon name="close" size="24" color="#fff" />
|
|
</view>
|
|
</view>
|
|
<view v-if="form.images.length >= 9" class="publish-images__more" @tap="chooseImage">
|
|
<image :src="form.images[8]" mode="aspectFill" class="publish-images__img" />
|
|
<view class="publish-images__more-mask">
|
|
<text class="publish-images__more-text">+{{ form.images.length - 8 }}</text>
|
|
<text class="publish-images__more-hint">点击继续添加</text>
|
|
</view>
|
|
</view>
|
|
<view v-if="form.images.length < 9" class="publish-images__add" @tap="chooseImage">
|
|
<u-icon name="plus" size="52" color="#ccc" />
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="publish-paid">
|
|
<view class="publish-paid__row">
|
|
<text class="publish-paid__label">收费阅读</text>
|
|
<switch :checked="isPaid" @change="isPaid = $event.detail.value" color="#185dff" />
|
|
</view>
|
|
<view v-if="isPaid" class="publish-paid__price">
|
|
<text class="publish-paid__label">积分价格</text>
|
|
<input v-model="pricePoints" type="number" class="publish-paid__input" placeholder="输入积分(1~9999)" />
|
|
</view>
|
|
</view>
|
|
|
|
<view class="publish-category">
|
|
<view class="publish-section__head">
|
|
<view class="publish-section__title-wrap">
|
|
<text class="publish-section__title">选择分类</text>
|
|
<text class="publish-section__required">必选</text>
|
|
</view>
|
|
<text class="publish-section__hint">{{ selectedCategory ? '已选择 1 个分类' : '请选择帖子所属分类' }}</text>
|
|
</view>
|
|
<view v-if="categoryList.length" class="publish-category__list">
|
|
<view v-for="category in visibleCategories" :key="category.id" class="publish-category__item"
|
|
:class="{ 'publish-category__item--active': selectedCategory === category.id }"
|
|
@tap="selectCategory(category.id)">
|
|
<text class="publish-category__name">{{ category.name }}</text>
|
|
</view>
|
|
</view>
|
|
<text v-else class="publish-section__empty">暂无可选分类</text>
|
|
</view>
|
|
|
|
<view class="publish-tags">
|
|
<view class="publish-section__head">
|
|
<view class="publish-section__title-wrap">
|
|
<text class="publish-section__title">选择标签</text>
|
|
<text class="publish-section__optional">可多选</text>
|
|
</view>
|
|
<text class="publish-section__hint">已选 {{ selectedTags.length }}/3</text>
|
|
</view>
|
|
<view class="publish-tags__list">
|
|
<view v-for="tag in tagList" :key="tag.id" class="publish-tags__item"
|
|
:class="{ 'publish-tags__item--active': selectedTags.includes(tag.id) }"
|
|
@tap="toggleTag(tag.id)">
|
|
<text>#{{ tag.name }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</scroll-view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { reactive, ref, computed, onMounted } from 'vue'
|
|
import { publishPost, getTagLists, getCommunityCategoryLists } from '@/api/community'
|
|
import { uploadImage } from '@/api/app'
|
|
import { getToken } from '@/utils/auth'
|
|
|
|
const statusBarHeight = ref(0)
|
|
const scrollHeight = ref(500)
|
|
const publishing = ref(false)
|
|
const tagList = ref<any[]>([])
|
|
const selectedTags = ref<number[]>([])
|
|
const categoryList = ref<any[]>([])
|
|
const selectedCategory = ref(0)
|
|
const visibleCategories = computed(() => categoryList.value.slice(0, 4))
|
|
|
|
const isPaid = ref(false)
|
|
const pricePoints = ref('')
|
|
|
|
const form = reactive({
|
|
content: '',
|
|
images: [] as string[]
|
|
})
|
|
|
|
const canPublish = computed(() => {
|
|
return selectedCategory.value > 0 && (form.content.trim().length > 0 || form.images.length > 0)
|
|
})
|
|
|
|
onMounted(() => {
|
|
const sysInfo = uni.getSystemInfoSync()
|
|
// #ifdef APP-PLUS || MP
|
|
statusBarHeight.value = sysInfo.statusBarHeight || 44
|
|
// #endif
|
|
// #ifdef H5
|
|
statusBarHeight.value = 20
|
|
// #endif
|
|
scrollHeight.value = sysInfo.windowHeight - statusBarHeight.value - 88
|
|
fetchPublishOptions()
|
|
})
|
|
|
|
const fetchPublishOptions = async () => {
|
|
try {
|
|
const [categories, tags] = await Promise.all([getCommunityCategoryLists(), getTagLists()])
|
|
categoryList.value = categories || []
|
|
tagList.value = tags || []
|
|
} catch (e) { }
|
|
}
|
|
|
|
const handleCancel = () => {
|
|
const pages = getCurrentPages()
|
|
if (pages.length > 1) {
|
|
uni.navigateBack()
|
|
} else {
|
|
uni.switchTab({ url: '/pages/community/community' })
|
|
}
|
|
}
|
|
|
|
const handlePublish = async () => {
|
|
if (!getToken()) {
|
|
uni.navigateTo({ url: '/pages/login/login' })
|
|
return
|
|
}
|
|
if (!selectedCategory.value) {
|
|
uni.showToast({ title: '请选择帖子分类', icon: 'none' })
|
|
return
|
|
}
|
|
if (!canPublish.value || publishing.value) return
|
|
publishing.value = true
|
|
try {
|
|
if (isPaid.value) {
|
|
const pts = parseInt(pricePoints.value)
|
|
if (!pts || pts < 1 || pts > 9999) {
|
|
uni.showToast({ title: '积分价格范围1~9999', icon: 'none' })
|
|
publishing.value = false
|
|
return
|
|
}
|
|
}
|
|
// 上传本地图片到服务器
|
|
const uploadedImages: string[] = []
|
|
for (const img of form.images) {
|
|
if (img.startsWith('http')) {
|
|
uploadedImages.push(img)
|
|
} else {
|
|
const token = getToken()
|
|
const res: any = await uploadImage(img, token || '')
|
|
if (res?.uri) {
|
|
uploadedImages.push(res.uri)
|
|
}
|
|
}
|
|
}
|
|
await publishPost({
|
|
content: form.content,
|
|
images: uploadedImages,
|
|
category_id: selectedCategory.value,
|
|
tag_ids: selectedTags.value,
|
|
post_type: 0,
|
|
is_paid: isPaid.value ? 1 : 0,
|
|
price_points: isPaid.value ? parseInt(pricePoints.value) : 0
|
|
})
|
|
uni.showToast({ title: '发布成功', icon: 'success' })
|
|
setTimeout(() => {
|
|
const pages = getCurrentPages()
|
|
if (pages.length > 1) {
|
|
uni.navigateBack()
|
|
} else {
|
|
uni.switchTab({ url: '/pages/community/community' })
|
|
}
|
|
}, 1200)
|
|
} catch (e) {
|
|
uni.showToast({ title: '发布失败', icon: 'none' })
|
|
} finally {
|
|
publishing.value = false
|
|
}
|
|
}
|
|
|
|
const chooseImage = () => {
|
|
const remaining = 18 - form.images.length
|
|
if (remaining <= 0) return
|
|
uni.chooseImage({
|
|
count: remaining,
|
|
sizeType: ['compressed'],
|
|
sourceType: ['album', 'camera'],
|
|
success: (res) => {
|
|
form.images.push(...res.tempFilePaths)
|
|
}
|
|
})
|
|
}
|
|
|
|
const removeImage = (idx: number) => {
|
|
form.images.splice(idx, 1)
|
|
}
|
|
|
|
const selectCategory = (id: number) => {
|
|
selectedCategory.value = id
|
|
}
|
|
|
|
const toggleTag = (id: number) => {
|
|
const idx = selectedTags.value.indexOf(id)
|
|
if (idx >= 0) {
|
|
selectedTags.value.splice(idx, 1)
|
|
} else {
|
|
if (selectedTags.value.length >= 3) {
|
|
uni.showToast({ title: '最多选择3个标签', icon: 'none' })
|
|
return
|
|
}
|
|
selectedTags.value.push(id)
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.publish-page {
|
|
min-height: 100vh;
|
|
background: #fff;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.publish-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 16rpx 30rpx;
|
|
border-bottom: 1rpx solid #f0f0f0;
|
|
|
|
&__left {
|
|
font-size: 30rpx;
|
|
color: #666;
|
|
width: 120rpx;
|
|
}
|
|
|
|
&__title {
|
|
font-size: 32rpx;
|
|
font-weight: 600;
|
|
color: #333;
|
|
}
|
|
|
|
&__right {
|
|
width: 120rpx;
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
}
|
|
}
|
|
|
|
.publish-btn {
|
|
width: 120rpx;
|
|
height: 56rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: #185dff;
|
|
color: #fff;
|
|
border-radius: 12rpx;
|
|
font-size: 26rpx;
|
|
white-space: nowrap;
|
|
|
|
&--disabled {
|
|
opacity: 0.5;
|
|
}
|
|
}
|
|
|
|
.publish-scroll {
|
|
flex: 1;
|
|
}
|
|
|
|
.publish-body {
|
|
padding: 30rpx;
|
|
}
|
|
|
|
.publish-textarea {
|
|
width: 100%;
|
|
min-height: 300rpx;
|
|
font-size: 30rpx;
|
|
line-height: 1.7;
|
|
color: #333;
|
|
}
|
|
|
|
.publish-images {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 16rpx;
|
|
margin-top: 24rpx;
|
|
|
|
&__item {
|
|
position: relative;
|
|
width: 200rpx;
|
|
height: 200rpx;
|
|
}
|
|
|
|
&__img {
|
|
width: 200rpx;
|
|
height: 200rpx;
|
|
border-radius: 12rpx;
|
|
}
|
|
|
|
&__del {
|
|
position: absolute;
|
|
top: -12rpx;
|
|
right: -12rpx;
|
|
width: 40rpx;
|
|
height: 40rpx;
|
|
background: rgba(0, 0, 0, 0.6);
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
&__add {
|
|
width: 200rpx;
|
|
height: 200rpx;
|
|
border: 2rpx dashed #ddd;
|
|
border-radius: 12rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
&__more {
|
|
position: relative;
|
|
width: 200rpx;
|
|
height: 200rpx;
|
|
border-radius: 12rpx;
|
|
overflow: hidden;
|
|
}
|
|
|
|
&__more-mask {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border-radius: 12rpx;
|
|
}
|
|
|
|
&__more-text {
|
|
font-size: 48rpx;
|
|
font-weight: 600;
|
|
color: #ffffff;
|
|
}
|
|
|
|
&__more-hint {
|
|
font-size: 20rpx;
|
|
color: rgba(255, 255, 255, 0.8);
|
|
margin-top: 4rpx;
|
|
}
|
|
}
|
|
|
|
.publish-paid {
|
|
padding: 24rpx 30rpx;
|
|
border-top: 16rpx solid #f5f5f5;
|
|
|
|
&__row {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
&__label {
|
|
font-size: 28rpx;
|
|
font-weight: 600;
|
|
color: #333;
|
|
}
|
|
|
|
&__price {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-top: 20rpx;
|
|
}
|
|
|
|
&__input {
|
|
width: 300rpx;
|
|
height: 64rpx;
|
|
background: #f5f5f5;
|
|
border-radius: 12rpx;
|
|
padding: 0 20rpx;
|
|
font-size: 28rpx;
|
|
text-align: right;
|
|
}
|
|
}
|
|
|
|
.publish-section__head {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 20rpx;
|
|
}
|
|
|
|
.publish-section__title-wrap {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10rpx;
|
|
}
|
|
|
|
.publish-section__title {
|
|
font-size: 28rpx;
|
|
font-weight: 600;
|
|
color: #333;
|
|
}
|
|
|
|
.publish-section__required,
|
|
.publish-section__optional {
|
|
padding: 3rpx 10rpx;
|
|
border-radius: 10rpx;
|
|
font-size: 20rpx;
|
|
}
|
|
|
|
.publish-section__required {
|
|
color: #ef4444;
|
|
background: #fff1f2;
|
|
}
|
|
|
|
.publish-section__optional {
|
|
color: #185dff;
|
|
background: #edf4ff;
|
|
}
|
|
|
|
.publish-section__hint,
|
|
.publish-section__empty {
|
|
color: #9aa3b2;
|
|
font-size: 22rpx;
|
|
}
|
|
|
|
.publish-category {
|
|
padding: 24rpx 30rpx;
|
|
border-top: 16rpx solid #f5f5f5;
|
|
|
|
&__list {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
gap: 16rpx;
|
|
max-height: 76rpx;
|
|
overflow: hidden;
|
|
}
|
|
|
|
&__item {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-width: 0;
|
|
height: 76rpx;
|
|
box-sizing: border-box;
|
|
padding: 0 8rpx;
|
|
border: 2rpx solid #edf0f5;
|
|
border-radius: 14rpx;
|
|
background: #fafbfc;
|
|
|
|
&--active {
|
|
border-color: #185dff;
|
|
background: #eef4ff;
|
|
|
|
.publish-category__name {
|
|
color: #185dff;
|
|
font-weight: 600;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
&__name {
|
|
overflow: hidden;
|
|
color: #4b5563;
|
|
font-size: 25rpx;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
}
|
|
|
|
.publish-tags {
|
|
padding: 24rpx 30rpx;
|
|
border-top: 16rpx solid #f5f5f5;
|
|
|
|
&__list {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 16rpx;
|
|
}
|
|
|
|
&__item {
|
|
padding: 10rpx 24rpx;
|
|
background: #f5f5f5;
|
|
border-radius: 12rpx;
|
|
font-size: 24rpx;
|
|
color: #666;
|
|
border: 2rpx solid transparent;
|
|
|
|
&--active {
|
|
background: #e5edff;
|
|
color: #185dff;
|
|
border-color: #185dff;
|
|
}
|
|
}
|
|
}
|
|
</style>
|