feat(数据表格): 实现详细数据表格的筛选和排序功能

- 添加筛选器变化事件处理,支持按中心领导、团队领导和组长筛选数据
- 修改表格排序逻辑以适配新数据结构
- 更新表格显示字段以匹配后端返回的数据格式
- 移除不再需要的格式化函数
This commit is contained in:
2025-08-18 13:27:03 +08:00
parent 8f709aa1f5
commit 57069e3a01
2 changed files with 144 additions and 47 deletions

View File

@@ -25,14 +25,14 @@
</select> </select>
</div> </div>
<div class="filter-group"> <div class="filter-group">
<label>经理:</label> <label>经理:</label>
<select v-model="filters.manager" :disabled="!filters.advancedManager"> <select v-model="filters.manager" @change="onManagerChange" :disabled="!filters.advancedManager">
<option value="">全部经理</option> <option value="">全部经理</option>
<option v-for="manager in availableManagers" :key="manager.name" :value="manager.name"> <option v-for="manager in availableManagers" :key="manager.name" :value="manager.name">
{{ manager.name }} {{ manager.name }}
</option> </option>
</select> </select>
</div> </div>
</div> </div>
<!-- 数据表格 --> <!-- 数据表格 -->
@@ -41,34 +41,36 @@
<thead> <thead>
<tr> <tr>
<th>人员</th> <th>人员</th>
<th @click="sortBy('dealRate')" class="sortable">成交率 <span class="sort-icon" :class="{ active: sortField === 'dealRate' }">{{ sortOrder === 'desc' ? '↓' : '↑' }}</span></th> <th @click="sortBy('conversion_rate')" class="sortable">成交率 <span class="sort-icon" :class="{ active: sortField === 'conversion_rate' }">{{ sortOrder === 'desc' ? '↓' : '↑' }}</span></th>
<th @click="sortBy('callDuration')" class="sortable">成交单数 <span class="sort-icon" :class="{ active: sortField === 'callDuration' }">{{ sortOrder === 'desc' ? '↓' : '↑' }}</span></th> <th @click="sortBy('total_deals')" class="sortable">成交单数 <span class="sort-icon" :class="{ active: sortField === 'total_deals' }">{{ sortOrder === 'desc' ? '↓' : '↑' }}</span></th>
<th>加微率</th> <th>加微率</th>
<th>入群率</th> <th>入群率</th>
<th>表单填写率</th> <th>表单填写率</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="person in filteredTableData" :key="person.id" @click="$emit('update:selectedPerson', person)" :class="{ selected: selectedPerson && selectedPerson.id === person.id }"> <tr v-for="(person, index) in displayTableData" :key="index" @click="$emit('update:selectedPerson', person)" :class="{ selected: selectedPerson && selectedPerson.leader_name === person.leader_name }">
<td> <td>
<div class="person-info"> <div class="person-info">
<div class="person-avatar">{{ person.name.charAt(0) }}</div> <div class="person-avatar">{{ person.leader_name?.charAt(0) }}</div>
<div> <div>
<div class="person-name">{{ person.name }}</div> <div class="person-name">{{ person.leader_name}}</div>
<div class="person-position">{{ person.position }}</div>
</div> </div>
</div> </div>
</td> </td>
<td> <td>
<div class="deal-rate"> <div class="deal-rate">
<span class="rate-value" :class="getRateClass(person.dealRate)">{{ person.dealRate }}%</span> <span class="rate-value" :class="getRateClass(parseFloat(person.conversion_rate))">{{ person.conversion_rate }}</span>
<div class="rate-bar"><div class="rate-fill" :style="{ width: person.dealRate + '%', backgroundColor: getRateColor(person.dealRate) }"></div></div> <div class="rate-bar"><div class="rate-fill" :style="{ width: parseFloat(person.conversion_rate) + '%', backgroundColor: getRateColor(parseFloat(person.conversion_rate)) }"></div></div>
</div> </div>
</td> </td>
<td>{{ formatDuration(person.callDuration) }}</td> <td>{{ person.total_deals }}</td>
<td>{{ person.callCount }}</td> <td>{{ person.plus_v_rate }}</td>
<td>¥{{ person.dealAmount.toLocaleString() }}</td> <td>{{ person.group_rate }}</td>
<td>{{ person.department }}</td> <td>{{ person.form_filling_rate }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -85,10 +87,10 @@ const props = defineProps({
selectedPerson: { type: Object, default: null }, selectedPerson: { type: Object, default: null },
levelTree: { type: Object, default: () => ({}) } levelTree: { type: Object, default: () => ({}) }
}); });
defineEmits(['update:selectedPerson']); const emit = defineEmits(['update:selectedPerson', 'filter-change']);
const filters = ref({ centerLeader: '', advancedManager: '', manager: '', dealStatus: '' }); const filters = ref({ centerLeader: '', advancedManager: '', manager: '', dealStatus: '' });
const sortField = ref('dealRate'); const sortField = ref('conversion_rate');
const sortOrder = ref('desc'); const sortOrder = ref('desc');
const centerLeaders = computed(() => { const centerLeaders = computed(() => {
@@ -107,25 +109,54 @@ const centerLeaders = computed(() => {
return manager ? manager.managers || [] : []; return manager ? manager.managers || [] : [];
}); });
const filteredTableData = computed(() => { // 显示表格数据直接使用API返回的数据
let filtered = props.tableData; const displayTableData = computed(() => {
if (filters.value.centerLeader) filtered = filtered.filter(item => item.centerLeader === filters.value.centerLeader); if (!props.tableData || !Array.isArray(props.tableData)) return [];
if (filters.value.advancedManager) filtered = filtered.filter(item => item.advancedManager === filters.value.advancedManager);
if (filters.value.manager) filtered = filtered.filter(item => item.manager === filters.value.manager); return props.tableData.sort((a, b) => {
return filtered.sort((a, b) => { let aValue = a[sortField.value];
const aValue = a[sortField.value], bValue = b[sortField.value]; let bValue = b[sortField.value];
// 处理百分比字符串
if (typeof aValue === 'string' && aValue.includes('%')) {
aValue = parseFloat(aValue.replace('%', ''));
}
if (typeof bValue === 'string' && bValue.includes('%')) {
bValue = parseFloat(bValue.replace('%', ''));
}
return sortOrder.value === 'desc' ? bValue - aValue : aValue - bValue; return sortOrder.value === 'desc' ? bValue - aValue : aValue - bValue;
}); });
}); });
const onCenterLeaderChange = () => { const onCenterLeaderChange = () => {
filters.value.advancedManager = ''; filters.value.advancedManager = '';
filters.value.manager = ''; filters.value.manager = '';
}; emitFilterChange();
};
const onAdvancedManagerChange = () => {
filters.value.manager = ''; const onAdvancedManagerChange = () => {
}; filters.value.manager = '';
emitFilterChange();
};
const onManagerChange = () => {
emitFilterChange();
};
const emitFilterChange = () => {
const filterParams = {};
if (filters.value.centerLeader) {
filterParams.center_leader = filters.value.centerLeader;
}
if (filters.value.advancedManager) {
filterParams.team_leader = filters.value.advancedManager;
}
if (filters.value.manager) {
filterParams.group_leader = filters.value.manager;
}
emit('filter-change', filterParams);
};
const sortBy = (field) => { const sortBy = (field) => {
if (sortField.value === field) sortOrder.value = sortOrder.value === 'desc' ? 'asc' : 'desc'; if (sortField.value === field) sortOrder.value = sortOrder.value === 'desc' ? 'asc' : 'desc';
@@ -137,10 +168,7 @@ const getRateClass = (rate) => {
const getRateColor = (rate) => { const getRateColor = (rate) => {
if (rate >= 80) return '#4CAF50'; if (rate >= 60) return '#FF9800'; return '#f44336'; if (rate >= 80) return '#4CAF50'; if (rate >= 60) return '#FF9800'; return '#f44336';
}; };
const formatDuration = (minutes) => { // 移除formatDuration函数,不再需要
const h = Math.floor(minutes / 60), m = minutes % 60;
return h > 0 ? `${h}h${m}m` : `${m}m`;
};
</script> </script>
<style scoped> <style scoped>

View File

@@ -69,9 +69,10 @@
<!-- 第五行 --> <!-- 第五行 -->
<div class="dashboard-row row-4"> <div class="dashboard-row row-4">
<DetailedDataTable <DetailedDataTable
:table-data="tableData" :table-data="detailData"
:level-tree="levelTree" :level-tree="levelTree"
v-model:selected-person="selectedPerson" v-model:selected-person="selectedPerson"
@filter-change="handleFilterChange"
/> />
<DataDetailCard :selected-person="selectedPerson" /> <DataDetailCard :selected-person="selectedPerson" />
</div> </div>
@@ -1006,17 +1007,83 @@ async function CusotomGetLevelTree() {
} }
// 获取详细数据表格 // 获取详细数据表格
const detailData = ref({}); const detailData = ref({});
async function getDetailData(level) { async function getDetailData(params) {
const params={ if(params?.center_leader){
level, // alert(11111)
}
try { try {
const res = await getDetailedDataTable(params) const res = await getDetailedDataTable(params)
console.log(1222222,res) console.log('详细数据表格:', res)
detailData.value = res.data detailData.value = res.data
} catch (error) { } catch (error) {
console.error("获取详细数据表格失败:", error); console.error("获取详细数据表格失败:", error);
} }
}else{
// alert(22222)
try {
const res = await getDetailedDataTable()
console.log('详细数据表格:', res)
/**
* data
:
[{leader_name: "郭可英", conversion_rate: "0.00%", total_deals: 0, plus_v_rate: "59.75%",…},…]
0
:
{leader_name: "郭可英", conversion_rate: "0.00%", total_deals: 0, plus_v_rate: "59.75%",…}
conversion_rate
:
"0.00%"
form_filling_rate
:
"59.75%"
group_rate
:
"54.09%"
leader_name
:
"郭可英"
plus_v_rate
:
"59.75%"
total_deals
:
0
1
:
{leader_name: "刘瑞", conversion_rate: "1.32%", total_deals: 7, plus_v_rate: "47.17%",…}
conversion_rate
:
"1.32%"
form_filling_rate
:
"47.17%"
group_rate
:
"39.25%"
leader_name
:
"刘瑞"
plus_v_rate
:
"47.17%"
total_deals
:
7
message
:
"获取详细数据表格成功"
*/
detailData.value = res.data
} catch (error) {
console.error("获取详细数据表格失败:", error);
}
}
}
// 处理筛选器变化
const handleFilterChange = (filterParams) => {
console.log('筛选器变化:', filterParams)
getDetailData(filterParams)
} }
onMounted(async() => { onMounted(async() => {
@@ -1029,6 +1096,8 @@ onMounted(async() => {
// await getCustomerTypeRatio('child_education') // await getCustomerTypeRatio('child_education')
// await getCustomerUrgency() // await getCustomerUrgency()
await CusotomGetLevelTree() await CusotomGetLevelTree()
await getDetailData()
}); });
</script> </script>