feat(录音管理): 实现无归属录音API集成及报告查看功能
- 添加API请求获取真实录音数据并处理展示 - 实现录音报告弹窗展示详细信息 - 优化录音列表UI布局和响应式设计
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
<span style="font-size: 14px;">客户转化全流程跟踪</span>
|
||||
</div>
|
||||
<div>
|
||||
<button @click="showUnassignedRecordingsModal = true" class="unassigned-recordings-btn">无归属录音</button>
|
||||
<button @click="handleUnassignedRecordingsClick" class="unassigned-recordings-btn">无归属录音</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -321,10 +321,18 @@
|
||||
<div v-else class="recordings-list">
|
||||
<div class="recording-item" v-for="recording in unassignedRecordings" :key="recording.id">
|
||||
<div class="recording-info">
|
||||
<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>
|
||||
<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 class="recording-actions">
|
||||
<button class="report-link" @click="viewReport(recording)">查看报告</button>
|
||||
<a :href="recording.downloadUrl" class="download-link" download>下载</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -332,10 +340,51 @@
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import axios from 'axios';
|
||||
import { useUserStore } from "@/stores/user";
|
||||
|
||||
// 弹窗相关的响应式数据
|
||||
const showModal = ref(false);
|
||||
@@ -343,27 +392,81 @@ const modalMessages = ref([]);
|
||||
|
||||
// 无归属录音弹窗
|
||||
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
|
||||
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(() => {
|
||||
// 全部阶段的数量就是前6个阶段的数量
|
||||
@@ -1373,11 +1476,48 @@ $indigo: #4f46e5;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.recording-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.recording-score {
|
||||
font-size: 0.8rem;
|
||||
color: #f59e0b;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.recording-duration {
|
||||
font-size: 0.8rem;
|
||||
color: $slate-600;
|
||||
}
|
||||
|
||||
.recording-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.report-link {
|
||||
background-color: #10b981;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 6px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.85rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: darken(#10b981, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
.download-link {
|
||||
background-color: $primary;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
padding: 6px 12px;
|
||||
padding: 6px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.85rem;
|
||||
transition: background-color 0.2s ease;
|
||||
@@ -1387,6 +1527,36 @@ $indigo: #4f46e5;
|
||||
}
|
||||
}
|
||||
|
||||
.report-modal {
|
||||
max-width: 600px;
|
||||
max-height: 80vh;
|
||||
}
|
||||
|
||||
.report-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.report-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.report-field label {
|
||||
font-weight: 600;
|
||||
color: $slate-700;
|
||||
}
|
||||
|
||||
.report-field span {
|
||||
color: $slate-600;
|
||||
padding: 8px 12px;
|
||||
background-color: $slate-50;
|
||||
border-radius: 4px;
|
||||
border: 1px solid $slate-200;
|
||||
}
|
||||
|
||||
.actionable-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -1715,8 +1885,8 @@ $indigo: #4f46e5;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
width: 90%;
|
||||
max-width: 600px;
|
||||
max-height: 60vh;
|
||||
max-width: 700px;
|
||||
max-height: 70vh; /* 增加高度 */
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
display: flex;
|
||||
@@ -1933,7 +2103,7 @@ $indigo: #4f46e5;
|
||||
/* --- 无归属录音弹窗样式 --- */
|
||||
.recordings-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); /* 响应式布局 */
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
@@ -1942,9 +2112,9 @@ $indigo: #4f46e5;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
padding: 8px;
|
||||
background-color: #f9fafb;
|
||||
border-radius: 6px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e5e7eb;
|
||||
transition: box-shadow 0.2s, transform 0.2s;
|
||||
}
|
||||
@@ -1957,7 +2127,6 @@ $indigo: #4f46e5;
|
||||
.recording-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.recording-name {
|
||||
@@ -1967,8 +2136,8 @@ $indigo: #4f46e5;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.recording-time, .recording-type {
|
||||
font-size: 0.875rem;
|
||||
.recording-time {
|
||||
font-size: 0.8rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
@@ -1982,32 +2151,14 @@ $indigo: #4f46e5;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.download-link {
|
||||
padding: 8px 12px;
|
||||
background-color: #3b82f6;
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
text-decoration: none;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.download-link:hover {
|
||||
background-color: #2563eb;
|
||||
}
|
||||
|
||||
/* 响应式布局 */
|
||||
@media (max-width: 768px) {
|
||||
.modal-content {
|
||||
max-width: 95%;
|
||||
}
|
||||
.recordings-list {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.recordings-list {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user