聊天修改
This commit is contained in:
parent
3468be4716
commit
b5bc4cd405
|
|
@ -76,3 +76,17 @@ export function getSearchBanner() {
|
|||
export function setSearchBanner(imageUrl: string) {
|
||||
return request.post('/admin/config/searchBanner', { imageUrl })
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取管家二维码
|
||||
*/
|
||||
export function getButlerQrcode() {
|
||||
return request.get('/admin/config/butlerQrcode')
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置管家二维码
|
||||
*/
|
||||
export function setButlerQrcode(imageUrl: string) {
|
||||
return request.post('/admin/config/butlerQrcode', { imageUrl })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,3 +66,13 @@ export function createTestUsers(count: number, gender?: number): Promise<number[
|
|||
export function deleteUser(id: number): Promise<void> {
|
||||
return request.delete(`/admin/users/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户联系次数
|
||||
* @param id 用户ID
|
||||
* @param contactCount 联系次数
|
||||
* @returns 操作结果
|
||||
*/
|
||||
export function updateContactCount(id: number, contactCount: number): Promise<void> {
|
||||
return request.put(`/admin/users/${id}/contact-count`, { contactCount })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,29 @@
|
|||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 管家二维码设置 -->
|
||||
<el-form-item label="管家二维码">
|
||||
<div class="qrcode-upload">
|
||||
<el-upload
|
||||
class="qrcode-uploader"
|
||||
:action="uploadUrl"
|
||||
:headers="uploadHeaders"
|
||||
:show-file-list="false"
|
||||
:on-success="handleQrcodeSuccess"
|
||||
:before-upload="beforeAvatarUpload"
|
||||
accept="image/*"
|
||||
>
|
||||
<img v-if="configForm.butlerQrcode" :src="getFullUrl(configForm.butlerQrcode)" class="qrcode-preview" />
|
||||
<el-icon v-else class="qrcode-uploader-icon"><Plus /></el-icon>
|
||||
</el-upload>
|
||||
<div class="avatar-tip">
|
||||
<p>建议尺寸:300x300像素</p>
|
||||
<p>支持格式:JPG、PNG</p>
|
||||
<p>小程序"联系我们"页面展示的二维码</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="saveBasicConfig" :loading="saving">
|
||||
保存配置
|
||||
|
|
@ -119,7 +142,9 @@ import {
|
|||
getPrivacyPolicy,
|
||||
setPrivacyPolicy,
|
||||
getSearchBanner,
|
||||
setSearchBanner
|
||||
setSearchBanner,
|
||||
getButlerQrcode,
|
||||
setButlerQrcode
|
||||
} from '@/api/config'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
|
|
@ -131,7 +156,8 @@ const activeTab = ref('basic')
|
|||
|
||||
const configForm = ref({
|
||||
defaultAvatar: '',
|
||||
searchBanner: ''
|
||||
searchBanner: '',
|
||||
butlerQrcode: ''
|
||||
})
|
||||
|
||||
const agreementForm = ref({
|
||||
|
|
@ -157,9 +183,10 @@ const getFullUrl = (url) => {
|
|||
|
||||
const loadConfig = async () => {
|
||||
try {
|
||||
const [avatarRes, bannerRes] = await Promise.all([
|
||||
const [avatarRes, bannerRes, qrcodeRes] = await Promise.all([
|
||||
getDefaultAvatar(),
|
||||
getSearchBanner()
|
||||
getSearchBanner(),
|
||||
getButlerQrcode()
|
||||
])
|
||||
if (avatarRes) {
|
||||
configForm.value.defaultAvatar = avatarRes.avatarUrl || ''
|
||||
|
|
@ -167,6 +194,9 @@ const loadConfig = async () => {
|
|||
if (bannerRes) {
|
||||
configForm.value.searchBanner = bannerRes.imageUrl || ''
|
||||
}
|
||||
if (qrcodeRes) {
|
||||
configForm.value.butlerQrcode = qrcodeRes.imageUrl || ''
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载配置失败:', error)
|
||||
}
|
||||
|
|
@ -209,6 +239,15 @@ const handleBannerSuccess = (response) => {
|
|||
}
|
||||
}
|
||||
|
||||
const handleQrcodeSuccess = (response) => {
|
||||
if (response.code === 0 && response.data) {
|
||||
configForm.value.butlerQrcode = response.data.url
|
||||
ElMessage.success('上传成功')
|
||||
} else {
|
||||
ElMessage.error(response.message || '上传失败')
|
||||
}
|
||||
}
|
||||
|
||||
const beforeAvatarUpload = (file) => {
|
||||
const isImage = file.type.startsWith('image/')
|
||||
const isLt2M = file.size / 1024 / 1024 < 2
|
||||
|
|
@ -234,6 +273,9 @@ const saveBasicConfig = async () => {
|
|||
if (configForm.value.searchBanner) {
|
||||
promises.push(setSearchBanner(configForm.value.searchBanner))
|
||||
}
|
||||
if (configForm.value.butlerQrcode) {
|
||||
promises.push(setButlerQrcode(configForm.value.butlerQrcode))
|
||||
}
|
||||
if (promises.length > 0) {
|
||||
await Promise.all(promises)
|
||||
ElMessage.success('保存成功')
|
||||
|
|
@ -401,6 +443,47 @@ onMounted(() => {
|
|||
object-fit: cover;
|
||||
}
|
||||
|
||||
.qrcode-upload {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.qrcode-uploader {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.qrcode-uploader :deep(.el-upload) {
|
||||
border: 1px dashed var(--el-border-color);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: var(--el-transition-duration-fast);
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.qrcode-uploader :deep(.el-upload:hover) {
|
||||
border-color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.qrcode-uploader-icon {
|
||||
font-size: 28px;
|
||||
color: #8c939d;
|
||||
}
|
||||
|
||||
.qrcode-preview {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
display: block;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.agreement-editor {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { useRoute, useRouter } from 'vue-router'
|
|||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ArrowLeft } from '@element-plus/icons-vue'
|
||||
import StatusTag from '@/components/StatusTag/index.vue'
|
||||
import { getUserDetail, updateUserStatus } from '@/api/user'
|
||||
import { getUserDetail, updateUserStatus, updateContactCount } from '@/api/user'
|
||||
import { getFullImageUrl } from '@/utils/image'
|
||||
import type { UserDetail } from '@/types/user.d'
|
||||
|
||||
|
|
@ -78,6 +78,34 @@ const handleToggleStatus = async () => {
|
|||
}
|
||||
}
|
||||
|
||||
// 修改联系次数
|
||||
const handleEditContactCount = async () => {
|
||||
if (!userDetail.value) return
|
||||
|
||||
try {
|
||||
const { value } = await ElMessageBox.prompt(
|
||||
'请输入新的联系次数',
|
||||
'修改联系次数',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputValue: String(userDetail.value.contactCount),
|
||||
inputPattern: /^\d+$/,
|
||||
inputErrorMessage: '请输入有效的数字'
|
||||
}
|
||||
)
|
||||
|
||||
const newCount = parseInt(value, 10)
|
||||
await updateContactCount(userId.value, newCount)
|
||||
ElMessage.success('修改成功')
|
||||
fetchUserDetail()
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('修改联系次数失败:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = (time: string) => {
|
||||
if (!time) return '-'
|
||||
|
|
@ -280,7 +308,16 @@ onMounted(() => {
|
|||
{{ formatTime(userDetail.memberExpireTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="剩余联系次数">
|
||||
{{ userDetail.contactCount }}
|
||||
<span>{{ userDetail.contactCount }}</span>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
size="small"
|
||||
style="margin-left: 8px;"
|
||||
@click="handleEditContactCount"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="注册时间">
|
||||
{{ formatTime(userDetail.createTime) }}
|
||||
|
|
|
|||
|
|
@ -9,13 +9,16 @@ import * as fc from 'fast-check'
|
|||
|
||||
/**
|
||||
* Message type constants
|
||||
* 与后端 MessageType 枚举保持一致
|
||||
*/
|
||||
const MessageType = {
|
||||
TEXT: 1,
|
||||
VOICE: 2,
|
||||
IMAGE: 3,
|
||||
EXCHANGE_WECHAT: 4,
|
||||
EXCHANGE_PHOTO: 5
|
||||
EXCHANGE_WECHAT_RESULT: 5,
|
||||
EXCHANGE_PHOTO: 6,
|
||||
EXCHANGE_PHOTO_RESULT: 7
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -16,9 +16,21 @@ export async function getSessions() {
|
|||
const response = await get('/chat/sessions')
|
||||
// 统一返回格式
|
||||
if (response && response.code === 0) {
|
||||
// 后端字段映射到前端字段
|
||||
const list = Array.isArray(response.data) ? response.data : (response.data?.list || [])
|
||||
const mappedList = list.map(item => ({
|
||||
sessionId: item.sessionId,
|
||||
targetUserId: item.otherUserId,
|
||||
targetNickname: item.otherNickname,
|
||||
targetAvatar: item.otherAvatar,
|
||||
lastMessage: item.lastMessageContent,
|
||||
lastMessageType: item.lastMessageType,
|
||||
lastMessageTime: item.lastMessageTime,
|
||||
unreadCount: item.unreadCount
|
||||
}))
|
||||
return {
|
||||
success: true,
|
||||
data: Array.isArray(response.data) ? response.data : (response.data?.list || [])
|
||||
data: mappedList
|
||||
}
|
||||
}
|
||||
return { success: false, data: [] }
|
||||
|
|
@ -104,6 +116,17 @@ export async function getUnreadCount() {
|
|||
return response
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取或创建会话
|
||||
*
|
||||
* @param {number} targetUserId - 目标用户ID
|
||||
* @returns {Promise<Object>} 会话ID
|
||||
*/
|
||||
export async function getOrCreateSession(targetUserId) {
|
||||
const response = await get('/chat/session', { targetUserId })
|
||||
return response
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传语音文件
|
||||
*
|
||||
|
|
@ -153,5 +176,6 @@ export default {
|
|||
exchangePhoto,
|
||||
respondExchange,
|
||||
getUnreadCount,
|
||||
getOrCreateSession,
|
||||
uploadVoice
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,10 +137,22 @@ export async function getMyUnlocked(pageIndex = 1, pageSize = 20) {
|
|||
return response
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否已解锁目标用户
|
||||
*
|
||||
* @param {number} targetUserId - 目标用户ID
|
||||
* @returns {Promise<Object>} 解锁状态 { isUnlocked, remainingUnlockQuota }
|
||||
*/
|
||||
export async function checkUnlock(targetUserId) {
|
||||
const response = await get('/interact/checkUnlock', { targetUserId })
|
||||
return response
|
||||
}
|
||||
|
||||
export default {
|
||||
recordView,
|
||||
favorite,
|
||||
unlock,
|
||||
checkUnlock,
|
||||
report,
|
||||
getViewedMe,
|
||||
getMyViewed,
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ const ENV = {
|
|||
}
|
||||
|
||||
// 当前环境 - 开发时使用 development,打包时改为 production
|
||||
const CURRENT_ENV = 'development'
|
||||
const CURRENT_ENV = 'production'
|
||||
|
||||
// 导出配置
|
||||
export const config = {
|
||||
|
|
|
|||
|
|
@ -141,6 +141,13 @@
|
|||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "协议"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/butler/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "联系我们"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
|
|
|
|||
304
miniapp/pages/butler/index.vue
Normal file
304
miniapp/pages/butler/index.vue
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
<template>
|
||||
<view class="butler-page">
|
||||
<!-- 页面加载状态 -->
|
||||
<Loading type="page" :loading="pageLoading" />
|
||||
|
||||
<!-- 顶部背景图 -->
|
||||
<view class="top-bg">
|
||||
<image src="/static/title_bg.png" mode="aspectFill" class="bg-img" />
|
||||
</view>
|
||||
|
||||
<!-- 自定义导航栏 -->
|
||||
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="navbar-content">
|
||||
<view class="navbar-back" @click="handleBack">
|
||||
<text class="back-icon">‹</text>
|
||||
</view>
|
||||
<text class="navbar-title">联系我们</text>
|
||||
<view class="navbar-placeholder"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<view class="content-area" :style="{ marginTop: (statusBarHeight + 44) + 'px' }">
|
||||
<view class="qrcode-card">
|
||||
<!-- 二维码图片 -->
|
||||
<view class="qrcode-wrapper">
|
||||
<image
|
||||
v-if="butlerQrcode"
|
||||
class="qrcode-img"
|
||||
:src="butlerQrcode"
|
||||
mode="aspectFit"
|
||||
@click="previewQrcode"
|
||||
/>
|
||||
<view v-else class="qrcode-placeholder">
|
||||
<text>暂无二维码</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 保存按钮 -->
|
||||
<button class="btn-save" @click="saveQrcode" :disabled="!butlerQrcode || saving">
|
||||
{{ saving ? '保存中...' : '保存图片' }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useConfigStore } from '@/store/config.js'
|
||||
import { getFullImageUrl } from '@/utils/image.js'
|
||||
import Loading from '@/components/Loading/index.vue'
|
||||
|
||||
const configStore = useConfigStore()
|
||||
|
||||
// 状态栏高度
|
||||
const statusBarHeight = ref(20)
|
||||
|
||||
// 页面状态
|
||||
const pageLoading = ref(true)
|
||||
const saving = ref(false)
|
||||
|
||||
// 管家二维码
|
||||
const butlerQrcode = computed(() => {
|
||||
const qrcode = configStore.butlerQrcode
|
||||
return qrcode ? getFullImageUrl(qrcode) : ''
|
||||
})
|
||||
|
||||
// 获取系统信息
|
||||
const getSystemInfo = () => {
|
||||
try {
|
||||
const res = uni.getSystemInfoSync()
|
||||
statusBarHeight.value = res.statusBarHeight || 20
|
||||
} catch (error) {
|
||||
console.error('获取系统信息失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 返回上一页
|
||||
const handleBack = () => {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
// 预览二维码
|
||||
const previewQrcode = () => {
|
||||
if (!butlerQrcode.value) return
|
||||
uni.previewImage({
|
||||
urls: [butlerQrcode.value]
|
||||
})
|
||||
}
|
||||
|
||||
// 保存二维码到相册
|
||||
const saveQrcode = async () => {
|
||||
if (!butlerQrcode.value || saving.value) return
|
||||
|
||||
saving.value = true
|
||||
|
||||
try {
|
||||
// 先下载图片
|
||||
const downloadRes = await new Promise((resolve, reject) => {
|
||||
uni.downloadFile({
|
||||
url: butlerQrcode.value,
|
||||
success: resolve,
|
||||
fail: reject
|
||||
})
|
||||
})
|
||||
|
||||
if (downloadRes.statusCode !== 200) {
|
||||
throw new Error('下载图片失败')
|
||||
}
|
||||
|
||||
// 保存到相册
|
||||
await new Promise((resolve, reject) => {
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: downloadRes.tempFilePath,
|
||||
success: resolve,
|
||||
fail: (err) => {
|
||||
// 检查是否是权限问题
|
||||
if (err.errMsg.includes('auth deny') || err.errMsg.includes('authorize')) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '需要您授权保存图片到相册',
|
||||
confirmText: '去设置',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.openSetting()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
uni.showToast({
|
||||
title: '保存成功',
|
||||
icon: 'success'
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('保存图片失败:', error)
|
||||
if (!error.errMsg?.includes('auth')) {
|
||||
uni.showToast({
|
||||
title: '保存失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化页面
|
||||
const initPage = async () => {
|
||||
pageLoading.value = true
|
||||
try {
|
||||
getSystemInfo()
|
||||
|
||||
// 强制重新加载配置以获取最新的二维码
|
||||
configStore.isLoaded = false
|
||||
await configStore.loadAppConfig()
|
||||
} catch (error) {
|
||||
console.error('初始化页面失败:', error)
|
||||
} finally {
|
||||
pageLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initPage()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.butler-page {
|
||||
height: 100vh;
|
||||
background-color: #f5f6fa;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// 顶部背景图
|
||||
.top-bg {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 400rpx;
|
||||
z-index: 0;
|
||||
|
||||
.bg-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义导航栏
|
||||
.custom-navbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
|
||||
.navbar-content {
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 24rpx;
|
||||
|
||||
.navbar-back {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.back-icon {
|
||||
font-size: 56rpx;
|
||||
color: #333;
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.navbar-placeholder {
|
||||
width: 80rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 内容区域
|
||||
.content-area {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 40rpx 30rpx;
|
||||
}
|
||||
|
||||
// 二维码卡片
|
||||
.qrcode-card {
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 60rpx 40rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
// 二维码容器
|
||||
.qrcode-wrapper {
|
||||
width: 480rpx;
|
||||
height: 480rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 60rpx;
|
||||
|
||||
.qrcode-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.qrcode-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #f5f5f5;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 保存按钮
|
||||
.btn-save {
|
||||
width: 320rpx;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
background: linear-gradient(135deg, #FFBDC2 0%, #FF8A93 100%);
|
||||
border-radius: 44rpx;
|
||||
font-size: 32rpx;
|
||||
color: #fff;
|
||||
border: none;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -3,13 +3,29 @@
|
|||
<!-- 加载状态 -->
|
||||
<Loading type="page" :loading="loading" />
|
||||
|
||||
<!-- 拨打电话底部弹窗 -->
|
||||
<view class="phone-popup-mask" v-if="showPhonePopup" @click="showPhonePopup = false">
|
||||
<view class="phone-popup" @click.stop>
|
||||
<view class="phone-popup-header">
|
||||
<text class="phone-popup-title">{{ targetNickname }}的联系电话</text>
|
||||
<text class="phone-popup-close" @click="showPhonePopup = false">×</text>
|
||||
</view>
|
||||
<view class="phone-popup-content">
|
||||
<text class="phone-number">{{ targetUserDetail?.phone || '暂无电话' }}</text>
|
||||
</view>
|
||||
<button class="phone-popup-btn" @click="handleMakeCall" :disabled="!targetUserDetail?.phone">
|
||||
拨打电话
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 自定义导航栏 -->
|
||||
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="navbar-content">
|
||||
<view class="navbar-back" @click="handleBack">
|
||||
<text class="back-icon">‹</text>
|
||||
</view>
|
||||
<text class="navbar-title">详情资料</text>
|
||||
<text class="navbar-title">聊天</text>
|
||||
<view class="navbar-actions">
|
||||
<text class="action-icon" @click="handleMore">···</text>
|
||||
<text class="action-icon" @click="handleViewProfile">⊙</text>
|
||||
|
|
@ -21,6 +37,7 @@
|
|||
<scroll-view
|
||||
class="content-scroll"
|
||||
scroll-y
|
||||
scroll-with-animation
|
||||
:scroll-into-view="scrollToId"
|
||||
:style="{ paddingTop: (statusBarHeight + 44) + 'px' }"
|
||||
@scrolltoupper="loadMoreMessages"
|
||||
|
|
@ -321,7 +338,7 @@
|
|||
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
import { useUserStore } from '@/store/user.js'
|
||||
import { useChatStore, MessageType, MessageStatus } from '@/store/chat.js'
|
||||
import { getMessages, sendMessage, exchangeWeChat, exchangePhoto, respondExchange, uploadVoice } from '@/api/chat.js'
|
||||
import { getMessages, sendMessage, exchangeWeChat, exchangePhoto, respondExchange, uploadVoice, getOrCreateSession } from '@/api/chat.js'
|
||||
import { getUserDetail } from '@/api/user.js'
|
||||
import Loading from '@/components/Loading/index.vue'
|
||||
import EmojiPicker from '@/components/EmojiPicker/index.vue'
|
||||
|
|
@ -368,6 +385,9 @@ const inputMode = ref('text')
|
|||
// 表情选择器显示状态
|
||||
const showEmojiPicker = ref(false)
|
||||
|
||||
// 电话弹窗显示状态
|
||||
const showPhonePopup = ref(false)
|
||||
|
||||
// 当前播放的语音ID
|
||||
const playingVoiceId = ref(null)
|
||||
const innerAudioContext = ref(null)
|
||||
|
|
@ -492,8 +512,9 @@ const handleSendMessage = async () => {
|
|||
|
||||
inputText.value = ''
|
||||
|
||||
const localId = Date.now()
|
||||
const localMessage = {
|
||||
id: Date.now(),
|
||||
id: localId,
|
||||
sessionId: sessionId.value,
|
||||
senderId: myUserId.value,
|
||||
receiverId: targetUserId.value,
|
||||
|
|
@ -516,19 +537,25 @@ const handleSendMessage = async () => {
|
|||
content
|
||||
})
|
||||
|
||||
if (res && res.code === 0) {
|
||||
localMessage.status = MessageStatus.SENT
|
||||
if (res.data && res.data.id) {
|
||||
localMessage.id = res.data.id
|
||||
} else if (res.data && res.data.messageId) {
|
||||
// 后端返回 MessageId 字段
|
||||
localMessage.id = res.data.messageId
|
||||
// 找到消息并更新状态
|
||||
const msgIndex = messages.value.findIndex(m => m.id === localId)
|
||||
if (msgIndex !== -1) {
|
||||
if (res && res.code === 0) {
|
||||
messages.value[msgIndex].status = MessageStatus.SENT
|
||||
if (res.data && res.data.id) {
|
||||
messages.value[msgIndex].id = res.data.id
|
||||
} else if (res.data && res.data.messageId) {
|
||||
messages.value[msgIndex].id = res.data.messageId
|
||||
}
|
||||
} else {
|
||||
messages.value[msgIndex].status = MessageStatus.FAILED
|
||||
}
|
||||
} else {
|
||||
localMessage.status = MessageStatus.FAILED
|
||||
}
|
||||
} catch (error) {
|
||||
localMessage.status = MessageStatus.FAILED
|
||||
const msgIndex = messages.value.findIndex(m => m.id === localId)
|
||||
if (msgIndex !== -1) {
|
||||
messages.value[msgIndex].status = MessageStatus.FAILED
|
||||
}
|
||||
uni.showToast({ title: '发送失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
|
@ -537,7 +564,10 @@ const handleSendMessage = async () => {
|
|||
const handleRetryMessage = async (message) => {
|
||||
if (message.status !== MessageStatus.FAILED) return
|
||||
|
||||
message.status = MessageStatus.SENDING
|
||||
const msgIndex = messages.value.findIndex(m => m.id === message.id)
|
||||
if (msgIndex === -1) return
|
||||
|
||||
messages.value[msgIndex].status = MessageStatus.SENDING
|
||||
|
||||
try {
|
||||
const res = await sendMessage({
|
||||
|
|
@ -548,17 +578,17 @@ const handleRetryMessage = async (message) => {
|
|||
})
|
||||
|
||||
if (res && res.code === 0) {
|
||||
message.status = MessageStatus.SENT
|
||||
messages.value[msgIndex].status = MessageStatus.SENT
|
||||
if (res.data?.messageId) {
|
||||
message.id = res.data.messageId
|
||||
messages.value[msgIndex].id = res.data.messageId
|
||||
}
|
||||
uni.showToast({ title: '发送成功', icon: 'success' })
|
||||
} else {
|
||||
message.status = MessageStatus.FAILED
|
||||
messages.value[msgIndex].status = MessageStatus.FAILED
|
||||
uni.showToast({ title: '发送失败', icon: 'none' })
|
||||
}
|
||||
} catch (error) {
|
||||
message.status = MessageStatus.FAILED
|
||||
messages.value[msgIndex].status = MessageStatus.FAILED
|
||||
uni.showToast({ title: '发送失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
|
@ -593,8 +623,9 @@ const handleSendVoice = async (voiceData) => {
|
|||
const voiceUrl = uploadRes.data.url
|
||||
|
||||
// 创建本地消息
|
||||
const localId = Date.now()
|
||||
const localMessage = {
|
||||
id: Date.now(),
|
||||
id: localId,
|
||||
sessionId: sessionId.value,
|
||||
senderId: myUserId.value,
|
||||
receiverId: targetUserId.value,
|
||||
|
|
@ -621,13 +652,17 @@ const handleSendVoice = async (voiceData) => {
|
|||
|
||||
uni.hideLoading()
|
||||
|
||||
if (res && res.code === 0) {
|
||||
localMessage.status = MessageStatus.SENT
|
||||
if (res.data && res.data.messageId) {
|
||||
localMessage.id = res.data.messageId
|
||||
// 找到消息并更新状态
|
||||
const msgIndex = messages.value.findIndex(m => m.id === localId)
|
||||
if (msgIndex !== -1) {
|
||||
if (res && res.code === 0) {
|
||||
messages.value[msgIndex].status = MessageStatus.SENT
|
||||
if (res.data && res.data.messageId) {
|
||||
messages.value[msgIndex].id = res.data.messageId
|
||||
}
|
||||
} else {
|
||||
messages.value[msgIndex].status = MessageStatus.FAILED
|
||||
}
|
||||
} else {
|
||||
localMessage.status = MessageStatus.FAILED
|
||||
}
|
||||
} else {
|
||||
uni.hideLoading()
|
||||
|
|
@ -823,7 +858,23 @@ const previewPhotos = (photos, index) => {
|
|||
|
||||
// 拨打电话
|
||||
const handleCall = () => {
|
||||
uni.showToast({ title: '请通过微信联系对方', icon: 'none' })
|
||||
showPhonePopup.value = true
|
||||
}
|
||||
|
||||
// 实际拨打电话
|
||||
const handleMakeCall = () => {
|
||||
const phone = targetUserDetail.value?.phone
|
||||
if (!phone) {
|
||||
uni.showToast({ title: '暂无电话号码', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: phone,
|
||||
fail: (err) => {
|
||||
console.error('拨打电话失败:', err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 查看用户资料
|
||||
|
|
@ -865,7 +916,11 @@ const previewImage = (url) => {
|
|||
}
|
||||
|
||||
const scrollToBottom = () => {
|
||||
scrollToId.value = 'bottom-anchor'
|
||||
// 先清空再设置,确保每次都能触发滚动
|
||||
scrollToId.value = ''
|
||||
nextTick(() => {
|
||||
scrollToId.value = 'bottom-anchor'
|
||||
})
|
||||
}
|
||||
|
||||
const handleBack = () => {
|
||||
|
|
@ -908,13 +963,36 @@ onMounted(async () => {
|
|||
// 加载目标用户详情
|
||||
loadTargetUserDetail()
|
||||
|
||||
// 如果没有sessionId但有targetUserId,先获取或创建会话
|
||||
if (!sessionId.value && targetUserId.value) {
|
||||
try {
|
||||
uni.showLoading({ title: '加载中...' })
|
||||
const res = await getOrCreateSession(targetUserId.value)
|
||||
uni.hideLoading()
|
||||
|
||||
if (res && res.code === 0 && res.data) {
|
||||
sessionId.value = res.data
|
||||
} else {
|
||||
uni.showToast({ title: res?.message || '创建会话失败', icon: 'none' })
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
uni.hideLoading()
|
||||
console.error('获取会话失败:', error)
|
||||
uni.showToast({ title: '网络错误', icon: 'none' })
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (sessionId.value) {
|
||||
chatStore.setCurrentSession(sessionId.value)
|
||||
loadMessages()
|
||||
} else if (targetUserId.value) {
|
||||
sessionId.value = targetUserId.value
|
||||
chatStore.setCurrentSession(sessionId.value)
|
||||
loadMessages()
|
||||
} else {
|
||||
uni.showToast({ title: '参数错误', icon: 'none' })
|
||||
setTimeout(() => {
|
||||
|
|
@ -1061,7 +1139,8 @@ onUnmounted(() => {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background-color: #f5f6fa;
|
||||
background-color: #f8f8f8;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// 自定义导航栏
|
||||
|
|
@ -1116,7 +1195,9 @@ onUnmounted(() => {
|
|||
// 内容滚动区域
|
||||
.content-scroll {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
padding-bottom: 280rpx;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
// 用户信息卡片
|
||||
|
|
@ -1713,4 +1794,76 @@ onUnmounted(() => {
|
|||
transform: scaleY(1.5);
|
||||
}
|
||||
}
|
||||
|
||||
// 拨打电话弹窗
|
||||
.phone-popup-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.phone-popup {
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
padding: 40rpx;
|
||||
padding-bottom: calc(40rpx + env(safe-area-inset-bottom));
|
||||
|
||||
.phone-popup-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
.phone-popup-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.phone-popup-close {
|
||||
font-size: 48rpx;
|
||||
color: #999;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.phone-popup-content {
|
||||
text-align: center;
|
||||
padding: 40rpx 0;
|
||||
|
||||
.phone-number {
|
||||
font-size: 56rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
letter-spacing: 4rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.phone-popup-btn {
|
||||
width: 100%;
|
||||
height: 96rpx;
|
||||
line-height: 96rpx;
|
||||
background: #4cd964;
|
||||
color: #fff;
|
||||
font-size: 34rpx;
|
||||
border-radius: 48rpx;
|
||||
border: none;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,16 @@
|
|||
:buttonText="dailyPopup?.buttonText" :linkUrl="dailyPopup?.linkUrl" @close="handleCloseDailyPopup"
|
||||
@confirm="handleDailyPopupConfirm" />
|
||||
|
||||
<!-- 解锁确认弹窗 -->
|
||||
<Popup
|
||||
:visible="showUnlockPopup"
|
||||
type="unlock"
|
||||
:remainingQuota="remainingUnlockQuota"
|
||||
@close="handleCloseUnlockPopup"
|
||||
@unlock="handleConfirmUnlock"
|
||||
@goMember="handleGoMember"
|
||||
/>
|
||||
|
||||
<!-- 会员广告条 - 固定在底部 -->
|
||||
<view class="member-ad-section" v-if="showMemberAd">
|
||||
<view class="member-ad-bar" :style="memberAdBgStyle">
|
||||
|
|
@ -128,6 +138,10 @@
|
|||
import {
|
||||
getRecommend
|
||||
} from '@/api/user.js'
|
||||
import {
|
||||
checkUnlock,
|
||||
unlock
|
||||
} from '@/api/interact.js'
|
||||
import {
|
||||
getFullImageUrl
|
||||
} from '@/utils/image.js'
|
||||
|
|
@ -165,6 +179,11 @@
|
|||
// 数据
|
||||
const recommendList = ref([])
|
||||
|
||||
// 解锁弹窗状态
|
||||
const showUnlockPopup = ref(false)
|
||||
const unlockTargetUserId = ref(0)
|
||||
const remainingUnlockQuota = ref(0)
|
||||
|
||||
// 计算属性 - 处理图片URL
|
||||
const banners = computed(() => {
|
||||
return configStore.banners.map(banner => ({
|
||||
|
|
@ -372,7 +391,7 @@
|
|||
}
|
||||
|
||||
// 联系用户
|
||||
const handleUserContact = (userId) => {
|
||||
const handleUserContact = async (userId) => {
|
||||
if (!userStore.isLoggedIn) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
|
|
@ -405,9 +424,77 @@
|
|||
return
|
||||
}
|
||||
|
||||
uni.navigateTo({
|
||||
url: `/pages/chat/index?targetUserId=${userId}`
|
||||
})
|
||||
// 检查是否已解锁
|
||||
try {
|
||||
uni.showLoading({ title: '加载中...' })
|
||||
const res = await checkUnlock(userId)
|
||||
uni.hideLoading()
|
||||
|
||||
if (res && res.code === 0 && res.data) {
|
||||
if (res.data.isUnlocked) {
|
||||
// 已解锁,直接跳转聊天
|
||||
uni.navigateTo({
|
||||
url: `/pages/chat/index?targetUserId=${userId}`
|
||||
})
|
||||
} else {
|
||||
// 未解锁,显示解锁弹窗
|
||||
unlockTargetUserId.value = userId
|
||||
remainingUnlockQuota.value = res.data.remainingUnlockQuota || 0
|
||||
showUnlockPopup.value = true
|
||||
}
|
||||
} else {
|
||||
uni.showToast({ title: '检查解锁状态失败', icon: 'none' })
|
||||
}
|
||||
} catch (error) {
|
||||
uni.hideLoading()
|
||||
console.error('检查解锁状态失败:', error)
|
||||
uni.showToast({ title: '网络错误', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭解锁弹窗
|
||||
const handleCloseUnlockPopup = () => {
|
||||
showUnlockPopup.value = false
|
||||
}
|
||||
|
||||
// 确认解锁
|
||||
const handleConfirmUnlock = async () => {
|
||||
if (remainingUnlockQuota.value <= 0) {
|
||||
showUnlockPopup.value = false
|
||||
uni.navigateTo({ url: '/pages/member/index' })
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
uni.showLoading({ title: '解锁中...' })
|
||||
const unlockRes = await unlock(unlockTargetUserId.value)
|
||||
uni.hideLoading()
|
||||
|
||||
if (unlockRes && unlockRes.code === 0) {
|
||||
showUnlockPopup.value = false
|
||||
uni.showToast({ title: '解锁成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/chat/index?targetUserId=${unlockTargetUserId.value}`
|
||||
})
|
||||
}, 1000)
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: unlockRes?.message || '解锁失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
uni.hideLoading()
|
||||
console.error('解锁失败:', error)
|
||||
uni.showToast({ title: '解锁失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
// 跳转会员页面
|
||||
const handleGoMember = () => {
|
||||
showUnlockPopup.value = false
|
||||
uni.navigateTo({ url: '/pages/member/index' })
|
||||
}
|
||||
|
||||
// 滚动到底部 - 加载更多
|
||||
|
|
@ -446,6 +533,8 @@
|
|||
showDailyPopup,
|
||||
dailyPopup,
|
||||
recommendList,
|
||||
showUnlockPopup,
|
||||
remainingUnlockQuota,
|
||||
initPage,
|
||||
loadRecommendList,
|
||||
handleSearchClick,
|
||||
|
|
@ -458,6 +547,9 @@
|
|||
handleDailyPopupConfirm,
|
||||
handleUserClick,
|
||||
handleUserContact,
|
||||
handleCloseUnlockPopup,
|
||||
handleConfirmUnlock,
|
||||
handleGoMember,
|
||||
handleScrollToLower,
|
||||
handleRefresh
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -50,31 +50,37 @@
|
|||
<!-- 互动统计区域 -->
|
||||
<view class="interact-section">
|
||||
<view class="interact-grid">
|
||||
<view class="interact-item" @click="navigateTo('/pages/interact/viewedMe')">
|
||||
<view class="interact-icon viewed">
|
||||
<image src="/static/ic_seen.png" mode="aspectFit" class="icon-img" />
|
||||
</view>
|
||||
<text class="interact-label">看过我</text>
|
||||
<view class="interact-badge" v-if="interactCounts.viewedMe > 0">
|
||||
<text>+{{ interactCounts.viewedMe }}</text>
|
||||
<view class="interact-item viewed" @click="navigateTo('/pages/interact/viewedMe')">
|
||||
<view class="item-card">
|
||||
<view class="icon-box">
|
||||
<image src="/static/ic_seen.png" mode="aspectFit" class="icon-img" />
|
||||
</view>
|
||||
<text class="item-label">看过我</text>
|
||||
<view class="item-count" v-if="interactCounts.viewedMe > 0">
|
||||
<text>+{{ interactCounts.viewedMe }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="interact-item" @click="navigateTo('/pages/interact/favoritedMe')">
|
||||
<view class="interact-icon favorited">
|
||||
<image src="/static/ic_collection.png" mode="aspectFit" class="icon-img" />
|
||||
</view>
|
||||
<text class="interact-label">收藏我</text>
|
||||
<view class="interact-badge" v-if="interactCounts.favoritedMe > 0">
|
||||
<text>+{{ interactCounts.favoritedMe }}</text>
|
||||
<view class="interact-item favorited" @click="navigateTo('/pages/interact/favoritedMe')">
|
||||
<view class="item-card">
|
||||
<view class="icon-box">
|
||||
<image src="/static/ic_collection.png" mode="aspectFit" class="icon-img" />
|
||||
</view>
|
||||
<text class="item-label">收藏我</text>
|
||||
<view class="item-count" v-if="interactCounts.favoritedMe > 0">
|
||||
<text>+{{ interactCounts.favoritedMe }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="interact-item" @click="navigateTo('/pages/interact/unlockedMe')">
|
||||
<view class="interact-icon unlocked">
|
||||
<image src="/static/ic_unlock.png" mode="aspectFit" class="icon-img" />
|
||||
</view>
|
||||
<text class="interact-label">解锁我</text>
|
||||
<view class="interact-badge" v-if="interactCounts.unlockedMe > 0">
|
||||
<text>+{{ interactCounts.unlockedMe }}</text>
|
||||
<view class="interact-item unlocked" @click="navigateTo('/pages/interact/unlockedMe')">
|
||||
<view class="item-card">
|
||||
<view class="icon-box">
|
||||
<image src="/static/ic_unlock.png" mode="aspectFit" class="icon-img" />
|
||||
</view>
|
||||
<text class="item-label">解锁我</text>
|
||||
<view class="item-count" v-if="interactCounts.unlockedMe > 0">
|
||||
<text>+{{ interactCounts.unlockedMe }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
|
@ -125,6 +131,11 @@
|
|||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- ICP备案号 -->
|
||||
<view class="icp-section">
|
||||
<text class="icp-text">备案号:苏ICP备2026001086号-1</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
|
@ -251,7 +262,7 @@ export default {
|
|||
|
||||
// 管家指导
|
||||
const handleButler = () => {
|
||||
uni.showToast({ title: '功能开发中', icon: 'none' })
|
||||
uni.navigateTo({ url: '/pages/butler/index' })
|
||||
}
|
||||
|
||||
// 实名认证
|
||||
|
|
@ -526,76 +537,83 @@ export default {
|
|||
|
||||
.interact-grid {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
|
||||
.interact-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
.interact-icon {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
.item-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 150rpx;
|
||||
height: 132rpx;
|
||||
border-radius: 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
.icon-img {
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
.icon-box {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 20rpx;
|
||||
margin-top: -18rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 6rpx;
|
||||
|
||||
.icon-img {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&.viewed {
|
||||
background: linear-gradient(135deg, #e8e0ff 0%, #d4c4ff 100%);
|
||||
}
|
||||
|
||||
&.favorited {
|
||||
background: linear-gradient(135deg, #ffe0e8 0%, #ffc4d4 100%);
|
||||
}
|
||||
|
||||
&.unlocked {
|
||||
background: linear-gradient(135deg, #fff3e0 0%, #ffe4c4 100%);
|
||||
}
|
||||
}
|
||||
|
||||
.interact-label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.interact-badge {
|
||||
position: absolute;
|
||||
top: -8rpx;
|
||||
right: -8rpx;
|
||||
min-width: 44rpx;
|
||||
height: 44rpx;
|
||||
padding: 0 12rpx;
|
||||
border-radius: 22rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
text {
|
||||
font-size: 24rpx;
|
||||
color: #fff;
|
||||
.item-label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
|
||||
.item-count {
|
||||
min-width: 56rpx;
|
||||
height: 40rpx;
|
||||
padding: 5rpx 0rpx;
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
text {
|
||||
font-size: 20rpx;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(1) .interact-badge {
|
||||
background: #9b7bff;
|
||||
// 看过我 - 紫色
|
||||
&.viewed .item-card {
|
||||
background: linear-gradient(0deg, rgba(237, 213, 255, 0.1) 0%, #E0D8FF 100%);
|
||||
|
||||
.item-count {
|
||||
background: #8b5cf6;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(2) .interact-badge {
|
||||
background: #ff6b8a;
|
||||
// 收藏我 - 粉色
|
||||
&.favorited .item-card {
|
||||
background: linear-gradient(0deg, rgba(255, 213, 240, 0.1) 0%, #FFC4D7 100%);
|
||||
|
||||
.item-count {
|
||||
background: #fb7185;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(3) .interact-badge {
|
||||
background: #ffb347;
|
||||
// 解锁我 - 黄色
|
||||
&.unlocked .item-card {
|
||||
background: linear-gradient(0deg, rgba(255, 239, 213, 0.1) 0%, #FFF5D8 100%);
|
||||
|
||||
.item-count {
|
||||
background: #f59e0b;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -689,4 +707,17 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ICP备案号
|
||||
.icp-section {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 40rpx 32rpx 60rpx;
|
||||
text-align: center;
|
||||
|
||||
.icp-text {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,22 @@
|
|||
@goMember="handleGoMember"
|
||||
/>
|
||||
|
||||
<!-- 拨打电话底部弹窗 -->
|
||||
<view class="phone-popup-mask" v-if="showPhonePopup" @click="showPhonePopup = false">
|
||||
<view class="phone-popup" @click.stop>
|
||||
<view class="phone-popup-header">
|
||||
<text class="phone-popup-title">{{ userDetail?.nickname }}的联系电话</text>
|
||||
<text class="phone-popup-close" @click="showPhonePopup = false">×</text>
|
||||
</view>
|
||||
<view class="phone-popup-content">
|
||||
<text class="phone-number">{{ userDetail?.phone || '暂无电话' }}</text>
|
||||
</view>
|
||||
<button class="phone-popup-btn" @click="handleMakeCall" :disabled="!userDetail?.phone">
|
||||
拨打电话
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 自定义导航栏 -->
|
||||
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="navbar-content">
|
||||
|
|
@ -282,6 +298,7 @@ const isUnlocked = ref(false)
|
|||
const remainingUnlockQuota = ref(0)
|
||||
const showProfilePopup = ref(false)
|
||||
const showUnlockPopup = ref(false)
|
||||
const showPhonePopup = ref(false)
|
||||
|
||||
// 获取系统信息
|
||||
const getSystemInfo = () => {
|
||||
|
|
@ -624,7 +641,25 @@ const handleCall = () => {
|
|||
showUnlockPopup.value = true
|
||||
return
|
||||
}
|
||||
uni.showToast({ title: '请通过微信联系对方', icon: 'none' })
|
||||
|
||||
// 已解锁,显示电话弹窗
|
||||
showPhonePopup.value = true
|
||||
}
|
||||
|
||||
// 拨打电话
|
||||
const handleMakeCall = () => {
|
||||
const phone = userDetail.value?.phone
|
||||
if (!phone) {
|
||||
uni.showToast({ title: '暂无电话号码', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: phone,
|
||||
fail: (err) => {
|
||||
console.error('拨打电话失败:', err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 复制微信号
|
||||
|
|
@ -672,6 +707,7 @@ onShareAppMessage(() => {
|
|||
.profile-detail-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f6fa;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// 自定义导航栏
|
||||
|
|
@ -718,12 +754,13 @@ onShareAppMessage(() => {
|
|||
|
||||
// 内容滚动区域
|
||||
.content-scroll {
|
||||
height: 100vh;
|
||||
padding-bottom: 160rpx;
|
||||
height: calc(100vh - 160rpx);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 24rpx;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
// 用户头像卡片
|
||||
|
|
@ -1114,4 +1151,76 @@ onShareAppMessage(() => {
|
|||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
// 拨打电话弹窗
|
||||
.phone-popup-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.phone-popup {
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
padding: 40rpx;
|
||||
padding-bottom: calc(40rpx + env(safe-area-inset-bottom));
|
||||
|
||||
.phone-popup-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
.phone-popup-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.phone-popup-close {
|
||||
font-size: 48rpx;
|
||||
color: #999;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.phone-popup-content {
|
||||
text-align: center;
|
||||
padding: 40rpx 0;
|
||||
|
||||
.phone-number {
|
||||
font-size: 56rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
letter-spacing: 4rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.phone-popup-btn {
|
||||
width: 100%;
|
||||
height: 96rpx;
|
||||
line-height: 96rpx;
|
||||
background: #4cd964;
|
||||
color: #fff;
|
||||
font-size: 34rpx;
|
||||
border-radius: 48rpx;
|
||||
border: none;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -7,13 +7,16 @@ import { defineStore } from 'pinia'
|
|||
|
||||
/**
|
||||
* 消息类型枚举
|
||||
* 与后端 MessageType 枚举保持一致
|
||||
*/
|
||||
export const MessageType = {
|
||||
TEXT: 1, // 文本消息
|
||||
VOICE: 2, // 语音消息
|
||||
IMAGE: 3, // 图片消息
|
||||
EXCHANGE_WECHAT: 4, // 交换微信
|
||||
EXCHANGE_PHOTO: 5 // 交换照片
|
||||
TEXT: 1, // 文本消息
|
||||
VOICE: 2, // 语音消息
|
||||
IMAGE: 3, // 图片消息
|
||||
EXCHANGE_WECHAT: 4, // 交换微信请求
|
||||
EXCHANGE_WECHAT_RESULT: 5, // 交换微信结果
|
||||
EXCHANGE_PHOTO: 6, // 交换照片请求
|
||||
EXCHANGE_PHOTO_RESULT: 7 // 交换照片结果
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ export const useConfigStore = defineStore('config', {
|
|||
// 系统配置
|
||||
defaultAvatar: getDefaultAvatar() || '/static/logo.png',
|
||||
searchBanner: '',
|
||||
butlerQrcode: '', // 管家指导二维码
|
||||
|
||||
// 弹窗配置
|
||||
dailyPopup: null,
|
||||
|
|
@ -77,6 +78,7 @@ export const useConfigStore = defineStore('config', {
|
|||
setDefaultAvatar(config.defaultAvatar)
|
||||
}
|
||||
this.searchBanner = config.searchBanner || ''
|
||||
this.butlerQrcode = config.butlerQrcode || ''
|
||||
|
||||
// 弹窗配置
|
||||
this.dailyPopup = config.dailyPopup || null
|
||||
|
|
|
|||
|
|
@ -173,6 +173,34 @@ public class AdminConfigController : ControllerBase
|
|||
var result = await _configService.SetSearchBannerAsync(request.ImageUrl);
|
||||
return result ? ApiResponse.Success("设置成功") : ApiResponse.Error(40001, "设置失败");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取管家二维码
|
||||
/// </summary>
|
||||
[HttpGet("butlerQrcode")]
|
||||
public async Task<ApiResponse<ButlerQrcodeResponse>> GetButlerQrcode()
|
||||
{
|
||||
var imageUrl = await _configService.GetButlerQrcodeAsync();
|
||||
return ApiResponse<ButlerQrcodeResponse>.Success(new ButlerQrcodeResponse
|
||||
{
|
||||
ImageUrl = imageUrl
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置管家二维码
|
||||
/// </summary>
|
||||
[HttpPost("butlerQrcode")]
|
||||
public async Task<ApiResponse> SetButlerQrcode([FromBody] SetButlerQrcodeRequest request)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.ImageUrl))
|
||||
{
|
||||
return ApiResponse.Error(40001, "图片URL不能为空");
|
||||
}
|
||||
|
||||
var result = await _configService.SetButlerQrcodeAsync(request.ImageUrl);
|
||||
return result ? ApiResponse.Success("设置成功") : ApiResponse.Error(40001, "设置失败");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -262,3 +290,25 @@ public class SetSearchBannerRequest
|
|||
/// </summary>
|
||||
public string ImageUrl { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 管家二维码响应
|
||||
/// </summary>
|
||||
public class ButlerQrcodeResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// 图片URL
|
||||
/// </summary>
|
||||
public string? ImageUrl { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置管家二维码请求
|
||||
/// </summary>
|
||||
public class SetButlerQrcodeRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 图片URL
|
||||
/// </summary>
|
||||
public string ImageUrl { get; set; } = string.Empty;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,6 +111,20 @@ public class AdminUserController : ControllerBase
|
|||
var result = await _adminUserService.DeleteUserAsync(id, adminId);
|
||||
return result ? ApiResponse.Success("删除成功") : ApiResponse.Error(40001, "删除失败");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新用户联系次数
|
||||
/// </summary>
|
||||
/// <param name="id">用户ID</param>
|
||||
/// <param name="request">联系次数更新请求</param>
|
||||
/// <returns>操作结果</returns>
|
||||
[HttpPut("{id}/contact-count")]
|
||||
public async Task<ApiResponse> UpdateContactCount(long id, [FromBody] UpdateContactCountRequest request)
|
||||
{
|
||||
var adminId = GetCurrentAdminId();
|
||||
var result = await _adminUserService.UpdateContactCountAsync(id, request.ContactCount, adminId);
|
||||
return result ? ApiResponse.Success("更新成功") : ApiResponse.Error(40001, "更新失败");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -128,3 +142,14 @@ public class CreateTestUsersRequest
|
|||
/// </summary>
|
||||
public int? Gender { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新联系次数请求
|
||||
/// </summary>
|
||||
public class UpdateContactCountRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 联系次数
|
||||
/// </summary>
|
||||
public int ContactCount { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -293,6 +293,29 @@ public class ChatController : ControllerBase
|
|||
return ApiResponse<int>.Success(count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取或创建会话
|
||||
/// </summary>
|
||||
/// <param name="targetUserId">目标用户ID</param>
|
||||
/// <returns>会话ID</returns>
|
||||
[HttpGet("session")]
|
||||
public async Task<ApiResponse<long>> GetOrCreateSession([FromQuery] long targetUserId)
|
||||
{
|
||||
if (targetUserId <= 0)
|
||||
{
|
||||
return ApiResponse<long>.Error(ErrorCodes.InvalidParameter, "目标用户ID无效");
|
||||
}
|
||||
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == targetUserId)
|
||||
{
|
||||
return ApiResponse<long>.Error(ErrorCodes.InvalidParameter, "不能与自己创建会话");
|
||||
}
|
||||
|
||||
var sessionId = await _chatService.GetOrCreateSessionAsync(userId, targetUserId);
|
||||
return ApiResponse<long>.Success(sessionId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前用户ID
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -226,6 +226,30 @@ public class InteractController : ControllerBase
|
|||
return ApiResponse<PagedResult<UnlockRecordResponse>>.Success(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否已解锁目标用户
|
||||
/// </summary>
|
||||
/// <param name="targetUserId">目标用户ID</param>
|
||||
/// <returns>解锁状态</returns>
|
||||
[HttpGet("checkUnlock")]
|
||||
public async Task<ApiResponse<CheckUnlockResponse>> CheckUnlock([FromQuery] long targetUserId)
|
||||
{
|
||||
if (targetUserId <= 0)
|
||||
{
|
||||
return ApiResponse<CheckUnlockResponse>.Error(ErrorCodes.InvalidParameter, "目标用户ID无效");
|
||||
}
|
||||
|
||||
var userId = GetCurrentUserId();
|
||||
var isUnlocked = await _interactService.IsUnlockedAsync(userId, targetUserId);
|
||||
var remainingQuota = await _interactService.GetRemainingUnlockQuotaAsync(userId);
|
||||
|
||||
return ApiResponse<CheckUnlockResponse>.Success(new CheckUnlockResponse
|
||||
{
|
||||
IsUnlocked = isUnlocked,
|
||||
RemainingUnlockQuota = remainingQuota
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前用户ID
|
||||
/// </summary>
|
||||
|
|
@ -256,3 +280,19 @@ public class FavoriteResponse
|
|||
/// </summary>
|
||||
public string? Message { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查解锁状态响应
|
||||
/// </summary>
|
||||
public class CheckUnlockResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否已解锁
|
||||
/// </summary>
|
||||
public bool IsUnlocked { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 剩余解锁次数
|
||||
/// </summary>
|
||||
public int RemainingUnlockQuota { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,4 +52,13 @@ public interface IAdminUserService
|
|||
/// <param name="adminId">操作管理员ID</param>
|
||||
/// <returns>是否成功</returns>
|
||||
Task<bool> DeleteUserAsync(long userId, long adminId);
|
||||
|
||||
/// <summary>
|
||||
/// 更新用户联系次数
|
||||
/// </summary>
|
||||
/// <param name="userId">用户ID</param>
|
||||
/// <param name="contactCount">联系次数</param>
|
||||
/// <param name="adminId">操作管理员ID</param>
|
||||
/// <returns>是否成功</returns>
|
||||
Task<bool> UpdateContactCountAsync(long userId, int contactCount, long adminId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,6 +89,11 @@ public class AppConfigResponse
|
|||
/// </summary>
|
||||
public string? SearchBanner { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 管家指导二维码URL
|
||||
/// </summary>
|
||||
public string? ButlerQrcode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 每日弹窗配置
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -69,4 +69,14 @@ public interface ISystemConfigService
|
|||
/// 设置搜索页Banner图URL
|
||||
/// </summary>
|
||||
Task<bool> SetSearchBannerAsync(string imageUrl);
|
||||
|
||||
/// <summary>
|
||||
/// 获取管家指导二维码URL
|
||||
/// </summary>
|
||||
Task<string?> GetButlerQrcodeAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 设置管家指导二维码URL
|
||||
/// </summary>
|
||||
Task<bool> SetButlerQrcodeAsync(string imageUrl);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -439,6 +439,32 @@ public class AdminUserService : IAdminUserService
|
|||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> UpdateContactCountAsync(long userId, int contactCount, long adminId)
|
||||
{
|
||||
var user = await _userRepository.GetByIdAsync(userId);
|
||||
if (user == null || user.IsDeleted)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.UserNotFound, "用户不存在");
|
||||
}
|
||||
|
||||
if (contactCount < 0)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.InvalidParameter, "联系次数不能为负数");
|
||||
}
|
||||
|
||||
var oldCount = user.ContactCount;
|
||||
user.ContactCount = contactCount;
|
||||
user.UpdateTime = DateTime.Now;
|
||||
|
||||
var result = await _userRepository.UpdateAsync(user);
|
||||
|
||||
_logger.LogInformation("管理员更新用户联系次数: AdminId={AdminId}, UserId={UserId}, OldCount={OldCount}, NewCount={NewCount}",
|
||||
adminId, userId, oldCount, contactCount);
|
||||
|
||||
return result > 0;
|
||||
}
|
||||
|
||||
#region Private Helper Methods
|
||||
|
||||
private static AdminUserListDto MapToUserListDto(User user, UserProfile? profile)
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ public class ConfigService : IConfigService
|
|||
var kingKongs = await GetKingKongsAsync();
|
||||
var defaultAvatar = await _systemConfigService.GetDefaultAvatarAsync();
|
||||
var searchBanner = await _systemConfigService.GetSearchBannerAsync();
|
||||
var butlerQrcode = await _systemConfigService.GetButlerQrcodeAsync();
|
||||
var dailyPopup = await GetPopupConfigAsync(1); // 每日弹窗
|
||||
var memberAdPopup = await GetPopupConfigAsync(3); // 会员广告弹窗
|
||||
|
||||
|
|
@ -56,6 +57,7 @@ public class ConfigService : IConfigService
|
|||
KingKongs = kingKongs,
|
||||
DefaultAvatar = defaultAvatar,
|
||||
SearchBanner = searchBanner,
|
||||
ButlerQrcode = butlerQrcode,
|
||||
DailyPopup = dailyPopup,
|
||||
MemberAdPopup = memberAdPopup
|
||||
};
|
||||
|
|
|
|||
|
|
@ -473,8 +473,10 @@ public class InteractService : IInteractService
|
|||
/// <inheritdoc />
|
||||
public async Task<bool> IsUnlockedAsync(long userId, long targetUserId)
|
||||
{
|
||||
// 检查双向解锁:只要任意一方解锁了对方,双方都可以聊天
|
||||
return await _unlockRepository.ExistsAsync(u =>
|
||||
u.UserId == userId && u.TargetUserId == targetUserId);
|
||||
(u.UserId == userId && u.TargetUserId == targetUserId) ||
|
||||
(u.UserId == targetUserId && u.TargetUserId == userId));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
|||
|
|
@ -38,6 +38,11 @@ public class SystemConfigService : ISystemConfigService
|
|||
/// </summary>
|
||||
public const string SearchBannerKey = "search_banner";
|
||||
|
||||
/// <summary>
|
||||
/// 管家二维码配置键
|
||||
/// </summary>
|
||||
public const string ButlerQrcodeKey = "butler_qrcode";
|
||||
|
||||
public SystemConfigService(
|
||||
IRepository<SystemConfig> configRepository,
|
||||
ILogger<SystemConfigService> logger)
|
||||
|
|
@ -160,4 +165,16 @@ public class SystemConfigService : ISystemConfigService
|
|||
{
|
||||
return await SetConfigValueAsync(SearchBannerKey, imageUrl, "搜索页Banner图URL");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<string?> GetButlerQrcodeAsync()
|
||||
{
|
||||
return await GetConfigValueAsync(ButlerQrcodeKey);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> SetButlerQrcodeAsync(string imageUrl)
|
||||
{
|
||||
return await SetConfigValueAsync(ButlerQrcodeKey, imageUrl, "管家指导二维码URL");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user