增加投诉功能

main
15004070936 1 week ago
parent 5801151881
commit d56c37facb

@ -29,7 +29,7 @@
}, },
computed: { computed: {
...mapState('chatStore', ['chats']) ...mapState('chatStore', ['chats'])
}, },
onLaunch() { onLaunch() {
// //
//checkupdate(); //checkupdate();
@ -114,19 +114,19 @@
// url: `/pages/chatbox/chat` // url: `/pages/chatbox/chat`
// }); // });
} }
if(!extras){ if(!extras){
uni.reLaunch({ uni.reLaunch({
url: `/pages/chatbox/chat` url: `/pages/chatbox/chat`
}); });
} }
} }
// //
if (notificationEventType == 'notificationArrived') { if (notificationEventType == 'notificationArrived') {
} }
}); });
jpushModule.getRegistrationID(result => { jpushModule.getRegistrationID(result => {
console.log("极光推送--注册ID", result.registerID) console.log("极光推送--注册ID", result.registerID)
@ -164,8 +164,8 @@
uni.navigateTo({ uni.navigateTo({
url: "/pages/login/login" url: "/pages/login/login"
}) })
} }
// //
let count = 0 let count = 0
myCache("this.chats",this.chats); myCache("this.chats",this.chats);
@ -176,9 +176,9 @@
}); });
myCache("massageCount",count); myCache("massageCount",count);
console.log("massageCountmassageCount",count); console.log("massageCountmassageCount",count);
//--------------------------------------------------- //---------------------------------------------------
}, },
methods: { methods: {
...mapActions('chatStore'), ...mapActions('chatStore'),
//--------------------------------------------------- //---------------------------------------------------
@ -307,7 +307,9 @@
} }
}, },
handlePrivateMessage(msg) { handlePrivateMessage(msg) {
msg.selfSend = msg.sendId === this.userId; msg.selfSend = String(msg.sendId) === String(this.userId);
console.log("msg.sendId",msg.sendId)
console.log("this.userId",this.userId)
const friendId = msg.selfSend ? msg.recvId : msg.sendId; const friendId = msg.selfSend ? msg.recvId : msg.sendId;
const chatInfo = { type: 'PRIVATE', targetId: friendId }; const chatInfo = { type: 'PRIVATE', targetId: friendId };
const type=Number(msg.type) const type=Number(msg.type)
@ -326,10 +328,17 @@
this.$store.dispatch('chatStore/readedMessage', { friendId: msg.sendId }); this.$store.dispatch('chatStore/readedMessage', { friendId: msg.sendId });
return; return;
} }
//
if (type === enums.MESSAGE_TYPE.RECALL) { if (type === enums.MESSAGE_TYPE.RECALL) {
this.$store.dispatch('chatStore/recallMessage', { msgInfo: msg, chatInfo }); this.$store.dispatch('chatStore/recallMessage', { msgInfo: msg, chatInfo });
return; return;
} }
//
if (type === enums.MESSAGE_TYPE.BLOCKED) {
this.$store.dispatch('chatStore/blockMessage', { msgInfo: msg, chatInfo });
return;
}
//
if (type === enums.MESSAGE_TYPE.FRIEND_NEW) { if (type === enums.MESSAGE_TYPE.FRIEND_NEW) {
this.$store.dispatch('friendStore/addFriend', JSON.parse(msg.content)); this.$store.dispatch('friendStore/addFriend', JSON.parse(msg.content));
// this.friendStore.addFriend(JSON.parse(msg.content)); // this.friendStore.addFriend(JSON.parse(msg.content));
@ -355,7 +364,8 @@
}, },
handleGroupMessage(msg) { handleGroupMessage(msg) {
msg.selfSend = msg.sendId === this.userId; msg.selfSend = String(msg.sendId) === String(this.userId);
// msg.selfSend = msg.sendId === this.userId;
const chatInfo = { type: 'GROUP', targetId: msg.groupId }; const chatInfo = { type: 'GROUP', targetId: msg.groupId };
const type=Number(msg.type) const type=Number(msg.type)
if (type === enums.MESSAGE_TYPE.LOADING) { if (type === enums.MESSAGE_TYPE.LOADING) {
@ -383,6 +393,11 @@
this.$store.dispatch('chatStore/recallMessage', { msgInfo: msg, chatInfo }); this.$store.dispatch('chatStore/recallMessage', { msgInfo: msg, chatInfo });
return; return;
} }
//
if (type === enums.MESSAGE_TYPE.BLOCKED) {
this.$store.dispatch('chatStore/blockMessage', { msgInfo: msg, chatInfo });
return;
}
if (type === enums.MESSAGE_TYPE.GROUP_NEW) { if (type === enums.MESSAGE_TYPE.GROUP_NEW) {
this.$store.dispatch('groupStore/addGroup', JSON.parse(msg.content)); this.$store.dispatch('groupStore/addGroup', JSON.parse(msg.content));
return; return;

@ -7,6 +7,7 @@ const MESSAGE_TYPE = {
VIDEO: 4, VIDEO: 4,
ORDER: 5, ORDER: 5,
PRODUCT: 6, PRODUCT: 6,
BLOCKED: 7,
RECALL: 10, RECALL: 10,
READED: 11, READED: 11,
RECEIPT: 12, RECEIPT: 12,
@ -57,7 +58,8 @@ const MESSAGE_STATUS = {
UNSEND: 0, UNSEND: 0,
SENDED: 1, SENDED: 1,
RECALL: 2, RECALL: 2,
READED: 3 READED: 3,
BLOCKED:4
} }
export { export {

@ -1,4 +1,4 @@
var utils = { var utils = {
//时间格式化 //时间格式化
formatDate: (value) => { formatDate: (value) => {
@ -47,10 +47,10 @@
MM = MM < 10 ? ('0' + MM) : MM; MM = MM < 10 ? ('0' + MM) : MM;
let d = date.getDate(); let d = date.getDate();
d = d < 10 ? ('0' + d) : d; d = d < 10 ? ('0' + d) : d;
return y + '/' + MM + '/' + d; return y + '/' + MM + '/' + d;
}, },
//字典数据根据key显示文字 //字典数据根据key显示文字
dataFilter:(arr, val='')=> { dataFilter:(arr, val='')=> {
let name = ""; let name = "";
@ -63,7 +63,7 @@
}); });
return name; return name;
}, },
//列表转换为树格式 //列表转换为树格式
listtoTree:(data)=>{ listtoTree:(data)=>{
let result = []; let result = [];
@ -88,8 +88,8 @@
return result; return result;
}, },
getRemoteFile: (path) => { getRemoteFile: (path) => {
return `https://www.sanduolantoyoga.com/yoga${path}`; return `https://yoga.runpengsoft.com/yoga${path}`;
} }
} }
// 格式化日期 // 格式化日期
export default utils; export default utils;

@ -95,6 +95,7 @@ $http.afterRequest = function (response) {
}, 2000); }, 2000);
} }
else { else {
return return
} }
} }

