feat: 工作计划/商机/要客页面新增客户经理列与汇总栏
- 表格新增「客户经理」列,暖灰色哈希标签,每人唯一颜色 - 页面顶部新增汇总栏,按经理分组显示各有多少条记录 - 汇总栏仅在有数据时显示,与亮灯表配色体系一致 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { todayStr } from '@/utils'
|
||||
@@ -19,6 +19,22 @@ const form = ref<any>({})
|
||||
const statusPick = ref<Record<string, string>>({})
|
||||
const keyStatuses = ['未开始', '进行中', '已完成']
|
||||
|
||||
const mgrColors = ['#1C3738','#4A6741','#5B7FA5','#7B7568','#8B6F47','#6B5B4F','#3D5A5C','#5C4A3D']
|
||||
function mgrColor(name: string) {
|
||||
if (!name) return '#909399'
|
||||
let h = 0; for (let i=0;i<name.length;i++) h = name.charCodeAt(i) + ((h<<5)-h)
|
||||
return mgrColors[Math.abs(h) % mgrColors.length]
|
||||
}
|
||||
|
||||
const managerSummary = computed(() => {
|
||||
const map: Record<string, number> = {}
|
||||
keyVisits.value.forEach((k: any) => {
|
||||
const n = k.manager_name || '未知'
|
||||
map[n] = (map[n] || 0) + 1
|
||||
})
|
||||
return Object.entries(map).sort((a, b) => b[1] - a[1])
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await Promise.all([loadItems(), loadCustomers(), loadUsers()])
|
||||
})
|
||||
@@ -117,6 +133,10 @@ async function quickStatusChange(row: any, newStatus: string) {
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="page-rule"></div>
|
||||
<div v-if="managerSummary.length" class="summary-bar">
|
||||
<span class="summary-label">客户经理汇总</span>
|
||||
<span v-for="[name, count] in managerSummary" :key="name" class="summary-chip" :style="{ background: mgrColor(name) }">{{ name }} · {{ count }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-card>
|
||||
@@ -131,6 +151,11 @@ async function quickStatusChange(row: any, newStatus: string) {
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="客户经理" width="100">
|
||||
<template #default="{ row }">
|
||||
<span class="mgr-tag" :style="{ background: mgrColor(row.manager_name) }">{{ row.manager_name || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="urgency_level" label="重要度" width="90">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" :type="row.urgency_level==='紧急'?'danger':row.urgency_level==='重要'?'warning':''">{{ row.urgency_level }}</el-tag>
|
||||
@@ -212,6 +237,10 @@ async function quickStatusChange(row: any, newStatus: string) {
|
||||
.page-title { margin: 0; font-family: 'ZCOOL XiaoWei', STSong, serif; font-size: 22px; font-weight: 400; color: var(--ink); letter-spacing: 0.06em; }
|
||||
.page-desc { margin: 4px 0 0; font-size: 13px; color: var(--c-text-muted); font-family: 'Noto Serif SC', STSong, serif; }
|
||||
.page-rule { width: 32px; height: 3px; background: var(--gold); margin-top: 12px; }
|
||||
.summary-bar { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-top: 14px; padding: 10px 14px; background: var(--c-bg-light, #faf9f6); border-radius: 6px; border: 1px solid var(--c-border, #e8e5df); }
|
||||
.summary-label { font-size: 12px; color: var(--c-text-muted); font-family: 'Noto Serif SC', STSong, serif; margin-right: 4px; }
|
||||
.summary-chip { display: inline-block; padding: 2px 10px; border-radius: 12px; font-size: 12px; color: #fff; white-space: nowrap; }
|
||||
.mgr-tag { display: inline-block; padding: 1px 9px; border-radius: 10px; font-size: 12px; color: #fff; white-space: nowrap; }
|
||||
.method-grid { display: flex; gap: 8px; }
|
||||
.empty { text-align: center; color: var(--c-text-muted); padding: 48px 0; font-family: 'Noto Serif SC', STSong, serif; }
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { todayStr } from '@/utils'
|
||||
@@ -17,6 +17,22 @@ const form = ref<any>({})
|
||||
const statusPick = ref<Record<string, string>>({})
|
||||
const miniStatuses = ['跟进中', '已签约', '已流失']
|
||||
|
||||
const mgrColors = ['#1C3738','#4A6741','#5B7FA5','#7B7568','#8B6F47','#6B5B4F','#3D5A5C','#5C4A3D']
|
||||
function mgrColor(name: string) {
|
||||
if (!name) return '#909399'
|
||||
let h = 0; for (let i=0;i<name.length;i++) h = name.charCodeAt(i) + ((h<<5)-h)
|
||||
return mgrColors[Math.abs(h) % mgrColors.length]
|
||||
}
|
||||
|
||||
const managerSummary = computed(() => {
|
||||
const map: Record<string, number> = {}
|
||||
miniBusiness.value.forEach((m: any) => {
|
||||
const n = m.manager_name || '未知'
|
||||
map[n] = (map[n] || 0) + 1
|
||||
})
|
||||
return Object.entries(map).sort((a, b) => b[1] - a[1])
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await Promise.all([loadItems(), loadCustomers()])
|
||||
})
|
||||
@@ -102,6 +118,10 @@ async function quickStatusChange(row: any, newStatus: string) {
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="page-rule"></div>
|
||||
<div v-if="managerSummary.length" class="summary-bar">
|
||||
<span class="summary-label">客户经理汇总</span>
|
||||
<span v-for="[name, count] in managerSummary" :key="name" class="summary-chip" :style="{ background: mgrColor(name) }">{{ name }} · {{ count }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-card>
|
||||
@@ -116,6 +136,11 @@ async function quickStatusChange(row: any, newStatus: string) {
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="客户经理" width="100">
|
||||
<template #default="{ row }">
|
||||
<span class="mgr-tag" :style="{ background: mgrColor(row.manager_name) }">{{ row.manager_name || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="product_type" label="产品类型" width="130" />
|
||||
<el-table-column prop="amount" label="金额" width="100" />
|
||||
<el-table-column prop="follow_up_detail" label="跟进内容" min-width="250" show-overflow-tooltip />
|
||||
@@ -181,5 +206,9 @@ async function quickStatusChange(row: any, newStatus: string) {
|
||||
.page-title { margin: 0; font-family: 'ZCOOL XiaoWei', STSong, serif; font-size: 22px; font-weight: 400; color: var(--ink); letter-spacing: 0.06em; }
|
||||
.page-desc { margin: 4px 0 0; font-size: 13px; color: var(--c-text-muted); font-family: 'Noto Serif SC', STSong, serif; }
|
||||
.page-rule { width: 32px; height: 3px; background: var(--gold); margin-top: 12px; }
|
||||
.summary-bar { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-top: 14px; padding: 10px 14px; background: var(--c-bg-light, #faf9f6); border-radius: 6px; border: 1px solid var(--c-border, #e8e5df); }
|
||||
.summary-label { font-size: 12px; color: var(--c-text-muted); font-family: 'Noto Serif SC', STSong, serif; margin-right: 4px; }
|
||||
.summary-chip { display: inline-block; padding: 2px 10px; border-radius: 12px; font-size: 12px; color: #fff; white-space: nowrap; }
|
||||
.mgr-tag { display: inline-block; padding: 1px 9px; border-radius: 10px; font-size: 12px; color: #fff; white-space: nowrap; }
|
||||
.empty { text-align: center; color: var(--c-text-muted); padding: 48px 0; font-family: 'Noto Serif SC', STSong, serif; }
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { todayStr } from '@/utils'
|
||||
@@ -17,6 +17,22 @@ const form = ref<any>({})
|
||||
const statusPick = ref<Record<string, string>>({})
|
||||
const planStatuses = ['计划中', '已完成', '已取消']
|
||||
|
||||
const mgrColors = ['#1C3738','#4A6741','#5B7FA5','#7B7568','#8B6F47','#6B5B4F','#3D5A5C','#5C4A3D']
|
||||
function mgrColor(name: string) {
|
||||
if (!name) return '#909399'
|
||||
let h = 0; for (let i=0;i<name.length;i++) h = name.charCodeAt(i) + ((h<<5)-h)
|
||||
return mgrColors[Math.abs(h) % mgrColors.length]
|
||||
}
|
||||
|
||||
const managerSummary = computed(() => {
|
||||
const map: Record<string, number> = {}
|
||||
workPlans.value.forEach((w: any) => {
|
||||
const n = w.manager_name || '未知'
|
||||
map[n] = (map[n] || 0) + 1
|
||||
})
|
||||
return Object.entries(map).sort((a, b) => b[1] - a[1])
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await Promise.all([loadPlans(), loadCustomers()])
|
||||
})
|
||||
@@ -102,6 +118,10 @@ async function quickStatusChange(row: any, newStatus: string) {
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="page-rule"></div>
|
||||
<div v-if="managerSummary.length" class="summary-bar">
|
||||
<span class="summary-label">客户经理汇总</span>
|
||||
<span v-for="[name, count] in managerSummary" :key="name" class="summary-chip" :style="{ background: mgrColor(name) }">{{ name }} · {{ count }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-card>
|
||||
@@ -116,6 +136,11 @@ async function quickStatusChange(row: any, newStatus: string) {
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="客户经理" width="100">
|
||||
<template #default="{ row }">
|
||||
<span class="mgr-tag" :style="{ background: mgrColor(row.manager_name) }">{{ row.manager_name || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="plan_content" label="工作计划" min-width="280" show-overflow-tooltip />
|
||||
<el-table-column prop="plan_date" label="计划时间" width="110" />
|
||||
<el-table-column label="状态" width="130">
|
||||
@@ -179,5 +204,9 @@ async function quickStatusChange(row: any, newStatus: string) {
|
||||
.page-title { margin: 0; font-family: 'ZCOOL XiaoWei', STSong, serif; font-size: 22px; font-weight: 400; color: var(--ink); letter-spacing: 0.06em; }
|
||||
.page-desc { margin: 4px 0 0; font-size: 13px; color: var(--c-text-muted); font-family: 'Noto Serif SC', STSong, serif; }
|
||||
.page-rule { width: 32px; height: 3px; background: var(--gold); margin-top: 12px; }
|
||||
.summary-bar { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-top: 14px; padding: 10px 14px; background: var(--c-bg-light, #faf9f6); border-radius: 6px; border: 1px solid var(--c-border, #e8e5df); }
|
||||
.summary-label { font-size: 12px; color: var(--c-text-muted); font-family: 'Noto Serif SC', STSong, serif; margin-right: 4px; }
|
||||
.summary-chip { display: inline-block; padding: 2px 10px; border-radius: 12px; font-size: 12px; color: #fff; white-space: nowrap; }
|
||||
.mgr-tag { display: inline-block; padding: 1px 9px; border-radius: 10px; font-size: 12px; color: #fff; white-space: nowrap; }
|
||||
.empty { text-align: center; color: var(--c-text-muted); padding: 48px 0; font-family: 'Noto Serif SC', STSong, serif; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user