4 changed files with 359 additions and 3 deletions
@ -0,0 +1,30 @@ |
|||||
|
import request from '@/utils/request'; |
||||
|
import { AxiosPromise } from 'axios'; |
||||
|
|
||||
|
/** |
||||
|
* 保存/更新代理悬浮球图片配置 |
||||
|
* @param data 配置参数 |
||||
|
*/ |
||||
|
export function updateAgentFloatBall(data: { |
||||
|
uniqueToken: string; |
||||
|
pcFloatBall: string; |
||||
|
mobileFloatBall: string; |
||||
|
}): AxiosPromise<any> { |
||||
|
return request({ |
||||
|
url: '/im/agent/updateFloatBall', |
||||
|
method: 'post', |
||||
|
data: data |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 根据token获取代理悬浮球配置 |
||||
|
* @param token 代理唯一标识 |
||||
|
*/ |
||||
|
export function getAgentFloatBallConfig(token: string): AxiosPromise<any> { |
||||
|
return request({ |
||||
|
url: '/im/agent/infoByToken', |
||||
|
method: 'get', |
||||
|
params: { token } |
||||
|
}); |
||||
|
} |
||||
@ -0,0 +1,247 @@ |
|||||
|
<template> |
||||
|
<div class="float-ball-setting"> |
||||
|
<div class="setting-header"> |
||||
|
<el-button type="primary" @click="showSettingDialog = true"> |
||||
|
<el-icon><Setting /></el-icon> |
||||
|
悬浮球设置 |
||||
|
</el-button> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 悬浮球设置弹窗 --> |
||||
|
<el-dialog |
||||
|
v-model="showSettingDialog" |
||||
|
title="悬浮球图片设置" |
||||
|
width="500px" |
||||
|
:close-on-click-modal="false" |
||||
|
> |
||||
|
<el-form :model="floatBallConfig" label-width="120px"> |
||||
|
<el-form-item label="PC端悬浮球"> |
||||
|
<div class="upload-section"> |
||||
|
<image-upload |
||||
|
v-model="floatBallConfig.pcImage" |
||||
|
v-model:thumb="floatBallConfig.pcImageThumb" |
||||
|
:width="80" |
||||
|
:height="80" |
||||
|
/> |
||||
|
<div class="upload-tip"> |
||||
|
<p>建议尺寸:60x60px</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</el-form-item> |
||||
|
|
||||
|
<el-form-item label="移动端悬浮球"> |
||||
|
<div class="upload-section"> |
||||
|
<image-upload |
||||
|
v-model="floatBallConfig.mobileImage" |
||||
|
v-model:thumb="floatBallConfig.mobileImageThumb" |
||||
|
:width="80" |
||||
|
:height="80" |
||||
|
/> |
||||
|
<div class="upload-tip"> |
||||
|
<p>建议尺寸:50x50px</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</el-form-item> |
||||
|
</el-form> |
||||
|
|
||||
|
<template #footer> |
||||
|
<span class="dialog-footer"> |
||||
|
<el-button @click="showSettingDialog = false">取消</el-button> |
||||
|
<el-button type="primary" @click="saveFloatBallConfig"> |
||||
|
保存设置 |
||||
|
</el-button> |
||||
|
</span> |
||||
|
</template> |
||||
|
</el-dialog> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
import { ref, onMounted, watch } from 'vue'; |
||||
|
import { Setting } from '@element-plus/icons-vue'; |
||||
|
import { ElMessage } from 'element-plus'; |
||||
|
import ImageUpload from '@/components/ImageUpload/index.vue'; |
||||
|
import { updateAgentFloatBall, getAgentFloatBallConfig } from '@/api/im/agent'; |
||||
|
|
||||
|
// 定义组件名称 |
||||
|
defineOptions({ |
||||
|
name: 'FloatBallSetting' |
||||
|
}); |
||||
|
|
||||
|
// 定义事件 |
||||
|
const emit = defineEmits<{ |
||||
|
(e: 'update:config', config: FloatBallConfig): void; |
||||
|
(e: 'save', config: FloatBallConfig): void; |
||||
|
}>(); |
||||
|
|
||||
|
// 定义props |
||||
|
interface Props { |
||||
|
initialConfig?: Partial<FloatBallConfig>; |
||||
|
token?: string; |
||||
|
} |
||||
|
|
||||
|
const props = withDefaults(defineProps<Props>(), { |
||||
|
initialConfig: () => ({}), |
||||
|
token: '' |
||||
|
}); |
||||
|
|
||||
|
// 悬浮球配置接口 |
||||
|
export interface FloatBallConfig { |
||||
|
pcImage: string; |
||||
|
pcImageThumb?: string; |
||||
|
mobileImage: string; |
||||
|
mobileImageThumb?: string; |
||||
|
} |
||||
|
|
||||
|
// 默认配置 |
||||
|
const defaultConfig: FloatBallConfig = { |
||||
|
pcImage: '', |
||||
|
pcImageThumb: '', |
||||
|
mobileImage: '', |
||||
|
mobileImageThumb: '' |
||||
|
}; |
||||
|
|
||||
|
const showSettingDialog = ref(false); |
||||
|
const floatBallConfig = ref<FloatBallConfig>({ ...defaultConfig }); |
||||
|
|
||||
|
// 从数据库加载配置 ✅ 核心修复 |
||||
|
async function loadConfigFromServer() { |
||||
|
if (!props.token) return; |
||||
|
|
||||
|
try { |
||||
|
const res = await getAgentFloatBallConfig(props.token); |
||||
|
if (res.data) { |
||||
|
const serverConfig = { |
||||
|
pcImage: res.data.pcFloatBall || '', |
||||
|
pcImageThumb: res.data.pcFloatBall || '', |
||||
|
mobileImage: res.data.mobileFloatBall || '', |
||||
|
mobileImageThumb: res.data.mobileFloatBall || '' |
||||
|
}; |
||||
|
floatBallConfig.value = { ...defaultConfig, ...serverConfig }; |
||||
|
|
||||
|
// 同步到本地存储做兼容 |
||||
|
const storageKey = `floatBallConfig_${props.token}`; |
||||
|
localStorage.setItem(storageKey, JSON.stringify(floatBallConfig.value)); |
||||
|
|
||||
|
emit('update:config', floatBallConfig.value); |
||||
|
} |
||||
|
} catch (err) { |
||||
|
console.error('加载服务器配置失败', err); |
||||
|
// 失败则读取本地 |
||||
|
loadFloatBallConfig(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 保存悬浮球配置 |
||||
|
async function saveFloatBallConfig() { |
||||
|
try { |
||||
|
if (!props.token) { |
||||
|
ElMessage.warning('未获取到代理标识,无法保存配置'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 调用后端接口保存到数据库 |
||||
|
await updateAgentFloatBall({ |
||||
|
uniqueToken: props.token, |
||||
|
pcFloatBall: floatBallConfig.value.pcImage, |
||||
|
mobileFloatBall: floatBallConfig.value.mobileImage |
||||
|
}); |
||||
|
|
||||
|
// 保存到本地 |
||||
|
const storageKey = `floatBallConfig_${props.token}`; |
||||
|
localStorage.setItem(storageKey, JSON.stringify(floatBallConfig.value)); |
||||
|
|
||||
|
emit('save', floatBallConfig.value); |
||||
|
emit('update:config', floatBallConfig.value); |
||||
|
|
||||
|
ElMessage.success('悬浮球设置保存成功!'); |
||||
|
showSettingDialog.value = false; |
||||
|
} catch (error) { |
||||
|
console.error(error); |
||||
|
ElMessage.error('保存失败,请重试'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 加载本地配置(降级方案) |
||||
|
function loadFloatBallConfig() { |
||||
|
const storageKey = `floatBallConfig_${props.token || 'default'}`; |
||||
|
const savedConfig = localStorage.getItem(storageKey); |
||||
|
|
||||
|
if (savedConfig) { |
||||
|
try { |
||||
|
const parsed = JSON.parse(savedConfig); |
||||
|
floatBallConfig.value = { ...defaultConfig, ...parsed }; |
||||
|
} catch (e) { |
||||
|
floatBallConfig.value = { ...defaultConfig }; |
||||
|
} |
||||
|
} else if (props.initialConfig && Object.keys(props.initialConfig).length > 0) { |
||||
|
floatBallConfig.value = { ...defaultConfig, ...props.initialConfig }; |
||||
|
} |
||||
|
emit('update:config', floatBallConfig.value); |
||||
|
} |
||||
|
|
||||
|
// 监听弹窗打开,加载服务器数据 ✅ |
||||
|
watch(showSettingDialog, (val) => { |
||||
|
if (val && props.token) { |
||||
|
loadConfigFromServer(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// 监听token变化 |
||||
|
watch(() => props.token, () => { |
||||
|
if (props.token) { |
||||
|
loadConfigFromServer(); |
||||
|
} else { |
||||
|
loadFloatBallConfig(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
onMounted(() => { |
||||
|
if (props.token) { |
||||
|
loadConfigFromServer(); |
||||
|
} else { |
||||
|
loadFloatBallConfig(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
defineExpose({ |
||||
|
showDialog: () => { |
||||
|
showSettingDialog.value = true; |
||||
|
}, |
||||
|
getConfig: () => floatBallConfig.value, |
||||
|
setConfig: (config: Partial<FloatBallConfig>) => { |
||||
|
floatBallConfig.value = { ...floatBallConfig.value, ...config }; |
||||
|
} |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss" scss> |
||||
|
.float-ball-setting { |
||||
|
.setting-header { |
||||
|
display: flex; |
||||
|
justify-content: flex-end; |
||||
|
margin-bottom: 16px; |
||||
|
padding: 0 4px; |
||||
|
} |
||||
|
|
||||
|
.upload-section { |
||||
|
display: flex; |
||||
|
gap: 16px; |
||||
|
align-items: center; |
||||
|
|
||||
|
.upload-tip { |
||||
|
p { |
||||
|
margin: 0; |
||||
|
color: #909399; |
||||
|
font-size: 12px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.dialog-footer { |
||||
|
display: flex; |
||||
|
justify-content: flex-end; |
||||
|
gap: 12px; |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
Loading…
Reference in new issue