@ -18,7 +18,7 @@
} }
}, },
"compatible" : { "compatible" : {
"ignoreVersion" : true //trueHBuilderX1.9.0 "ignoreVersion" : true //trueHBuilderX1.9.0
}, },
"splashscreen" : { "splashscreen" : {
"alwaysShowBeforeRender" : true, "alwaysShowBeforeRender" : true,
@ -385,7 +385,7 @@
"disableHostCheck" : true, // Host "disableHostCheck" : true, // Host
"proxy" : { "proxy" : {
"/apis" : { "/apis" : {
"target" : "https://www.sanduolantoyoga.com", "target" : "https://yoga.runpengsoft.com",
"changeOrigin" : true, "changeOrigin" : true,
"secure" : false, "secure" : false,
"pathRewrite" : { "pathRewrite" : {

@ -172,11 +172,28 @@
"path" : "pages/chatbox/chat-box", "path" : "pages/chatbox/chat-box",
"style" : "style" :
{ {
"navigationBarTitleText": "消息2", "navigationBarTitleText": "消息",
"enablePullDownRefresh": false "enablePullDownRefresh": false,
"app-plus": {
"titleNView": {
"buttons": [
{
"text": "\uE684", // 使 unicode Unicode iconfont
"fontSrc": "/static/font.ttf", // 使
"fontSize": "22px",
"float": "right"
}
]
}
}
}
},
{
"path": "pages/chatbox/report",
"style": {
"navigationBarTitleText": "投诉举报"
} }
}, },
{ {
"path" : "pages/product/list", "path" : "pages/product/list",
"style": { "style": {
@ -440,10 +457,10 @@
"enablePullDownRefresh": false "enablePullDownRefresh": false
} }
}, },
{ {
"path" : "pages/user/info", "path" : "pages/user/info",
"style" : "style" :
{ {
"navigationBarTitleText": "", "navigationBarTitleText": "",
"enablePullDownRefresh": false "enablePullDownRefresh": false
@ -451,7 +468,7 @@
}, },
{ {
"path" : "pages/user/infofw", "path" : "pages/user/infofw",
"style" : "style" :
{ {
"navigationBarTitleText": "用户服务协议", "navigationBarTitleText": "用户服务协议",
"enablePullDownRefresh": false "enablePullDownRefresh": false
@ -459,13 +476,13 @@
}, },
{ {
"path" : "pages/user/infoys", "path" : "pages/user/infoys",
"style" : "style" :
{ {
"navigationBarTitleText": "隐私政策条款", "navigationBarTitleText": "隐私政策条款",
"enablePullDownRefresh": false "enablePullDownRefresh": false
} }
}, },
// //
{ {

@ -11,6 +11,31 @@
> >
<view v-if="chat" v-for="(msgInfo, idx) in chat.messages" :key="idx"> <view v-if="chat" v-for="(msgInfo, idx) in chat.messages" :key="idx">
<!-- ========== 选择模式显示CheckBox ========== -->
<view v-if="selectionMode && canReport(msgInfo)" class="message-wrapper"
@click="toggleSelectMessage(msgInfo.id)">
<!-- :class="{ self: msgInfo.selfSend }"-->
<view class="checkbox-container">
<view class="checkbox" :class="{ checked: selectedMessageIds.includes(msgInfo.id) }">
<text v-if="selectedMessageIds.includes(msgInfo.id)"></text>
</view>
</view>
<chat-message-item :ref="'message'+msgInfo.id" v-if="idx >= showMinIdx"
:headImage="headImage(msgInfo)"
@call="onRtCall(msgInfo)"
:showName="showName(msgInfo)"
@recall="onRecallMessage"
@delete="onDeleteMessage"
@copy="onCopyMessage"
@longPressHead="onLongPressHead(msgInfo)"
@download="onDownloadFile"
@audioStateChange="onAudioStateChange"
:id="'chat-item-' + idx"
:msgInfo="msgInfo"
:groupMembers="groupMembers">
</chat-message-item>
</view>
<view v-else="!selectionMode" >
<chat-message-item :ref="'message'+msgInfo.id" v-if="idx >= showMinIdx" <chat-message-item :ref="'message'+msgInfo.id" v-if="idx >= showMinIdx"
:headImage="headImage(msgInfo)" :headImage="headImage(msgInfo)"
@call="onRtCall(msgInfo)" @call="onRtCall(msgInfo)"
@ -25,6 +50,7 @@
:msgInfo="msgInfo" :msgInfo="msgInfo"
:groupMembers="groupMembers"> :groupMembers="groupMembers">
</chat-message-item> </chat-message-item>
</view>
</view> </view>
<view id="chat-bottom" style="height: 2rpx;"></view> <view id="chat-bottom" style="height: 2rpx;"></view>
</scroll-view> </scroll-view>
@ -34,6 +60,23 @@
</view> </view>
<submit ref="submitRef" @inputs="onInputs" @send-message="onSendMessage" @heights="onHeights" /> <submit ref="submitRef" @inputs="onInputs" @send-message="onSendMessage" @heights="onHeights" />
</view> </view>
<!-- ========== 选择模式底部操作栏独立 fixed 层级避免嵌套 fixed 渲染异常 ========== -->
<view v-if="selectionMode" class="selection-bar">
<view class="selection-right">
<text class="selection-count">已选 {{ selectedMessageIds.length }} </text>
<button class="btn-cancel-selection" @click="cancelSelection"></button>
<button class="btn-confirm-selection" @click="nextStep"></button>
</view>
</view>
<!-- ========== 投诉底部弹窗点击右上角符号弹出 ========== -->
<view v-if="showReportMenu" class="report-menu-mask" @click="closeReportMenu">
<view class="report-menu-panel" @click.stop>
<view class="report-menu-title">请选择操作</view>
<view class="report-menu-item report-menu-complain" @click="enterSelectionMode"></view>
<view class="report-menu-item" @click="closeReportMenu"></view>
</view>
</view>
</view> </view>
</template> </template>
@ -81,7 +124,12 @@ export default {
isReadOnly: false, isReadOnly: false,
playingAudio: null, playingAudio: null,
isInBottom: true, isInBottom: true,
newMessageSize: 0 newMessageSize: 0,
// ===== =====
selectionMode: false, //
selectedMessageIds: [], // ID
showReportMenu: false, // /
selectableTypes: [0, 'TEXT', 1, 'IMAGE', 2, 'AUDIO', 3, 'VIDEO'] //
}; };
}, },
computed: { computed: {
@ -206,6 +254,9 @@ export default {
}, },
onUnload() { onUnload() {
this.unListenKeyboard(); this.unListenKeyboard();
// 退
this.selectionMode = false;
this.selectedMessageIds = [];
}, },
onShow() { onShow() {
if (this.needScrollToBottom) { if (this.needScrollToBottom) {
@ -216,6 +267,113 @@ export default {
}, },
methods: { methods: {
// ==================== ====================
/**
* 获取可选的消息列表排除系统提示消息
*/
getAvailableMessages() {
return (this.chat?.messages || []).filter(msg => {
//
if (msg.type === 'TIP_TEXT') return false;
//
if (msg.isRecalled) return false;
return true;
});
},
canReport(msgInfo) {
if (!msgInfo) return false
// 1.
if (msgInfo.selfSend) return false
// 2. ""
if (msgInfo.type === 21) return false
if (msgInfo.type === 20) return false
return true
},
/**
* 切换选择模式
*/
enterSelectionMode() {
this.showReportMenu = false; //
this.selectionMode = true;
this.selectedMessageIds = [];
//
uni.hideKeyboard();
//
this.switchChatTabBox('none');
},
/**
* 关闭投诉底部弹窗
*/
closeReportMenu() {
this.showReportMenu = false;
},
/**
* 取消选择模式
*/
cancelSelection() {
this.selectionMode = false;
this.selectedMessageIds = [];
},
/**
* 切换单条消息的选中状态
*/
toggleSelectMessage(msgId) {
//
const msg = this.chat.messages.find(m => m.id === msgId);
if (!msg || !this.canReport(msg)) return
const idx = this.selectedMessageIds.indexOf(msgId);
if (idx > -1) {
this.selectedMessageIds.splice(idx, 1);
} else {
this.selectedMessageIds.push(msgId);
}
},
/**
* 点击"下一步"校验选择并跳转到投诉页
*/
nextStep() {
if (this.selectedMessageIds.length === 0) {
uni.showToast({ title: '请至少选择一条消息', icon: 'none' });
return;
}
//
const sendType = this.chat.type === 'GROUP' ? '1' : '2';
const senderId = this.chat.targetId;
const senderName = this.chat.showName || '';
const senderAvatar = this.chat.headImage || '';
// IDURL
//
const selectedRecords = this.selectedMessageIds
.map(id => this.chat.messages.find(m => m.id === id))
.filter(msg => !!msg);
//
uni.setStorageSync('report_selected_messages', selectedRecords);
uni.setStorageSync('report_send_type', sendType);
uni.setStorageSync('report_sender_id', senderId);
uni.setStorageSync('report_sender_name', senderName);
uni.setStorageSync('report_sender_avatar', senderAvatar);
// 退
this.selectionMode = false;
this.selectedMessageIds = [];
//
uni.navigateTo({
url: `/pages/chatbox/report?sendType=${sendType}&senderId=${senderId}&fromChat=true`
});
},
onNavigationBarButtonTap(e) {
// /
if (this.selectionMode) return; //
this.showReportMenu = true;
},
// Vuex actions // Vuex actions
...mapActions('chatStore', [ ...mapActions('chatStore', [
'insertMessage', 'insertMessage',
@ -1190,4 +1348,137 @@ export default {
} }
} }
/* ========== 选择模式样式 ========== */
.message-wrapper {
display: flex;
align-items: flex-start;
padding: 4rpx 0;
transition: background 0.2s;
/* 本人发送的消息:整行靠右,保持消息原右侧位置不变 */
&.self {
flex-direction: row-reverse;
justify-content: flex-start;
}
}
.message-wrapper .checkbox-container {
padding: 20rpx 12rpx 0 16rpx;
flex-shrink: 0;
align-self: stretch;
}
.checkbox {
width: 42rpx;
height: 42rpx;
border-radius: 50%;
border: 2rpx solid #ccc;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
color: #fff;
flex-shrink: 0;
background: #fff;
transition: all 0.2s;
&.checked {
background: $im-color-primary;
border-color: $im-color-primary;
}
}
/* ========== 底部选择操作栏 ========== */
.selection-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #ffffff;
border-top: 1rpx solid #e8e8e8;
padding: 16rpx 24rpx;
padding-bottom: env(safe-area-inset-bottom, 16rpx);
display: flex;
align-items: center;
justify-content: flex-end;
z-index: 1000;
box-shadow: 0 -4rpx 20rpx rgba(0,0,0,0.06);
.selection-right {
display: flex;
align-items: center;
gap: 16rpx;
.selection-count {
font-size: 26rpx;
color: #999;
margin-right: 8rpx;
}
.btn-cancel-selection {
padding: 10rpx 28rpx;
height: 60rpx;
line-height: 40rpx;
background: #f5f7fa;
color: #666;
font-size: 26rpx;
border-radius: 30rpx;
border: none;
}
.btn-confirm-selection {
padding: 10rpx 32rpx;
height: 60rpx;
line-height: 40rpx;
background: $im-color-primary;
color: #fff;
font-size: 26rpx;
border-radius: 30rpx;
border: none;
box-shadow: 0 4rpx 12rpx $im-color-primary;
&:disabled {
background: #ccc;
box-shadow: none;
}
}
}
}
/* ========== 投诉底部弹窗样式 ========== */
.report-menu-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.45);
z-index: 999;
display: flex;
align-items: flex-end;
}
.report-menu-panel {
width: 100%;
background: #ffffff;
border-radius: 24rpx 24rpx 0 0;
padding-bottom: env(safe-area-inset-bottom, 24rpx);
overflow: hidden;
}
.report-menu-title {
text-align: center;
font-size: 26rpx;
color: #999999;
padding: 24rpx 0 12rpx;
}
.report-menu-item {
height: 100rpx;
line-height: 100rpx;
text-align: center;
font-size: 32rpx;
color: #333333;
border-top: 1rpx solid #f0f0f0;
&:active {
background: #f5f5f5;
}
}
.report-menu-complain {
color: $im-color-primary;
font-weight: 600;
}
</style> </style>

@ -0,0 +1,563 @@
<template>
<view class="report-container">
<!-- ========== 被投诉人信息 ========== -->
<view class="target-info">
<view class="target-avatar">
<image :src="targetAvatar || '/static/default-avatar.png'" mode="aspectFill"></image>
<view v-if="isGroup" class="group-badge"></view>
</view>
<view class="target-detail">
<text class="target-type">{{ targetName }}</text>
</view>
</view>
<!-- ========== 聊天记录展示只读 ========== -->
<view class="selected-messages">
<view class="selected-header">
<text class="selected-title">投诉的聊天记录</text>
</view>
<view v-if="allMessages.length === 0" class="empty-selected">
暂无聊天记录可投诉
</view>
<view v-else class="message-list">
<view v-for="msg in allMessages" :key="msg.id" class="message-item">
<view class="msg-content-wrapper">
<view class="msg-header">
<image :src="getSenderAvatar(msg)" mode="aspectFill"></image>
<text class="sender-name">{{ getSenderName(msg) }}</text>
<text class="msg-time">{{ formatTime(msg.sendTime) }}</text>
</view>
<view class="msg-body">
<text>{{ getFullMessageContent(msg) }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- ========== 投诉原因 ========== -->
<view class="reason-section">
<view class="section-title">投诉原因 <text class="required">*</text></view>
<radio-group @change="onReasonChange">
<label class="reason-item" v-for="item in reasonList" :key="item.value">
<radio :value="String(item.value)" :checked="selectedReason === item.value" color="#00a89b" />
<text class="reason-text">{{ item.label }}</text>
</label>
</radio-group>
</view>
<!-- ========== 补充描述 ========== -->
<view class="desc-section">
<view class="section-title">补充描述 <text class="optional">选填</text></view>
<textarea
class="desc-input"
v-model="description"
placeholder="请详细描述您遇到的问题,以便我们更快处理..."
maxlength="200"
auto-height
/>
<view class="char-count">{{ description.length }}/200</view>
</view>
<!-- ========== 提交按钮 ========== -->
<view class="btn-wrapper">
<button class="submit-btn" @click="submitReport" >
提交投诉
</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
// ===== =====
sendType: '1',
senderId: '',
// ===== =====
targetName: '',
targetAvatar: '',
isGroup: false,
// ===== / =====
groupMembers: [],
// ===== =====
allMessages: [],
// ===== =====
reasonList: [
{ label: '色情低俗', value: 1 },
{ label: '暴力恐怖', value: 2 },
{ label: '政治敏感', value: 3 },
{ label: '骚扰谩骂', value: 4 },
{ label: '虚假诈骗', value: 5 },
{ label: '违规宣传', value: 6 },
{ label: '其他', value: 7 }
],
selectedReason: null,
description: '',
// ===== =====
currentUser: {}
};
},
computed: {
},
onLoad(options) {
this.sendType = options.sendType || '1';
this.senderId = options.senderId || '';
this.isGroup = this.sendType === '2';
this.currentUser = uni.getStorageSync('user') || {};
// ===== fromChat=true =====
if (options.fromChat === 'true') {
this.initFromChatCache();
return;
}
// ===== =====
if (!this.senderId) {
uni.showToast({ title: '参数错误', icon: 'none' });
setTimeout(() => uni.navigateBack(), 1500);
return;
}
this.initPage();
},
methods: {
// ==================== ====================
async initPage() {
uni.showLoading({ title: '加载中...' });
try {
if (this.isGroup) {
await this.loadGroupInfo();
} else {
await this.loadUserInfo();
}
} catch (e) {
console.error('加载失败', e);
uni.showToast({ title: '加载失败,请重试', icon: 'none' });
} finally {
uni.hideLoading();
}
},
// ==================== fromChat ====================
initFromChatCache() {
const cachedRecords = uni.getStorageSync('report_selected_messages') || [];
const cachedSenderId = uni.getStorageSync('report_sender_id');
const cachedSendType = uni.getStorageSync('report_send_type');
const cachedSenderName = uni.getStorageSync('report_sender_name');
const cachedSenderAvatar = uni.getStorageSync('report_sender_avatar');
// 退
if (!cachedRecords || cachedRecords.length === 0) {
uni.showToast({ title: '未获取到聊天记录', icon: 'none' });
if (this.senderId) {
this.initPage();
} else {
setTimeout(() => uni.navigateBack(), 1500);
}
return;
}
//
this.allMessages = cachedRecords;
//
this.senderId = cachedSenderId || this.senderId;
if (cachedSendType) {
this.sendType = String(cachedSendType);
this.isGroup = this.sendType === '2';
}
this.targetName = cachedSenderName || this.targetName;
this.targetAvatar = cachedSenderAvatar || this.targetAvatar;
// /
if (this.isGroup && this.senderId) {
this.loadGroupMembersOnly();
}
// 使
this.clearReportCache();
},
// fromChat 使
async loadGroupMembersOnly() {
try {
const memberRes = await uni.$http.get(`/api/group/members/${this.senderId}`);
this.groupMembers = memberRes.data.data || [];
} catch (e) {
console.error('加载群成员失败', e);
}
},
//
clearReportCache() {
uni.removeStorageSync('report_selected_messages');
uni.removeStorageSync('report_selected_records');
uni.removeStorageSync('report_sender_id');
uni.removeStorageSync('report_send_type');
uni.removeStorageSync('report_sender_name');
uni.removeStorageSync('report_sender_avatar');
},
// ==================== ====================
async loadGroupInfo() {
const res = await uni.$http.get(`/api/group/find/${this.senderId}`);
const group = res.data.data;
this.targetName = group.groupName;
this.targetAvatar = group.headImage || '';
const memberRes = await uni.$http.get(`/api/group/members/${this.senderId}`);
this.groupMembers = memberRes.data.data || [];
},
async loadUserInfo() {
const res = await uni.$http.get(`/api/friend/find/${this.senderId}`);
const user = res.data.data;
this.targetName = user.nickName || user.showNickName || '未知用户';
this.targetAvatar = user.headImage || '';
},
// ==================== ====================
getSenderAvatar(msg) {
if (msg.selfSend) {
return this.currentUser.avatar || '';
}
if (this.isGroup && msg.sendId) {
const member = this.groupMembers.find(m => m.userId === msg.sendId);
return member ? member.headImage : '';
}
return this.targetAvatar;
},
getSenderName(msg) {
if (msg.selfSend) {
return this.currentUser.nickName || '我';
}
if (this.isGroup && msg.sendId) {
const member = this.groupMembers.find(m => m.userId === msg.sendId);
return member ? (member.showNickName || member.nickName) : '未知';
}
return this.targetName;
},
// ==================== ====================
getFullMessageContent(msg) {
try {
const type = msg.type;
// -
if (type === "0" || type === 'TEXT') {
return msg.content || '';
}
//
if (type === "1" || type === 'IMAGE') {
return '📷 [图片]';
}
//
if (type === "2" || type === 'AUDIO') {
return '🎤 [语音]';
}
//
if (type === "3" || type === 'VIDEO') {
return '🎬 [视频]';
}
//
if (type === "4" || type === 'FILE') {
try {
const data = JSON.parse(msg.content);
return `📄 [文件] ${data.name || ''}`;
} catch (e) {
return '📄 [文件]';
}
}
//
return '[其他消息]';
} catch (e) {
return '[消息]';
}
},
// ==================== ====================
formatTime(timestamp) {
if (!timestamp) return '';
const date = new Date(timestamp);
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${month}/${day} ${hours}:${minutes}`;
},
// ==================== ====================
onReasonChange(e) {
this.selectedReason = Number(e.detail.value);
},
// ==================== ====================
async submitReport() {
let tip = '';
if (!this.selectedReason) {
tip = '请选择投诉原因';
uni.showToast({ title: tip || '请完善投诉信息', icon: 'none' });
return;
}
if (this.allMessages.length === 0) {
tip = '请至少选择一条聊天记录';
uni.showToast({ title: tip || '请完善投诉信息', icon: 'none' });
return;
}
uni.showLoading({ title: '提交中...' });
try {
//
const messageIds = this.allMessages.map(m => m.id);
const res = await uni.$http.post('/api/report/submit', {
targetUserId: this.senderId,
targetType: this.isGroup ? 2 : 1,
targetGroupId: this.isGroup ? this.senderId : null,
messageIds,
messageDetails: this.allMessages, // content/type/sendId/sendTime
reason: this.selectedReason,
description: this.description.trim()
});
if (res.data.success ) {
uni.hideLoading();
uni.showToast({ title: '投诉已提交,我们会尽快处理', icon: 'success' });
setTimeout(() => uni.navigateBack(), 1500);
} else {
uni.hideLoading();
uni.showToast({ title: res.data.msg || '提交失败', icon: 'none' });
}
} catch (e) {
uni.hideLoading();
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
console.error('举报提交失败', e);
}
}
}
};
</script>
<style lang="scss" scoped>
.report-container {
min-height: 100vh;
background-color: #f5f7fa;
padding: 30rpx;
box-sizing: border-box;
}
/* ========== 被投诉人信息 ========== */
.target-info {
background: #ffffff;
border-radius: 20rpx;
padding: 30rpx;
display: flex;
align-items: center;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.05);
.target-avatar {
position: relative;
width: 100rpx;
height: 100rpx;
image {
width: 100%;
height: 100%;
border-radius: 50%;
background: #eee;
}
.group-badge {
position: absolute;
bottom: -4rpx;
right: -4rpx;
background: $im-color-primary;
color: #fff;
font-size: 20rpx;
padding: 2rpx 12rpx;
border-radius: 20rpx;
}
}
.target-detail {
flex: 1;
margin-left: 24rpx;
.target-type {
font-size: 34rpx;
font-weight: 600;
color: #333;
display: block;
}
}
}
/* ========== 聊天记录展示 ========== */
.selected-messages {
background: #ffffff;
border-radius: 20rpx;
padding: 24rpx;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.05);
.selected-header {
display: flex;
justify-content: space-between;
align-items: center;
.selected-title {
font-size: 30rpx;
font-weight: 600;
color: #333;
}
}
.empty-selected {
margin-top: 16rpx;
padding: 30rpx 0;
text-align: center;
color: #ccc;
font-size: 26rpx;
border: 2rpx dashed #eee;
border-radius: 12rpx;
}
.message-list {
margin-top: 16rpx;
}
}
/* ========== 聊天记录条目(只读展示) ========== */
.message-item {
display: flex;
align-items: flex-start;
padding: 16rpx 10rpx;
border-bottom: 1rpx solid #f5f5f5;
&:last-child {
border-bottom: none;
}
.msg-content-wrapper {
flex: 1;
min-width: 0;
.msg-header {
display: flex;
align-items: center;
gap: 12rpx;
image {
width: 36rpx;
height: 36rpx;
border-radius: 50%;
background: #eee;
flex-shrink: 0;
}
.sender-name {
font-size: 24rpx;
color: #333;
font-weight: 500;
}
.msg-time {
font-size: 20rpx;
color: #bbb;
margin-left: auto;
flex-shrink: 0;
}
}
.msg-body {
font-size: 28rpx;
color: #555;
margin-top: 4rpx;
word-break: break-all;
line-height: 1.6;
white-space: pre-wrap;
}
}
}
/* ========== 投诉原因 & 描述 ========== */
.reason-section, .desc-section {
background: #ffffff;
border-radius: 20rpx;
padding: 24rpx;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.05);
}
.section-title {
font-size: 30rpx;
font-weight: 600;
color: #333;
margin-bottom: 16rpx;
.required {
color: #e64340;
}
.optional {
font-weight: 400;
color: #999;
font-size: 24rpx;
}
}
.reason-item {
display: flex;
align-items: center;
padding: 14rpx 0;
border-bottom: 1rpx solid #f5f5f5;
&:last-child {
border-bottom: none;
}
radio {
transform: scale(0.8);
margin-right: 16rpx;
}
.reason-text {
font-size: 28rpx;
color: #333;
}
}
.desc-input {
width: 100%;
min-height: 140rpx;
padding: 16rpx;
background: #f8fafc;
border-radius: 12rpx;
font-size: 28rpx;
color: #333;
border: none;
box-sizing: border-box;
}
.char-count {
text-align: right;
font-size: 24rpx;
color: #bbb;
margin-top: 8rpx;
}
/* ========== 提交按钮 ========== */
.btn-wrapper {
padding: 20rpx 0 40rpx;
.submit-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background: $im-color-primary;
color: #fff;
font-size: 32rpx;
border-radius: 44rpx;
border: none;
box-shadow: 0 4rpx 12rpx $im-color-primary;
&:disabled {
background: #ccc;
box-shadow: none;
}
}
}
</style>

@ -6,11 +6,11 @@
<!-- 登录方式切换 Tabs--> <!-- 登录方式切换 Tabs-->
<view class="login-tabs"> <view class="login-tabs">
<view class="tab-item" :class="{ active: loginType === 'pwd' }" @click="loginType = 'pwd'"> <!-- <view class="tab-item" :class="{ active: loginType === 'pwd' }" @click="loginType = 'pwd'">-->
密码登录 <!-- 密码登录-->
</view> <!-- </view>-->
<view class="tab-item" :class="{ active: loginType === 'sms' }" @click="loginType = 'sms'"> <view class="tab-item" :class="{ active: loginType === 'sms' }" @click="loginType = 'sms'">
短信登录(待审核) 短信登录
</view> </view>
</view> </view>
@ -28,16 +28,16 @@
<uni-forms-item prop="password"> <uni-forms-item prop="password">
<uni-easyinput type="password" placeholder="请输入密码" v-model="form.password" @blur="mmhandleBlur" <uni-easyinput type="password" placeholder="请输入密码" v-model="form.password" @blur="mmhandleBlur"
maxlength="12" :inputBorder="false" :clearable="false" :placeholderStyle="placeholderStyle" /> maxlength="12" :inputBorder="false" :clearable="false" :placeholderStyle="placeholderStyle" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item prop="code"> <uni-forms-item prop="code">
<uni-easyinput type="text" placeholder="请输入验证码" v-model.trim="form.code" :inputBorder="false" <uni-easyinput type="text" placeholder="请输入验证码" v-model.trim="form.code" :inputBorder="false"
:clearable="false" :placeholderStyle="placeholderStyle" /> :clearable="false" :placeholderStyle="placeholderStyle" />
<image :src="checkimg" class="checkimg" mode="scaleToFill"></image> <image :src="checkimg" class="checkimg" mode="scaleToFill"></image>
<view class="codebtn" @click="getcode">?</view> <view class="codebtn" @click="getcode">?</view>
</uni-forms-item> </uni-forms-item>
<button type="primary" class="logincla" @click="loginpwdDo"></button> <button type="primary" class="logincla" @click="loginpwdDo"></button>
</block> </block>
@ -70,11 +70,11 @@
<view v-if="iflogin" class="loginno">..</view> <view v-if="iflogin" class="loginno">..</view>
<!-- <view class="regcon"> <!-- <view class="regcon">
<view class="regtxtr" @click="gotoregister"> </view> <view class="regtxtr" @click="gotoregister"> </view>
</view> --> </view> -->
<view class="xycon"> <view class="xycon">
<view class="sitem"> <view class="sitem">
<view class="selbtn"> <view class="selbtn">
@ -82,7 +82,7 @@
</view> </view>
<view class="conright"> <view class="conright">
<text class="sname"> <text class="sname">
我已阅读并同意 我已阅读并同意
</text> </text>
<text class="adress" @click="gotofw"> <text class="adress" @click="gotofw">
用户服务协议 用户服务协议
@ -95,8 +95,8 @@
</text> </text>
</view> </view>
</view> </view>
</view> </view>
<view class="regcon"> <view class="regcon">
<!-- <view class="regtxtr" @click="gotoregister"> </view>--> <!-- <view class="regtxtr" @click="gotoregister"> </view>-->
@ -132,16 +132,16 @@
gcj02:"1", gcj02:"1",
address:'', address:'',
longitude:'', // longitude:'', //
latitude:'', // latitude:'', //
checkbox1: [1], checkbox1: [1],
checkboxitem: [{ checkboxitem: [{
text: '', text: '',
value: 1, value: 1,
}], }],
ifxy: false, // ifxy: false, //
loginType:'pwd', loginType:'sms',
smsCountdown: 0, // smsCountdown: 0, //
smsTimer: null, // smsTimer: null, //
} }
}, },
onLoad(){ onLoad(){
@ -355,7 +355,7 @@
}, },
// //
async loginpwdDo(){ async loginpwdDo(){
// //
if(!this.checkxy()){ if(!this.checkxy()){
return false; return false;
@ -509,7 +509,7 @@
var shoplist=[shop] var shoplist=[shop]
myCache('shoplist',shoplist); myCache('shoplist',shoplist);
} }
}, },
gotofw(){ gotofw(){
uni.navigateTo({ uni.navigateTo({
url: `/pages/user/infofw` url: `/pages/user/infofw`
@ -519,7 +519,7 @@
uni.navigateTo({ uni.navigateTo({
url: `/pages/user/infoys` url: `/pages/user/infoys`
}); });
}, },
// ========== ========== // ========== ==========
/** /**
* 发送短信验证码 * 发送短信验证码
@ -603,7 +603,7 @@
async loginSmsDo() { async loginSmsDo() {
const phone = this.form.phonenumber; const phone = this.form.phonenumber;
const smsCode = this.form.smsCode; const smsCode = this.form.smsCode;
// //
if(!this.checkxy()){ if(!this.checkxy()){
return false; return false;
@ -672,7 +672,7 @@
duration: 2000 duration: 2000
}); });
} }
}, },
} }
} }
@ -887,7 +887,7 @@
padding: 0 0 40rpx 0; padding: 0 0 40rpx 0;
} }
.xycon{ .xycon{
display: flex; display: flex;
align-items: center; align-items: center;
@ -910,18 +910,18 @@
margin-left: 10rpx; margin-left: 10rpx;
} }
.sname{ .sname{
font-size: 24rpx; font-size: 24rpx;
font-weight: 400; font-weight: 400;
color: #23262F; color: #23262F;
line-height: 36rpx; line-height: 36rpx;
} }
.adress{ .adress{
font-size: 24rpx; font-size: 24rpx;
font-weight: 400; font-weight: 400;
color: #00a89b; color: #00a89b;
line-height: 36rpx; line-height: 36rpx;
} }
} }
} }
</style> </style>

@ -69,7 +69,7 @@
{ {
text: '未知', text: '未知',
value: 0 value: 0
}, },
{ {
text: '男', text: '男',
value: 2 value: 2
@ -116,7 +116,7 @@
// //
this.getmember(); this.getmember();
}, },
onShow() { onShow() {
this.iflogin(); this.iflogin();
}, },
methods: { methods: {
@ -153,7 +153,7 @@
var token = uni.getStorageSync("token"); var token = uni.getStorageSync("token");
try{ try{
uni.uploadFile({ uni.uploadFile({
url: 'https://www.sanduolantoyoga.com/yoga/api/my/avatarUpload', // url: 'https://yoga.runpengsoft.com/yoga/api/my/avatarUpload', //
filePath: tempFilePaths, filePath: tempFilePaths,
name: 'file', name: 'file',
header: { header: {
@ -162,7 +162,7 @@
}, },
success: res => { success: res => {
uni.hideLoading(); uni.hideLoading();
myCache("ifuserEdit", true); myCache("ifuserEdit", true);
var data = JSON.parse(res.data); var data = JSON.parse(res.data);
if (data.success) { if (data.success) {
uni.showToast({ uni.showToast({
@ -218,24 +218,25 @@
}, },
async savedo() { async savedo() {
var that = this; var that = this;
try { try {
uni.showLoading({ uni.showLoading({
title: '信息保存中....' title: '信息保存中....'
}); });
const { data: res } = await uni.$http.post("/api/my/modifyMyInformation", that.valiFormData); const { data: res } = await uni.$http.post("/api/my/modifyMyInformation", that.valiFormData);
if (res.success) { if (res.success) {
this.msgType = 'success'; this.msgType = 'success';
this.messageText = `提交成功!`; this.messageText = `提交成功!`;
this.$refs.message.open(); this.$refs.message.open();
myCache("ifuserEdit", true); myCache("ifuserEdit", true);
var user = myCache('userInfo'); var user = myCache('userInfo');
myCache('user',{ myCache('user',{
userid:user.userid, userid:res.data.id,
nickName:that.valiFormData.nickName, nickName:res.data.nickName,
username:user.username, username:user.username,
userphone:user.userphone, userphone:user.userphone,
signature:that.valiFormData.signature, signature:res.data.signature,
avatar:that.valiFormData.avatar avatar:res.data.avatar
}); });
setTimeout(() => { setTimeout(() => {
// //
@ -397,4 +398,4 @@
padding: 10rpx 48rpx; padding: 10rpx 48rpx;
box-sizing: border-box; box-sizing: border-box;
} }
</style> </style>

@ -248,7 +248,33 @@ const chatModule = {
} }
chat.stored = false; chat.stored = false;
}, },
BLOCK_MESSAGE(state, { idx, targetId, blockContent, sendTime }) {
const chat = state.chats[idx];
if (!chat) return;
for (let m of chat.messages) {
if (m.id === targetId) {
m.status = MESSAGE_STATUS.BLOCKED;
m.content = blockContent;
m.type = MESSAGE_TYPE.TIP_TEXT; // 改为提示型消息
// 如果屏蔽的是最后一条消息,更新会话摘要
if (chat.messages[chat.messages.length - 1] === m) {
chat.lastContent = blockContent;
chat.lastSendTime = sendTime;
chat.sendNickName = '';
}
break;
}
}
// 处理引用消息(如有引用该消息的内容,也屏蔽)
for (let m of chat.messages) {
if (m.quoteMessage && m.quoteMessage.id === targetId) {
m.quoteMessage.content = "内容已被屏蔽";
m.quoteMessage.status = MESSAGE_STATUS.BLOCKED;
m.quoteMessage.type = MESSAGE_TYPE.TIP_TEXT;
}
}
chat.stored = false;
},
// 更新会话头像和名称 // 更新会话头像和名称
UPDATE_CHAT_AVATAR_NAME(state, { type, targetId, headImage, showName }) { UPDATE_CHAT_AVATAR_NAME(state, { type, targetId, headImage, showName }) {
for (let chat of state.chats) { for (let chat of state.chats) {
@ -481,8 +507,12 @@ const chatModule = {
let atAll = chat.atAll || false; let atAll = chat.atAll || false;
// 未读计数 // 未读计数
if (!msgInfo.selfSend && msgInfoStatus !== MESSAGE_STATUS.READED && if (!msgInfo.selfSend
msgInfoStatus !== MESSAGE_STATUS.RECALL && msgInfoType !== MESSAGE_TYPE.TIP_TEXT) { && msgInfoStatus !== MESSAGE_STATUS.READED
&& msgInfoStatus !== MESSAGE_STATUS.RECALL
&& msgInfoStatus !== MESSAGE_STATUS.BLOCKED
&& msgInfoType !== MESSAGE_TYPE.TIP_TEXT
) {
unreadCount++; unreadCount++;
} }
@ -589,6 +619,39 @@ const chatModule = {
dispatch('saveToStorage', { withColdMessage: isCold }); dispatch('saveToStorage', { withColdMessage: isCold });
}, },
/**
* 系统/管理员屏蔽违规消息
* @param {Object} context - Vuex action 上下文
* @param {Object} payload - { msgInfo, chatInfo }
* msgInfo: { id: 被屏蔽的消息ID, sendTime: 消息发送时间 }
*/
blockMessage({ getters, commit, dispatch }, { msgInfo, chatInfo }) {
const idx = getters.findChatIdx(chatInfo);
if (idx === -1) return;
const chat = getters.findChat(chatInfo);
const targetId = msgInfo.content; // 被屏蔽的消
const blockContent = '该消息因违规已被屏蔽';
// 判断是否为冷消息(用于存储优化)
let isCold = false;
for (let i = 0; i < chat.messages.length; i++) {
if (chat.messages[i].id === targetId) {
isCold = i < chat.hotMinIdx;
break;
}
}
// 提交 mutation 修改消息状态
commit('BLOCK_MESSAGE', {
idx,
targetId,
blockContent,
sendTime: msgInfo.sendTime || Date.now(),
});
// 屏蔽不增加未读计数(系统操作,不打扰用户)
// 直接保存到存储
dispatch('saveToStorage', { withColdMessage: isCold });
},
// ========== 更新会话信息(好友) ========== // ========== 更新会话信息(好友) ==========
updateChatFromFriend({ commit, dispatch }, friend) { updateChatFromFriend({ commit, dispatch }, friend) {
commit('UPDATE_CHAT_AVATAR_NAME', { commit('UPDATE_CHAT_AVATAR_NAME', {

@ -5,9 +5,9 @@ function myCache(key, value, seconds = 3600 * 240) {// 默认是24小时
// 设置缓存 // 设置缓存
let expire = nowTime + Number(seconds); let expire = nowTime + Number(seconds);
uni.setStorageSync(key,JSON.stringify(value) + '§' +expire) uni.setStorageSync(key,JSON.stringify(value) + '§' +expire)
} }
else if (key && !value) { else if (key && !value) {
if(value==undefined){ if(value==undefined){
// 获取缓存 // 获取缓存
let val = uni.getStorageSync(key); let val = uni.getStorageSync(key);
@ -39,25 +39,25 @@ function getRemoteFile(path) {
} }
function getRemoteFile0(path) { function getRemoteFile0(path) {
if (path.includes("/profile/upload/")) { if (path.includes("/profile/upload/")) {
return `https://www.sanduolantoyoga.com/yoga${path}`; return `https://yoga.runpengsoft.com/yoga${path}`;
} }
else{ else{
return `https://www.sanduolantoyoga.com/yoga/profile/upload${path}`; return `https://yoga.runpengsoft.com/yoga/profile/upload${path}`;
} }
} }
function getRemoteHtmlFile(str) { function getRemoteHtmlFile(str) {
// const reg = new RegExp('"/profile/upload/', 'g'); //g,表示全部替换。 // const reg = new RegExp('"/profile/upload/', 'g'); //g,表示全部替换。
// str = str.replace(reg, '"https://www.sanduolantoyoga.com/profile/upload/'); // str = str.replace(reg, '"https://yoga.runpengsoft.com/profile/upload/');
// const reg1 = new RegExp('\'/profile/upload/', 'g'); //g,表示全部替换。 // const reg1 = new RegExp('\'/profile/upload/', 'g'); //g,表示全部替换。
// str = str.replace(reg1, '\'https://www.sanduolantoyoga.com/profile/upload/'); // str = str.replace(reg1, '\'https://yoga.runpengsoft.com/profile/upload/');
return str; return str;
} }
function getRemoteHtmlFile0(str) { function getRemoteHtmlFile0(str) {
// https://xcfzk.com/ // https://xcfzk.com/
const reg = new RegExp('"/profile/upload/', 'g'); //g,表示全部替换。 const reg = new RegExp('"/profile/upload/', 'g'); //g,表示全部替换。
str = str.replace(reg, '"https://www.sanduolantoyoga.com/profile/upload/'); str = str.replace(reg, '"https://yoga.runpengsoft.com/profile/upload/');
const reg1 = new RegExp('\'/profile/upload/', 'g'); //g,表示全部替换。 const reg1 = new RegExp('\'/profile/upload/', 'g'); //g,表示全部替换。
str = str.replace(reg1, '\'https://www.sanduolantoyoga.com/profile/upload/'); str = str.replace(reg1, '\'https://yoga.runpengsoft.com/profile/upload/');
return str; return str;
} }
function getNowDate() { function getNowDate() {
@ -126,7 +126,7 @@ function getcartLoseNum() {
return num return num
}; };
// function getmessageNum() { // function getmessageNum() {
// var user=myCache("user"); // var user=myCache("user");
// var userid = user.userid? user.userid:''; // var userid = user.userid? user.userid:'';
// var num=0; // var num=0;
// if(userid){ // if(userid){
@ -140,7 +140,7 @@ function getcartLoseNum() {
// return num // return num
// } // }
// }; // };
function getmessageNum() { function getmessageNum() {
var num= myCache("massageCount")?myCache("massageCount"):0; var num= myCache("massageCount")?myCache("massageCount"):0;
return num; return num;
}; };

Loading…
Cancel
Save