提交 f8a92a1f 作者: 施汉文

Merge branch 'release' into shw-feat-style

......@@ -16,8 +16,7 @@
<title>企微侧边栏</title>
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0,shrink-to-fit=no,user-scalable=no"> -->
<script src="https://g.alicdn.com/dingding/dinglogin/0.0.5/ddLogin.js"></script>
<!-- iconpark CDN链接 -->
<script src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/icons_27278_172.4236cc17b47ad2f8ea4b21bbf191bb50.es5.js"></script>
<script src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/svg_27278_173.e64d61edfe0d4824e2eeb0b7f478e568.js"></script>
</head>
<body>
<noscript>
......
......@@ -95,6 +95,14 @@
</el-menu-item>
</el-menu>
</div>
<<<<<<< HEAD =======
<!-- 绑定的 w 账号 -->
<bindUserList />
</div>
<div class="mobile-content">
<router-view></router-view>
>>>>>>> release
</div>
</div>
</template>
......@@ -175,6 +183,11 @@ export default {
icon: "gongnengicon-renwuliebiao",
},
{
label: "用户待办",
path: "/userToDo",
hasRedDot: false, // 红点状态
},
{
label: "微言助手",
path: "/aiChat",
icon: "gongnengicon-weiyanzhushou",
......@@ -197,7 +210,9 @@ export default {
"client_online_status",
"userid",
]),
...mapState("game", ["taskData"]),
// ...mapState("game", ["taskData"]),
// ...mapState("user", ["external_userid", "token", "userInfo"]),
...mapState("game", ["taskData", "accountSelect"]),
// 计算任务列表是否需要显示红点
hasTaskRedDot() {
return this.taskData.user_task > 0 || this.taskData.account_task > 0;
......@@ -258,7 +273,7 @@ export default {
newVal,
window.location.href,
this.token,
Cookies.get("token")
Cookies.get("token"),
);
// 强制更新组件
this.$forceUpdate();
......@@ -279,6 +294,16 @@ export default {
},
mounted() {
// 页面刷新时从 Cookie 恢复 token 到 store
// Cookies.set(
// "token",
// "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOjQwOTAsImRhdGEiOnsiY3Nlcl9pZCI6NDA5MCwiY3Nlcl9uYW1lIjoi5q-b57uG5LqaIn0sImlhdCI6MTc2ODU0Mjk0MiwiZXhwIjoxNzcxMTM0OTQyLCJuYmYiOjE3Njg1NDI5NDIsInN1YiI6InRva2Vu6K6k6K-BIiwianRpIjoiMjU0Zjg3OGQ3NzMyNWUyMzMyNDAwZTEwZWJkMjFkY2YifQ.3hHc6iQP-Xkz9Q5rMIOFENDdh5P-NSaRs4Y4ffbJcMg"
// );
// Cookies.set("corp_id", "wweaefe716636df3d1");
// Cookies.set("userid", "maoxiya");
// Cookies.set("cser_name", "毛细亚");
// Cookies.set("external_userid", "wm5rUgMgAAG_vfF4AHClsrS1S6MImVsQ");
// Cookies.set("cser_id", 4090);
// Cookies.set("weixin_blongs_id", 2862);
const cookieToken = Cookies.get("token");
if (cookieToken && !this.token) {
this.set_token(cookieToken);
......
......@@ -68,3 +68,19 @@ export function answerComment(data) {
data
})
}
// 会话生命线列表
export function sessionSummaryList(data) {
return request({
url: '/sidebar/client_session/sessionSummaryList',
method: 'post',
data
})
}
// 重新生成会话总结
export function regenerateSummary(data) {
return request({
url: '/sidebar/client_session/regenerateSummary',
method: 'post',
data
})
}
......@@ -1467,6 +1467,21 @@ export function sendEmail(data) {
})
})
}
// 获取种花道具列表
export function getPropList(data) {
return new Promise((resolve, reject) => {
cross_systemRequest({
system: 'zhangyou',
api: '/api/member/getPropData',
params: data
}).then((res) => {
resolve(res)
}).catch((error) => {
reject(error)
})
})
}
// 获取开/合服天数
export function getServerDayApi(data) {
return new Promise((resolve, reject) => {
......@@ -1481,6 +1496,20 @@ export function getServerDayApi(data) {
})
})
}
// 种花专属链接
export function getFlowerLink(data) {
return new Promise((resolve, reject) => {
cross_systemRequest({
system: 'zhangyou',
api: '/api/member/getPropUrl',
params: data
}).then((res) => {
resolve(res)
}).catch((error) => {
reject(error)
})
})
}
// 项目-视频分类
export function teachingVideoCategoryListApi(data) {
return new Promise((resolve, reject) => {
......@@ -1509,3 +1538,61 @@ export function teachingVideoVideoListApi(data) {
})
})
}
// 发送分身包
export function memberRegGameCloneLink(data) {
return new Promise((resolve, reject) => {
cross_systemRequest({
system: 'zhangyou',
api: '/api/member/memberRegGameCloneLink',
params: data
}).then((res) => {
resolve(res)
}).catch((error) => {
reject(error)
})
})
}
// 带教记录列表
export function roleTeachingList(data) {
return new Promise((resolve, reject) => {
cross_systemRequest({
system: 'zhangyou',
api: '/api/role/roleTeachingList',
params: data
}).then((res) => {
resolve(res)
}).catch((error) => {
reject(error)
})
})
}
// 新增带教
export function roleTeachingAdd(data) {
return new Promise((resolve, reject) => {
cross_systemRequest({
system: 'zhangyou',
api: '/api/role/roleTeachingAdd',
params: data
}).then((res) => {
resolve(res)
}).catch((error) => {
reject(error)
})
})
}
// 获取带教次数
export function roleTeachingNum(data) {
return new Promise((resolve, reject) => {
cross_systemRequest({
system: 'zhangyou',
api: '/api/role/roleTeachingNum',
params: data
}).then((res) => {
resolve(res)
}).catch((error) => {
reject(error)
})
})
}
......@@ -300,3 +300,35 @@ export function getMemberInfoApi(data) {
data,
});
}
// 角色礼包列表
export function getRoleSendingCodeList(data) {
return request({
url: returnApi('/corp_gift_package_list/getRoleSendingCodeList'),
method: 'post',
data,
});
}
// 跟进任务记录
export function corp_follow_up_task_index(data) {
return request({
url: returnApi('/corp_follow_up_task/index'),
method: 'post',
data
})
}
// 【智能待办】工作台-我的
export function corpIntelligentTaskMineList(data) {
return request({
url: returnApi('/corp_intelligent_task/mineList'),
method: 'post',
data
})
}
// 【智能待办】工作台-完成、未完成
export function corpIntelligentTaskExternalList(data) {
return request({
url: returnApi('/corp_intelligent_task/externalList'),
method: 'post',
data
})
}
\ No newline at end of file
<svg t="1765950660083" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5445" width="200" height="200"><path d="M512 938.666667C276.362667 938.666667 85.333333 747.637333 85.333333 512S276.362667 85.333333 512 85.333333s426.666667 191.029333 426.666667 426.666667-191.029333 426.666667-426.666667 426.666667z m0-64c200.298667 0 362.666667-162.368 362.666667-362.666667S712.298667 149.333333 512 149.333333 149.333333 311.701333 149.333333 512s162.368 362.666667 362.666667 362.666667z" p-id="5446"></path></svg>
\ No newline at end of file
<template>
<div class="loading rowFlex allCenter">
<svg-icon icon-class="loading" class="loadingIcon" />
<!-- <svg-icon icon-class="loading" class="loadingIcon" /> -->
<i class="el-icon-loading loadingIcon text-primary"></i>
<p class="text">加载中</p>
</div>
</template>
</template>
<script>
export default {
name: 'loading',
<script>
export default {
name: "loading",
data() {
return {
}
return {};
},
mounted() {},
methods: {}
}
</script>
<style lang="scss" scoped>
.loading{
methods: {},
};
</script>
<style lang="scss" scoped>
.loading {
position: absolute;
left: 50%;
transform: translateX(-50%);
top: 0;
.loadingIcon{
.loadingIcon {
font-size: 24px;
animation: rotage linear 1s infinite;
}
.text{
color: #409EFF;
.text {
color: #409eff;
font-size: 14px;
margin-left: 5px;
}
}
@keyframes rotage {
0%{
}
@keyframes rotage {
0% {
transform: rotate(0deg);
}
100%{
100% {
transform: rotate(360deg);
}
}
</style>
\ No newline at end of file
}
</style>
<template>
<div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" />
<svg v-else :class="svgClass" aria-hidden="true" :style="{ display: iconClass || svgName ? '' : 'none' }"
v-on="$listeners">
<div
v-if="isExternal"
:style="styleExternalIcon"
class="svg-external-icon svg-icon"
v-on="$listeners"
/>
<svg
v-else
:class="svgClass"
class="iconpark-icon"
aria-hidden="true"
:style="{ display: iconClass || svgName ? '' : 'none' }"
v-on="$listeners"
>
<use :xlink:href="iconName" />
</svg>
</template>
<script lang="jsx">
// doc: https://panjiachen.github.io/vue-element-admin-site/feature/component/svg-icon.html#usage
import { isExternal } from '@/utils/validate'
import { isExternal } from "@/utils/validate";
export default {
name: 'SvgIcon',
name: "SvgIcon",
props: {
iconClass: {
type: String,
default: ''
default: "",
},
svgName: {
type: String,
default: ''
default: "",
},
className: {
type: String,
default: ''
}
default: "",
},
},
computed: {
isExternal() {
return isExternal(this.iconClass)
return isExternal(this.iconClass);
},
iconName() {
if (this.svgName) {
return `#${this.svgName}`
return `#${this.svgName}`;
}
return `#icon-${this.iconClass}`
return `#icon-${this.iconClass}`;
},
svgClass() {
if (this.className) {
return 'svg-icon ' + this.className
return "svg-icon " + this.className;
} else {
return 'svg-icon'
return "svg-icon";
}
},
styleExternalIcon() {
return {
mask: `url(${this.iconClass}) no-repeat 50% 50%`,
'-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`
}
}
}
}
"-webkit-mask": `url(${this.iconClass}) no-repeat 50% 50%`,
};
},
},
};
</script>
<style scoped>
......
import Vue from 'vue'
{/* <iconpark-icon name="icon-fuzhi"></iconpark-icon> */}
// {/* <iconpark-icon name="icon-fuzhi"></iconpark-icon> */}
const copy = {
// 当被绑定的元素插入到DOM中时
inserted: function(el, binding) {
// 创建复制图标元素
const copyIcon = document.createElement('iconpark-icon')
// const copyIcon = document.createElement('div')
// copyIcon.setAttribute('icon-class', 'copy')
copyIcon.name='icon-fuzhi'
const copyIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
copyIcon.innerHTML = '<use href="#icon-fuzhi"></use>'
copyIcon.setAttribute('class', 'iconpark-icon')
copyIcon.style.cursor = 'pointer'
copyIcon.style.marginLeft = '8px'
copyIcon.style.fontSize = '16px'
copyIcon.style.height = '16px'
copyIcon.style.width = '16px'
copyIcon.title = '点击复制'
copyIcon.innerHTML='<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#a)"><mask id="b" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="14" height="14"><path d="M14 0H0v14h14z" fill="#fff"/></mask><g mask="url(#b)" stroke="#267ef0" stroke-width=".875" stroke-linecap="round" stroke-linejoin="round"><path d="M9.333 7.525v2.45c0 2.042-.816 2.858-2.858 2.858h-2.45c-2.042 0-2.858-.816-2.858-2.858v-2.45c0-2.042.816-2.858 2.858-2.858h2.45c2.042 0 2.858.816 2.858 2.858"/><path d="M12.833 4.025v2.45c0 2.042-.816 2.858-2.858 2.858h-.642V7.525c0-2.042-.816-2.858-2.858-2.858H4.667v-.642c0-2.042.816-2.858 2.858-2.858h2.45c2.042 0 2.858.816 2.858 2.858"/></g></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h14v14H0z"/></clipPath></defs></svg>'
// 设置元素的position为relative,确保图标的absolute定位正确
if (getComputedStyle(el).position === 'static') {
......@@ -21,10 +19,10 @@ const copy = {
}
// 添加复制图标到元素后面
el.insertBefore(copyIcon, el.nextSibling)
el.appendChild(copyIcon)
// 复制功能实现
copyIcon.addEventListener('click', async function(e) {
const clickHandler = async function(e) {
// 阻止事件冒泡
e.stopPropagation()
try {
......@@ -65,7 +63,11 @@ const copy = {
console.error('复制失败:', error)
Vue.prototype.$message.error('复制失败,请手动复制')
}
})
}
copyIcon.addEventListener('click', clickHandler)
// 存储事件处理器引用,以便在unbind时正确移除
copyIcon.__clickHandler = clickHandler
// 存储图标引用,以便在组件卸载时清理
el.__copyIcon = copyIcon
......@@ -75,11 +77,29 @@ const copy = {
unbind: function(el) {
// 清理事件监听器和元素
if (el.__copyIcon) {
el.__copyIcon.removeEventListener('click', null)
el.parentNode.removeChild(el.__copyIcon)
try {
// 移除事件监听器
const clickHandler = el.__copyIcon.__clickHandler
if (clickHandler) {
el.__copyIcon.removeEventListener('click', clickHandler)
delete el.__copyIcon.__clickHandler
}
// 安全地移除图标元素
if (el.__copyIcon && el.__copyIcon.parentNode) {
el.__copyIcon.parentNode.removeChild(el.__copyIcon)
} else if (el.__copyIcon && el.__copyIcon.remove) {
// 如果节点还在DOM中,使用 remove() 方法
el.__copyIcon.remove()
}
} catch (error) {
// 忽略移除节点时的错误,可能节点已经被移除了
console.warn('移除复制图标时出错:', error)
} finally {
delete el.__copyIcon
}
}
}
}
export default copy
\ No newline at end of file
......@@ -16,6 +16,7 @@ import taskList from '@/views/taskList.vue'
import aiChat from '@/views/components/aiChat/aiChat.vue'
import Cookies from 'js-cookie'
import store from '@/store'
import UserAgency from '@/views/userAgency/index.vue'
Vue.use(VueRouter)
import { getParams } from '@/utils/index'
const routes = [
......@@ -91,6 +92,11 @@ const routes = [
component: taskList
},
{
path: '/userToDo',
name: 'userToDo',
component: UserAgency
},
{
path: '/aiChat',
name: 'aiChat',
component: aiChat
......
......@@ -25,24 +25,40 @@
</div>
</template>
<script>
import AreaTransferApply from './components/ApplyRecords/AreaTransferApply.vue'
import errorHandle from './components/ApplyRecords/errorHandle.vue'
import TerminalTransfer from './components/ApplyRecords/TerminaTranfer.vue'
import report from './components/roleInfo/report.vue'
import AreaTransferApply from "./components/ApplyRecords/AreaTransferApply.vue";
import errorHandle from "./components/ApplyRecords/errorHandle.vue";
import TerminalTransfer from "./components/ApplyRecords/TerminaTranfer.vue";
import report from "./components/roleInfo/report.vue";
export default {
name: 'applyRecord',
name: "applyRecord",
components: {
AreaTransferApply,
errorHandle,
TerminalTransfer,
report
report,
},
data() {
return {
activeTab: 'report'
}
activeTab: "report",
};
},
created() {
const related_task_info = JSON.parse(
sessionStorage.getItem("related_task_info")
);
if (related_task_info) {
const type = {
6: "report",
12: "errorHandle",
14: "serve",
13: "terminal",
};
this.activeTab = type[related_task_info.related_task_type] || "report";
} else {
this.activeTab = "report";
}
}
},
};
</script>
<style lang="scss" scoped>
......
......@@ -33,10 +33,10 @@
@click="previewVideo(item)"
>
<div class="flex items-center">
<iconpark-icon
name="xiaoxicaozuo-chakan"
class="mr-[4px] text-[14px]"
></iconpark-icon>
<svg-icon
svgName="xiaoxicaozuo-chakan"
class="mr-[4px] h-[14px] w-[14px] text-[14px]"
></svg-icon>
预览
</div>
</el-button>
......@@ -44,10 +44,10 @@
@click="sendVideo(item)"
class="h-[24px] ml-[8px] cursor-pointer hover:bg-[#E7F1FD] text-[12px] w-[58px] p-0 flex justify-center items-center rounded-full bg-[#F7F8FA] text-[#267EF0]"
>
<iconpark-icon
name="icon-fasonghuashu"
class="text-[14px] mr-[4px]"
></iconpark-icon>
<svg-icon
svgName="icon-fasonghuashu"
class="text-[14px] w-[14px] h-[14px] mr-[4px]"
></svg-icon>
<span> 发送</span>
</div>
</div>
......
<template>
<div
class="p-[16px] h-full overflow-y-auto"
ref="conversationLifeline"
@scroll="handleScroll"
>
<div
class="flex justify-between"
v-for="(v, index) in sessionSummaryList"
:key="v._id"
>
<div class="flex flex-col py-[4px] min-w-[24px] shrink-0 items-center">
<div class="bg-primary h-[12px] w-[12px] rounded-full"></div>
<div class="pt-[10px] flex-1">
<div class="w-[1px] h-full bg-[#D6D9E0]"></div>
</div>
</div>
<div class="text-[#323335] text-[14px] flex-1">
<div class="leading-[20px] text-[12px] text-[#C9CDD4]">
{{ v.allot_time }}{{ v.close_time }}
</div>
<div
class="py-[8px] px-[12px] rounded-[8px] border-[1px] border-solid border-[#E5E6EB]"
>
<div class="flex items-center justify-between">
<span class="font-medium">会话总结</span>
<span class="text-[#86909C] text-[14px]"
>客服:{{ v.zq_user_name }}</span
>
</div>
<div class="px-[12px] py-[8px] rounded-[4px] bg-[#F7F8FA] mt-[4px]">
{{ v.session_summary }}
</div>
<div class="pt-[8px]">
<span class="font-medium">会话分析</span>
</div>
<div class="px-[12px] py-[8px] rounded-[4px] bg-[#F7F8FA] mt-[4px]">
{{ v.session_analysis }}
</div>
<div class="flex flex-wrap gap-[8px] mt-[8px]">
<div
v-for="(tag, tagIndex) in v.session_summary_label_str?.split(',')"
:key="tagIndex"
class="px-[6px] leading-[22px] rounded-[4px] bg-[#F5F5F5] text-[#131920] text-[13px]"
>
{{ tag }}
</div>
</div>
</div>
<div class="flex justify-end">
<el-button
type="text"
icon="el-icon-redo"
:loading="v.loading"
@click="getRegenerateSummary(v)"
>重新生成</el-button
>
</div>
</div>
</div>
<div
v-if="sessionSummaryList.length === 0 && !loading"
class="text-center py-8 text-[#C9CDD4]"
>
暂无会话总结
</div>
<div v-if="loading" class="text-center py-4 text-[#C9CDD4]">加载中...</div>
</div>
</template>
<script>
import { sessionSummaryList, regenerateSummary } from "@/api/aiChat";
import { mapState } from "vuex";
export default {
name: "ConversationLifeline",
data() {
return {
sessionSummaryList: [],
loading: false,
page: 1,
pageSize: 10,
total: 0,
isLastPage: false,
};
},
computed: {
...mapState("game", ["chatUserInfo"]),
...mapState("user", ["corp_id"]),
},
mounted() {
this.getSessionSummaryList();
},
methods: {
/**
* 获取会话总结列表
* @param {boolean} isLoadMore - 是否为加载更多
*/
async getSessionSummaryList(isLoadMore = false) {
// 如果已经是最后一页,则不再加载
if (isLoadMore && this.isLastPage) {
return;
}
try {
this.loading = true;
// 如果是加载更多,则页码+1
const currentPage = isLoadMore ? this.page + 1 : 1;
// 获取企业ID和外部用户ID
const corpId = this.corp_id;
const externalUserId = this.chatUserInfo.external_userid;
const params = {
corp_id: corpId,
external_userid: externalUserId,
page: currentPage,
page_size: this.pageSize,
};
const res = await sessionSummaryList(params);
if (res.status_code === 1) {
const newData = res.data.data.map((item) => ({
...item,
loading: false,
}));
const totalCount = res.data.page_info.total;
// 合并数据
if (isLoadMore) {
this.sessionSummaryList = [...this.sessionSummaryList, ...newData];
this.page = currentPage;
} else {
this.sessionSummaryList = newData;
this.page = 1;
}
this.total = totalCount;
// 判断是否为最后一页
this.isLastPage = this.sessionSummaryList.length >= this.total;
console.log("会话总结列表数据:", res.data);
return this.sessionSummaryList;
} else {
this.$message({
message: res.data.msg,
type: "error",
});
return [];
}
} catch (error) {
console.error("获取会话总结列表失败:", error);
this.$message({
message: "获取会话总结列表失败",
type: "error",
});
return [];
} finally {
this.loading = false;
}
},
/**
* 滚动到底部触发分页
*/
handleScroll() {
const container = this.$refs.conversationLifeline;
if (!container || this.loading || this.isLastPage) return;
// 滚动到底部的判断条件
const scrollTop = container.scrollTop;
const scrollHeight = container.scrollHeight;
const clientHeight = container.clientHeight;
// 当距离底部20px时触发加载更多
if (scrollHeight - scrollTop - clientHeight < 20) {
this.getSessionSummaryList(true);
}
},
// 重新生成会话总结
async getRegenerateSummary(data) {
data.loading = true;
try {
await regenerateSummary({ session_id: data.session_id });
if (res.status_code === 1) {
this.$message({
message: "重新生成成功",
type: "success",
});
// 重新加载数据
this.getSessionSummaryList();
} else {
this.$message({
message: res.data.msg,
type: "error",
});
}
} catch (error) {
console.error("重新生成会话总结失败:", error);
} finally {
data.loading = false;
}
},
},
};
</script>
......@@ -8,48 +8,56 @@
<el-tab-pane label="AI 跟进记录" name="aiFollow">
<summaryList v-if="activeName === 'aiFollow'" />
</el-tab-pane>
<el-tab-pane label="跟进任务记录" name="aiFollowTask">
<followTask v-if="activeName === 'aiFollowTask'" />
</el-tab-pane>
<el-tab-pane label="会话生命线" name="ConversationLifeline">
<ConversationLifeline v-if="activeName === 'ConversationLifeline'" />
</el-tab-pane>
</el-tabs>
</div>
</div>
</template>
<script>
import aiArgenChat from './aiArgenChat.vue'
import summaryList from './summaryList.vue'
import { mapActions } from 'vuex'
import aiArgenChat from "./aiArgenChat.vue";
import summaryList from "./summaryList.vue";
import followTask from "./followTask.vue";
import ConversationLifeline from "./ConversationLifeline.vue";
import { mapActions } from "vuex";
export default {
name: 'quickSendGame',
name: "quickSendGame",
components: {
aiArgenChat,
summaryList,
followTask,
ConversationLifeline,
},
data() {
return {
activeName: 'aiChat'
}
},
created() {
activeName: "aiChat",
};
},
created() {},
mounted() {
// this.initializeWecom()
},
methods: {
...mapActions('user', ['initWecom']),
...mapActions("user", ["initWecom"]),
async initializeWecom() {
try {
console.log('🚀 开始初始化企业微信 SDK')
const result = await this.initWecom()
console.log('✅ 企业微信 SDK 初始化成功', result)
console.log("🚀 开始初始化企业微信 SDK");
const result = await this.initWecom();
console.log("✅ 企业微信 SDK 初始化成功", result);
} catch (error) {
console.error('❌ 企业微信 SDK 初始化失败:', error)
console.error("❌ 企业微信 SDK 初始化失败:", error);
}
},
}
}
},
};
</script>
<style lang="scss" scoped>
.quickSendGame {
::v-deep .el-tabs__nav-next,
::v-deep .el-tabs__nav-prev {
line-height: 50px;
......
<template>
<div class="follow-task">
<!-- 消息列表 -->
<div v-infinite-scroll="requestDataList" v-loading="loading"
:infinite-scroll-disabled="!isloadMore" class="follow-task__scroll">
<div v-if="messageList.length > 0" class="follow-task__list">
<div v-for="(item, index) in messageList" :key="index" class="follow-task__item">
<div class="follow-task__date">{{ `${item.message_log_start_date} - ${item.message_log_end_date}` }}</div>
<div class="follow-task__content">
<div v-if="item.summary" class="follow-task__detail">
<div class="follow-task__detail-text" v-html="item.summary"></div>
</div>
<div v-else class="follow-task__no-detail">
<span>暂无详情</span>
</div>
</div>
</div>
</div>
<noContent v-else-if="!loading && messageList.length == 0" />
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import { corp_follow_up_task_index } from '@/api/works'
import { throttle } from '@/utils'
import noContent from '@/components/noContent.vue'
export default {
name: 'FollowTask',
props: {
chatUserDetails: {
typeof: Object,
default: () => { }
}
},
data() {
return {
loading: false,
isloadMore: true,
messageList: [],
pageInfo: {
page: 0,
page_size: 20
}
}
},
components: {
noContent
},
computed: {
...mapState('game', ['accountSelect', 'bindGameUserList']),
...mapState('user', ['corp_id']),
},
watch: {
accountSelect(newVal) {
if (newVal && newVal !== '' ) {
this.pageInfo = {
page: 0,
page_size: 20
}
this.isloadMore = true
this.messageList = []
this.requestDataList()
}
}
},
mounted() {
this.pageInfo = {
page: 0,
page_size: 20
}
this.isloadMore = true
this.messageList = []
this.loading = true
this.requestDataList()
},
methods: {
requestDataList: throttle(function () {
if (!this.isloadMore) {
return false
}
this.loading = true
this.pageInfo.page += 1
const bindGameUser = this.bindGameUserList.find(item => item.member_id == this.accountSelect)
const data = {
username: bindGameUser.username || '',
corp_id: this.corp_id,
status: 1,
...this.pageInfo
}
corp_follow_up_task_index(data).then(
(res) => {
this.loading = false
if (res.data.data && res.data.data.length < 20) {
this.isloadMore = false
}
this.messageList = this.messageList.concat(res.data.data)
if (res.data.page_info) {
this.pageInfo = res.data.page_info
}
},
(err) => {
this.loading = false
}
)
}, 500)
}
}
</script>
<style lang="scss" scoped>
.follow-task {
width: 100%;
height: 100%;
background: #fff;
&__scroll {
width: 100%;
height: 100%;
overflow: auto;
overflow-x: hidden;
padding: 16px;
}
&__empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: #999;
font-size: 14px;
.svg-icon {
font-size: 300px;
margin-bottom: 16px;
}
}
&__list {
display: flex;
flex-direction: column;
gap: 12px;
}
&__item {
display: flex;
flex-direction: column;
gap: 6px;
}
&__date {
font-size: 12px;
color: #c9cdd4;
font-weight: 400;
font-family: 'PingFang SC', sans-serif;
line-height: 1.6666666666666667em;
}
&__content {
background: #f7f8fa;
border-radius: 2px 6px 6px 6px;
padding: 8px 12px;
}
&__detail {
display: flex;
gap: 10px;
}
&__detail-text {
font-size: 14px;
color: #323335;
line-height: 1.5714285714285714em;
font-weight: 400;
font-family: 'PingFang SC', sans-serif;
word-break: break-word;
}
&__no-detail {
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
color: #999;
font-size: 14px;
}
}
</style>
......@@ -19,7 +19,7 @@
</el-input>
<el-popover placement="bottom" width="180" trigger="click">
<div>
<div>注游戏</div>
<div class="mt-[12px]">主游戏名</div>
<el-select
v-model="form.main_game_id"
filterable
......@@ -40,7 +40,7 @@
>
</el-option>
</el-select>
<div>区服</div>
<div class="mt-[12px]">区服</div>
<el-select
v-model.trim="form.server_info"
filterable
......@@ -61,8 +61,15 @@
>
</el-option>
</el-select>
<div class="mt-[12px]">CP角色ID</div>
<el-input
v-model.trim="form.cp_role_id"
:disabled="form.main_game_id == ''"
placeholder="请输入CP角色ID"
>
</el-input>
<div class="flex items-center justify-end">
<el-button type="text" @click="" class="text-[#323335]">
<el-button type="text" @click="resizeData" class="text-[#323335]">
重置
</el-button>
<el-button
......@@ -79,10 +86,10 @@
slot="reference"
class="shrink-0 !h-[32px] !w-[32px] ml-[8px] !p-[7px]"
>
<iconpark-icon
name="icon-shaixuan"
<svg-icon
svgName="icon-shaixuan"
class="text-[16px] text-[#D6D9E0]"
></iconpark-icon>
></svg-icon>
</el-button>
</el-popover>
</div>
......@@ -103,13 +110,13 @@
>{{ "" }}</el-radio
>
<div class="mx-[12px]">
<iconpark-icon
name="icon-shouqi"
<svg-icon
svgName="icon-shouqi"
class="text-[18px] text-[#B0B2B5] transition-transform"
:class="{ 'rotate-90': item.isExpand }"
v-show="!item.loading"
@click.stop="expandTag(item)"
></iconpark-icon>
></svg-icon>
<i class="el-icon-loading" v-show="item.loading"></i>
</div>
<div class="flex-1">
......@@ -177,7 +184,7 @@
<p class="text">
{{
moment(item.userDetails.seq_time * 1000).format(
"YYYY-MM-DD"
"YYYY-MM-DD",
)
}}
</p>
......@@ -187,7 +194,7 @@
<p class="text">
{{
moment(item.userDetails.reg_time * 1000).format(
"YYYY-MM-DD"
"YYYY-MM-DD",
)
}}
</p>
......@@ -251,6 +258,18 @@
</el-option>
</el-select>
</el-form-item>
<el-form-item
label="请输入CP角色ID"
prop="username"
>
<el-input
v-model.trim="form.cp_role_id"
placeholder="请先选择主游戏"
:disabled="form.main_game_id==''"
class="input-with-select"
>
</el-input>
</el-form-item>
<el-form-item label="请输入区服" prop="server_info">
<el-select
v-model.trim="form.server_info"
......@@ -274,11 +293,7 @@
</el-select>
</el-form-item>
<el-form-item label="请输入角色名" prop="role_name">
<el-input
v-model.trim="form.role_name"
placeholder="请输入角色"
class="input-with-select"
>
<el-input v-model.trim="form.role_name" :disabled="form.main_game_id==''" placeholder="请先选择主游戏" class="input-with-select">
</el-input>
</el-form-item>
<el-form-item>
......@@ -405,6 +420,7 @@ export default {
member_id: "",
username: "",
role_name: "",
cp_role_id: "",
main_game_id: "",
server_info: "",
},
......@@ -533,7 +549,7 @@ export default {
},
expandTag(item) {
const index = this.tableList.findIndex(
(i) => i.member_id == item.member_id
(i) => i.member_id == item.member_id,
);
if (!item.userDetails) {
this.$set(this.tableList[index], "loading", true);
......@@ -567,6 +583,7 @@ export default {
role_name: "",
main_game_id: "",
server_info: "",
cp_role_id: "",
};
this.inputValue = "";
this.tableList = [];
......@@ -670,12 +687,15 @@ export default {
text-align: left;
margin-bottom: 10px;
}
.content {
.content-wrapper {
width: 100%;
overflow: auto;
padding-bottom: 200px;
}
.content {
width: 100%;
padding: 0 10px;
.inputContent {
width: 100%;
}
......
......@@ -142,6 +142,13 @@ export default {
}
}
},
show_game_name(item) {
if (process.env.NODE_ENV == "production") {
return item.main_game_id == 187;
} else {
return item.main_game_id == 174;
}
},
addNewUser() {
this.showLayer = true;
},
......@@ -162,7 +169,7 @@ export default {
this.chatUserDetails.self_defined_columns.length > 0
) {
this.memberCheckList = this.chatUserDetails.self_defined_columns.map(
(item) => item.name
(item) => item.name,
);
} else {
this.memberCheckList = [];
......
......@@ -41,6 +41,18 @@
>
</el-table-column>
<el-table-column
label="CP角色ID"
prop="cp_role_id"
width="140"
>
</el-table-column>
<el-table-column
label="马甲包"
prop="game_name"
width="140"
>
</el-table-column>
<el-table-column
label="充值金额"
prop="recharge_total"
>
......
<template>
<div v-loading="loading" class="role-gift-container">
<div ref="giftList" class="gift-list" @scroll="handleScroll">
<div v-for="item in giftList" :key="item._id" class="gift-item">
<div class="gift-info">
<div v-if="item.gift_package_group_name ">分组: {{ item.gift_package_group_name }}</div>
<div>礼包名称: {{ item.gift_package_name }}</div>
<div>发送时间: {{ item.send_time }}</div>
<div class="giftCodeText">{{ item.code }}</div>
<div>领取角色: <span v-if="item.role_name">{{ item.role_name }}</span> <span v-else>-</span> </div>
<div>W 账号: {{ item.username || '-' }}</div>
<div class="rowFlex spaceBetween columnCenter gift-sender">
<div>发送客服: {{ item.cser_name || '自助链接' }}</div>
<i class="el-icon-document-copy" style="cursor: pointer;" @click="handleCopy(item.code)"></i>
</div>
</div>
</div>
<div v-if="loading" class="loading-more">加载中...</div>
<div v-if="!hasMore && giftList.length > 0" class="no-more">没有更多数据了</div>
<div v-if="giftList.length == 0" class="noContent rowFlex allCenter">
<svg-icon icon-class="noContent" />
</div>
</div>
</div>
</template>
<script>
import { getRoleSendingCodeList } from '@/api/works'
import { debounce,copyText, sendChatMessage} from '@/utils/index'
import { mapState } from 'vuex'
export default {
name: 'roleGift',
data() {
return {
loading: false,
giftList: [],
page: 1,
pageSize: 20,
hasMore: true
}
},
computed: {
...mapState('game', ['accountSelect'])
},
created() {
if(this.accountSelect && this.accountSelect !== ''){
this.fetchGiftList()
} else {
this.$message.warning('请先关联W账号!')
}
},
methods: {
async fetchGiftList() {
if (this.loading || !this.hasMore) return
this.loading = true
try {
const params = {
page: this.page,
page_size: this.pageSize,
member_id: this.accountSelect
}
const res = await getRoleSendingCodeList(params)
if (res.data && res.data.data) {
this.giftList = this.page === 1 ? res.data.data : [...this.giftList, ...res.data.data]
this.hasMore = res.data.data.length === this.pageSize
this.page++
}
} catch (error) {
console.error('获取礼包列表失败:', error)
} finally {
this.loading = false
}
},
handleScroll: debounce(function(e) {
const { scrollHeight, scrollTop, clientHeight } = e.target
if (scrollHeight - scrollTop - clientHeight < 50) {
this.fetchGiftList()
}
}, 500),
handleCopy(code) {
console.log(code, 'code')
copyText('giftCodeText', this)
sendChatMessage(code, 'text')
}
}
}
</script>
<style lang="scss" scoped>
.role-gift-container {
height: calc(100vh - 70px);
width: 100%;
background-color: #fff;
.gift-list {
height: 100%;
overflow-y: auto;
padding: 10px;
.gift-item {
padding: 16px;
background: #F7F8FA;
border-radius: 12px;
border: 1px solid #E5E6EB;
margin-bottom:12px;
.gift-info {
div{
line-height: 26px;
}
.gift-name {
font-weight: 400;
font-size: 14px;
color: #4E5969;
text-align: left;
font-style: normal;
}
.gift-code {
display: flex;
align-items: center;
justify-content: space-between;
border-radius: 4px;
font-family: PingFangSC, PingFang SC;
font-weight: 500;
font-size: 14px;
color: #323335;
text-align: justify;
font-style: normal;
span {
color: #323335;
font-size: 14px;
font-family: PingFangSC-Medium;
}
.el-button {
padding: 5px 12px;
border: 1px solid #dcdfe6;
border-radius: 4px;
font-size: 13px;
color: #606266;
&:hover {
color: #409eff;
border-color: #c6e2ff;
background-color: #ecf5ff;
}
}
}
.gift-sender {
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 12px;
color: #4E5969;
line-height: 18px;
text-align: justify;
font-style: normal;
i{
color: #00BF8A;
font-size: 14px;
}
}
}
}
.loading-more,
.no-more,
.empty-data {
text-align: center;
padding: 16px;
color: #999999;
font-size: 13px;
}
}
}
</style>
\ No newline at end of file
......@@ -6,7 +6,7 @@
<div v-if="item.gift_package_group_name">分组: {{ item.gift_package_group_name }}</div>
<div>礼包名称: {{ item.gift_package_name }}</div>
<div>发送时间: {{ item.send_time }}</div>
<div>礼包码: {{ item.code }}</div>
<div class="giftCodeText">礼包码: {{ item.code }}</div>
<div>领取角色: <span v-if="item.role_name && item.role_name != 0">{{ item.role_name }}</span> <span
v-else>-</span>
</div>
......
<template>
<el-drawer
title="选择角色"
:visible="dialogVisible"
direction="rtl"
size="80%"
@close="close"
>
<div class="flower-link-dialog">
<el-form
:model="form"
label-position="top"
:rules="rules"
ref="formRef"
>
<el-form-item label="W账号" prop="member_id">
<el-select
v-model="form.member_id"
placeholder="请选择W账号"
style="width: 100%"
@change="handleAccountChange"
>
<el-option
v-for="item in bindGameUserList"
:key="item.member_id"
:label="item.username"
:value="item.member_id"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="角色" prop="role_id">
<el-select
v-model="form.role_id"
placeholder="请选择角色"
style="width: 100%"
@change="handleRoleChange"
>
<el-option
v-for="item in roleList"
:key="item.role_id"
:label="item.role_name"
:value="item.role_id"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="道具" prop="prop_id">
<el-select
v-model="form.prop_id"
placeholder="请选择道具"
style="width: 100%"
:disabled="!form.role_id"
>
<el-option
v-for="item in propList"
:key="item.prop_id"
:label="item.prop_name"
:value="item.prop_id"
></el-option>
</el-select>
</el-form-item>
</el-form>
<div class="drawer-footer">
<el-button @click="close">取消</el-button>
<el-button type="primary" :loading="loading" @click="submitForm">确定</el-button>
</div>
</div>
</el-drawer>
</template>
<script>
import { getRoleHoLo, getPropList, getFlowerLink } from '@/api/game'
export default {
name: 'FlowerLinkDialog',
props: {
dialogVisible: {
type: Boolean,
default: false
},
bindGameUserList: {
type: Array,
default: () => []
},
member_id: {
type: String,
default: ''
}
},
data() {
return {
form: {
member_id: '',
role_id: '',
prop_id: ''
},
rules: {
member_id: [{ required: true, message: '请选择W账号', trigger: 'change' }],
role_id: [{ required: true, message: '请选择角色', trigger: 'change' }],
prop_id: [{ required: true, message: '请选择道具', trigger: 'change' }]
},
loading: false,
roleList: [],
propList: [],
}
},
mounted() {
this.form.member_id = this.member_id
this.handleAccountChange(this.member_id)
},
methods: {
// 处理W账号变化
async handleAccountChange(memberId) {
this.form.role_id = ''
this.form.prop_id = ''
this.roleList = []
this.propList = []
if (memberId) {
await this.requestRoleList(memberId)
}
},
// 处理角色变化
async handleRoleChange(roleId) {
this.form.prop_id = ''
this.propList = []
if (roleId && this.form.member_id) {
await this.requestPropList(this.form.member_id, roleId)
}
},
// 请求角色列表
requestRoleList(memberId) {
return new Promise((resolve, reject) => {
const data = {
api_search_name: '',
member_id: memberId,
search_type: 'list',
page: 1,
page_size: 100
}
getRoleHoLo(data).then(
(res) => {
if (res.status_code == 1) {
this.roleList = res.data.data || []
} else {
this.roleList = []
this.$message.error(res.msg || '获取角色列表失败')
}
resolve()
},
(err) => {
reject(err)
}
)
})
},
// 请求道具列表
requestPropList(memberId, roleId) {
return new Promise((resolve, reject) => {
const data = {
member_id: memberId,
role_id: roleId
}
getPropList(data).then(
(res) => {
if (res.status_code == 1) {
this.propList = res.data.data || []
} else {
this.propList = []
this.$message.error(res.msg || '获取道具列表失败')
}
resolve()
},
(err) => {
reject(err)
}
)
})
},
// 提交表单
submitForm() {
this.$refs.formRef.validate(async (valid) => {
if (valid) {
try {
this.loading = true
// 获取选中的道具信息,用于获取main_game_id
const selectedProp = this.propList.find(item => item.prop_id === this.form.prop_id)
if (!selectedProp) {
this.$message.error('未找到选中的道具信息')
return
}
// 请求种花专属链接
const res = await getFlowerLink({
main_game_id: selectedProp.main_game_id,
prop_id: this.form.prop_id
})
if (res.status_code == 1) {
// 发送成功,通过事件通知父组件
this.$emit('success', res.data.data.url)
this.close()
} else {
this.$message.error(res.msg || '获取种花链接失败')
}
} catch (error) {
this.$message.error(res.msg)
} finally {
this.loading = false
}
}
})
},
// 重置表单
close() {
this.form = {
member_id: this.member_id,
role_id: '',
prop_id: ''
}
this.roleList = []
this.propList = []
this.$emit('update:dialogVisible', false)
if (this.$refs.formRef) {
this.$refs.formRef.resetFields()
}
}
}
}
</script>
<style lang="scss" scoped>
.flower-link-dialog {
padding: 20px;
padding-top: 0px;
}
.el-form {
margin-bottom: 30px;
}
.el-form-item {
margin-bottom: 20px;
}
.el-form-item__label {
font-weight: 500;
}
.drawer-footer {
position: absolute;
bottom: 20px;
left: 20px;
right: 20px;
display: flex;
justify-content: flex-end;
padding-top: 20px;
border-top: 1px solid #ebeef5;
background-color: #fff;
}
</style>
<!--
* @Author: maoxiya 937667504@qq.com
* @Date: 2025-08-28 15:59:46
* @LastEditors: maoxiya 937667504@qq.com
* @LastEditTime: 2025-09-02 09:51:24
* @LastEditors: 金多虾 937667504@qq.com
* @LastEditTime: 2026-01-13 10:21:28
* @FilePath: /company_wx_frontend/src/views/works/component/chat/giftCodeDialog.vue
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
......@@ -54,6 +54,7 @@ export default {
member_id: '',
username: '',
role_id: '',
recharge_total: 0,
role_name: '',
},
rules: {
......@@ -81,13 +82,14 @@ export default {
handleMemberIdChange(value) {
this.form.role_id = ''
this.form.role_name = ''
this.form.recharge_total = 0
const userInfo = this.bindGameUserList.find(item => item.value === value)
this.form.username = userInfo.label || userInfo.username
this.form.member_id = value
},
handleSelectionChange(roleInfo) {
console.log(roleInfo, 'roleInfo')
this.form.role_name = roleInfo.label || ''
this.form.recharge_total = roleInfo.recharge_total || 0
},
closeDialog() {
this.$emit('update:dialogVisible', false)
......
......@@ -326,6 +326,7 @@
this.$emit('confirm', res.data.data)
this.$message.success('发送成功')
}
} catch (error) {
this.loading = false
console.error('获取链接失败:', error)
......
......@@ -147,7 +147,6 @@ export default {
// list = [{ msgtype: 'text', text: { content: str }}]
this.sendChatMessage(str,'text')
}
// this.set_sendSkillMessage(list)
// 3:召回 召回的时候请求召回染色的接口
this.recallChannelSeq()
}
......
......@@ -37,9 +37,6 @@ export default {
close() {
this.$emit('update:show', false)
},
// const list = [{ msgtype: 'text', text: { content: `${str}${item.url}` }}]
// this.set_sendSkillMessage(list)
onConfirm() {
if (this.webForm.channel_id === '') {
this.$message.warning('请选择渠道')
......@@ -105,8 +102,6 @@ export default {
// list = [{ msgtype: 'text', text: { content: str }}]
this.sendChatMessage(str,'text')
}
// this.set_sendSkillMessage(list)
// this.sendChatMessage(str,'text')
// 3:召回 召回的时候请求召回染色的接口
this.recallChannelSeq()
}
......
......@@ -17,7 +17,7 @@
<el-collapse v-model="collapseActive">
<div
v-for="(items, indexs) in roleList"
:key="indexs"
:key="items.role_id"
class="contentItem"
>
<div class="title"></div>
......@@ -170,12 +170,31 @@
</div>
<div class="item rowFlex columnCenter spaceBetween">
<div class="rowFlex columnCenter">
<span class="label">主服开服天数:</span>
<p class="text" v-if="items.main_server_open_day">
{{ items?.main_server_open_day }}天
</p>
</div>
</div>
<div class="item rowFlex columnCenter spaceBetween">
<div class="rowFlex columnCenter">
<span class="label">开/合服天数:</span>
<p class="text" v-if="items.server_day">
{{ items?.server_day }}天
</p>
</div>
</div>
<!-- 带教记录 -->
<div class="item rowFlex columnCenter spaceBetween">
<div class="rowFlex columnCenter">
<span class="label">带教次数:</span>
<el-button
type="text"
style="cursor: pointer;z-index:10;"
@click.stop="openMentorRecordDrawer(items)"
>{{ items.teach_num || 0 }}次</el-button>
</div>
</div>
</div>
</el-collapse-item>
</div>
......@@ -196,24 +215,39 @@
:show.sync="showAppeal"
:appeal-info="appealInfo"
/>
<!-- 带教记录弹窗 -->
<el-drawer
:visible.sync="showMentorRecord"
v-if="showMentorRecord"
:title="`${currentRoleName}带教记录`"
size="300px"
:append-to-body="true"
>
<mentorRecord
:role-id="currentRoleId"
@refresh="handleMentorRecordRefresh"
/>
</el-drawer>
</div>
</template>
<script>
import { mapState, mapMutations, mapActions } from "vuex";
import { getRoleHoLo, marketingRoleGrade, getServerDayApi } from "@/api/game";
import { getRoleHoLo, marketingRoleGrade, getServerDayApi,roleTeachingNum } from "@/api/game";
import noContent from "@/components/noContent.vue";
import appeal from "./layer/appeal.vue";
import watchMember from "@/mixins/watchMember";
import { createDetails } from "@/views/popup/RecentActivitiesPopup/index.js";
import { createRoleRecentActivityNotPushNum } from "@/views/hooks/useGetCount.js";
import vipLevel from "@/views/userInfo/components/gameInfo/vipLevel.vue";
import mentorRecord from "@/views/userInfo/components/gameInfo/mentorRecord.vue";
export default {
name: "roleInfo",
components: {
noContent,
appeal,
vipLevel,
mentorRecord,
},
data() {
return {
......@@ -232,6 +266,9 @@ export default {
recentActivitiesPopupInstance: null, //近期要开模块弹框
roleRecentActivityNotPushNumInstance: null, //侧边栏计数弹框
numRoleIdList: [],
showMentorRecord: false, // 带教记录弹窗显示状态
currentRoleId: null, // 当前查看带教记录的角色ID
currentRoleName: '' // 当前查看带教记录的角色名称
};
},
computed: {
......@@ -240,7 +277,11 @@ export default {
watch: {
collapseActive(newVal, oldVal) {
if (newVal.length > 0) {
this.handleChange(newVal.filter((item) => !oldVal.includes(item)));
const newOpenedItems = newVal.filter(item => !oldVal.includes(item))
this.handleChange(newOpenedItems)
// 处理带教次数获取
this.handleChangeRoleTeachingNum(newOpenedItems)
}
},
},
......@@ -295,7 +336,6 @@ export default {
});
}
},
async handleChange(v) {
const index = this.roleList.findIndex(
(item) => v.includes(item.role_id) && !item.server_day
......@@ -305,6 +345,8 @@ export default {
role_id: this.roleList[index].role_id,
});
this.roleList[index].server_day = res.data.data?.server_day;
this.roleList[index].main_server_open_day =
res.data.data?.main_server_open_day;
this.roleList = [...this.roleList];
}
},
......@@ -344,6 +386,67 @@ export default {
}
);
},
/**
* 打开带教记录弹窗
*/
openMentorRecordDrawer(roleItem) {
console.log(roleItem, 'roleItem')
this.currentRoleId = roleItem.role_id
this.currentRoleName = `${roleItem.role_name}-${roleItem.server_name}`
this.showMentorRecord = true
},
/**
* 带教记录刷新后,更新角色列表中的带教次数
*/
async handleMentorRecordRefresh(roleId,teachingListLength) {
// 重新获取角色列表,更新带教次数
const index = this.roleList.findIndex(item => item.role_id === roleId)
if (index !== -1) {
this.$set(this.roleList[index], 'teach_num', teachingListLength)
}
this.roleList = this.roleList.concat([])
},
/**
* 处理角色展开时获取带教次数
* @param {Array} openedRoleIds - 新展开的角色ID数组
*/
handleChangeRoleTeachingNum(openedRoleIds) {
if (!openedRoleIds || openedRoleIds.length === 0) {
return
}
// 遍历新展开的角色,获取带教次数
openedRoleIds.forEach(roleId => {
const roleItem = this.roleList.find(item => item.role_id === roleId)
if (roleItem) {
// 如果已经存在 teach_num 字段,则不请求接口
if (roleItem.teach_num === undefined || roleItem.teach_num === null) {
this.getRoleTeachingNum(roleId)
}
}
})
},
/**
* 获取角色带教次数
* @param {String|Number} roleId - 角色ID
*/
async getRoleTeachingNum(roleId) {
try {
const res = await roleTeachingNum({
role_id: roleId
})
console.log(res,'res')
if (res.status_code === 1) {
// 更新对应角色的 teach_num 字段
const index = this.roleList.findIndex(item => item.role_id === roleId)
if (index !== -1) {
this.$set(this.roleList[index], 'teach_num', res.data.data?.role_teaching_num || 0)
this.roleList = [...this.roleList]
}
}
} catch (error) {
console.error('获取带教次数失败:', error)
}
}
},
beforeDestroy() {
this.recentActivitiesPopupInstance.destroy();
......
......@@ -201,7 +201,6 @@ export default {
this.requestGroup();
},
methods: {
// ...mapMutations('common', ['set_sendSkillMessage', 'set_isEditSkill']),
sendMessage: debounce(function (item, id) {
console.log(item, id, "sendMessage");
}, 500),
......@@ -211,11 +210,6 @@ export default {
// 复制内容到粘贴板
if (item.msgtype == "text") {
if (item && item.text && item.text.content) {
// copyToClipboard(
// item.text.content,
// (message) => this.$message.success(message),
// (message) => this.$message.error(message)
// )
this.sendChatMessage(item.text.content);
}
} else if (item.msgtype == "image" && item.image.picurl) {
......@@ -248,7 +242,7 @@ export default {
fail: (err) => {
console.log(err, "发送图片链接失败");
this.$message.error(
"图片发送失败:" + (err.errMsg || err.message || "未知错误")
"图片发送失败:" + (err.errMsg || err.message || "未知错误"),
);
},
});
......@@ -408,7 +402,7 @@ export default {
this.groupDataList = res.data.data;
this.groupDataList.map((item, index) => {
const text = item.message.attachments.find(
(item) => item.msgtype === "text"
(item) => item.msgtype === "text",
);
if (text) {
item.title = text.text.content;
......
<!--
* @Author: 金多虾 937667504@qq.com
* @Date: 2025-09-08 10:15:29
* @LastEditors: 金多虾 937667504@qq.com
* @LastEditTime: 2025-12-10 14:52:24
* @FilePath: /company_app/src/views/giftRecord.vue
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<template>
<div class="gift-tab-container-apply-gift">
<el-tabs v-model="activeTab">
......@@ -7,18 +15,23 @@
<el-tab-pane label="企微礼包" name="wx">
<wx-gift v-if="activeTab == 'wx'"></wx-gift>
</el-tab-pane>
<el-tab-pane label="角色礼包" name="role">
<role-gift v-if="activeTab === 'role'"></role-gift>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import EmailGift from './components/giftRecord/emailGift.vue'
import WxGift from './components/giftRecord/wxGift.vue'
import RoleGift from './components/giftRecord/roleGift.vue'
import { mapActions } from 'vuex'
export default {
name: 'giftRecord',
components: {
EmailGift,
WxGift
WxGift,
RoleGift
},
created() {
this.initializeWecom()
......
......@@ -381,7 +381,7 @@ export default {
}
this.$store.commit(
"user/set_client_online_status",
statusRes.data.client_online_status
statusRes.data.client_online_status,
);
}
......@@ -486,7 +486,7 @@ export default {
if (res.status_code == 1) {
this.$message.success(res.msg);
const index = this.bindGameUserList.findIndex(
(item) => item.member_id == this.accountSelect
(item) => item.member_id == this.accountSelect,
);
this.bindGameUserList.splice(index, 1);
this.set_accountSelect(this.bindGameUserList[0].member_id);
......@@ -502,7 +502,7 @@ export default {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
},
)
.then((res) => {
const data = {
......@@ -529,7 +529,7 @@ export default {
// 关联客服
relationKfh() {
const username = this.bindGameUserList.find(
(item) => item.value == this.accountSelect
(item) => item.value == this.accountSelect,
);
const params = {
member_id: this.accountSelect,
......
......@@ -334,7 +334,14 @@ export default {
};
},
computed: {
...mapState("game", ["accountSelect"]),
...mapState("game", ["accountSelect", "bindGameUserList"]),
bindGameUserInfo: {
get() {
return this.bindGameUserList.find(
(item) => item.member_id == this.accountSelect,
);
},
},
},
watch: {
accountSelect: {
......@@ -362,16 +369,16 @@ export default {
getMemberLabel(data).then((res) => {
if (res?.data?.data?.length > 0) {
const change_user = res.data.data.find(
(item) => item.label_type == 2
(item) => item.label_type == 2,
);
const change_name = res.data.data.find(
(item) => item.label_type == 3
(item) => item.label_type == 3,
);
const change_risk = res.data.data.find(
(item) => item.label_type == 4
(item) => item.label_type == 4,
); // 风险用户
const change_appraisal = res.data.data.find(
(item) => item.label_type == 6
(item) => item.label_type == 6,
); // 石锤用户
this.change_user = change_user.label_value;
this.change_name = change_name.label_value;
......@@ -387,6 +394,13 @@ export default {
}
});
},
show_game_name() {
if (process.env.NODE_ENV == "production") {
return this.bindGameUserInfo.main_game_id == 187;
} else {
return this.bindGameUserInfo.main_game_id == 174;
}
},
// 白名单
changeNameFn() {
const data = {
......@@ -456,7 +470,7 @@ export default {
changeUserMobile() {
if (
!/^1((3[0-9])|(4[1579])|(5[0-9])|(6[6])|(7[0-9])|(8[0-9])|(9[0-9]))\d{8}$/.test(
this.newMobileValue
this.newMobileValue,
)
) {
this.$message.warning("请填写正确的手机号");
......@@ -495,7 +509,7 @@ export default {
this.$set(
this.chatUserDetails,
"intelligence_tag_group",
res.data.intel_tag
res.data.intel_tag,
);
} else {
this.$message.warning("每个用户半个小时仅能更新一次");
......
<!--
* @Author: 金多虾 937667504@qq.com
* @Date: 2025-12-11 11:01:15
* @LastEditors: 金多虾 937667504@qq.com
* @LastEditTime: 2025-12-20 11:25:56
* @FilePath: /company_wx_frontend/src/views/works/component/gameInfo/roleInfo/mentorRecord.vue
* @Description: 带教记录组件
-->
<template>
<div class="mentor-record-page">
<!-- 提示信息和添加按钮区域 -->
<div class="mentor-record-page__toolbar">
<el-button
v-if="teachingList.length < 5"
type="primary"
size="small"
@click="showAddForm = !showAddForm"
>添加记录</el-button>
</div>
<!-- 新增记录表单 -->
<div v-if="showAddForm && teachingList.length < 5" class="mentor-record-page__add-form">
<el-input
v-model="formData.content"
type="textarea"
:rows="3"
placeholder="请输入"
maxlength="500"
show-word-limit
class="mentor-record-page__add-form-input"
/>
<div class="mentor-record-page__add-form-buttons">
<el-button size="mini" @click="handleCancelAdd">取消</el-button>
<el-button type="primary" size="mini" :loading="submitLoading" @click="handleSubmitAdd">保存</el-button>
</div>
</div>
<!-- 带教记录列表 -->
<div class="mentor-record-page__list">
<div
v-for="(item, index) in teachingList"
:key="item.id || index"
class="mentor-record-page__list-item"
>
<p class="mentor-record-page__list-item-title">{{ item.teaching_num }}次带教</p>
<p class="mentor-record-page__list-item-content">{{ item.teaching_text }}</p>
<div class="mentor-record-page__list-item-footer">
<span class="mentor-record-page__list-item-creator">新增人:{{ item.update_user || '-' }}</span>
<span class="mentor-record-page__list-item-divider">|</span>
<span class="mentor-record-page__list-item-time">{{item.update_time }}</span>
</div>
</div>
<div v-if="teachingList.length === 0 && !loading" class="mentor-record-page__empty">
<svg-icon icon-class="noContent" />
<p>暂无带教记录</p>
</div>
</div>
</div>
</template>
<script>
import { roleTeachingList, roleTeachingAdd } from '@/api/game'
import { mapState } from 'vuex'
export default {
name: 'MentorRecord',
props: {
roleId: {
type: [String, Number],
required: true
},
},
data() {
return {
loading: false,
submitLoading: false,
showAddForm: false,
teachingList: [],
formData: {
content: ''
}
}
},
watch: {
roleId: {
immediate: true,
handler(newVal) {
if (newVal) {
this.getTeachingList()
}
}
}
},
computed: {
...mapState('user', ['userInfo'])
},
methods: {
/**
* 获取带教记录列表
*/
async getTeachingList() {
if (!this.roleId) {
return
}
try {
this.loading = true
const res = await roleTeachingList({
role_id: this.roleId
})
if (res.status_code === 1) {
this.teachingList = res.data.data || []
// 按teaching_num倒序排列
this.teachingList.sort((a, b) => {
return (b.teaching_num || 0) - (a.teaching_num || 0)
})
} else {
this.$message({
message: res.data.msg || '获取带教记录失败',
type: 'error'
})
}
} catch (error) {
console.error('获取带教记录失败:', error)
this.$message({
message: '获取带教记录失败,请稍后重试',
type: 'error'
})
} finally {
this.loading = false
}
},
/**
* 新增带教记录
*/
async handleSubmitAdd() {
if (!this.formData.content || !this.formData.content.trim()) {
this.$message({
message: '请输入带教记录内容',
type: 'warning'
})
return
}
try {
this.submitLoading = true
const res = await roleTeachingAdd({
role_id: this.roleId,
teaching_num: this.teachingList.length + 1,
teaching_text: this.formData.content.trim(),
create_user_id: this.userInfo.id,
create_user: this.userInfo.username
})
if (res.status_code === 1) {
this.$message({
message: res.data.msg || '添加成功',
type: 'success'
})
this.formData.content = ''
this.showAddForm = false
// 重新获取列表
await this.getTeachingList()
// 通知父组件更新带教次数
this.$emit('refresh',this.roleId,this.teachingList.length)
} else {
this.$message({
message: res.data.msg || '添加失败',
type: 'error'
})
}
} catch (error) {
console.error('新增带教记录失败:', error)
this.$message({
message: '添加失败,请稍后重试',
type: 'error'
})
} finally {
this.submitLoading = false
}
},
/**
* 取消新增
*/
handleCancelAdd() {
this.formData.content = ''
this.showAddForm = false
},
}
}
</script>
<style lang="scss" scoped>
.mentor-record-page {
width: 100%;
height: 100%;
padding: 12px;
padding-top: 0;
background: #fff;
border-right: 1px solid #ebedf0;
display: flex;
flex-direction: column;
overflow: hidden;
&__header {
display: flex;
align-items: center;
padding: 12px 0;
padding-top: 0;
border-bottom: 1px solid #ebedf0;
&-title {
font-family: PingFangSC-Medium, PingFang SC;
font-size: 14px;
font-weight: 500;
line-height: 22px;
color: #131920;
}
}
&__toolbar {
display: flex;
align-items: center;
justify-content: flex-end;
margin-bottom: 20px;
&-tip {
font-family: PingFangSC-Regular, PingFang SC;
font-size: 12px;
font-weight: 400;
line-height: 20px;
color: #909399;
}
}
&__add-form {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 12px;
&-input {
::v-deep .el-textarea__inner {
border: 1px solid #d6d9e0;
border-radius: 6px;
padding: 4px 6px;
font-size: 13px;
line-height: 22px;
color: #323335;
min-height: 60px;
resize: none;
&::placeholder {
color: #c9cdd4;
}
}
}
&-buttons {
display: flex;
gap: 8px;
justify-content: flex-end;
}
}
&__list {
flex: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 12px;
&-item {
background: #fff;
border: 1px solid #ebedf0;
border-radius: 6px;
padding: 8px;
display: flex;
flex-direction: column;
gap: 4px;
&-title {
font-family: PingFangSC-Regular, PingFang SC;
font-size: 12px;
font-weight: 400;
line-height: 20px;
color: #6d7176;
}
&-content {
font-family: PingFangSC-Regular, PingFang SC;
font-size: 15px;
font-weight: 500;
line-height: 22px;
color: #131920;
word-break: break-all;
}
&-footer {
display: flex;
align-items: center;
gap: 4px;
}
&-creator,
&-time {
font-family: PingFangSC-Regular, PingFang SC;
font-size: 12px;
font-weight: 400;
line-height: 20px;
color: #b0b2b5;
}
&-divider {
color: #ebedf0;
font-size: 12px;
}
}
}
&__empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px 0;
color: #909399;
font-size: 14px;
p {
margin-top: 12px;
}
}
}
</style>
......@@ -23,6 +23,15 @@ module.exports = {
plugins: [
function({ addUtilities }) {
const newUtilities = {
'.truncate': {
display: '-webkit-box',
'-webkit-box-orient': 'vertical',
'-webkit-line-clamp': '1',
'overflow': 'hidden',
'text-overflow': 'ellipsis',
'word-wrap': 'break-word',
'word-break': 'break-word',
},
'.truncate-2': {
display: '-webkit-box',
'-webkit-box-orient': 'vertical',
......
const { defineConfig } = require('@vue/cli-service')
const path = require('path')
const fs = require('fs')
// 构建前删除 company_app 文件夹
function removeOutputDir() {
const outputDir = path.join(__dirname, 'company_app')
if (fs.existsSync(outputDir)) {
fs.rmSync(outputDir, { recursive: true, force: true })
console.log('✅ 已删除 company_app 文件夹')
}
}
// 只在构建模式下删除
if (process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'staging') {
removeOutputDir()
}
function resolve(dir) {
return path.join(__dirname, dir)
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论