| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 |
- <template>
- <view class="media-recorder">
- <view class="toolbar">
- <!-- 拍照按钮 -->
- <button class="tool-btn" @click="chooseImage">
- <text class="icon">📷</text>
- <text>拍照</text>
- </button>
- <!-- 录像按钮 -->
- <button class="tool-btn" @click="chooseVideo">
- <text class="icon">📹</text>
- <text>录像</text>
- </button>
- <!-- 录音按钮 -->
- <button class="tool-btn" @click="toggleRecording">
- <text class="icon">🎤</text>
- <text>{{ isRecording ? '停止' : '录音' }}</text>
- </button>
- <!-- 文字按钮 -->
- <button class="tool-btn" @click="showTextInput = !showTextInput">
- <text class="icon">✏️</text>
- <text>文字</text>
- </button>
- </view>
- <!-- 文字输入框 -->
- <view v-if="showTextInput" class="text-input-area">
- <textarea
- v-model="textContent"
- placeholder="输入文字记录..."
- class="text-input"
- maxlength="500"
- />
- <button class="btn-add-text" @click="addTextNote">添加</button>
- </view>
- <!-- 媒体预览列表 -->
- <view class="media-list" v-if="mediaItems.length > 0">
- <view class="media-item" v-for="(item, index) in mediaItems" :key="index">
- <image v-if="item.type === 'image'" :src="item.url" class="media-preview" mode="aspectFill" />
- <view v-else-if="item.type === 'video'" class="media-preview video-preview">
- <text class="icon">📹</text>
- </view>
- <view v-else-if="item.type === 'audio'" class="media-preview audio-preview">
- <text class="icon">🎤</text>
- <text class="duration">{{ item.duration }}s</text>
- </view>
- <view v-else-if="item.type === 'text'" class="media-preview text-preview">
- <text>{{ item.content }}</text>
- </view>
- <button class="btn-delete" @click="removeMedia(index)">×</button>
- </view>
- </view>
- </view>
- </template>
- <script>
- import config from '@/config.js'
- export default {
- name: 'MediaRecorder',
- props: {
- taskId: {
- type: Number,
- required: true
- },
- creatorId: {
- type: Number,
- required: true
- }
- },
- data() {
- return {
- mediaItems: [],
- textContent: '',
- showTextInput: false,
- isRecording: false,
- recorderManager: null,
- recordingStartTime: 0
- }
- },
- created() {
- // 初始化录音管理器
- this.recorderManager = uni.getRecorderManager()
- this.recorderManager.onStop((res) => {
- const duration = (Date.now() - this.recordingStartTime) / 1000
- this.uploadAudio(res.tempFilePath, duration)
- })
- },
- methods: {
- // 选择图片
- chooseImage() {
- uni.chooseImage({
- count: 9,
- sizeType: ['compressed'],
- sourceType: ['album', 'camera'],
- success: (res) => {
- res.tempFilePaths.forEach(path => {
- this.uploadFile(path, 'image')
- })
- }
- })
- },
- // 选择视频
- chooseVideo() {
- uni.chooseVideo({
- sourceType: ['album', 'camera'],
- maxDuration: 60,
- success: (res) => {
- this.uploadFile(res.tempFilePath, 'video')
- }
- })
- },
- // 开始/停止录音
- toggleRecording() {
- if (this.isRecording) {
- this.recorderManager.stop()
- this.isRecording = false
- } else {
- this.recordingStartTime = Date.now()
- this.recorderManager.start()
- this.isRecording = true
- }
- },
- // 添加文字记录
- addTextNote() {
- if (!this.textContent.trim()) {
- uni.showToast({ title: '请输入内容', icon: 'none' })
- return
- }
- this.mediaItems.push({
- type: 'text',
- content: this.textContent,
- uploaded: false
- })
- this.textContent = ''
- this.showTextInput = false
- this.$emit('change', this.mediaItems)
- },
- // 上传文件
- uploadFile(filePath, type) {
- uni.showLoading({ title: '上传中...' })
- uni.uploadFile({
- url: config.api('/api/media/upload'),
- filePath: filePath,
- name: 'file',
- formData: {
- taskId: this.taskId,
- creatorId: this.creatorId,
- type: type
- },
- header: {
- 'Authorization': 'Bearer ' + uni.getStorageSync('token')
- },
- success: (res) => {
- const data = JSON.parse(res.data)
- if (data.code === 200) {
- this.mediaItems.push({
- id: data.data.id,
- type: type,
- url: data.data.url,
- uploaded: true
- })
- this.$emit('change', this.mediaItems)
- } else {
- uni.showToast({ title: data.message || '上传失败', icon: 'none' })
- }
- },
- fail: () => {
- uni.showToast({ title: '上传失败', icon: 'none' })
- },
- complete: () => {
- uni.hideLoading()
- }
- })
- },
- // 上传音频
- uploadAudio(filePath, duration) {
- this.uploadFile(filePath, 'audio')
- this.mediaItems[this.mediaItems.length - 1].duration = Math.round(duration)
- },
- // 删除媒体
- removeMedia(index) {
- this.mediaItems.splice(index, 1)
- this.$emit('change', this.mediaItems)
- },
- // 获取所有媒体数据
- getMediaItems() {
- return this.mediaItems
- }
- }
- }
- </script>
- <style lang="scss" scoped>
- .media-recorder {
- margin: 20rpx 0;
- }
- .toolbar {
- display: flex;
- gap: 20rpx;
- justify-content: space-around;
- padding: 20rpx;
- background: #fff;
- border-radius: 12rpx;
- }
- .tool-btn {
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 20rpx;
- background: #f8f8f8;
- border-radius: 12rpx;
- font-size: 24rpx;
- .icon {
- font-size: 48rpx;
- margin-bottom: 10rpx;
- }
- }
- .text-input-area {
- margin-top: 20rpx;
- background: #fff;
- border-radius: 12rpx;
- padding: 20rpx;
- }
- .text-input {
- width: 100%;
- min-height: 150rpx;
- border: 1rpx solid #ddd;
- border-radius: 8rpx;
- padding: 20rpx;
- }
- .btn-add-text {
- margin-top: 20rpx;
- background: #F97316;
- color: #fff;
- }
- .media-list {
- display: flex;
- flex-wrap: wrap;
- gap: 20rpx;
- margin-top: 20rpx;
- }
- .media-item {
- position: relative;
- width: 200rpx;
- height: 200rpx;
- }
- .media-preview {
- width: 100%;
- height: 100%;
- border-radius: 8rpx;
- object-fit: cover;
- }
- .video-preview, .audio-preview, .text-preview {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- background: #f8f8f8;
- .icon {
- font-size: 64rpx;
- }
- .duration {
- font-size: 24rpx;
- color: #666;
- margin-top: 10rpx;
- }
- }
- .text-preview {
- padding: 20rpx;
- font-size: 24rpx;
- text-align: center;
- }
- .btn-delete {
- position: absolute;
- top: -10rpx;
- right: -10rpx;
- width: 40rpx;
- height: 40rpx;
- border-radius: 50%;
- background: #ff4444;
- color: #fff;
- font-size: 32rpx;
- line-height: 40rpx;
- padding: 0;
- }
- </style>
|