Files
hejiu/pages/circle-publish/circle-publish.vue
T
cg fabbb167cf feat(circle): 添加酒友圈社交模块
- 新增酒友圈、圈子详情、发布动态、好友列表、活动创建等页面配置
- 添加酒友圈相关API接口,包括动态、评论、好友、活动等功能
- 实现模拟数据用于前端开发和测试
- 创建评论项、空状态、活动卡片、动态卡片、好友项等组件
- 编写酒友圈模块详细的接口文档,定义数据结构和业务规则
2026-07-20 22:53:04 +08:00

431 lines
9.6 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="themeClass" class="page-container publish-page">
<!-- 顶部导航 -->
<view class="publish-nav" :style="{ paddingTop: navPaddingTop }">
<view class="publish-nav-inner">
<view class="publish-back" @click="goBack">
<text class="publish-back-icon"></text>
</view>
<text class="publish-nav-title">发布动态</text>
<view class="publish-send" :class="{ disabled: !canSubmit }" @click="handlePublish">
<text class="publish-send-text">发布</text>
</view>
</view>
</view>
<scroll-view class="publish-scroll" scroll-y>
<view class="publish-body">
<!-- 关联饮酒记录 -->
<view class="publish-record" v-if="lastRecord">
<view class="record-badge">
<text class="record-badge-icon">🍻</text>
</view>
<view class="record-info">
<text class="record-title">关联最近饮酒记录</text>
<text class="record-desc">{{ recordSummary }}</text>
</view>
<view class="record-check" :class="{ checked: linkRecord }" @click="linkRecord = !linkRecord">
<text class="record-check-icon">{{ linkRecord ? '✓' : '' }}</text>
</view>
</view>
<!-- 文字输入 -->
<textarea
class="publish-textarea"
v-model="text"
placeholder="分享你的饮酒故事..."
placeholder-class="publish-placeholder"
:maxlength="500"
auto-height
/>
<!-- 图片选择 -->
<view class="publish-images">
<view class="publish-img-item" v-for="(img, i) in images" :key="i">
<image class="publish-img" :src="img" mode="aspectFill"></image>
<view class="publish-img-del" @click="removeImage(i)">
<text class="publish-img-del-icon">×</text>
</view>
</view>
<view v-if="images.length < 9" class="publish-img-add" @click="chooseImage">
<text class="publish-img-add-icon">+</text>
<text class="publish-img-add-text">{{ images.length }}/9</text>
</view>
</view>
<!-- 可见范围 -->
<view class="publish-visibility">
<text class="publish-vis-label">谁可以看</text>
<view class="publish-vis-options">
<view
class="publish-vis-opt"
:class="{ active: visibility === 'friends' }"
@click="visibility = 'friends'"
>
<text>所有酒友</text>
</view>
<view
class="publish-vis-opt"
:class="{ active: visibility === 'private' }"
@click="visibility = 'private'"
>
<text>仅自己</text>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
import { PublishFeed } from '../../common/api'
import { getRecords } from '../../common/mock-data'
import themeMixin from '../../common/theme-mixin'
export default {
mixins: [themeMixin],
data() {
const menuBtn = uni.getMenuButtonBoundingClientRect()
return {
navPaddingTop: menuBtn.top + 'px',
text: '',
images: [],
visibility: 'friends',
linkRecord: true,
lastRecord: null,
publishing: false
}
},
computed: {
canSubmit() {
return (this.text.trim() || this.images.length) && !this.publishing
},
recordSummary() {
if (!this.lastRecord) return ''
const drinks = this.lastRecord.drinks || []
if (!drinks.length) return '戒酒打卡'
return drinks.map(d => `${d.brand || d.product || d.category} ${d.amount}${d.unit}`).join('、')
}
},
onLoad() {
this.loadLastRecord()
},
methods: {
goBack() {
uni.navigateBack()
},
loadLastRecord() {
try {
const records = getRecords()
const drank = records.find(r => r.mode === 'drank')
if (drank) this.lastRecord = drank
} catch (e) { /* ignore */ }
},
chooseImage() {
const remaining = 9 - this.images.length
uni.chooseImage({
count: remaining,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
this.images = [...this.images, ...res.tempFilePaths].slice(0, 9)
}
})
},
removeImage(index) {
this.images.splice(index, 1)
},
async handlePublish() {
if (!this.canSubmit) return
this.publishing = true
try {
await PublishFeed({
text: this.text,
images: this.images,
recordId: this.linkRecord && this.lastRecord ? this.lastRecord.id : null,
visibility: this.visibility
})
uni.showToast({ title: '发布成功 🍻', icon: 'none' })
setTimeout(() => uni.navigateBack(), 800)
} catch (e) {
uni.showToast({ title: '发布失败', icon: 'none' })
}
this.publishing = false
}
}
}
</script>
<style lang="scss" scoped>
.publish-page {
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
}
.publish-nav {
background: $bg-base;
padding-left: $sp-lg;
padding-right: $sp-lg;
position: relative;
z-index: 10;
}
.publish-nav-inner {
display: flex;
align-items: center;
justify-content: space-between;
height: 88rpx;
}
.publish-back {
width: 64rpx;
height: 64rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: $bg-card;
&:active { background: $bg-card-alt; }
}
.publish-back-icon {
font-size: $fs-xl;
color: $text-primary;
font-weight: $fw-bold;
}
.publish-nav-title {
font-size: $fs-base;
font-weight: $fw-bold;
color: $text-primary;
}
.publish-send {
padding: $sp-sm $sp-lg;
background: linear-gradient(135deg, $amber, $amber-deep);
border-radius: $radius-full;
&.disabled { opacity: 0.4; }
&:active { transform: scale(0.95); }
}
.publish-send-text {
font-size: $fs-sm;
font-weight: $fw-bold;
color: $text-on-amber;
}
.publish-scroll {
flex: 1;
height: 0;
}
.publish-body {
padding: $sp-lg;
padding-bottom: calc(env(safe-area-inset-bottom) + #{$sp-2xl});
}
/* 关联记录 */
.publish-record {
display: flex;
align-items: center;
gap: $sp-md;
padding: $sp-lg;
background: rgba(232,168,56,0.06);
border: 1rpx solid rgba(232,168,56,0.15);
border-radius: $radius-lg;
margin-bottom: $sp-xl;
}
.record-badge {
width: 72rpx;
height: 72rpx;
border-radius: $radius-md;
background: rgba(232,168,56,0.12);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.record-badge-icon {
font-size: 36rpx;
}
.record-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 4rpx;
}
.record-title {
font-size: $fs-sm;
font-weight: $fw-medium;
color: $text-primary;
}
.record-desc {
font-size: $fs-xs;
color: $text-tertiary;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.record-check {
width: 44rpx;
height: 44rpx;
border-radius: 50%;
border: 2rpx solid var(--border-subtle, rgba(255,255,255,0.10));
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
&.checked {
background: $amber;
border-color: $amber;
}
}
.record-check-icon {
font-size: $fs-sm;
color: $text-on-amber;
font-weight: $fw-bold;
}
/* 文字输入 */
.publish-textarea {
width: 100%;
min-height: 240rpx;
font-size: $fs-base;
color: $text-primary;
line-height: $lh-loose;
padding: $sp-lg;
background: $bg-card;
border-radius: $radius-xl;
border: 1rpx solid var(--border-faint, rgba(255,255,255,0.08));
margin-bottom: $sp-xl;
box-sizing: border-box;
}
.publish-placeholder {
color: $text-tertiary;
}
/* 图片 */
.publish-images {
display: flex;
flex-wrap: wrap;
gap: $sp-sm;
margin-bottom: $sp-xl;
}
.publish-img-item {
position: relative;
width: calc(33.33% - 11rpx);
height: 200rpx;
}
.publish-img {
width: 100%;
height: 100%;
border-radius: $radius-md;
}
.publish-img-del {
position: absolute;
top: -12rpx;
right: -12rpx;
width: 40rpx;
height: 40rpx;
border-radius: 50%;
background: $coral;
display: flex;
align-items: center;
justify-content: center;
}
.publish-img-del-icon {
font-size: $fs-sm;
color: #fff;
font-weight: $fw-bold;
}
.publish-img-add {
width: calc(33.33% - 11rpx);
height: 200rpx;
border-radius: $radius-md;
border: 2rpx dashed var(--border-dashed, rgba(255,255,255,0.12));
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8rpx;
&:active {
background: var(--bg-hover, rgba(255,255,255,0.08));
}
}
.publish-img-add-icon {
font-size: $fs-xl;
color: $text-tertiary;
}
.publish-img-add-text {
font-size: $fs-xs;
color: $text-tertiary;
}
/* 可见范围 */
.publish-visibility {
display: flex;
align-items: center;
justify-content: space-between;
padding: $sp-lg;
background: $bg-card;
border-radius: $radius-lg;
border: 1rpx solid var(--border-faint, rgba(255,255,255,0.08));
}
.publish-vis-label {
font-size: $fs-base;
color: $text-primary;
font-weight: $fw-medium;
}
.publish-vis-options {
display: flex;
gap: $sp-sm;
}
.publish-vis-opt {
padding: 8rpx 24rpx;
border-radius: $radius-full;
background: $bg-elevated;
border: 1rpx solid var(--border-micro, rgba(255,255,255,0.05));
text {
font-size: $fs-sm;
color: $text-tertiary;
}
&.active {
background: rgba(232,168,56,0.12);
border-color: rgba(232,168,56,0.3);
text {
color: $amber;
font-weight: $fw-medium;
}
}
}
</style>