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