feat: 支局长/分管领导可为任意客户经理代填记录

后端:
- 5个模块的 Create/Update schema 新增可选 manager_id 字段
- POST 端点: director/leader 可指定 manager_id,manager 角色自动用自身
- PUT 端点: director/leader 可修改 manager_id,manager 角色禁止修改

前端(移动端+桌面端共10个页面):
- 支局长/领导可见「客户经理」下拉选择框
- 创建和编辑时均可指定记录归属的客户经理
- 涵盖今日拜访、今日纪要、工作计划、商机跟单、要客拜访五个模块

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-12 18:19:05 +08:00
parent c489a8d6fd
commit 4453ee2ca0
19 changed files with 145 additions and 15 deletions
@@ -6,9 +6,12 @@ import api from '@/api/index'
import { compressImage } from '@/utils/image'
import ImagePreview from '@/components/ImagePreview.vue'
import EditLogPanel from '@/components/EditLogPanel.vue'
import { useAuthStore } from '@/stores/auth'
const auth = useAuthStore()
const activeTab = ref('visits')
const loading = ref(false)
const allManagers = ref<any[]>([])
const visits = ref<any[]>([])
const workPlans = ref<any[]>([])
@@ -39,6 +42,9 @@ const keyStatuses = ['未开始', '进行中', '已完成']
onMounted(async () => {
await Promise.all([loadAll(), loadCustomers(), loadUsers()])
if (auth.isDirector || auth.isLeader) {
api.get('/users/', { params: { role: 'manager' } }).then(r => { allManagers.value = r.data }).catch(() => {})
}
})
async function loadUsers() {
@@ -117,11 +123,11 @@ async function loadAll() {
function openCreate(type: string) {
dialogMode.value = 'create'; dialogType.value = type
dialogTimeRange.value = null
if (type === 'visit') form.value = { customer_id: '', visit_date: todayStr(), visit_method: '上门', time_range: '', visitor_name: '', visitor_phone: '', communication_content: '', customer_demand: '', companions: [], companion_names: [] }
else if (type === 'note') form.value = { note_date: todayStr(), category: '其他', content: '', time_range: '' }
else if (type === 'plan') form.value = { customer_id: '', plan_content: '', plan_date: todayStr(), status: '计划中' }
else if (type === 'mini') form.value = { customer_id: '', product_type: '', amount: '', follow_up_detail: '', status: '跟进中', expected_revenue_date: '' }
else if (type === 'key') { form.value = { customer_id: '', urgency_level: '一般', description: '', progress_status: '未开始', planned_date: '', planned_visitor: '', visit_target: '' }; plannedVisitors.value = [] }
if (type === 'visit') form.value = { customer_id: '', visit_date: todayStr(), visit_method: '上门', time_range: '', visitor_name: '', visitor_phone: '', communication_content: '', customer_demand: '', companions: [], companion_names: [], manager_id: auth.userId || '' }
else if (type === 'note') form.value = { note_date: todayStr(), category: '其他', content: '', time_range: '', manager_id: auth.userId || '' }
else if (type === 'plan') form.value = { customer_id: '', plan_content: '', plan_date: todayStr(), status: '计划中', manager_id: auth.userId || '' }
else if (type === 'mini') form.value = { customer_id: '', product_type: '', amount: '', follow_up_detail: '', status: '跟进中', expected_revenue_date: '', manager_id: auth.userId || '' }
else if (type === 'key') { form.value = { customer_id: '', urgency_level: '一般', description: '', progress_status: '未开始', planned_date: '', planned_visitor: '', visit_target: '', manager_id: auth.userId || '' }; plannedVisitors.value = [] }
dialogVisible.value = true
}
@@ -480,6 +486,11 @@ const notesByDate = computed(() => {
</template>
</el-select>
</el-form-item>
<el-form-item v-if="(auth.isDirector || auth.isLeader) && ['visit','plan','mini','key','note'].includes(dialogType)" label="客户经理">
<el-select v-model="form.manager_id" filterable placeholder="选择客户经理" style="width:100%">
<el-option v-for="m in allManagers" :key="m.id" :label="m.name" :value="m.id" />
</el-select>
</el-form-item>
<el-form-item v-if="dialogType === 'visit'" label="日期"><el-date-picker v-model="form.visit_date" type="date" style="width:100%" /></el-form-item>
<el-form-item v-if="dialogType === 'note'" label="日期"><el-date-picker v-model="form.note_date" type="date" style="width:100%" /></el-form-item>
<el-form-item v-if="dialogType === 'plan'" label="计划拜访时间"><el-date-picker v-model="form.plan_date" type="date" style="width:100%" /></el-form-item>