Files
hejiu/pages/circle-publish/circle-publish.vue
T
cg c7c023975d feat(circle): 集成HaveADrink SDK实现酒友圈功能
- 集成HaveADrink SDK版本从1.0.4升级至1.0.8
- 实现酒友圈API接口,替换原有的mock数据
- 添加地理位置权限配置(requiredPrivateInfos)
- 重构API调用方式,统一使用client实例调用真实接口
- 更新WebSocket协议与SDK保持一致,支持聊天、输入状态、已读回执等功能
- 实现游标分页获取动态信息流
- 添加文件上传API支持图片上传
- 优化API错误处理,支持业务级错误提示
- 调整酒局状态显示,新增upcoming待开始状态
- 优化自我感觉标签映射逻辑,支持更多状态类型
- 更新websocket连接认证方式,使用Authorization header传递token
- 添加服务端连接确认和被踢下线事件处理
2026-07-22 23:03:40 +08:00

447 lines
10 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, paddingRight: navPaddingRight }">
<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, UploadImage } from '../../common/api'
import client from '../../common/api'
import themeMixin from '../../common/theme-mixin'
export default {
mixins: [themeMixin],
data() {
const menuBtn = uni.getMenuButtonBoundingClientRect()
const sysInfo = uni.getSystemInfoSync()
return {
navPaddingTop: menuBtn.top + 'px',
navPaddingRight: (sysInfo.windowWidth - menuBtn.left + 8) + '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()
},
async loadLastRecord() {
try {
const res = await client.GetRecords({ page: 1, pageSize: 5, month: '', mode: '' })
const list = res.list || []
const drank = list.find(r => String(r.mode) === 'drank' || String(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 {
// 先上传图片获取远程URL
let uploadedUrls = []
if (this.images.length) {
uni.showLoading({ title: '上传图片中...' })
for (const img of this.images) {
const upRes = await UploadImage({ filePath: img })
if (upRes.data && upRes.data.url) {
uploadedUrls.push(upRes.data.url)
}
}
uni.hideLoading()
}
await PublishFeed({
text: this.text,
images: uploadedUrls,
recordId: this.linkRecord && this.lastRecord ? this.lastRecord.id : null,
visibility: this.visibility
})
uni.showToast({ title: '发布成功 🍻', icon: 'none' })
setTimeout(() => uni.navigateBack(), 800)
} catch (e) {
uni.hideLoading()
// 全局拦截器已弹出错误提示
}
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>