You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
447 lines
20 KiB
447 lines
20 KiB
<template>
|
|
<view class="my-space-page">
|
|
<view class="top-safe" :style="{ height: menuButtonInfo.top + 'px' }"></view>
|
|
<view class="float-bar">
|
|
<view class="home-back" @tap="back">
|
|
<text class="home-back-icon">‹</text>
|
|
<text>返回资料</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="space-hero">
|
|
<image class="hero-avatar-img" v-if="profile.avatarUrl" :src="profile.avatarUrl" mode="aspectFill"></image>
|
|
<view class="hero-avatar" v-else>{{ profile.avatarText || '我' }}</view>
|
|
<view class="hero-main">
|
|
<view class="hero-title-row">
|
|
<view class="hero-title">我的个人空间</view>
|
|
<view class="publish-btn" @tap="openComposer">+ 发动态</view>
|
|
</view>
|
|
<view class="hero-sub">{{ profile.anonymousName || '半匿名漂流者' }} · {{ profile.currentMode === 'e' ? '轻轻热闹' : '安静陪伴' }}</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="moment-empty" v-if="!moments.length && !loadingMoments">还没有动态,发一条让同频的人认识你</view>
|
|
<view class="moment-card" v-for="item in moments" :key="item.id">
|
|
<view class="moment-top">
|
|
<text class="moment-time">{{ momentTime(item.createTime) }}</text>
|
|
<text class="moment-state blocked" v-if="item.localState === 'failed'">{{ item.failTip || '发布失败' }}</text>
|
|
<text class="moment-state blocked" v-else-if="item.status === 2">未通过审核</text>
|
|
<text class="moment-del" @tap="removeMoment(item)">删除</text>
|
|
</view>
|
|
<view class="moment-text" v-if="item.content">{{ item.content }}</view>
|
|
<view class="moment-grid" v-if="item.imageList && item.imageList.length" :class="'g' + item.imageList.length">
|
|
<image v-for="(img, idx) in item.imageList" :key="idx" :src="img" mode="aspectFill" @tap="previewMomentImages(item, idx)"></image>
|
|
</view>
|
|
<view class="moment-video" v-if="item.videoUrl" @tap="playMomentVideo(item)">
|
|
<image class="moment-video-poster" v-if="item.videoPoster" :src="item.videoPoster" mode="aspectFill"></image>
|
|
<view class="moment-video-holder" v-else></view>
|
|
<view class="moment-play">▶</view>
|
|
</view>
|
|
</view>
|
|
<view class="moment-more" v-if="loadingMoments">加载中...</view>
|
|
<view class="moment-more" v-else-if="hasMoreMoments && moments.length" @tap="loadMoments(false)">查看更早的动态</view>
|
|
<view class="moment-more" v-else-if="!hasMoreMoments && moments.length">没有更多动态了</view>
|
|
|
|
<view class="composer-mask" v-if="showComposer" @tap="closeComposer">
|
|
<view class="composer" @tap.stop>
|
|
<view class="composer-title">发动态</view>
|
|
<textarea class="composer-input" v-model="composeText" maxlength="1000"
|
|
placeholder="此刻想说点什么..." :show-confirm-bar="false"></textarea>
|
|
<view class="composer-grid">
|
|
<view class="composer-thumb" v-for="(img, idx) in composeImages" :key="idx" @tap="retryComposeImage(img)">
|
|
<image :src="img.local" mode="aspectFill"></image>
|
|
<view class="thumb-state" v-if="img.status === 'uploading'">上传中</view>
|
|
<view class="thumb-state failed" v-else-if="img.status === 'failed'">失败·点我重试</view>
|
|
<view class="thumb-del" @tap.stop="removeComposeImage(idx)">×</view>
|
|
</view>
|
|
<view class="composer-thumb" v-if="composeVideo" @tap="retryComposeVideo">
|
|
<image v-if="composeVideo.poster" :src="composeVideo.poster" mode="aspectFill"></image>
|
|
<view class="thumb-video-mark">▶ 视频</view>
|
|
<view class="thumb-state" v-if="composeVideo.status === 'uploading'">上传中</view>
|
|
<view class="thumb-state failed" v-else-if="composeVideo.status === 'failed'">失败·点我重试</view>
|
|
<view class="thumb-del" @tap.stop="composeVideo = null">×</view>
|
|
</view>
|
|
<view class="composer-add" v-if="canAddMedia" @tap="chooseMomentMedia">+</view>
|
|
</view>
|
|
<view class="composer-tip">最多 5 张图片,或 1 个视频 · 内容需通过审核</view>
|
|
<view class="composer-submit" :class="{ disabled: publishing }" @tap="submitMoment">发布</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import { getIeProfile, pageIeMoments, publishIeMoment, deleteIeMoment } from '@/common/ieApi.js'
|
|
import tui from '@/common/httpRequest.js'
|
|
|
|
export default {
|
|
data() {
|
|
return {
|
|
menuButtonInfo: { top: 44 },
|
|
profile: {},
|
|
moments: [],
|
|
momentPage: 1,
|
|
hasMoreMoments: true,
|
|
loadingMoments: false,
|
|
showComposer: false,
|
|
composeText: '',
|
|
composeImages: [],
|
|
composeVideo: null,
|
|
publishing: false
|
|
}
|
|
},
|
|
computed: {
|
|
canAddMedia() {
|
|
if (this.composeVideo) return false
|
|
return this.composeImages.length < 5
|
|
}
|
|
},
|
|
onLoad() {
|
|
if (uni.getMenuButtonBoundingClientRect) this.menuButtonInfo = uni.getMenuButtonBoundingClientRect()
|
|
this.loadProfile()
|
|
this.loadMoments(true)
|
|
},
|
|
onReachBottom() {
|
|
this.loadMoments(false)
|
|
},
|
|
async onPullDownRefresh() {
|
|
try {
|
|
await Promise.all([
|
|
this.loadProfile(),
|
|
this.loadMoments(true, true)
|
|
])
|
|
} finally {
|
|
uni.stopPullDownRefresh()
|
|
}
|
|
},
|
|
methods: {
|
|
back() {
|
|
uni.navigateBack({ fail: () => uni.redirectTo({ url: '/package1/ieBrowser/universe' }) })
|
|
},
|
|
async loadProfile() {
|
|
const profile = await getIeProfile()
|
|
if (profile) this.profile = profile
|
|
},
|
|
async loadMoments(reset, force = false) {
|
|
if (this.loadingMoments && !force) return
|
|
if (!reset && !this.hasMoreMoments) return
|
|
this.loadingMoments = true
|
|
try {
|
|
const page = reset ? 1 : this.momentPage
|
|
const result = await pageIeMoments(null, page, 10)
|
|
const records = (result && result.records) || []
|
|
this.moments = reset ? this.mergeLocalMoments(records) : this.moments.concat(records)
|
|
this.hasMoreMoments = records.length >= 10
|
|
this.momentPage = page + 1
|
|
} finally {
|
|
this.loadingMoments = false
|
|
}
|
|
},
|
|
localMomentKey() {
|
|
const userId = uni.getStorageSync('id') || this.profile.userId || 'me'
|
|
return 'ie_my_space_local_moments_' + userId
|
|
},
|
|
saveLocalMoments() {
|
|
const locals = this.moments.filter(item => String(item.id || '').indexOf('local-') === 0 &&
|
|
item.createAt && Date.now() - item.createAt < 30 * 60 * 1000)
|
|
if (locals.length) uni.setStorageSync(this.localMomentKey(), locals.slice(0, 4))
|
|
else uni.removeStorageSync(this.localMomentKey())
|
|
},
|
|
loadLocalMoments() {
|
|
const list = uni.getStorageSync(this.localMomentKey())
|
|
return Array.isArray(list) ? list : []
|
|
},
|
|
mergeLocalMoments(records) {
|
|
const remote = records || []
|
|
const locals = this.loadLocalMoments().map(item => {
|
|
if ((item.localState === 'uploading' || item.localState === 'publishing') &&
|
|
item.createAt && Date.now() - item.createAt > 10 * 60 * 1000) {
|
|
item.localState = 'failed'
|
|
item.failTip = '发布中断,请删除后重发'
|
|
}
|
|
return item
|
|
})
|
|
return locals.length ? locals.concat(remote) : remote
|
|
},
|
|
momentTime(timeStr) {
|
|
if (!timeStr) return ''
|
|
const time = new Date(String(timeStr).replace(/-/g, '/')).getTime()
|
|
if (!time) return ''
|
|
const diff = Date.now() - time
|
|
if (diff < 60000) return '刚刚'
|
|
if (diff < 3600000) return Math.floor(diff / 60000) + ' 分钟前'
|
|
if (diff < 86400000) return Math.floor(diff / 3600000) + ' 小时前'
|
|
if (diff < 172800000) return '昨天'
|
|
const d = new Date(time)
|
|
return (d.getMonth() + 1) + '月' + d.getDate() + '日'
|
|
},
|
|
previewMomentImages(item, index) {
|
|
uni.previewImage({ urls: item.imageList || [], current: item.imageList[index] })
|
|
},
|
|
playMomentVideo(item) {
|
|
if (item.status === 0) {
|
|
uni.showToast({ title: '视频审核中,通过后对方可见', icon: 'none' })
|
|
}
|
|
if (!item.videoUrl) return
|
|
uni.navigateTo({ url: '/package1/ieBrowser/videoPreview?url=' + encodeURIComponent(item.videoUrl) })
|
|
},
|
|
removeMoment(item) {
|
|
if (String(item.id).indexOf('local-') === 0) {
|
|
this.moments = this.moments.filter(m => m.id !== item.id)
|
|
this.saveLocalMoments()
|
|
return
|
|
}
|
|
uni.showModal({
|
|
title: '删除动态',
|
|
content: '删除后无法恢复,确定删除吗?',
|
|
success: async (res) => {
|
|
if (!res.confirm) return
|
|
await deleteIeMoment(item.id)
|
|
this.moments = this.moments.filter(m => m.id !== item.id)
|
|
this.saveLocalMoments()
|
|
}
|
|
})
|
|
},
|
|
openComposer() {
|
|
this.showComposer = true
|
|
},
|
|
closeComposer() {
|
|
this.showComposer = false
|
|
},
|
|
removeComposeImage(index) {
|
|
this.composeImages.splice(index, 1)
|
|
},
|
|
addComposeImages(paths) {
|
|
const items = (paths || [])
|
|
.filter(p => !!p)
|
|
.slice(0, 5 - this.composeImages.length)
|
|
.map(p => ({ local: p, url: '', status: 'uploading' }))
|
|
if (!items.length) return
|
|
this.composeImages = this.composeImages.concat(items)
|
|
items.forEach(item => this.uploadComposeImage(item))
|
|
},
|
|
async uploadComposeImage(item) {
|
|
item.status = 'uploading'
|
|
try {
|
|
item.url = await this.silentUpload(item.local)
|
|
item.status = 'done'
|
|
} catch (e) {
|
|
item.status = 'failed'
|
|
}
|
|
},
|
|
retryComposeImage(item) {
|
|
if (item.status === 'failed') this.uploadComposeImage(item)
|
|
},
|
|
async uploadComposeVideo() {
|
|
const video = this.composeVideo
|
|
if (!video) return
|
|
video.status = 'uploading'
|
|
try {
|
|
video.url = await this.silentUpload(video.local)
|
|
if (video.poster) {
|
|
try { video.posterUrl = await this.silentUpload(video.poster) } catch (e) { video.posterUrl = '' }
|
|
}
|
|
video.status = 'done'
|
|
} catch (e) {
|
|
video.status = 'failed'
|
|
}
|
|
},
|
|
retryComposeVideo() {
|
|
if (this.composeVideo && this.composeVideo.status === 'failed') this.uploadComposeVideo()
|
|
},
|
|
chooseMomentMedia() {
|
|
const hasImages = this.composeImages.length > 0
|
|
if (!uni.chooseMedia) {
|
|
uni.chooseImage({
|
|
count: 5 - this.composeImages.length,
|
|
success: (res) => {
|
|
this.addComposeImages(res.tempFilePaths || [])
|
|
}
|
|
})
|
|
return
|
|
}
|
|
uni.chooseMedia({
|
|
count: hasImages ? 5 - this.composeImages.length : 5,
|
|
mediaType: hasImages ? ['image'] : ['image', 'video'],
|
|
maxDuration: 60,
|
|
success: (res) => {
|
|
const files = res.tempFiles || []
|
|
if (!files.length) return
|
|
if (res.type === 'video') {
|
|
const file = files[0]
|
|
if (file.size > 100 * 1024 * 1024) {
|
|
uni.showToast({ title: '视频不能超过100MB,请先剪辑', icon: 'none' })
|
|
return
|
|
}
|
|
if (file.duration && file.duration > 61) {
|
|
uni.showToast({ title: '视频不能超过60秒', icon: 'none' })
|
|
return
|
|
}
|
|
this.composeVideo = {
|
|
local: file.tempFilePath,
|
|
poster: file.thumbTempFilePath || '',
|
|
size: file.size,
|
|
url: '',
|
|
posterUrl: '',
|
|
status: 'uploading'
|
|
}
|
|
this.uploadComposeVideo()
|
|
return
|
|
}
|
|
const oversize = files.find(f => f.size > 10 * 1024 * 1024)
|
|
if (oversize) {
|
|
uni.showToast({ title: '单张图片不能超过10MB', icon: 'none' })
|
|
return
|
|
}
|
|
this.addComposeImages(files.map(f => f.tempFilePath))
|
|
}
|
|
})
|
|
},
|
|
silentUpload(filePath) {
|
|
return new Promise((resolve, reject) => {
|
|
uni.uploadFile({
|
|
url: tui.interfaceUrl() + '/upload/file',
|
|
filePath,
|
|
name: 'file',
|
|
timeout: 300000,
|
|
header: { accessToken: uni.getStorageSync('hiver_token') },
|
|
success: (res) => {
|
|
try {
|
|
const d = JSON.parse(String(res.data || '{}').replace(/\ufeff/g, ''))
|
|
const fileObj = d.result !== undefined ? d.result : d.data
|
|
const url = typeof fileObj === 'string' ? fileObj : (fileObj && (fileObj.url || fileObj.fileUrl || fileObj.path || fileObj.fullPath))
|
|
if (d.code % 100 === 0 && url) resolve(url)
|
|
else reject(new Error(d.message || '上传失败'))
|
|
} catch (e) {
|
|
reject(e)
|
|
}
|
|
},
|
|
fail: () => reject(new Error('上传失败'))
|
|
})
|
|
})
|
|
},
|
|
async waitComposeUploads(images, video) {
|
|
const uploading = () => images.some(item => item.status === 'uploading') ||
|
|
(video && video.status === 'uploading')
|
|
while (uploading()) {
|
|
await new Promise(resolve => setTimeout(resolve, 500))
|
|
}
|
|
if (images.some(item => item.status !== 'done')) throw new Error('图片上传失败')
|
|
if (video && video.status !== 'done') throw new Error('视频上传失败')
|
|
},
|
|
async submitMoment() {
|
|
if (this.publishing) return
|
|
const content = this.composeText.trim()
|
|
if (!content && !this.composeImages.length && !this.composeVideo) {
|
|
uni.showToast({ title: '写点什么或选张图片吧', icon: 'none' })
|
|
return
|
|
}
|
|
const images = this.composeImages
|
|
const video = this.composeVideo
|
|
if (images.some(item => item.status === 'failed') || (video && video.status === 'failed')) {
|
|
uni.showToast({ title: '有图片/视频上传失败,点缩略图重试', icon: 'none' })
|
|
return
|
|
}
|
|
this.publishing = true
|
|
const localCard = {
|
|
id: 'local-' + Date.now(),
|
|
content,
|
|
imageList: images.map(item => item.local),
|
|
videoUrl: video ? video.local : '',
|
|
videoPoster: video ? video.poster : '',
|
|
status: 1,
|
|
localState: (images.some(item => item.status === 'uploading') || (video && video.status === 'uploading')) ? 'uploading' : 'publishing',
|
|
failTip: '',
|
|
createTime: this.formatNow(),
|
|
createAt: Date.now()
|
|
}
|
|
this.moments.unshift(localCard)
|
|
this.saveLocalMoments()
|
|
this.showComposer = false
|
|
this.composeText = ''
|
|
this.composeImages = []
|
|
this.composeVideo = null
|
|
this.publishing = false
|
|
uni.showToast({ title: '发布成功', icon: 'none' })
|
|
try {
|
|
await this.waitComposeUploads(images, video)
|
|
localCard.localState = 'publishing'
|
|
this.saveLocalMoments()
|
|
const payload = { content, images: images.map(item => item.url) }
|
|
if (video) {
|
|
payload.videoUrl = video.url
|
|
payload.videoPoster = video.posterUrl || ''
|
|
}
|
|
const moment = await publishIeMoment(payload)
|
|
if (!moment) throw new Error('发布失败,请稍后再试')
|
|
const index = this.moments.findIndex(m => m.id === localCard.id)
|
|
if (index >= 0) {
|
|
moment.imageList = localCard.imageList.length ? localCard.imageList : moment.imageList
|
|
moment.videoPoster = localCard.videoPoster || moment.videoPoster
|
|
this.moments.splice(index, 1, moment)
|
|
this.saveLocalMoments()
|
|
}
|
|
} catch (e) {
|
|
localCard.localState = 'failed'
|
|
localCard.failTip = (e && e.message) || '发布失败,请删除后重发'
|
|
this.saveLocalMoments()
|
|
uni.showToast({ title: localCard.failTip, icon: 'none' })
|
|
}
|
|
},
|
|
formatNow() {
|
|
const d = new Date()
|
|
const pad = n => (n < 10 ? '0' + n : '' + n)
|
|
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + ' ' +
|
|
pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds())
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
page { background: #f7f9ff; }
|
|
.my-space-page { min-height: 100vh; padding: 0 32rpx 80rpx; box-sizing: border-box; color: #161b2e; background: radial-gradient(circle at 50% 8%, rgba(169,255,231,.36), transparent 320rpx), radial-gradient(circle at 86% 30%, rgba(255,184,209,.2), transparent 340rpx), linear-gradient(180deg, #fbfdff, #eef4ff 62%, #fff4e8); }
|
|
.float-bar { position: relative; z-index: 5; display: flex; align-items: center; }
|
|
.home-back { display: flex; align-items: center; height: 58rpx; padding: 0 22rpx 0 12rpx; border: 1rpx solid rgba(255,255,255,.88); border-radius: 999rpx; color: rgba(22,27,46,.66); background: rgba(255,255,255,.66); backdrop-filter: blur(20rpx); box-shadow: 0 14rpx 36rpx rgba(96,112,160,.12), inset 0 1rpx 0 rgba(255,255,255,.95); font-size: 23rpx; font-weight: 800; }
|
|
.home-back-icon { margin-right: 6rpx; padding-bottom: 6rpx; font-size: 40rpx; line-height: 50rpx; font-weight: 400; }
|
|
.publish-btn { flex-shrink: 0; height: 54rpx; line-height: 54rpx; padding: 0 20rpx; border-radius: 999rpx; color: #11162a; background: #a9ffe7; font-size: 22rpx; font-weight: 900; box-shadow: 0 12rpx 28rpx rgba(96,200,170,.24); }
|
|
.space-hero { display: flex; align-items: center; margin-top: 28rpx; padding: 30rpx; border-radius: 38rpx; background: rgba(255,255,255,.66); border: 1rpx solid rgba(255,255,255,.82); box-shadow: 0 22rpx 70rpx rgba(96,112,160,.14); }
|
|
.hero-avatar, .hero-avatar-img { flex-shrink: 0; width: 112rpx; height: 112rpx; margin-right: 24rpx; border-radius: 50%; }
|
|
.hero-avatar { line-height: 112rpx; text-align: center; color: #11162a; background: linear-gradient(145deg, #effffb, #a9ffe7); font-size: 38rpx; font-weight: 900; }
|
|
.hero-main { flex: 1; min-width: 0; }
|
|
.hero-title-row { display: flex; align-items: center; justify-content: space-between; gap: 16rpx; }
|
|
.hero-title { font-size: 36rpx; font-weight: 900; }
|
|
.hero-sub { margin-top: 10rpx; color: rgba(22,27,46,.48); font-size: 23rpx; }
|
|
.moment-empty { margin-top: 60rpx; text-align: center; color: rgba(22,27,46,.4); font-size: 25rpx; }
|
|
.moment-card { margin-top: 24rpx; padding: 26rpx; border-radius: 28rpx; background: rgba(255,255,255,.85); border: 1rpx solid rgba(255,255,255,.9); box-shadow: 0 12rpx 32rpx rgba(96,112,160,.08); }
|
|
.moment-top { display: flex; align-items: center; }
|
|
.moment-time { flex: 1; color: rgba(22,27,46,.42); font-size: 21rpx; }
|
|
.moment-state { margin-right: 16rpx; padding: 4rpx 14rpx; border-radius: 999rpx; font-size: 20rpx; font-weight: 700; }
|
|
.moment-state.auditing { color: #b07b1f; background: rgba(255,196,87,.18); }
|
|
.moment-state.blocked { color: #d2486a; background: rgba(255,107,146,.14); }
|
|
.moment-del { color: rgba(22,27,46,.38); font-size: 22rpx; padding: 4rpx 8rpx; }
|
|
.moment-text { margin-top: 14rpx; font-size: 27rpx; line-height: 42rpx; word-break: break-all; }
|
|
.moment-grid { display: flex; flex-wrap: wrap; margin-top: 16rpx; }
|
|
.moment-grid image { width: 196rpx; height: 196rpx; margin: 0 10rpx 10rpx 0; border-radius: 16rpx; background: rgba(22,27,46,.05); }
|
|
.moment-grid.g1 image { width: 400rpx; height: 400rpx; border-radius: 22rpx; }
|
|
.moment-grid.g2 image { width: 300rpx; height: 300rpx; }
|
|
.moment-video { position: relative; margin-top: 16rpx; width: 440rpx; height: 290rpx; border-radius: 22rpx; overflow: hidden; background: #1a2034; }
|
|
.moment-video-poster, .moment-video-holder { width: 100%; height: 100%; }
|
|
.moment-play { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 84rpx; height: 84rpx; line-height: 84rpx; text-align: center; border-radius: 50%; color: #fff; background: rgba(0,0,0,.45); font-size: 32rpx; }
|
|
.moment-more { margin-top: 26rpx; text-align: center; color: rgba(22,27,46,.36); font-size: 22rpx; padding: 12rpx 0; }
|
|
.composer-mask { position: fixed; inset: 0; z-index: 120; display: flex; align-items: flex-end; background: rgba(13,17,32,.5); backdrop-filter: blur(10rpx); }
|
|
.composer { width: 100%; padding: 36rpx 32rpx calc(40rpx + env(safe-area-inset-bottom)); border-radius: 40rpx 40rpx 0 0; background: #ffffff; box-sizing: border-box; }
|
|
.composer-title { font-size: 32rpx; font-weight: 800; text-align: center; }
|
|
.composer-input { width: 100%; height: 200rpx; margin-top: 24rpx; padding: 22rpx; border-radius: 22rpx; background: #f4f6fc; font-size: 27rpx; box-sizing: border-box; }
|
|
.composer-grid { display: flex; flex-wrap: wrap; margin-top: 20rpx; }
|
|
.composer-thumb { position: relative; width: 150rpx; height: 150rpx; margin: 0 14rpx 14rpx 0; border-radius: 16rpx; overflow: hidden; background: #1a2034; }
|
|
.composer-thumb image { width: 100%; height: 100%; }
|
|
.thumb-del { position: absolute; right: 0; top: 0; width: 40rpx; height: 40rpx; line-height: 36rpx; text-align: center; color: #fff; background: rgba(0,0,0,.55); border-radius: 0 0 0 16rpx; font-size: 26rpx; }
|
|
.thumb-video-mark { position: absolute; left: 0; bottom: 0; right: 0; padding: 4rpx 0; text-align: center; color: #fff; background: rgba(0,0,0,.45); font-size: 20rpx; }
|
|
.thumb-state { position: absolute; left: 0; top: 0; right: 0; bottom: 0; display: flex; align-items: center; justify-content: center; color: #fff; background: rgba(0,0,0,.42); font-size: 20rpx; }
|
|
.thumb-state.failed { color: #ffd9e2; background: rgba(160,30,60,.55); }
|
|
.composer-add { width: 150rpx; height: 150rpx; line-height: 146rpx; text-align: center; border-radius: 16rpx; border: 2rpx dashed rgba(22,27,46,.18); color: rgba(22,27,46,.34); font-size: 52rpx; box-sizing: border-box; }
|
|
.composer-tip { margin-top: 8rpx; color: rgba(22,27,46,.38); font-size: 21rpx; }
|
|
.composer-submit { margin-top: 28rpx; height: 92rpx; line-height: 92rpx; text-align: center; border-radius: 999rpx; color: #11162a; background: linear-gradient(135deg, #a9ffe7, #7be3c8); font-size: 30rpx; font-weight: 800; box-shadow: 0 14rpx 32rpx rgba(96,200,170,.32); }
|
|
.composer-submit.disabled { opacity: .6; }
|
|
</style>
|
|
|