Compare commits

...

2 Commits

Author SHA1 Message Date
57be345996 feat(录音管理): 实现无归属录音API集成及报告查看功能
- 添加API请求获取真实录音数据并处理展示
- 实现录音报告弹窗展示详细信息
- 优化录音列表UI布局和响应式设计
2025-10-15 18:56:11 +08:00
3ed490d6dc fix: 将客户表单请求参数从name改为phone
客户表单接口现在需要使用手机号而非姓名作为查询参数,以更准确地识别客户
2025-10-15 17:48:31 +08:00
2 changed files with 224 additions and 73 deletions

View File

@@ -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>

View File

@@ -578,7 +578,7 @@ async function getCustomerForm() {
const routeParams = getRequestParams()
const params = {
user_name: routeParams.user_name || userStore.userInfo.username,
customer_name: selectedContact.value.name,
phone: selectedContact.value.phone,
}
try {
const res = await getCustomerFormInfo(params)