Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
C
company_app
概览
概览
详情
活动
周期分析
版本库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
统计图
问题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程表
图表
维基
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
毛细亚
company_app
Commits
0e8febea
提交
0e8febea
authored
1月 16, 2026
作者:
施汉文
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
✨
feat: 7.4工作台功能同步,生命线、智能待办
上级
77693b65
全部展开
隐藏空白字符变更
内嵌
并排
正在显示
18 个修改的文件
包含
932 行增加
和
328 行删除
+932
-328
index.html
public/index.html
+1
-1
App.vue
src/App.vue
+8
-3
aiChat.js
src/api/aiChat.js
+16
-0
works.js
src/api/works.js
+17
-0
loading.vue
src/components/loading.vue
+40
-40
index.vue
src/components/svgIcon/index.vue
+64
-53
copy.js
src/directive/copy/copy.js
+9
-6
index.js
src/router/index.js
+6
-0
applyRecord.vue
src/views/applyRecord.vue
+26
-11
AreaTransferApply.vue
src/views/components/ApplyRecords/AreaTransferApply.vue
+0
-0
TerminaTranfer.vue
src/views/components/ApplyRecords/TerminaTranfer.vue
+0
-0
errorHandle.vue
src/views/components/ApplyRecords/errorHandle.vue
+0
-0
index.vue
src/views/components/InstructionalVideo/index.vue
+8
-8
ConversationLifeline.vue
src/views/components/aiChat/ConversationLifeline.vue
+200
-0
aiChat.vue
src/views/components/aiChat/aiChat.vue
+134
-132
report.vue
src/views/components/roleInfo/report.vue
+87
-73
index.vue
src/views/userAgency/index.vue
+311
-0
tailwind.config.js
tailwind.config.js
+5
-1
没有找到文件。
public/index.html
浏览文件 @
0e8febea
...
@@ -16,7 +16,7 @@
...
@@ -16,7 +16,7 @@
<title>
企微侧边栏
</title>
<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"> -->
<!-- <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>
<script
src=
"https://g.alicdn.com/dingding/dinglogin/0.0.5/ddLogin.js"
></script>
<script
src=
"https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/
icons_27278_172.5464f393968eda872f41eab2242bbbd7.es5
.js"
></script>
<script
src=
"https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/
svg_27278_173.e64d61edfe0d4824e2eeb0b7f478e568
.js"
></script>
</head>
</head>
<body>
<body>
<noscript>
<noscript>
...
...
src/App.vue
浏览文件 @
0e8febea
...
@@ -57,7 +57,7 @@
...
@@ -57,7 +57,7 @@
<!-- 绑定的 w 账号 -->
<!-- 绑定的 w 账号 -->
<bindUserList
/>
<bindUserList
/>
</div>
</div>
<div
class=
"mobile-content"
>
<div
class=
"mobile-content"
v-if=
"accountSelect"
>
<router-view></router-view>
<router-view></router-view>
</div>
</div>
</div>
</div>
...
@@ -126,6 +126,11 @@ export default {
...
@@ -126,6 +126,11 @@ export default {
hasRedDot
:
false
,
// 红点状态
hasRedDot
:
false
,
// 红点状态
},
},
{
{
label
:
"用户待办"
,
path
:
"/userToDo"
,
hasRedDot
:
false
,
// 红点状态
},
{
label
:
"微言助手"
,
label
:
"微言助手"
,
path
:
"/aiChat"
,
path
:
"/aiChat"
,
},
},
...
@@ -142,7 +147,7 @@ export default {
...
@@ -142,7 +147,7 @@ export default {
},
},
computed
:
{
computed
:
{
...
mapState
(
"user"
,
[
"external_userid"
,
"token"
,
"userInfo"
]),
...
mapState
(
"user"
,
[
"external_userid"
,
"token"
,
"userInfo"
]),
...
mapState
(
"game"
,
[
"taskData"
]),
...
mapState
(
"game"
,
[
"taskData"
,
"accountSelect"
]),
// 计算任务列表是否需要显示红点
// 计算任务列表是否需要显示红点
hasTaskRedDot
()
{
hasTaskRedDot
()
{
return
this
.
taskData
.
user_task
>
0
||
this
.
taskData
.
account_task
>
0
;
return
this
.
taskData
.
user_task
>
0
||
this
.
taskData
.
account_task
>
0
;
...
@@ -204,7 +209,7 @@ export default {
...
@@ -204,7 +209,7 @@ export default {
// 页面刷新时从 Cookie 恢复 token 到 store
// 页面刷新时从 Cookie 恢复 token 到 store
// Cookies.set(
// Cookies.set(
// "token",
// "token",
// "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOjQwOTAsImRhdGEiOnsiY3Nlcl9pZCI6NDA5MCwiY3Nlcl9uYW1lIjoi5q-b57uG5LqaIn0sImlhdCI6MTc2
NTE3MjI0NywiZXhwIjoxNzY3NzY0MjQ3LCJuYmYiOjE3NjUxNzIyNDcsInN1YiI6InRva2Vu6K6k6K-BIiwianRpIjoiN2Q5NjYxNDVjNjgyZWU0Y2UyY2Y2MTc2ZjA0NTNlNGMifQ.QpEtYzoXK11RHwn8la-OMoS-BnlTyEEAa0lmlpYu2IQ
"
// "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOjQwOTAsImRhdGEiOnsiY3Nlcl9pZCI6NDA5MCwiY3Nlcl9uYW1lIjoi5q-b57uG5LqaIn0sImlhdCI6MTc2
ODU0Mjk0MiwiZXhwIjoxNzcxMTM0OTQyLCJuYmYiOjE3Njg1NDI5NDIsInN1YiI6InRva2Vu6K6k6K-BIiwianRpIjoiMjU0Zjg3OGQ3NzMyNWUyMzMyNDAwZTEwZWJkMjFkY2YifQ.3hHc6iQP-Xkz9Q5rMIOFENDdh5P-NSaRs4Y4ffbJcMg
"
// );
// );
// Cookies.set("corp_id", "wweaefe716636df3d1");
// Cookies.set("corp_id", "wweaefe716636df3d1");
// Cookies.set("userid", "maoxiya");
// Cookies.set("userid", "maoxiya");
...
...
src/api/aiChat.js
浏览文件 @
0e8febea
...
@@ -68,3 +68,19 @@ export function answerComment(data) {
...
@@ -68,3 +68,19 @@ export function answerComment(data) {
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
})
}
src/api/works.js
浏览文件 @
0e8febea
...
@@ -316,3 +316,19 @@ export function corp_follow_up_task_index(data) {
...
@@ -316,3 +316,19 @@ export function corp_follow_up_task_index(data) {
data
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
src/components/loading.vue
浏览文件 @
0e8febea
<
template
>
<
template
>
<div
class=
"loading rowFlex allCenter"
>
<div
class=
"loading rowFlex allCenter"
>
<svg-icon
icon-class=
"loading"
class=
"loadingIcon"
/>
<!--
<svg-icon
icon-class=
"loading"
class=
"loadingIcon"
/>
-->
<p
class=
"text"
>
加载中
</p>
</div>
<i
class=
"el-icon-loading loadingIcon text-primary"
></i>
</
template
>
<p
class=
"text"
>
加载中
</p>
</div>
<
script
>
</
template
>
export
default
{
name
:
'loading'
,
<
script
>
data
()
{
export
default
{
return
{
name
:
"loading"
,
}
data
()
{
},
return
{};
mounted
()
{},
},
methods
:
{}
mounted
()
{},
methods
:
{},
};
</
script
>
<
style
lang=
"scss"
scoped
>
.loading
{
position
:
absolute
;
left
:
50%
;
transform
:
translateX
(
-50%
);
top
:
0
;
.loadingIcon
{
font-size
:
24px
;
animation
:
rotage
linear
1s
infinite
;
}
}
</
script
>
.text
{
<
style
lang=
"scss"
scoped
>
color
:
#409eff
;
.loading
{
font-size
:
14px
;
position
:
absolute
;
margin-left
:
5px
;
left
:
50%
;
transform
:
translateX
(
-50%
);
top
:
0
;
.loadingIcon{
font-size
:
24px
;
animation
:
rotage
linear
1s
infinite
;
}
.text
{
color
:
#409EFF
;
font-size
:
14px
;
margin-left
:
5px
;
}
}
}
@keyframes
rotage
{
}
0
%
{
@keyframes
rotage
{
transform
:
rotate
(
0deg
);
0
%
{
}
transform
:
rotate
(
0deg
);
100
%
{
transform
:
rotate
(
360deg
);
}
}
}
</
style
>
100
%
{
\ No newline at end of file
transform
:
rotate
(
360deg
);
}
}
</
style
>
src/components/svgIcon/index.vue
浏览文件 @
0e8febea
<
template
>
<
template
>
<div
v-if=
"isExternal"
:style=
"styleExternalIcon"
class=
"svg-external-icon svg-icon"
v-on=
"$listeners"
/>
<div
<svg
v-else
:class=
"svgClass"
aria-hidden=
"true"
:style=
"
{ display: iconClass || svgName ? '' : 'none' }"
v-if=
"isExternal"
v-on="$listeners">
:style=
"styleExternalIcon"
<use
:xlink:href=
"iconName"
/>
class=
"svg-external-icon svg-icon"
</svg>
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
>
</
template
>
<
script
lang=
"jsx"
>
<
script
lang=
"jsx"
>
// doc: https://panjiachen.github.io/vue-element-admin-site/feature/component/svg-icon.html#usage
// 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
{
export
default
{
name
:
'SvgIcon'
,
name
:
"SvgIcon"
,
props
:
{
props
:
{
iconClass
:
{
iconClass
:
{
type
:
String
,
type
:
String
,
default
:
''
default
:
""
,
},
svgName
:
{
type
:
String
,
default
:
''
},
className
:
{
type
:
String
,
default
:
''
}
},
},
computed
:
{
svgName
:
{
isExternal
()
{
type
:
String
,
return
isExternal
(
this
.
iconClass
)
default
:
""
,
},
},
iconName
()
{
className
:
{
if
(
this
.
svgName
)
{
type
:
String
,
return
`#
${
this
.
svgName
}
`
default
:
""
,
}
},
return
`#icon-
${
this
.
iconClass
}
`
},
},
computed
:
{
svgClass
()
{
isExternal
()
{
if
(
this
.
className
)
{
return
isExternal
(
this
.
iconClass
);
return
'svg-icon '
+
this
.
className
},
}
else
{
iconName
()
{
return
'svg-icon'
if
(
this
.
svgName
)
{
}
return
`#
${
this
.
svgName
}
`
;
},
}
styleExternalIcon
()
{
return
`#icon-
${
this
.
iconClass
}
`
;
return
{
},
mask
:
`url(
${
this
.
iconClass
}
) no-repeat 50% 50%`
,
svgClass
()
{
'-webkit-mask'
:
`url(
${
this
.
iconClass
}
) no-repeat 50% 50%`
if
(
this
.
className
)
{
}
return
"svg-icon "
+
this
.
className
;
}
}
else
{
}
return
"svg-icon"
;
}
}
},
styleExternalIcon
()
{
return
{
mask
:
`url(
${
this
.
iconClass
}
) no-repeat 50% 50%`
,
"-webkit-mask"
:
`url(
${
this
.
iconClass
}
) no-repeat 50% 50%`
,
};
},
},
};
</
script
>
</
script
>
<
style
scoped
>
<
style
scoped
>
.svg-icon
{
.svg-icon
{
width
:
1em
;
width
:
1em
;
height
:
1em
;
height
:
1em
;
/* vertical-align: -0.15em; */
/* vertical-align: -0.15em; */
fill
:
currentColor
;
fill
:
currentColor
;
overflow
:
hidden
;
overflow
:
hidden
;
}
}
.svg-external-icon
{
.svg-external-icon
{
background-color
:
currentColor
;
background-color
:
currentColor
;
mask-size
:
cover
!important
;
mask-size
:
cover
!important
;
display
:
inline-block
;
display
:
inline-block
;
}
}
</
style
>
</
style
>
src/directive/copy/copy.js
浏览文件 @
0e8febea
import
Vue
from
'vue'
import
Vue
from
'vue'
{
/* <iconpark-icon name="icon-fuzhi"></iconpark-icon> */
}
//
{/* <iconpark-icon name="icon-fuzhi"></iconpark-icon> */}
const
copy
=
{
const
copy
=
{
// 当被绑定的元素插入到DOM中时
// 当被绑定的元素插入到DOM中时
inserted
:
function
(
el
,
binding
)
{
inserted
:
function
(
el
,
binding
)
{
// 创建复制图标元素
// 创建复制图标元素
const
copyIcon
=
document
.
createElement
(
'iconpark-icon'
)
const
copyIcon
=
document
.
createElementNS
(
'http://www.w3.org/2000/svg'
,
'svg'
)
copyIcon
.
name
=
'icon-fuzhi'
copyIcon
.
innerHTML
=
'<use href="#icon-fuzhi"></use>'
copyIcon
.
setAttribute
(
'class'
,
'iconpark-icon'
)
copyIcon
.
style
.
cursor
=
'pointer'
copyIcon
.
style
.
cursor
=
'pointer'
copyIcon
.
style
.
marginLeft
=
'8px'
copyIcon
.
style
.
marginLeft
=
'8px'
copyIcon
.
style
.
fontSize
=
'16px'
copyIcon
.
style
.
height
=
'16px'
copyIcon
.
style
.
width
=
'16px'
copyIcon
.
title
=
'点击复制'
copyIcon
.
title
=
'点击复制'
// 设置元素的position为relative,确保图标的absolute定位正确
// 设置元素的position为relative,确保图标的absolute定位正确
...
@@ -17,7 +19,7 @@ const copy = {
...
@@ -17,7 +19,7 @@ const copy = {
}
}
// 添加复制图标到元素后面
// 添加复制图标到元素后面
el
.
insertBefore
(
copyIcon
,
el
.
nextSibling
)
el
.
appendChild
(
copyIcon
)
// 复制功能实现
// 复制功能实现
const
clickHandler
=
async
function
(
e
)
{
const
clickHandler
=
async
function
(
e
)
{
...
@@ -100,4 +102,4 @@ const copy = {
...
@@ -100,4 +102,4 @@ const copy = {
}
}
}
}
export
default
copy
export
default
copy
\ No newline at end of file
src/router/index.js
浏览文件 @
0e8febea
...
@@ -15,6 +15,7 @@ import taskList from '@/views/taskList.vue'
...
@@ -15,6 +15,7 @@ import taskList from '@/views/taskList.vue'
import
aiChat
from
'@/views/components/aiChat/aiChat.vue'
import
aiChat
from
'@/views/components/aiChat/aiChat.vue'
import
Cookies
from
'js-cookie'
import
Cookies
from
'js-cookie'
import
store
from
'@/store'
import
store
from
'@/store'
import
UserAgency
from
'@/views/userAgency/index.vue'
Vue
.
use
(
VueRouter
)
Vue
.
use
(
VueRouter
)
import
{
getParams
}
from
'@/utils/index'
import
{
getParams
}
from
'@/utils/index'
const
routes
=
[
const
routes
=
[
...
@@ -85,6 +86,11 @@ const routes = [
...
@@ -85,6 +86,11 @@ const routes = [
component
:
taskList
component
:
taskList
},
},
{
{
path
:
'/userToDo'
,
name
:
'userToDo'
,
component
:
UserAgency
},
{
path
:
'/aiChat'
,
path
:
'/aiChat'
,
name
:
'aiChat'
,
name
:
'aiChat'
,
component
:
aiChat
component
:
aiChat
...
...
src/views/applyRecord.vue
浏览文件 @
0e8febea
...
@@ -25,24 +25,40 @@
...
@@ -25,24 +25,40 @@
</div>
</div>
</
template
>
</
template
>
<
script
>
<
script
>
import
AreaTransferApply
from
'./components/ApplyRecords/AreaTransferApply.vue'
import
AreaTransferApply
from
"./components/ApplyRecords/AreaTransferApply.vue"
;
import
errorHandle
from
'./components/ApplyRecords/errorHandle.vue'
import
errorHandle
from
"./components/ApplyRecords/errorHandle.vue"
;
import
TerminalTransfer
from
'./components/ApplyRecords/TerminaTranfer.vue'
import
TerminalTransfer
from
"./components/ApplyRecords/TerminaTranfer.vue"
;
import
report
from
'./components/roleInfo/report.vue'
import
report
from
"./components/roleInfo/report.vue"
;
export
default
{
export
default
{
name
:
'applyRecord'
,
name
:
"applyRecord"
,
components
:
{
components
:
{
AreaTransferApply
,
AreaTransferApply
,
errorHandle
,
errorHandle
,
TerminalTransfer
,
TerminalTransfer
,
report
report
,
},
},
data
()
{
data
()
{
return
{
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
>
</
script
>
<
style
lang=
"scss"
scoped
>
<
style
lang=
"scss"
scoped
>
...
@@ -75,4 +91,4 @@ export default {
...
@@ -75,4 +91,4 @@ export default {
line-height
:
50px
;
line-height
:
50px
;
}
}
}
}
</
style
>
</
style
>
\ No newline at end of file
src/views/components/ApplyRecords/AreaTransferApply.vue
浏览文件 @
0e8febea
差异被折叠。
点击展开。
src/views/components/ApplyRecords/TerminaTranfer.vue
浏览文件 @
0e8febea
差异被折叠。
点击展开。
src/views/components/ApplyRecords/errorHandle.vue
浏览文件 @
0e8febea
差异被折叠。
点击展开。
src/views/components/InstructionalVideo/index.vue
浏览文件 @
0e8febea
...
@@ -33,10 +33,10 @@
...
@@ -33,10 +33,10 @@
@
click=
"previewVideo(item)"
@
click=
"previewVideo(item)"
>
>
<div
class=
"flex items-center"
>
<div
class=
"flex items-center"
>
<
iconpark
-icon
<
svg
-icon
n
ame=
"xiaoxicaozuo-chakan"
svgN
ame=
"xiaoxicaozuo-chakan"
class=
"mr-[4px] text-[14px]"
class=
"mr-[4px]
h-[14px] w-[14px]
text-[14px]"
></
iconpark
-icon>
></
svg
-icon>
预览
预览
</div>
</div>
</el-button>
</el-button>
...
@@ -44,10 +44,10 @@
...
@@ -44,10 +44,10 @@
@
click=
"sendVideo(item)"
@
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]"
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
<
svg
-icon
n
ame=
"icon-fasonghuashu"
svgN
ame=
"icon-fasonghuashu"
class=
"text-[14px] mr-[4px]"
class=
"text-[14px]
w-[14px] h-[14px]
mr-[4px]"
></
iconpark
-icon>
></
svg
-icon>
<span>
发送
</span>
<span>
发送
</span>
</div>
</div>
</div>
</div>
...
...
src/views/components/aiChat/ConversationLifeline.vue
0 → 100644
浏览文件 @
0e8febea
<
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
});
this
.
$message
({
message
:
"重新生成成功"
,
type
:
"success"
,
});
// 重新加载数据
this
.
getSessionSummaryList
();
}
catch
(
error
)
{
console
.
error
(
"重新生成会话总结失败:"
,
error
);
}
finally
{
data
.
loading
=
false
;
}
},
},
};
</
script
>
src/views/components/aiChat/aiChat.vue
浏览文件 @
0e8febea
<
template
>
<
template
>
<div
class=
"quickSendGame columnFlex"
>
<div
class=
"quickSendGame columnFlex"
>
<div
class=
"content search-form"
>
<div
class=
"content search-form"
>
<el-tabs
v-model=
"activeName"
>
<el-tabs
v-model=
"activeName"
>
<el-tab-pane
label=
"AI微言"
name=
"aiChat"
>
<el-tab-pane
label=
"AI微言"
name=
"aiChat"
>
<aiArgenChat
v-if=
"activeName === 'aiChat'"
/>
<aiArgenChat
v-if=
"activeName === 'aiChat'"
/>
</el-tab-pane>
</el-tab-pane>
<el-tab-pane
label=
"AI 跟进记录"
name=
"aiFollow"
>
<el-tab-pane
label=
"AI 跟进记录"
name=
"aiFollow"
>
<summaryList
v-if=
"activeName === 'aiFollow'"
/>
<summaryList
v-if=
"activeName === 'aiFollow'"
/>
</el-tab-pane>
</el-tab-pane>
<el-tab-pane
label=
"跟进任务记录"
name=
"aiFollowTask"
>
<el-tab-pane
label=
"跟进任务记录"
name=
"aiFollowTask"
>
<followTask
v-if=
"activeName === 'aiFollowTask'"
/>
<followTask
v-if=
"activeName === 'aiFollowTask'"
/>
</el-tab-pane>
</el-tab-pane>
</el-tabs>
<el-tab-pane
label=
"会话生命线"
name=
"ConversationLifeline"
>
</div>
<ConversationLifeline
v-if=
"activeName === 'ConversationLifeline'"
/>
</el-tab-pane>
</el-tabs>
</div>
</div>
</div>
</
template
>
</
template
>
<
script
>
<
script
>
import
aiArgenChat
from
'./aiArgenChat.vue'
import
aiArgenChat
from
"./aiArgenChat.vue"
;
import
summaryList
from
'./summaryList.vue'
import
summaryList
from
"./summaryList.vue"
;
import
followTask
from
'./followTask.vue'
import
followTask
from
"./followTask.vue"
;
import
{
mapActions
}
from
'vuex'
import
ConversationLifeline
from
"./ConversationLifeline.vue"
;
import
{
mapActions
}
from
"vuex"
;
export
default
{
export
default
{
name
:
'quickSendGame'
,
name
:
"quickSendGame"
,
components
:
{
components
:
{
aiArgenChat
,
aiArgenChat
,
summaryList
,
summaryList
,
followTask
,
followTask
,
ConversationLifeline
,
},
data
()
{
return
{
activeName
:
"aiChat"
,
};
},
created
()
{},
mounted
()
{
// this.initializeWecom()
},
methods
:
{
...
mapActions
(
"user"
,
[
"initWecom"
]),
async
initializeWecom
()
{
try
{
console
.
log
(
"🚀 开始初始化企业微信 SDK"
);
const
result
=
await
this
.
initWecom
();
console
.
log
(
"✅ 企业微信 SDK 初始化成功"
,
result
);
}
catch
(
error
)
{
console
.
error
(
"❌ 企业微信 SDK 初始化失败:"
,
error
);
}
},
},
data
()
{
},
return
{
};
activeName
:
'aiChat'
}
},
created
()
{
},
mounted
()
{
// this.initializeWecom()
},
methods
:
{
...
mapActions
(
'user'
,
[
'initWecom'
]),
async
initializeWecom
()
{
try
{
console
.
log
(
'🚀 开始初始化企业微信 SDK'
)
const
result
=
await
this
.
initWecom
()
console
.
log
(
'✅ 企业微信 SDK 初始化成功'
,
result
)
}
catch
(
error
)
{
console
.
error
(
'❌ 企业微信 SDK 初始化失败:'
,
error
)
}
},
}
}
</
script
>
</
script
>
<
style
lang=
"scss"
scoped
>
<
style
lang=
"scss"
scoped
>
.quickSendGame
{
.quickSendGame
{
::v-deep
.el-tabs__nav-next,
::v-deep
.el-tabs__nav-prev
{
line-height
:
50px
;
}
::v-deep
.el-tabs__nav-next,
width
:
100
%;
::v-deep
.el-tabs__nav-prev
{
height
:
100
%;
line-height
:
50px
;
background
:
#fff
;
}
width
:
100
%;
height
:
100
%;
background
:
#fff
;
::v-deep
.el-tabs__item
{
padding
:
0
15px
;
}
.detailsTitle
{
width
:
100%
;
padding
:
0
10px
;
height
:
60px
;
font-size
:
18px
;
font-family
:
PingFangSC-Medium
,
PingFang
SC
;
font-weight
:
500
;
color
:
#333333
;
border-bottom
:
1px
solid
#ebeef5
;
border-left
:
1px
solid
#ebeef5
;
p
{
color
:
#333333
;
}
}
.content
{
width
:
100%
;
height
:
calc
(
100%
-
10px
);
::v-deep
.el-tabs__header
{
::v-deep
.el-tabs__item
{
margin
:
0
0
20px
;
padding
:
0
15px
;
}
}
}
::v-deep
.el-tabs--border-card
.is-active
{
.detailsTitle
{
border
:
none
!important
;
width
:
100%
;
}
padding
:
0
10px
;
height
:
60px
;
::v-deep
.is-active
{
font-size
:
18px
;
border
:
none
;
font-family
:
PingFangSC-Medium
,
PingFang
SC
;
font-weight
:
500
;
color
:
#333333
;
border-bottom
:
1px
solid
#ebeef5
;
border-left
:
1px
solid
#ebeef5
;
p
{
color
:
#333333
;
}
}
}
.inputContent
{
.content
{
width
:
100%
;
width
:
100%
;
height
:
calc
(
100%
-
10px
);
::v-deep
.el-input
{
::v-deep
.el-tabs__header
{
width
:
80%
;
margin
:
0
0
20px
;
}
}
}
}
::v-deep
.el-tabs
,
::v-deep
.el-tabs--border-card
.is-active
{
.el-tabs__content
,
border
:
none
!important
;
.el-tab-pane
{
}
width
:
100%
;
height
:
100%
;
}
::v-deep
.el-tabs__content
{
::v-deep
.is-active
{
width
:
100%
;
border
:
none
;
height
:
calc
(
100%
-
50px
);
}
}
::v-deep
.el-tab-pane
{
.inputContent
{
width
:
100%
;
width
:
100%
;
height
:
100%
;
}
.scrollList
{
::v-deep
.el-input
{
width
:
100%
;
width
:
80%
;
height
:
calc
(
100%
-
40px
);
overflow
:
auto
;
}
}
}
.draggable
{
::v-deep
.el-tabs
,
position
:
relative
;
.el-tabs__content
,
transition
:
all
0.3s
;
.el-tab-pane
{
width
:
100%
;
height
:
100%
;
}
.icon
{
::v-deep
.el-tabs__content
{
position
:
absolute
;
width
:
100%
;
left
:
10px
;
height
:
calc
(
100%
-
50px
);
top
:
15px
;
}
z-index
:
10
;
}
}
::v-deep
.el-icon-circle-close
{
::v-deep
.el-tab-pane
{
color
:
#fff
;
width
:
100%
;
}
height
:
100%
;
}
.bate
{
.scrollList
{
width
:
42px
;
width
:
100%
;
height
:
20px
;
height
:
calc
(
100%
-
40px
);
background
:
linear-gradient
(
135deg
,
#6ee7e9
0%
,
#9ff2cd
47%
,
#e3fdb2
100%
);
overflow
:
auto
;
border-radius
:
10px
;
}
padding
:
0px
10px
3px
10px
;
.draggable
{
position
:
relative
;
transition
:
all
0.3s
;
.icon
{
position
:
absolute
;
left
:
10px
;
top
:
15px
;
z-index
:
10
;
}
}
}
::v-deep
.el-icon-circle-close
{
color
:
#fff
;
}
.bate
{
width
:
42px
;
height
:
20px
;
background
:
linear-gradient
(
135deg
,
#6ee7e9
0%
,
#9ff2cd
47%
,
#e3fdb2
100%
);
border-radius
:
10px
;
padding
:
0px
10px
3px
10px
;
}
}
}
</
style
>
</
style
>
\ No newline at end of file
src/views/components/roleInfo/report.vue
浏览文件 @
0e8febea
差异被折叠。
点击展开。
src/views/userAgency/index.vue
0 → 100644
浏览文件 @
0e8febea
<
template
>
<div
class=
"flex flex-col h-full"
>
<div
class=
"py-[19px] px-[16px] text-[14px] text-[#86909C] space-x-[24px] border-b-[1px] border-solid border-[#F2F3F5]"
>
<span
v-for=
"value in tabList"
:key=
"value.value"
@
click=
"handleTabClick(value.value)"
class=
"cursor-pointer"
:class=
"
{ '!text-[#323335] font-medium': activeTab === value.value }"
>
{{
value
.
name
}}
</span
>
</div>
<div
class=
"flex-1 overflow-y-auto"
@
scroll=
"handleScroll"
>
<div
class=
"p-[16px] space-y-[6px] border-b-[1px] border-solid border-[#F2F3F5]"
v-for=
"(value, index) in list"
:key=
"value.id || index"
>
<div
class=
"flex space-x-[16px] text-[#323335]"
>
<div
class=
"w-[56px] text-[#86909C]"
:class=
"
{ 'user-agency': value.status === 0 }"
>
<el-tag
:type=
"['success', 'info'][value.status]"
>
{{
value
.
status_text
}}
</el-tag>
</div>
<div
class=
"truncate flex-1 flex items-center text-[16px] font-medium"
>
{{
value
.
name
}}
</div>
</div>
<div
class=
"flex space-x-[16px] text-[#323335]"
>
<div
class=
"w-[56px] text-[#86909C]"
>
待办详情
</div>
<div
class=
"flex-1 relative"
:ref=
"(el) => (detailRefs[index] = el)"
>
<div
class=
"text-[14px] break-words transition-all duration-300"
:class=
"
{ 'max-h-[22px] overflow-hidden': !value.showMore }"
v-show="!value.showMore"
>
{{
value
.
detail
}}
</div>
<div
class=
"text-[14px] break-words"
v-show=
"value.showMore"
>
{{
value
.
detail
}}
</div>
<div
v-if=
"value.shouldShowMore"
class=
"bg-white absolute bottom-0 right-0 cursor-pointer flex items-center pl-[16px]"
@
click=
"toggleShowMore(index)"
>
{{
value
.
showMore
?
"收起"
:
"展开"
}}
<svg-icon
svgName=
"icon-zhankai"
class=
"w-[12px] h-[12px]"
:class=
"
{ 'rotate-180': value.showMore }"
>
</svg-icon>
</div>
</div>
</div>
<div
class=
"flex space-x-[16px] text-[#323335]"
>
<div
class=
"w-[56px] text-[#86909C]"
>
创建时间
</div>
<div
class=
"flex-1 flex items-center text-[14px] break-words"
>
{{
value
.
created_at
}}
</div>
</div>
<div
class=
"flex space-x-[16px] text-[#323335]"
>
<div
class=
"w-[56px] text-[#86909C]"
>
归属客服
</div>
<div
class=
"flex-1 flex items-center text-[14px] break-words"
>
{{
value
.
cser_name
}}
</div>
</div>
<div
class=
"flex space-x-[16px] text-[#323335]"
>
<div
class=
"w-[56px] text-[#86909C]"
>
来源会话
</div>
<div
class=
"flex-1 flex items-center text-[14px] break-words"
>
2025-12-22 14:21:12至2025-12-22 15:21:12
</div>
</div>
<div
class=
"flex space-x-[16px] text-[#323335]"
>
<div
class=
"w-[56px] text-[#86909C]"
>
关联工单
</div>
<div
class=
"flex-1 flex items-center text-[14px] break-words"
>
玩家申诉(通过)
<el-button
v-if=
"value.related_task_id"
type=
"text"
class=
"!py-0"
@
click=
"handleClick(value)"
>
工单详情
</el-button
>
</div>
</div>
<div
class=
"flex space-x-[16px] text-[#323335]"
v-if=
"value.related_task_type"
>
<div
class=
"w-[56px] text-[#86909C]"
>
待办类型
</div>
<div
class=
"flex-1 flex items-center text-[14px] break-words flex-wrap gap-[6px]"
>
<div
class=
"px-[6px] bg-[#F2F3F5] rounded-[4px] shrink-0"
>
{{
related_task_type_text
[
value
.
related_task_type
]
}}
</div>
</div>
</div>
<div
class=
"flex space-x-[16px] text-[#323335]"
>
<div
class=
"w-[56px] text-[#86909C]"
>
完成会话
</div>
<div
class=
"flex-1 flex items-center text-[14px] break-words flex-wrap gap-[6px]"
>
2025-12-22 14:21:12至2025-12-22 15:21:12
</div>
</div>
</div>
<div
v-if=
"loading"
class=
"relative flex justify-center py-[20px]"
>
<loading
/>
</div>
</div>
</div>
</
template
>
<
script
>
import
{
mapMutations
,
mapState
}
from
"vuex"
;
import
{
corpIntelligentTaskExternalList
,
corpIntelligentTaskMineList
,
}
from
"@/api/works"
;
import
loading
from
"@/components/loading"
;
export
default
{
name
:
"UserAgency"
,
components
:
{
loading
,
},
data
()
{
return
{
activeTab
:
0
,
related_task_type_text
:
{
6
:
"举报申请"
,
12
:
"玩家误操作"
,
14
:
"转区申请"
,
13
:
"玩家转端"
,
},
tabList
:
[
{
name
:
"未完成"
,
value
:
0
,
},
{
name
:
"已完成"
,
value
:
1
,
},
{
name
:
"我的待办"
,
value
:
2
,
},
],
detailRefs
:
[],
show
:
false
,
page_info
:
{
page
:
1
,
page_size
:
10
,
total
:
0
,
},
list
:
[],
loading
:
false
,
};
},
computed
:
{
...
mapState
(
"game"
,
[
"chatUserInfo"
]),
},
methods
:
{
handleClick
(
v
)
{
sessionStorage
.
setItem
(
"related_task_info"
,
JSON
.
stringify
({
related_task_id
:
v
.
related_task_id
,
related_task_type
:
v
.
related_task_type
,
})
);
// 跳转到申请记录
this
.
$router
.
push
({
path
:
"/applyRecord"
,
});
},
handleTabClick
(
value
)
{
if
(
this
.
activeTab
===
value
)
return
;
this
.
activeTab
=
value
;
this
.
page_info
.
page
=
1
;
this
.
list
=
[];
this
.
getList
(
value
);
},
handleScroll
(
e
)
{
const
{
scrollTop
,
scrollHeight
,
clientHeight
}
=
e
.
target
;
// 当滚动到底部100px以内时加载更多
if
(
scrollHeight
-
scrollTop
-
clientHeight
<
100
&&
!
this
.
loading
)
{
// 检查是否还有更多数据可以加载
if
(
this
.
list
.
length
<
this
.
page_info
.
total
)
{
this
.
loadMore
();
}
}
},
loadMore
()
{
this
.
page_info
.
page
++
;
this
.
getList
(
this
.
activeTab
);
},
toggleShowMore
(
index
)
{
this
.
$set
(
this
.
list
[
index
],
"showMore"
,
!
this
.
list
[
index
].
showMore
);
},
checkTextOverflow
()
{
this
.
$nextTick
(()
=>
{
this
.
detailRefs
.
forEach
((
ref
,
index
)
=>
{
if
(
ref
)
{
// 创建一个临时元素来测量文本高度
const
tempEl
=
document
.
createElement
(
"div"
);
tempEl
.
className
=
"text-[14px] break-words"
;
tempEl
.
style
.
position
=
"absolute"
;
tempEl
.
style
.
visibility
=
"hidden"
;
tempEl
.
style
.
width
=
"100%"
;
tempEl
.
style
.
whiteSpace
=
"normal"
;
tempEl
.
innerHTML
=
this
.
list
[
index
].
detail
;
ref
.
appendChild
(
tempEl
);
// 计算一行文字的高度(假设行高为1.5倍字体大小)
const
lineHeight
=
parseInt
(
window
.
getComputedStyle
(
tempEl
).
lineHeight
);
const
fontHeight
=
parseInt
(
window
.
getComputedStyle
(
tempEl
).
fontSize
);
const
expectedLineHeight
=
lineHeight
||
fontHeight
*
1.5
;
// 测量文本总高度
const
totalHeight
=
tempEl
.
offsetHeight
;
// 如果内容高度大于一行高度,则显示展开收起按钮
const
shouldShowMore
=
totalHeight
>
expectedLineHeight
;
this
.
$set
(
this
.
list
[
index
],
"shouldShowMore"
,
shouldShowMore
);
// 移除临时元素
ref
.
removeChild
(
tempEl
);
}
});
});
},
async
getList
(
value
=
0
)
{
// 如果正在加载或者已经是最后一页,则不发起请求
if
(
this
.
loading
||
(
this
.
list
.
length
>=
this
.
page_info
.
total
&&
this
.
page_info
.
total
>
0
)
)
{
return
;
}
this
.
loading
=
true
;
const
params
=
{
status
:
value
,
page
:
this
.
page_info
.
page
,
page_size
:
this
.
page_info
.
page_size
,
external_userid
:
this
.
chatUserInfo
.
external_userid
,
};
let
api
;
try
{
if
(
value
!==
2
)
{
api
=
corpIntelligentTaskExternalList
;
}
else
{
delete
params
.
external_userid
;
delete
params
.
status
;
api
=
corpIntelligentTaskMineList
;
}
const
res
=
await
api
(
params
);
const
newList
=
res
.
data
.
data
.
map
((
item
)
=>
({
...
item
,
showMore
:
false
,
shouldShowMore
:
false
,
}));
// 如果是第一页则替换列表,否则合并列表
if
(
this
.
page_info
.
page
===
1
)
{
this
.
list
=
newList
;
}
else
{
this
.
list
=
[...
this
.
list
,
...
newList
];
}
this
.
page_info
.
total
=
res
.
data
.
page_info
.
total
;
// 检查文字是否溢出
this
.
checkTextOverflow
();
}
catch
(
error
)
{
console
.
error
(
"获取数据失败:"
,
error
);
this
.
$message
({
message
:
"获取数据失败"
,
type
:
"error"
,
});
}
finally
{
this
.
loading
=
false
;
}
},
},
mounted
()
{
this
.
getList
();
},
};
</
script
>
<
style
scoped
lang=
"scss"
>
.user-agency
{
.el-tag.el-tag--success
{
color
:
#3491fa
;
background-color
:
#e8f7ff
;
border-color
:
#c3e7fe
;
}
}
</
style
>
tailwind.config.js
浏览文件 @
0e8febea
...
@@ -11,7 +11,11 @@ module.exports = {
...
@@ -11,7 +11,11 @@ module.exports = {
purge
:
[
'./index.html'
,
'./src/**/*.{vue,js,ts,jsx,tsx}'
],
purge
:
[
'./index.html'
,
'./src/**/*.{vue,js,ts,jsx,tsx}'
],
darkMode
:
false
,
darkMode
:
false
,
theme
:
{
theme
:
{
extend
:
{}
extend
:
{
colors
:
{
primary
:
'#409EFF'
,
}
}
},
},
variants
:
{
variants
:
{
extend
:
{}
extend
:
{}
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论