feat: 导入增强 + 客户合并 + 同伴自定义 + 性能优化
=== 导入系统全面增强 === - 5 Sheet 完整导入:拜访/计划/商机/要客/纪要 - 同访人智能解析:中英文逗号/顿号/分号 → 系统用户UUID + 外部人员TEXT - 客户自动创建:Excel中不存在的客户自动入库 - 模糊名称匹配:去空格 + 包含关系纠错 - manager_id 修正:导入时用客户分配的经理(非Excel列/非导入人) - 模板更新:Sheet5「客户经理」→「填报人」,同访人示例含逗号分隔 === 客户合并功能 === - PUT 改名碰撞检测 → 409 + 合并预览 - GET merge-preview / POST merge 端点 - 事务级迁移:Visit/WorkPlan/MiniBusiness/KeyVisit/联系人/分配 - 去重逻辑:联系人(name+phone)、分配(manager+role) - last_visit_date 取最大值 === 拜访记录完善 === - visits 新增 companion_names TEXT[] 列 - 同访人支持自定义输入(外部人员),el-select allow-create - 移动端 VisitForm + PC端 ManagerWorkspace 统一 - 周报「客户经理」→「相关人员」(创建人+同访人) - 纪要「客户经理」→「填报人」 - 删除拜访后重新计算 customer.last_visit_date === 客户选择放开 === - 客户经理可看到全部客户(不再限自己分配的) - 拜访时客户下拉返回全量 === 前端性能优化 === - Element Plus 按需加载 (unplugin-vue-components + unplugin-element-plus) - 周报照片URL并行请求 (Promise.all) - onMounted 三路并行 (loadReport + dropdowns + AI) - nginx Cache-Control: immutable for /assets/ - Google Fonts preconnect hints - 图片上传前 Canvas 压缩 (max 1920px, quality 0.8) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed, watch } from 'vue'
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { dashboardApi } from '@/api/dashboard'
|
||||
@@ -21,51 +21,6 @@ const planDate = ref(todayStr())
|
||||
const planContent = ref('')
|
||||
const planSaving = ref(false)
|
||||
|
||||
// ── Batch selection ──
|
||||
const selectedCards = ref<Set<string>>(new Set())
|
||||
const batchDialogVisible = ref(false)
|
||||
const batchPlanDate = ref(todayStr())
|
||||
const batchPlanContent = ref('')
|
||||
const batchSaving = ref(false)
|
||||
|
||||
watch(expandedManagers, () => { selectedCards.value.clear() })
|
||||
|
||||
function toggleCardSelect(customerId: string) {
|
||||
const s = new Set(selectedCards.value)
|
||||
if (s.has(customerId)) s.delete(customerId)
|
||||
else s.add(customerId)
|
||||
selectedCards.value = s
|
||||
}
|
||||
|
||||
function openBatchDialog() {
|
||||
if (selectedCards.value.size === 0) { ElMessage.warning('请先勾选客户卡片'); return }
|
||||
batchPlanContent.value = ''
|
||||
batchPlanDate.value = todayStr()
|
||||
batchDialogVisible.value = true
|
||||
}
|
||||
|
||||
async function handleBatchCreate() {
|
||||
if (!batchPlanContent.value.trim()) { ElMessage.warning('请输入计划内容'); return }
|
||||
batchSaving.value = true
|
||||
let created = 0
|
||||
for (const cid of selectedCards.value) {
|
||||
try {
|
||||
await api.post('/work-plans/', {
|
||||
customer_id: cid,
|
||||
plan_content: batchPlanContent.value.trim(),
|
||||
plan_date: batchPlanDate.value,
|
||||
status: '计划中',
|
||||
})
|
||||
created++
|
||||
} catch (_) { /* continue */ }
|
||||
}
|
||||
ElMessage.success(`已为 ${created} 个客户制定计划`)
|
||||
batchDialogVisible.value = false
|
||||
selectedCards.value.clear()
|
||||
batchSaving.value = false
|
||||
await loadData()
|
||||
}
|
||||
|
||||
const referenceMonth = computed(() => {
|
||||
const d = new Date()
|
||||
d.setMonth(d.getMonth() + monthOffset.value)
|
||||
@@ -204,14 +159,7 @@ const statusLabel: Record<string, string> = { green: '本月已拜访', yellow:
|
||||
</div>
|
||||
|
||||
<div v-if="expandedManagers.has(m.manager_id)" class="customer-grid">
|
||||
<div class="batch-actions" v-if="selectedCards.size > 0" style="width:100%;margin-bottom:8px">
|
||||
<el-button type="primary" size="small" @click="openBatchDialog">📋 批量制定计划 ({{ selectedCards.size }}个)</el-button>
|
||||
<el-button size="small" @click="selectedCards.clear()">取消选择</el-button>
|
||||
</div>
|
||||
<div v-for="cust in m.customers" :key="cust.customer_id" class="customer-card" :class="['customer-card--' + cust.status, { 'card-selected': selectedCards.has(cust.customer_id) }]" @click="openCustomerDialog(cust)">
|
||||
<div v-if="cust.status !== 'green' && cust.status !== 'gray'" class="card-check" @click.stop="toggleCardSelect(cust.customer_id)">
|
||||
<span v-if="selectedCards.has(cust.customer_id)">☑</span><span v-else>☐</span>
|
||||
</div>
|
||||
<div v-for="cust in m.customers" :key="cust.customer_id" class="customer-card" :class="`customer-card--${cust.status}`" @click="openCustomerDialog(cust)">
|
||||
<div class="card-status-stripe"></div>
|
||||
<div class="card-body">
|
||||
<div class="card-name-row">
|
||||
@@ -322,23 +270,6 @@ const statusLabel: Record<string, string> = { green: '本月已拜访', yellow:
|
||||
</el-link>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- ═══ Batch Plan Dialog ═══ -->
|
||||
<el-dialog v-model="batchDialogVisible" title="批量制定拜访计划" width="480px">
|
||||
<p style="margin:0 0 12px;color:var(--c-text-muted)">将为 <strong>{{ selectedCards.size }}</strong> 个客户统一制定拜访计划:</p>
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="计划时间">
|
||||
<el-date-picker v-model="batchPlanDate" type="date" style="width:100%" value-format="YYYY-MM-DD" />
|
||||
</el-form-item>
|
||||
<el-form-item label="计划内容">
|
||||
<el-input v-model="batchPlanContent" type="textarea" :rows="3" placeholder="统一的拜访计划内容" :disabled="batchSaving" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="batchDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="batchSaving" @click="handleBatchCreate">批量制定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -445,15 +376,4 @@ const statusLabel: Record<string, string> = { green: '本月已拜访', yellow:
|
||||
.dlg-plan-item.dlg-plan-overdue { background: #FBF1EE; margin: 2px -4px; padding: 4px; border-radius: 4px; }
|
||||
.dlg-plan-form { background: var(--c-bg-light, #faf9f6); padding: 10px 12px; border-radius: 6px; }
|
||||
.plan-form-row { display: flex; gap: 8px; align-items: center; }
|
||||
|
||||
/* ═══ Card Checkbox ═══ */
|
||||
.card-check {
|
||||
position: absolute; top: 4px; right: 4px;
|
||||
width: 22px; height: 22px; display: flex; align-items: center; justify-content: center;
|
||||
cursor: pointer; font-size: 14px; color: var(--warm-gray);
|
||||
border-radius: 4px; background: rgba(255,255,255,0.8);
|
||||
z-index: 2;
|
||||
}
|
||||
.card-check:hover { color: var(--ink); background: rgba(196,147,74,0.1); }
|
||||
.customer-card.card-selected { border-color: var(--gold); box-shadow: 0 0 0 2px rgba(196,147,74,0.2); }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user