diff --git a/pages/chat/chat.vue b/pages/chat/chat.vue
index 1ec87bd..2136bd4 100644
--- a/pages/chat/chat.vue
+++ b/pages/chat/chat.vue
@@ -31,7 +31,7 @@
class="row"
:class="{ mine: m.mine, pick: selecting }"
@click="selecting ? togglePick(m) : tapMsg(m)"
- @longpress="act(m)"
+ @longpress="act(m, i)"
>
@@ -41,7 +41,7 @@
{{ sender(m) }}
-
+
{{ quoteLine(m.quote) }}
@@ -81,20 +81,13 @@
-
-
- 消息操作
- {{ actPreview }}
-
- ▶播放
- 文复制
- ↪转发
- ❝引用
- 多多选
- ↩撤回
- 删删除
+
+
+
+
+ {{ it.label }}
- 取消
+
@@ -202,6 +195,12 @@ export default {
recWillCancel: false,
recSec: 0,
actionMsg: null,
+ popTop: 0,
+ popLeft: 8,
+ popW: 224,
+ caretLeft: 24,
+ popBelow: false,
+ popAt: 0,
quoting: null,
selecting: false,
picked: [],
@@ -232,8 +231,25 @@ export default {
const m = this.actionMsg
return !!(m && m.mine && !m.recalled && Date.now() - Number(m.time) < 120000)
},
- actPreview() {
- return previewOf(this.actionMsg)
+ actItems() {
+ const m = this.actionMsg
+ if (!m) return []
+ const items = []
+ if (m.kind === 'voice') items.push({ k: 'play', icon: 'play', label: '播放' })
+ else items.push({ k: 'copy', icon: 'copy', label: '复制' })
+ items.push({ k: 'forward', icon: 'share', label: '转发' })
+ items.push({ k: 'quote', icon: 'chat', label: '引用' })
+ items.push({ k: 'select', icon: 'view-list', label: '多选' })
+ if (this.canRecall) items.push({ k: 'recall', icon: 'rollback', label: '撤回' })
+ items.push({ k: 'delete', icon: 'delete', label: '删除' })
+ return items
+ },
+ popBoxStyle() {
+ return {
+ top: this.popTop + 'px',
+ left: this.popLeft + 'px',
+ width: this.popW + 'px'
+ }
}
},
onLoad(q) {
@@ -592,14 +608,53 @@ export default {
}
})
},
- act(m) {
+ act(m, index) {
if (!m || m.recalled || this.selecting) return
this.more = false
this.emoji = false
this.hideKb()
+ this.popAt = Date.now()
this.actionMsg = m
+ this.$nextTick(() => this.placePop(index))
+ },
+ placePop(index) {
+ const q = uni.createSelectorQuery().in(this)
+ q.select('#bubble-' + index).boundingClientRect()
+ q.exec((res) => {
+ const r = res && res[0]
+ if (!r || !r.width) return
+ const sys = uni.getSystemInfoSync()
+ const n = this.actItems.length
+ const cols = Math.min(n, 5)
+ const itemW = 56
+ const w = cols * itemW
+ const rows = Math.ceil(n / cols)
+ const h = rows * 62 + 8
+ let left = r.left + r.width / 2 - w / 2
+ left = Math.max(8, Math.min(left, sys.windowWidth - w - 8))
+ const below = r.top < h + 16
+ const top = below ? r.bottom + 10 : r.top - h - 10
+ const caret = r.left + r.width / 2 - left - 6
+ this.popW = w
+ this.popLeft = left
+ this.popTop = Math.max(8, top)
+ this.popBelow = below
+ this.caretLeft = Math.max(14, Math.min(caret, w - 26))
+ })
+ },
+ runAct(k) {
+ if (k === 'copy') return this.doCopy()
+ if (k === 'play') return this.doPlay()
+ if (k === 'forward') return this.doForward()
+ if (k === 'quote') return this.doQuote()
+ if (k === 'select') return this.enterSelect()
+ if (k === 'recall') return this.doRecall()
+ if (k === 'delete') return this.doDelete()
+ },
+ closeAct() {
+ if (Date.now() - this.popAt < 400) return
+ this.actionMsg = null
},
- closeAct() { this.actionMsg = null },
liveMsg() {
const c = store.findConv(this.sid)
const m = this.actionMsg
@@ -840,19 +895,39 @@ export default {
.em { width: 12.5%; text-align: center; font-size: 44rpx; line-height: 88rpx; }
.hint { text-align: center; font-size: 24rpx; color: #9CA3AF; padding: 24rpx; background: #fff; }
.ico { padding: 8rpx 8rpx; font-size: 40rpx; color: #111827; letter-spacing: 2rpx; }
-.mask { position: fixed; left: 0; right: 0; top: 0; bottom: 0; background: rgba(17, 24, 39, 0.45); z-index: 40; display: flex; align-items: flex-end; }
-.sheet { width: 100%; background: #fff; border-radius: 28rpx 28rpx 0 0; padding: 28rpx 28rpx 12rpx; }
-.sheet-t { display: block; text-align: center; font-size: 30rpx; font-weight: 650; }
-.sheet-s { display: block; text-align: center; font-size: 24rpx; color: #9CA3AF; margin: 8rpx 0 24rpx; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.sheet-grid { display: flex; flex-wrap: wrap; gap: 16rpx; }
-.cell {
- width: calc(25% - 12rpx); height: 140rpx; background: #F5F7FA; border-radius: 16rpx;
- display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 22rpx; color: #374151;
+.pop-mask { position: fixed; left: 0; right: 0; top: 0; bottom: 0; z-index: 40; }
+.pop {
+ position: fixed;
+ display: flex;
+ flex-wrap: wrap;
+ background: #4C4C4C;
+ border-radius: 8px;
+ padding: 4px 0;
+ box-sizing: border-box;
}
-.ce { font-size: 32rpx; margin-bottom: 8rpx; color: #111827; font-weight: 650; }
-.sheet-cancel {
- margin: 20rpx 0 8rpx; height: 88rpx; border-radius: 16rpx; background: #F5F7FA;
- display: flex; align-items: center; justify-content: center; font-size: 28rpx; color: #111827;
+.pop-item {
+ width: 56px;
+ height: 62px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ color: #fff;
+}
+.pop-lab { margin-top: 4px; font-size: 12px; line-height: 1; color: #fff; }
+.pop-caret {
+ position: absolute;
+ width: 0;
+ height: 0;
+ border: 6px solid transparent;
+ border-top-color: #4C4C4C;
+ bottom: -12px;
+}
+.pop.below .pop-caret {
+ top: -12px;
+ bottom: auto;
+ border-top-color: transparent;
+ border-bottom-color: #4C4C4C;
}
.selbar { display: flex; background: #fff; border-top: 1rpx solid #E8ECF2; }
.selbtn { flex: 1; height: 96rpx; display: flex; align-items: center; justify-content: center; font-size: 28rpx; color: #1677FF; }