|
|
|
|
<template>
|
|
|
|
|
<div class="getCode_container">
|
|
|
|
|
<div class="content">
|
|
|
|
|
<el-card shadow="never">
|
|
|
|
|
<!-- 客服选择 -->
|
|
|
|
|
<div class="kefu-selector">
|
|
|
|
|
<span class="kefu-label">指定客服:</span>
|
|
|
|
|
<el-select v-model="selectedKefuId" placeholder="不指定(系统自动分配)" size="small" style="width: 240px" @change="onKefuChange">
|
|
|
|
|
<el-option v-for="item in kefuList" :key="item.id" :label="item.nickName" :value="item.id">
|
|
|
|
|
<div style="display: flex; align-items: center; gap: 8px">
|
|
|
|
|
<el-avatar :src="item.headImageThumb" :size="24" v-if="item.headImageThumb" />
|
|
|
|
|
<span>{{ item.nickName }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</el-option>
|
|
|
|
|
</el-select>
|
|
|
|
|
<el-button
|
|
|
|
|
v-if="selectedKefuId !== ''"
|
|
|
|
|
size="small"
|
|
|
|
|
type="danger"
|
|
|
|
|
plain
|
|
|
|
|
@click="onClearKefu"
|
|
|
|
|
style="margin-left: 8px;"
|
|
|
|
|
>
|
|
|
|
|
清除
|
|
|
|
|
</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 使用悬浮球设置组件 -->
|
|
|
|
|
<FloatBallSetting
|
|
|
|
|
ref="floatBallSettingRef"
|
|
|
|
|
:token="uniqueToken"
|
|
|
|
|
@save="handleFloatBallConfigSaved"
|
|
|
|
|
@update:config="handleFloatBallConfigUpdate"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<el-collapse v-model="activeName" accordion>
|
|
|
|
|
<el-collapse-item title="超链接" name="1">
|
|
|
|
|
<alink :tokeninfo="token" :site-url="siteUrl" :unique-token="uniqueToken" :kefu-id="selectedKefuId" @cget-copy="getCopy"></alink>
|
|
|
|
|
</el-collapse-item>
|
|
|
|
|
<el-collapse-item title="网页内嵌" name="2">
|
|
|
|
|
<wangye
|
|
|
|
|
:tokeninfo="token"
|
|
|
|
|
:site-url="siteUrl"
|
|
|
|
|
:unique-token="uniqueToken"
|
|
|
|
|
:kefu-id="selectedKefuId"
|
|
|
|
|
:float-config="floatBallConfig"
|
|
|
|
|
@cget-copy="getCopy"
|
|
|
|
|
></wangye>
|
|
|
|
|
</el-collapse-item>
|
|
|
|
|
</el-collapse>
|
|
|
|
|
</el-card>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { ref, computed, onMounted, getCurrentInstance } from 'vue';
|
|
|
|
|
// import { useStore } from 'vuex';
|
|
|
|
|
// import { adminAppCustomer, appReset } from '@/api/kefu';
|
|
|
|
|
import alink from './components/alink.vue';
|
|
|
|
|
import { ElMessage } from 'element-plus';
|
|
|
|
|
|
|
|
|
|
import wangye from './components/wangye.vue';
|
|
|
|
|
import FloatBallSetting from './components/FloatBallSetting.vue';
|
|
|
|
|
import { listUser, listAllCustomer } from '@/api/im/user/customer';
|
|
|
|
|
import type { FloatBallConfig } from './components/FloatBallSetting.vue';
|
|
|
|
|
import { getInfo } from '@/api/login';
|
|
|
|
|
import { getDefaultCustomer, saveDefaultCustomer } from '@/api/im/agent';
|
|
|
|
|
// import kaifa from './components/kaifa';
|
|
|
|
|
// import setting from './components/setting';
|
|
|
|
|
|
|
|
|
|
const activeName = ref('1');
|
|
|
|
|
const floatBallSettingRef = ref<InstanceType<typeof FloatBallSetting>>();
|
|
|
|
|
const uniqueToken = ref('');
|
|
|
|
|
const { proxy } = getCurrentInstance() as any;
|
|
|
|
|
|
|
|
|
|
// const store = useStore();
|
|
|
|
|
// const isMobile = computed(() => store.state.media?.isMobile);
|
|
|
|
|
// const categoryId = computed(() => store.state.userLevel?.categoryId);
|
|
|
|
|
// const labelWidth = computed(() => (isMobile.value ? undefined : 75));
|
|
|
|
|
// const labelPosition = computed(() => (isMobile.value ? 'top' : 'left'));
|
|
|
|
|
// 👇 定义悬浮球配置(默认空)
|
|
|
|
|
const floatBallConfig = ref<FloatBallConfig>({
|
|
|
|
|
pcImage: '',
|
|
|
|
|
mobileImage: ''
|
|
|
|
|
});
|
|
|
|
|
const token = ref<any>('');
|
|
|
|
|
const canfrime = ref(false);
|
|
|
|
|
const srcUrl = ref(`${location.origin}/customerServer.js`);
|
|
|
|
|
const siteUrl = ref(`${location.origin}`);
|
|
|
|
|
const cloneTip = ref(false);
|
|
|
|
|
const canCustomerServer = ref('');
|
|
|
|
|
|
|
|
|
|
const selectedKefuId = ref<number | ''>('');
|
|
|
|
|
const kefuList = ref<any[]>([]);
|
|
|
|
|
const kefuSaving = ref(false);
|
|
|
|
|
const linkUrl = computed(() => `${location.origin}/chat/index?token=${token.value?.token_md5}&noCanClose=1`);
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
// 页面加载就获取当前代理的 uniqueToken ✅
|
|
|
|
|
fetchAgentToken();
|
|
|
|
|
});
|
|
|
|
|
// onMounted(() => {
|
|
|
|
|
// getAdminAppCustomer();
|
|
|
|
|
// });
|
|
|
|
|
|
|
|
|
|
// async function getAdminAppCustomer() {
|
|
|
|
|
// try {
|
|
|
|
|
// const mod: any = await import('@/api/kefu');
|
|
|
|
|
// if (mod && typeof mod.adminAppCustomer === 'function') {
|
|
|
|
|
// const res: any = await mod.adminAppCustomer();
|
|
|
|
|
// if (res.status === 200 && res.data?.list?.length) {
|
|
|
|
|
// token.value = res.data.list[0];
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// } catch (e) {
|
|
|
|
|
// // ignore if module not present
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// 获取当前登录用户的 uniqueToken
|
|
|
|
|
async function fetchAgentToken() {
|
|
|
|
|
try {
|
|
|
|
|
const res = await getInfo();
|
|
|
|
|
if (res.data?.tokenInfo?.uniqueToken) {
|
|
|
|
|
// 拿到真正的 token!
|
|
|
|
|
uniqueToken.value = res.data.tokenInfo.uniqueToken;
|
|
|
|
|
await Promise.all([loadKefuList(), loadDefaultKefu()]);
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('获取token失败', e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadKefuList() {
|
|
|
|
|
try {
|
|
|
|
|
const res = await listAllCustomer();
|
|
|
|
|
// 新接口返回的是 R<List<ImUserVo>>,结构是 res.data
|
|
|
|
|
kefuList.value = (res as any)?.data || [];
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('获取客服列表失败', e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 加载已保存的默认客服ID
|
|
|
|
|
async function loadDefaultKefu() {
|
|
|
|
|
try {
|
|
|
|
|
const kefuId = await getDefaultCustomer();
|
|
|
|
|
if (kefuId.data) {
|
|
|
|
|
selectedKefuId.value = kefuId.data;
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('获取默认客服失败', e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function onKefuChange(val: number | '') {
|
|
|
|
|
if (kefuSaving.value) return;
|
|
|
|
|
kefuSaving.value = true;
|
|
|
|
|
try {
|
|
|
|
|
await saveDefaultCustomer(val || null);
|
|
|
|
|
ElMessage.success('已保存');
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('保存默认客服失败', e);
|
|
|
|
|
} finally {
|
|
|
|
|
kefuSaving.value = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function onClearKefu() {
|
|
|
|
|
if (kefuSaving.value) return;
|
|
|
|
|
selectedKefuId.value = '';
|
|
|
|
|
kefuSaving.value = true;
|
|
|
|
|
try {
|
|
|
|
|
await saveDefaultCustomer(null);
|
|
|
|
|
ElMessage.success('已清除');
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('清除默认客服失败', e);
|
|
|
|
|
} finally {
|
|
|
|
|
kefuSaving.value = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理悬浮球配置更新
|
|
|
|
|
function handleFloatBallConfigUpdate(config: FloatBallConfig) {
|
|
|
|
|
floatBallConfig.value = config;
|
|
|
|
|
// 这里可以将配置应用到客服组件
|
|
|
|
|
console.log('悬浮球配置已更新:', config);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理悬浮球配置保存
|
|
|
|
|
function handleFloatBallConfigSaved(config: FloatBallConfig) {
|
|
|
|
|
floatBallConfig.value = config;
|
|
|
|
|
// 可以在这里执行额外的保存逻辑,比如同步到服务器
|
|
|
|
|
console.log('悬浮球配置已保存:', config);
|
|
|
|
|
|
|
|
|
|
// 如果需要保存到服务器,可以在这里调用API
|
|
|
|
|
// saveFloatBallConfigToServer(config);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function resetToken() {
|
|
|
|
|
canfrime.value = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// async function confirme() {
|
|
|
|
|
// if (!token.value?.id) return;
|
|
|
|
|
// try {
|
|
|
|
|
// const mod: any = await import('@/api/kefu');
|
|
|
|
|
// if (mod && typeof mod.appReset === 'function') {
|
|
|
|
|
// const res: any = await mod.appReset(token.value.id);
|
|
|
|
|
// if (res?.status === 200) {
|
|
|
|
|
// token.value = { ...token.value, token: res.data.token };
|
|
|
|
|
// proxy?.$Message?.success('token 已重置');
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// } catch (e) {
|
|
|
|
|
// // ignore
|
|
|
|
|
// }
|
|
|
|
|
// canfrime.value = false;
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
function cancel() {
|
|
|
|
|
canfrime.value = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getCopy(id: string) {
|
|
|
|
|
const elem = document.getElementById(id);
|
|
|
|
|
if (!elem) return;
|
|
|
|
|
const content = copyToClipboard(elem as HTMLElement);
|
|
|
|
|
if (content) cloneTip.value = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function copyToClipboard(elem: HTMLElement) {
|
|
|
|
|
const targetId = '_hiddenCopyText_';
|
|
|
|
|
let target: HTMLTextAreaElement | HTMLInputElement | null = null as any;
|
|
|
|
|
const tagName = elem.tagName;
|
|
|
|
|
const isInput = tagName === 'INPUT' || tagName === 'TEXTAREA';
|
|
|
|
|
let origSelectionStart: number | undefined;
|
|
|
|
|
let origSelectionEnd: number | undefined;
|
|
|
|
|
|
|
|
|
|
if (isInput) {
|
|
|
|
|
target = elem as HTMLInputElement;
|
|
|
|
|
origSelectionStart = (elem as HTMLInputElement).selectionStart as number;
|
|
|
|
|
origSelectionEnd = (elem as HTMLInputElement).selectionEnd as number;
|
|
|
|
|
} else {
|
|
|
|
|
target = document.getElementById(targetId) as HTMLTextAreaElement;
|
|
|
|
|
if (!target) {
|
|
|
|
|
target = document.createElement('textarea');
|
|
|
|
|
target.style.position = 'absolute';
|
|
|
|
|
target.style.left = '-9999px';
|
|
|
|
|
target.style.top = '0';
|
|
|
|
|
target.id = targetId;
|
|
|
|
|
document.body.appendChild(target);
|
|
|
|
|
}
|
|
|
|
|
target.textContent = elem.textContent || '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const currentFocus = document.activeElement as HTMLElement | null;
|
|
|
|
|
target.focus();
|
|
|
|
|
(target as HTMLTextAreaElement).setSelectionRange(0, (target as HTMLTextAreaElement).value.length);
|
|
|
|
|
|
|
|
|
|
let succeed = false;
|
|
|
|
|
try {
|
|
|
|
|
succeed = document.execCommand('copy');
|
|
|
|
|
} catch (e) {
|
|
|
|
|
succeed = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (currentFocus && typeof currentFocus.focus === 'function') {
|
|
|
|
|
currentFocus.focus();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isInput && target) {
|
|
|
|
|
(elem as HTMLInputElement).setSelectionRange(origSelectionStart as number, origSelectionEnd as number);
|
|
|
|
|
} else if (target) {
|
|
|
|
|
target.textContent = '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
proxy?.$Message?.success('已成功复制到粘贴板');
|
|
|
|
|
return succeed;
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss">
|
|
|
|
|
.getCode_container {
|
|
|
|
|
.content {
|
|
|
|
|
width: 100%;
|
|
|
|
|
color: #323437;
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
margin-top: 18px;
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
padding: 10px;
|
|
|
|
|
}
|
|
|
|
|
.font-w {
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
margin: 10px 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.text-i {
|
|
|
|
|
text-indent: 2em;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.content > p {
|
|
|
|
|
margin-bottom: 6px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.code-content-wrap {
|
|
|
|
|
clear: both;
|
|
|
|
|
border: 1px solid #e4e4e4;
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
padding: 12px 17px;
|
|
|
|
|
background-color: #f8f8f8;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.other-wrap {
|
|
|
|
|
margin: 4px 0;
|
|
|
|
|
text-align: right;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.textarea {
|
|
|
|
|
border: none;
|
|
|
|
|
/* height: 40px; */
|
|
|
|
|
width: 100%;
|
|
|
|
|
outline: 0;
|
|
|
|
|
resize: none;
|
|
|
|
|
background-color: #f8f8f8;
|
|
|
|
|
font-family: Arial;
|
|
|
|
|
color: #323437;
|
|
|
|
|
line-height: 24px;
|
|
|
|
|
text-align: left;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.code {
|
|
|
|
|
border: none;
|
|
|
|
|
/* height: 40px; */
|
|
|
|
|
width: 100%;
|
|
|
|
|
outline: 0;
|
|
|
|
|
resize: none;
|
|
|
|
|
background-color: #f8f8f8;
|
|
|
|
|
font-family: Arial;
|
|
|
|
|
color: #323437;
|
|
|
|
|
line-height: 24px;
|
|
|
|
|
text-align: left;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn {
|
|
|
|
|
display: inline-block;
|
|
|
|
|
zoom: 1;
|
|
|
|
|
padding: 6px 16px;
|
|
|
|
|
border: 1px solid #d9dbdc;
|
|
|
|
|
border-radius: 2px;
|
|
|
|
|
line-height: 1;
|
|
|
|
|
color: #323437;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
outline: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn.btn-blue {
|
|
|
|
|
color: #fff;
|
|
|
|
|
background-color: #4f97e7;
|
|
|
|
|
border-color: #3085e3;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.setting-highlight {
|
|
|
|
|
color: #f15755;
|
|
|
|
|
margin-left: 5px;
|
|
|
|
|
line-height: 30px;
|
|
|
|
|
}
|
|
|
|
|
.fenlei {
|
|
|
|
|
margin: 10px 0;
|
|
|
|
|
border: 1px solid #eee;
|
|
|
|
|
padding: 30px;
|
|
|
|
|
padding-bottom: 10px;
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
}
|
|
|
|
|
.typetitle {
|
|
|
|
|
padding: 4px 7px;
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.ivu-modal-confirm {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
.modimg {
|
|
|
|
|
width: 40px !important;
|
|
|
|
|
height: 40px !important;
|
|
|
|
|
margin-right: 30px;
|
|
|
|
|
}
|
|
|
|
|
</style>
|