@@ -6,7 +6,7 @@
< span style = "font-size: 14px;" > 客户转化全流程跟踪 < / span >
< span style = "font-size: 14px;" > 客户转化全流程跟踪 < / span >
< / div >
< / div >
< div >
< div >
< button @click ="show UnassignedRecordingsModal = true " class = "unassigned-recordings-btn" > 无归属录音 < / button >
< button @click ="handle UnassignedRecordingsClick " class = "unassigned-recordings-btn" > 无归属录音 < / button >
< / div >
< / div >
< / div >
< / div >
@@ -321,11 +321,58 @@
< div v-else class = "recordings-list" >
< div v-else class = "recordings-list" >
< div class = "recording-item" v-for = "recording in unassignedRecordings" :key="recording.id" >
< div class = "recording-item" v-for = "recording in unassignedRecordings" :key="recording.id" >
< div class = "recording-info" >
< div class = "recording-info" >
< span class = "recording-name" > { { recording . name } } < / span >
< div class = "recording-header" >
< span class = "recording-name" > { { recording . name } } < / span >
< span class = "recording-score" > 分数 : { { recording . score } } < / span >
< / div >
< span class = "recording-time" > { { recording . time } } < / span >
< span class = "recording-time" > { { recording . time } } < / span >
< span class = "recording-type" > { { recording . type } } < / span >
< div class = "recording-details" style = "display: flex; justify-content: space-between; align-items: center;gap: 8px;" >
< span class = "recording-duration" > 时长 : { { recording . duration } } < / span >
< span class = "recording-type" > { { recording . type } } < / span >
< / div >
< / div >
< / div >
< a :href = "recording.downloadUrl" class = "download-link" download > 下载 < / a >
< div class = "recording-actions" >
< button class = "report-link" @click ="viewReport(recording)" > 查看报告 < / button >
< a :href = "recording.downloadUrl" class = "download-link" download > 下载 < / a >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
<!-- 报告弹窗 -- >
< div v-if = "showReportModal" class="modal-overlay" @click="showReportModal = false" >
< div class = "modal-content report-modal" @click.stop >
< div class = "modal-header" >
< h3 > 录音报告 < / h3 >
< button class = "close-btn" @click ="showReportModal = false" > × < / button >
< / div >
< div class = "modal-body" >
< div class = "report-content" >
< div class = "report-field" >
< label > 录音名称 : < / label >
< span > { { reportData . name } } < / span >
< / div >
< div class = "report-field" >
< label > 录音时间 : < / label >
< span > { { reportData . time } } < / span >
< / div >
< div class = "report-field" >
< label > 录音类型 : < / label >
< span > { { reportData . type } } < / span >
< / div >
< div class = "report-field" >
< label > 录音时长 : < / label >
< span > { { reportData . duration } } < / span >
< / div >
< div class = "report-field" >
< label > 录音分数 : < / label >
< span > { { reportData . score } } < / span >
< / div >
< div class = "report-field" >
< label > 详细信息 : < / label >
< span style = "white-space: pre-wrap; max-height: 300px; overflow-y: auto;" > { { reportData . details } } < / span >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
@@ -336,6 +383,8 @@
< script setup >
< script setup >
import { computed , ref } from 'vue' ;
import { computed , ref } from 'vue' ;
import axios from 'axios' ;
import { useUserStore } from "@/stores/user" ;
// 弹窗相关的响应式数据
// 弹窗相关的响应式数据
const showModal = ref ( false ) ;
const showModal = ref ( false ) ;
@@ -343,27 +392,81 @@ const modalMessages = ref([]);
// 无归属录音弹窗
// 无归属录音弹窗
const showUnassignedRecordingsModal = ref ( false ) ;
const showUnassignedRecordingsModal = ref ( false ) ;
const unassignedRecordings = ref ( [ ] ) ; // 初始化为空数组
// 报告弹窗相关数据
const showReportModal = ref ( false ) ;
const reportData = ref ( { } ) ;
// 处理无归属录音按钮点击事件
const handleUnassignedRecordingsClick = async ( ) => {
try {
const userStore = useUserStore ( ) ;
const user _name = userStore . userInfo ? . username || 'example_user' ;
const response = await axios . post ( 'https://mldash.nycjy.cn/api/v1/sales_timeline/get_sale_call_info' , {
user _name : user _name
} ) ;
console . log ( 'API Response:' , response . data . data ) ;
// --- 开始数据处理逻辑 ---
const apiData = response . data . data ;
const processedRecordings = [ ] ;
let uniqueId = 0 ;
// 1. 遍历API返回数据中的每个分类 (例如 "20分钟通话", "视频通话" 等)
for ( const category in apiData ) {
if ( Object . prototype . hasOwnProperty . call ( apiData , category ) ) {
const categoryData = apiData [ category ] ;
// 2. 遍历该分类下的 'records' 数组
if ( categoryData && Array . isArray ( categoryData . records ) ) {
categoryData . records . forEach ( record => {
// 3. 将每个API返回的录音记录转换成前端模板需要的格式
processedRecordings . push ( {
id : uniqueId ++ , // 使用自增ID作为v-for的key
name : ` 录音 ${ uniqueId } ` , // API没有提供名称, 我们创建一个
time : new Date ( record . record _create _time ) . toLocaleString ( 'zh-CN' ) , // 格式化时间
type : record . record _tag , // 使用 record_tag 作为类型
downloadUrl : record . record _file _addr , // 使用 record_file_addr 作为下载链接
duration : record . call _duration ? ` ${ record . call _duration } 分钟 ` : '未知' , // 使用 call_duration 并添加单位
score : record . score || '暂无' , // 使用 score 字段
details : record . report _content || '暂无详细报告内容。' // 存储报告内容
} ) ;
} ) ;
}
}
}
// 4. 将处理好的数据赋值给 unassignedRecordings
unassignedRecordings . value = processedRecordings ;
// --- 数据处理逻辑结束 ---
showUnassignedRecordingsModal . value = true ;
} catch ( error ) {
console . error ( 'API请求失败:' , error ) ;
alert ( '获取录音列表失败,请稍后再试。' ) ;
}
} ;
// 查看报告函数
const viewReport = ( recording ) => {
// 设置报告数据, 直接从传入的recording对象中获取
reportData . value = {
name : recording . name ,
time : recording . time ,
type : recording . type ,
duration : recording . duration ,
score : recording . score ,
details : recording . details // 直接使用我们处理好的 details 字段
} ;
// 显示报告弹窗
showReportModal . value = true ;
} ;
const unassignedRecordings = ref ( [
{ id : 1 , name : '录音A' , time : '2023-10-27 10:00' , type : '通话录音' , downloadUrl : '#' } ,
{ id : 2 , name : '录音B' , time : '2023-10-27 11:30' , type : '会议录音' , downloadUrl : '#' } ,
{ id : 3 , name : '录音C' , time : '2023-10-28 14:00' , type : '通话录音' , downloadUrl : '#' } ,
{ id : 4 , name : '录音D' , time : '2023-10-29 09:00' , type : '通话录音' , downloadUrl : '#' } ,
{ id : 5 , name : '录音E' , time : '2023-10-29 16:30' , type : '会议录音' , downloadUrl : '#' } ,
{ id : 6 , name : '录音F' , time : '2023-10-30 11:00' , type : '通话录音' , downloadUrl : '#' } ,
{ id : 1 , name : '录音A' , time : '2023-10-27 10:00' , type : '通话录音' , downloadUrl : '#' } ,
{ id : 2 , name : '录音B' , time : '2023-10-27 11:30' , type : '会议录音' , downloadUrl : '#' } ,
{ id : 3 , name : '录音C' , time : '2023-10-28 14:00' , type : '通话录音' , downloadUrl : '#' } ,
{ id : 4 , name : '录音D' , time : '2023-10-29 09:00' , type : '通话录音' , downloadUrl : '#' } ,
{ id : 5 , name : '录音E' , time : '2023-10-29 16:30' , type : '会议录音' , downloadUrl : '#' } ,
{ id : 6 , name : '录音F' , time : '2023-10-30 11:00' , type : '通话录音' , downloadUrl : '#' } ,
{ id : 1 , name : '录音A' , time : '2023-10-27 10:00' , type : '通话录音' , downloadUrl : '#' } ,
{ id : 2 , name : '录音B' , time : '2023-10-27 11:30' , type : '会议录音' , downloadUrl : '#' } ,
{ id : 3 , name : '录音C' , time : '2023-10-28 14:00' , type : '通话录音' , downloadUrl : '#' } ,
{ id : 4 , name : '录音D' , time : '2023-10-29 09:00' , type : '通话录音' , downloadUrl : '#' } ,
{ id : 5 , name : '录音E' , time : '2023-10-29 16:30' , type : '会议录音' , downloadUrl : '#' } ,
{ id : 6 , name : '录音F' , time : '2023-10-30 11:00' , type : '通话录音' , downloadUrl : '#' } ,
] ) ;
// 定义props
// 定义props
const props = defineProps ( {
const props = defineProps ( {
@@ -405,7 +508,7 @@ const props = defineProps({
}
}
} ) ;
} ) ;
const emit = defineEmits ( [ 'stage-select' , 'select-contact' ] ) ;
const emit = defineEmits ( [ 'stage-select' , 'select-contact' , 'sub-stage-select' ]) ;
const totalCustomers = computed ( ( ) => {
const totalCustomers = computed ( ( ) => {
// 全部阶段的数量就是前6个阶段的数量
// 全部阶段的数量就是前6个阶段的数量
@@ -1373,11 +1476,48 @@ $indigo: #4f46e5;
margin - top : 4 px ;
margin - top : 4 px ;
}
}
. recording - header {
display : flex ;
justify - content : space - between ;
align - items : center ;
}
. recording - score {
font - size : 0.8 rem ;
color : # f59e0b ;
font - weight : 600 ;
}
. recording - duration {
font - size : 0.8 rem ;
color : $slate - 600 ;
}
. recording - actions {
display : flex ;
gap : 8 px ;
}
. report - link {
background - color : # 10 b981 ;
color : white ;
border : none ;
padding : 6 px 10 px ;
border - radius : 4 px ;
font - size : 0.85 rem ;
cursor : pointer ;
transition : background - color 0.2 s ease ;
& : hover {
background - color : darken ( # 10 b981 , 10 % ) ;
}
}
. download - link {
. download - link {
background - color : $primary ;
background - color : $primary ;
color : white ;
color : white ;
text - decoration : none ;
text - decoration : none ;
padding : 6 px 12 px ;
padding : 6 px 10 px ;
border - radius : 4 px ;
border - radius : 4 px ;
font - size : 0.85 rem ;
font - size : 0.85 rem ;
transition : background - color 0.2 s ease ;
transition : background - color 0.2 s ease ;
@@ -1387,6 +1527,36 @@ $indigo: #4f46e5;
}
}
}
}
. report - modal {
max - width : 600 px ;
max - height : 80 vh ;
}
. report - content {
display : flex ;
flex - direction : column ;
gap : 12 px ;
}
. report - field {
display : flex ;
flex - direction : column ;
gap : 4 px ;
}
. report - field label {
font - weight : 600 ;
color : $slate - 700 ;
}
. report - field span {
color : $slate - 600 ;
padding : 8 px 12 px ;
background - color : $slate - 50 ;
border - radius : 4 px ;
border : 1 px solid $slate - 200 ;
}
. actionable - list {
. actionable - list {
display : flex ;
display : flex ;
flex - direction : column ;
flex - direction : column ;
@@ -1715,8 +1885,8 @@ $indigo: #4f46e5;
background : white ;
background : white ;
border - radius : 8 px ;
border - radius : 8 px ;
width : 90 % ;
width : 90 % ;
max - width : 6 00px ;
max - width : 7 00px ;
max - height : 6 0vh ;
max - height : 7 0vh ; /* 增加高度 */
overflow : hidden ;
overflow : hidden ;
box - shadow : 0 4 px 20 px rgba ( 0 , 0 , 0 , 0.15 ) ;
box - shadow : 0 4 px 20 px rgba ( 0 , 0 , 0 , 0.15 ) ;
display : flex ;
display : flex ;
@@ -1932,21 +2102,21 @@ $indigo: #4f46e5;
/* --- 无归属录音弹窗样式 --- */
/* --- 无归属录音弹窗样式 --- */
. recordings - list {
. recordings - list {
display : grid ;
display : grid ;
grid - template - columns : repeat ( 3 , 1 fr ) ;
grid - template - columns : repeat ( auto - fill , minmax ( 200 px , 1 fr ) ) ; /* 响应式布局 */
gap : 16 px ;
gap : 16 px ;
}
}
. recording - item {
. recording - item {
display : flex ;
display : flex ;
flex - direction : column ;
flex - direction : column ;
justify - content : space - between ;
justify - content : space - between ;
gap : 12 px ;
gap : 12 px ;
padding : 16 px ;
padding : 8 px ;
background - color : # f9fafb ;
background - color : # f9fafb ;
border - radius : 6 px ;
border - radius : 8 px ;
border : 1 px solid # e5e7eb ;
border : 1 px solid # e5e7eb ;
transition : box - shadow 0.2 s , transform 0.2 s ;
transition : box - shadow 0.2 s , transform 0.2 s ;
}
}
. recording - item : hover {
. recording - item : hover {
@@ -1955,21 +2125,20 @@ $indigo: #4f46e5;
}
}
. recording - info {
. recording - info {
display : flex ;
display : flex ;
flex - direction : column ;
flex - direction : column ;
gap : 4 px ;
}
}
. recording - name {
. recording - name {
font - weight : 600 ;
font - weight : 600 ;
color : # 374151 ;
color : # 374151 ;
font - size : 1 rem ;
font - size : 1 rem ;
word - break : break - all ;
word - break : break - all ;
}
}
. recording - time , . recording - type {
. recording - time {
font - size : 0.875 rem ;
font - size : 0.8 rem ;
color : # 6 b7280 ;
color : # 6 b7280 ;
}
}
. recording - type {
. recording - type {
@@ -1982,32 +2151,14 @@ $indigo: #4f46e5;
font - weight : 500 ;
font - weight : 500 ;
}
}
. download - link {
padding : 8 px 12 px ;
background - color : # 3 b82f6 ;
color : white ;
border - radius : 4 px ;
text - decoration : none ;
font - size : 0.875 rem ;
font - weight : 500 ;
text - align : center ;
transition : background - color 0.2 s ;
}
. download - link : hover {
background - color : # 2563 eb ;
}
/* 响应式布局 */
/* 响应式布局 */
@ media ( max - width : 768 px ) {
@ media ( max - width : 768 px ) {
. modal - content {
max - width : 95 % ;
}
. recordings - list {
. recordings - list {
grid - template - columns : repeat ( 2 , 1 fr ) ;
grid - template - columns : repeat ( auto - fill , minmax ( 180 px , 1 fr ) ) ;
}
}
}
}
@ media ( max - width : 480 px ) {
. recordings - list {
grid - template - columns : 1 fr ;
}
}
< / style >
< / style >