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:
@@ -80,7 +80,7 @@ async def create_note(
|
|||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
):
|
):
|
||||||
note = DailyNote(
|
note = DailyNote(
|
||||||
manager_id=uuid.UUID(current_user["user_id"]),
|
manager_id=data.manager_id if (current_user["role"] in ("director", "leader") and data.manager_id) else uuid.UUID(current_user["user_id"]),
|
||||||
note_date=parse_date(data.note_date),
|
note_date=parse_date(data.note_date),
|
||||||
category=data.category,
|
category=data.category,
|
||||||
content=data.content,
|
content=data.content,
|
||||||
@@ -108,6 +108,8 @@ async def update_note(
|
|||||||
|
|
||||||
old_snapshot = {"note_date": str(note.note_date), "category": note.category, "content": note.content, "time_range": note.time_range}
|
old_snapshot = {"note_date": str(note.note_date), "category": note.category, "content": note.content, "time_range": note.time_range}
|
||||||
update_data = data.model_dump(exclude_unset=True)
|
update_data = data.model_dump(exclude_unset=True)
|
||||||
|
if current_user["role"] not in ("director", "leader"):
|
||||||
|
update_data.pop("manager_id", None)
|
||||||
if "note_date" in update_data and update_data["note_date"]:
|
if "note_date" in update_data and update_data["note_date"]:
|
||||||
update_data["note_date"] = parse_date(update_data["note_date"])
|
update_data["note_date"] = parse_date(update_data["note_date"])
|
||||||
for k, v in update_data.items():
|
for k, v in update_data.items():
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ async def create_key_visit(
|
|||||||
planned_date=data.planned_date,
|
planned_date=data.planned_date,
|
||||||
planned_visitor=data.planned_visitor,
|
planned_visitor=data.planned_visitor,
|
||||||
visit_target=data.visit_target,
|
visit_target=data.visit_target,
|
||||||
manager_id=uuid.UUID(current_user["user_id"]),
|
manager_id=data.manager_id if (current_user["role"] in ("director", "leader") and data.manager_id) else uuid.UUID(current_user["user_id"]),
|
||||||
)
|
)
|
||||||
init_entry(k, current_user["name"])
|
init_entry(k, current_user["name"])
|
||||||
db.add(k)
|
db.add(k)
|
||||||
@@ -87,6 +87,8 @@ async def update_key_visit(
|
|||||||
|
|
||||||
old_snapshot = {"customer_id": str(k.customer_id), "urgency_level": k.urgency_level, "description": k.description, "progress_status": k.progress_status, "planned_date": k.planned_date, "planned_visitor": k.planned_visitor, "visit_target": k.visit_target}
|
old_snapshot = {"customer_id": str(k.customer_id), "urgency_level": k.urgency_level, "description": k.description, "progress_status": k.progress_status, "planned_date": k.planned_date, "planned_visitor": k.planned_visitor, "visit_target": k.visit_target}
|
||||||
update_data = data.model_dump(exclude_unset=True)
|
update_data = data.model_dump(exclude_unset=True)
|
||||||
|
if current_user["role"] not in ("director", "leader"):
|
||||||
|
update_data.pop("manager_id", None)
|
||||||
for key, v in update_data.items():
|
for key, v in update_data.items():
|
||||||
setattr(k, key, v)
|
setattr(k, key, v)
|
||||||
new_snapshot = {"customer_id": str(k.customer_id), "urgency_level": k.urgency_level, "description": k.description, "progress_status": k.progress_status, "planned_date": k.planned_date, "planned_visitor": k.planned_visitor, "visit_target": k.visit_target}
|
new_snapshot = {"customer_id": str(k.customer_id), "urgency_level": k.urgency_level, "description": k.description, "progress_status": k.progress_status, "planned_date": k.planned_date, "planned_visitor": k.planned_visitor, "visit_target": k.visit_target}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ async def create_mini_business(
|
|||||||
amount=data.amount,
|
amount=data.amount,
|
||||||
follow_up_detail=data.follow_up_detail,
|
follow_up_detail=data.follow_up_detail,
|
||||||
status=data.status,
|
status=data.status,
|
||||||
manager_id=uuid.UUID(current_user["user_id"]),
|
manager_id=data.manager_id if (current_user["role"] in ("director", "leader") and data.manager_id) else uuid.UUID(current_user["user_id"]),
|
||||||
expected_revenue_date=data.expected_revenue_date,
|
expected_revenue_date=data.expected_revenue_date,
|
||||||
)
|
)
|
||||||
init_entry(m, current_user["name"])
|
init_entry(m, current_user["name"])
|
||||||
@@ -85,6 +85,8 @@ async def update_mini_business(
|
|||||||
|
|
||||||
old_snapshot = {"customer_id": str(m.customer_id), "product_type": m.product_type, "amount": m.amount, "follow_up_detail": m.follow_up_detail, "status": m.status, "expected_revenue_date": m.expected_revenue_date}
|
old_snapshot = {"customer_id": str(m.customer_id), "product_type": m.product_type, "amount": m.amount, "follow_up_detail": m.follow_up_detail, "status": m.status, "expected_revenue_date": m.expected_revenue_date}
|
||||||
update_data = data.model_dump(exclude_unset=True)
|
update_data = data.model_dump(exclude_unset=True)
|
||||||
|
if current_user["role"] not in ("director", "leader"):
|
||||||
|
update_data.pop("manager_id", None)
|
||||||
for k, v in update_data.items():
|
for k, v in update_data.items():
|
||||||
setattr(m, k, v)
|
setattr(m, k, v)
|
||||||
new_snapshot = {"customer_id": str(m.customer_id), "product_type": m.product_type, "amount": m.amount, "follow_up_detail": m.follow_up_detail, "status": m.status, "expected_revenue_date": m.expected_revenue_date}
|
new_snapshot = {"customer_id": str(m.customer_id), "product_type": m.product_type, "amount": m.amount, "follow_up_detail": m.follow_up_detail, "status": m.status, "expected_revenue_date": m.expected_revenue_date}
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ async def create_visit(
|
|||||||
companions=data.companions,
|
companions=data.companions,
|
||||||
companion_names=data.companion_names,
|
companion_names=data.companion_names,
|
||||||
photos=data.photos,
|
photos=data.photos,
|
||||||
manager_id=uuid.UUID(current_user["user_id"]),
|
manager_id=data.manager_id if (current_user["role"] in ("director", "leader") and data.manager_id) else uuid.UUID(current_user["user_id"]),
|
||||||
)
|
)
|
||||||
init_entry(visit, current_user["name"])
|
init_entry(visit, current_user["name"])
|
||||||
db.add(visit)
|
db.add(visit)
|
||||||
@@ -212,6 +212,8 @@ async def update_visit(
|
|||||||
}
|
}
|
||||||
|
|
||||||
update_data = data.model_dump(exclude_unset=True)
|
update_data = data.model_dump(exclude_unset=True)
|
||||||
|
if current_user["role"] not in ("director", "leader"):
|
||||||
|
update_data.pop("manager_id", None)
|
||||||
if "visit_date" in update_data and update_data["visit_date"]:
|
if "visit_date" in update_data and update_data["visit_date"]:
|
||||||
update_data["visit_date"] = parse_date(update_data["visit_date"])
|
update_data["visit_date"] = parse_date(update_data["visit_date"])
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ async def create_work_plan(
|
|||||||
customer_id=data.customer_id,
|
customer_id=data.customer_id,
|
||||||
plan_content=data.plan_content,
|
plan_content=data.plan_content,
|
||||||
plan_date=parse_date(data.plan_date),
|
plan_date=parse_date(data.plan_date),
|
||||||
manager_id=uuid.UUID(current_user["user_id"]),
|
manager_id=data.manager_id if (current_user["role"] in ("director", "leader") and data.manager_id) else uuid.UUID(current_user["user_id"]),
|
||||||
status=data.status,
|
status=data.status,
|
||||||
)
|
)
|
||||||
init_entry(wp, current_user["name"])
|
init_entry(wp, current_user["name"])
|
||||||
@@ -83,6 +83,8 @@ async def update_work_plan(
|
|||||||
|
|
||||||
old_snapshot = {"customer_id": str(wp.customer_id), "plan_content": wp.plan_content, "plan_date": str(wp.plan_date), "status": wp.status}
|
old_snapshot = {"customer_id": str(wp.customer_id), "plan_content": wp.plan_content, "plan_date": str(wp.plan_date), "status": wp.status}
|
||||||
update_data = data.model_dump(exclude_unset=True)
|
update_data = data.model_dump(exclude_unset=True)
|
||||||
|
if current_user["role"] not in ("director", "leader"):
|
||||||
|
update_data.pop("manager_id", None)
|
||||||
if "plan_date" in update_data and update_data["plan_date"]:
|
if "plan_date" in update_data and update_data["plan_date"]:
|
||||||
update_data["plan_date"] = parse_date(update_data["plan_date"])
|
update_data["plan_date"] = parse_date(update_data["plan_date"])
|
||||||
for k, v in update_data.items():
|
for k, v in update_data.items():
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class DailyNoteCreate(BaseModel):
|
|||||||
category: str = "其他"
|
category: str = "其他"
|
||||||
content: str = ""
|
content: str = ""
|
||||||
time_range: str = ""
|
time_range: str = ""
|
||||||
|
manager_id: Optional[uuid.UUID] = None
|
||||||
|
|
||||||
|
|
||||||
class DailyNoteUpdate(BaseModel):
|
class DailyNoteUpdate(BaseModel):
|
||||||
@@ -16,6 +17,7 @@ class DailyNoteUpdate(BaseModel):
|
|||||||
category: Optional[str] = None
|
category: Optional[str] = None
|
||||||
content: Optional[str] = None
|
content: Optional[str] = None
|
||||||
time_range: Optional[str] = None
|
time_range: Optional[str] = None
|
||||||
|
manager_id: Optional[uuid.UUID] = None
|
||||||
|
|
||||||
|
|
||||||
class DailyNoteOut(BaseModel):
|
class DailyNoteOut(BaseModel):
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ class KeyVisitCreate(BaseModel):
|
|||||||
planned_date: str = ""
|
planned_date: str = ""
|
||||||
planned_visitor: str = ""
|
planned_visitor: str = ""
|
||||||
visit_target: str = ""
|
visit_target: str = ""
|
||||||
|
manager_id: Optional[uuid.UUID] = None
|
||||||
|
|
||||||
|
|
||||||
class KeyVisitUpdate(BaseModel):
|
class KeyVisitUpdate(BaseModel):
|
||||||
@@ -21,6 +22,7 @@ class KeyVisitUpdate(BaseModel):
|
|||||||
planned_date: Optional[str] = None
|
planned_date: Optional[str] = None
|
||||||
planned_visitor: Optional[str] = None
|
planned_visitor: Optional[str] = None
|
||||||
visit_target: Optional[str] = None
|
visit_target: Optional[str] = None
|
||||||
|
manager_id: Optional[uuid.UUID] = None
|
||||||
|
|
||||||
|
|
||||||
class KeyVisitOut(BaseModel):
|
class KeyVisitOut(BaseModel):
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ class MiniBusinessCreate(BaseModel):
|
|||||||
follow_up_detail: str = ""
|
follow_up_detail: str = ""
|
||||||
status: str = "跟进中"
|
status: str = "跟进中"
|
||||||
expected_revenue_date: str = ""
|
expected_revenue_date: str = ""
|
||||||
|
manager_id: Optional[uuid.UUID] = None
|
||||||
|
|
||||||
|
|
||||||
class MiniBusinessUpdate(BaseModel):
|
class MiniBusinessUpdate(BaseModel):
|
||||||
@@ -19,6 +20,7 @@ class MiniBusinessUpdate(BaseModel):
|
|||||||
follow_up_detail: Optional[str] = None
|
follow_up_detail: Optional[str] = None
|
||||||
status: Optional[str] = None
|
status: Optional[str] = None
|
||||||
expected_revenue_date: Optional[str] = None
|
expected_revenue_date: Optional[str] = None
|
||||||
|
manager_id: Optional[uuid.UUID] = None
|
||||||
|
|
||||||
|
|
||||||
class MiniBusinessOut(BaseModel):
|
class MiniBusinessOut(BaseModel):
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class VisitCreate(BaseModel):
|
|||||||
companions: list[uuid.UUID] = []
|
companions: list[uuid.UUID] = []
|
||||||
companion_names: list[str] = []
|
companion_names: list[str] = []
|
||||||
photos: list[str] = []
|
photos: list[str] = []
|
||||||
|
manager_id: Optional[uuid.UUID] = None # director/leader can specify
|
||||||
|
|
||||||
|
|
||||||
class VisitUpdate(BaseModel):
|
class VisitUpdate(BaseModel):
|
||||||
@@ -28,6 +29,7 @@ class VisitUpdate(BaseModel):
|
|||||||
communication_content: Optional[str] = None
|
communication_content: Optional[str] = None
|
||||||
customer_demand: Optional[str] = None
|
customer_demand: Optional[str] = None
|
||||||
companions: Optional[list[uuid.UUID]] = None
|
companions: Optional[list[uuid.UUID]] = None
|
||||||
|
manager_id: Optional[uuid.UUID] = None
|
||||||
companion_names: Optional[list[str]] = None
|
companion_names: Optional[list[str]] = None
|
||||||
photos: Optional[list[str]] = None
|
photos: Optional[list[str]] = None
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class WorkPlanCreate(BaseModel):
|
|||||||
plan_content: str = ""
|
plan_content: str = ""
|
||||||
plan_date: str # "YYYY-MM-DD"
|
plan_date: str # "YYYY-MM-DD"
|
||||||
status: str = "计划中"
|
status: str = "计划中"
|
||||||
|
manager_id: Optional[uuid.UUID] = None
|
||||||
|
|
||||||
|
|
||||||
class WorkPlanUpdate(BaseModel):
|
class WorkPlanUpdate(BaseModel):
|
||||||
@@ -16,6 +17,7 @@ class WorkPlanUpdate(BaseModel):
|
|||||||
plan_content: Optional[str] = None
|
plan_content: Optional[str] = None
|
||||||
plan_date: Optional[str] = None
|
plan_date: Optional[str] = None
|
||||||
status: Optional[str] = None
|
status: Optional[str] = None
|
||||||
|
manager_id: Optional[uuid.UUID] = None
|
||||||
|
|
||||||
|
|
||||||
class WorkPlanOut(BaseModel):
|
class WorkPlanOut(BaseModel):
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const loading = ref(false)
|
|||||||
const isLight = computed(() => themeStore.currentTheme === 'light')
|
const isLight = computed(() => themeStore.currentTheme === 'light')
|
||||||
const keyVisits = ref<any[]>([])
|
const keyVisits = ref<any[]>([])
|
||||||
const customers = ref<any[]>([])
|
const customers = ref<any[]>([])
|
||||||
|
const allManagers = ref<any[]>([])
|
||||||
const shotRef = ref<HTMLElement | null>(null)
|
const shotRef = ref<HTMLElement | null>(null)
|
||||||
const { capturing, captureEl } = useScreenshot()
|
const { capturing, captureEl } = useScreenshot()
|
||||||
const allUsers = ref<any[]>([])
|
const allUsers = ref<any[]>([])
|
||||||
@@ -79,6 +80,9 @@ const managerSummary = computed(() => {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await Promise.all([loadItems(), loadCustomers(), loadUsers(), loadManagerColors()])
|
await Promise.all([loadItems(), loadCustomers(), loadUsers(), loadManagerColors()])
|
||||||
|
if (auth.isDirector || auth.isLeader) {
|
||||||
|
api.get('/users/', { params: { role: 'manager' } }).then(r => { allManagers.value = r.data }).catch(() => {})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
async function loadUsers() {
|
async function loadUsers() {
|
||||||
@@ -132,7 +136,7 @@ async function loadItems() {
|
|||||||
|
|
||||||
function openCreate() {
|
function openCreate() {
|
||||||
dialogMode.value = 'create'
|
dialogMode.value = 'create'
|
||||||
form.value = { customer_id: '', urgency_level: '一般', description: '', progress_status: '未开始', planned_date: '', planned_visitor: '', visit_target: '' }
|
form.value = { customer_id: '', urgency_level: '一般', description: '', progress_status: '未开始', planned_date: '', planned_visitor: '', visit_target: '', manager_id: auth.userId || '' }
|
||||||
plannedVisitors.value = []
|
plannedVisitors.value = []
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
}
|
}
|
||||||
@@ -304,6 +308,11 @@ async function quickStatusChange(row: any, newStatus: string) {
|
|||||||
</template>
|
</template>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item v-if="auth.isDirector || auth.isLeader" 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 label="紧急重要度">
|
<el-form-item label="紧急重要度">
|
||||||
<div class="method-grid">
|
<div class="method-grid">
|
||||||
<el-button v-for="u in ['一般','重要','紧急']" :key="u" size="small"
|
<el-button v-for="u in ['一般','重要','紧急']" :key="u" size="small"
|
||||||
|
|||||||
@@ -6,9 +6,12 @@ import api from '@/api/index'
|
|||||||
import { compressImage } from '@/utils/image'
|
import { compressImage } from '@/utils/image'
|
||||||
import ImagePreview from '@/components/ImagePreview.vue'
|
import ImagePreview from '@/components/ImagePreview.vue'
|
||||||
import EditLogPanel from '@/components/EditLogPanel.vue'
|
import EditLogPanel from '@/components/EditLogPanel.vue'
|
||||||
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
|
||||||
|
const auth = useAuthStore()
|
||||||
const activeTab = ref('visits')
|
const activeTab = ref('visits')
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
const allManagers = ref<any[]>([])
|
||||||
|
|
||||||
const visits = ref<any[]>([])
|
const visits = ref<any[]>([])
|
||||||
const workPlans = ref<any[]>([])
|
const workPlans = ref<any[]>([])
|
||||||
@@ -39,6 +42,9 @@ const keyStatuses = ['未开始', '进行中', '已完成']
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await Promise.all([loadAll(), loadCustomers(), loadUsers()])
|
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() {
|
async function loadUsers() {
|
||||||
@@ -117,11 +123,11 @@ async function loadAll() {
|
|||||||
function openCreate(type: string) {
|
function openCreate(type: string) {
|
||||||
dialogMode.value = 'create'; dialogType.value = type
|
dialogMode.value = 'create'; dialogType.value = type
|
||||||
dialogTimeRange.value = null
|
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: [] }
|
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: '' }
|
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: '计划中' }
|
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: '' }
|
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: '' }; plannedVisitors.value = [] }
|
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
|
dialogVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,6 +486,11 @@ const notesByDate = computed(() => {
|
|||||||
</template>
|
</template>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</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 === '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 === '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>
|
<el-form-item v-if="dialogType === 'plan'" label="计划拜访时间"><el-date-picker v-model="form.plan_date" type="date" style="width:100%" /></el-form-item>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const loading = ref(false)
|
|||||||
const isLight = computed(() => themeStore.currentTheme === 'light')
|
const isLight = computed(() => themeStore.currentTheme === 'light')
|
||||||
const miniBusiness = ref<any[]>([])
|
const miniBusiness = ref<any[]>([])
|
||||||
const customers = ref<any[]>([])
|
const customers = ref<any[]>([])
|
||||||
|
const allManagers = ref<any[]>([])
|
||||||
const shotRef = ref<HTMLElement | null>(null)
|
const shotRef = ref<HTMLElement | null>(null)
|
||||||
const { capturing, captureEl } = useScreenshot()
|
const { capturing, captureEl } = useScreenshot()
|
||||||
|
|
||||||
@@ -64,6 +65,9 @@ const managerSummary = computed(() => {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await Promise.all([loadItems(), loadCustomers(), loadManagerColors()])
|
await Promise.all([loadItems(), loadCustomers(), loadManagerColors()])
|
||||||
|
if (auth.isDirector || auth.isLeader) {
|
||||||
|
api.get('/users/', { params: { role: 'manager' } }).then(r => { allManagers.value = r.data }).catch(() => {})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const customerSearch = ref('')
|
const customerSearch = ref('')
|
||||||
@@ -110,7 +114,7 @@ async function loadItems() {
|
|||||||
|
|
||||||
function openCreate() {
|
function openCreate() {
|
||||||
dialogMode.value = 'create'
|
dialogMode.value = 'create'
|
||||||
form.value = { customer_id: '', product_type: '', amount: '', follow_up_detail: '', status: '跟进中', expected_revenue_date: '' }
|
form.value = { customer_id: '', product_type: '', amount: '', follow_up_detail: '', status: '跟进中', expected_revenue_date: '', manager_id: auth.userId || '' }
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,6 +275,11 @@ async function quickStatusChange(row: any, newStatus: string) {
|
|||||||
</template>
|
</template>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item v-if="auth.isDirector || auth.isLeader" 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 label="产品类型"><el-input v-model="form.product_type" placeholder="如:云专线、SD-WAN" /></el-form-item>
|
<el-form-item label="产品类型"><el-input v-model="form.product_type" placeholder="如:云专线、SD-WAN" /></el-form-item>
|
||||||
<el-form-item label="金额"><el-input v-model="form.amount" placeholder="如:50000元/年" /></el-form-item>
|
<el-form-item label="金额"><el-input v-model="form.amount" placeholder="如:50000元/年" /></el-form-item>
|
||||||
<el-form-item label="跟进内容"><el-input v-model="form.follow_up_detail" type="textarea" :rows="4" placeholder="请输入跟进详情" /></el-form-item>
|
<el-form-item label="跟进内容"><el-input v-model="form.follow_up_detail" type="textarea" :rows="4" placeholder="请输入跟进详情" /></el-form-item>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const themeStore = useThemeStore()
|
|||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const workPlans = ref<any[]>([])
|
const workPlans = ref<any[]>([])
|
||||||
const customers = ref<any[]>([])
|
const customers = ref<any[]>([])
|
||||||
|
const allManagers = ref<any[]>([])
|
||||||
const shotRef = ref<HTMLElement | null>(null)
|
const shotRef = ref<HTMLElement | null>(null)
|
||||||
const { capturing, captureEl } = useScreenshot()
|
const { capturing, captureEl } = useScreenshot()
|
||||||
|
|
||||||
@@ -65,6 +66,9 @@ const managerSummary = computed(() => {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await Promise.all([loadPlans(), loadCustomers(), loadManagerColors()])
|
await Promise.all([loadPlans(), loadCustomers(), loadManagerColors()])
|
||||||
|
if (auth.isDirector || auth.isLeader) {
|
||||||
|
api.get('/users/', { params: { role: 'manager' } }).then(r => { allManagers.value = r.data }).catch(() => {})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const customerSearch = ref('')
|
const customerSearch = ref('')
|
||||||
@@ -111,7 +115,7 @@ async function loadPlans() {
|
|||||||
|
|
||||||
function openCreate() {
|
function openCreate() {
|
||||||
dialogMode.value = 'create'
|
dialogMode.value = 'create'
|
||||||
form.value = { customer_id: '', plan_content: '', plan_date: todayStr(), status: '计划中' }
|
form.value = { customer_id: '', plan_content: '', plan_date: todayStr(), status: '计划中', manager_id: auth.userId || '' }
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,6 +274,11 @@ async function quickStatusChange(row: any, newStatus: string) {
|
|||||||
</template>
|
</template>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item v-if="auth.isDirector || auth.isLeader" 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 label="计划拜访时间">
|
<el-form-item label="计划拜访时间">
|
||||||
<el-date-picker v-model="form.plan_date" type="date" style="width:100%" />
|
<el-date-picker v-model="form.plan_date" type="date" style="width:100%" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|||||||
@@ -3,13 +3,16 @@ import { ref, onMounted, computed } from 'vue'
|
|||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { todayStr } from '@/utils'
|
import { todayStr } from '@/utils'
|
||||||
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import api from '@/api/index'
|
import api from '@/api/index'
|
||||||
import EditLogPanel from '@/components/EditLogPanel.vue'
|
import EditLogPanel from '@/components/EditLogPanel.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
const auth = useAuthStore()
|
||||||
const isEdit = computed(() => !!route.params.id)
|
const isEdit = computed(() => !!route.params.id)
|
||||||
const submitLoading = ref(false)
|
const submitLoading = ref(false)
|
||||||
|
const allManagers = ref<any[]>([])
|
||||||
|
|
||||||
const categories = ['行政事务', '合同整理', '发票处理', '内部会议', '培训学习', '其他']
|
const categories = ['行政事务', '合同整理', '发票处理', '内部会议', '培训学习', '其他']
|
||||||
|
|
||||||
@@ -24,6 +27,7 @@ const form = ref({
|
|||||||
category: '其他',
|
category: '其他',
|
||||||
content: '',
|
content: '',
|
||||||
time_range: '',
|
time_range: '',
|
||||||
|
manager_id: auth.userId || '',
|
||||||
})
|
})
|
||||||
|
|
||||||
function onTimeRangeChange(val: [string, string] | null) {
|
function onTimeRangeChange(val: [string, string] | null) {
|
||||||
@@ -36,6 +40,9 @@ function parseTimeRange(str: string): [string, string] | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
if (auth.isDirector || auth.isLeader) {
|
||||||
|
try { const res = await api.get('/users/', { params: { role: 'manager' } }); allManagers.value = res.data } catch (_) {}
|
||||||
|
}
|
||||||
if (isEdit.value) {
|
if (isEdit.value) {
|
||||||
try {
|
try {
|
||||||
const res = await api.get(`/daily-notes/${route.params.id}`)
|
const res = await api.get(`/daily-notes/${route.params.id}`)
|
||||||
@@ -45,6 +52,7 @@ onMounted(async () => {
|
|||||||
category: n.category,
|
category: n.category,
|
||||||
content: n.content || '',
|
content: n.content || '',
|
||||||
time_range: n.time_range || '',
|
time_range: n.time_range || '',
|
||||||
|
manager_id: n.manager_id || auth.userId || '',
|
||||||
}
|
}
|
||||||
timeRangeValue.value = parseTimeRange(n.time_range)
|
timeRangeValue.value = parseTimeRange(n.time_range)
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
@@ -105,6 +113,13 @@ async function handleDelete() {
|
|||||||
<el-date-picker v-model="form.note_date" type="date" style="width:100%" />
|
<el-date-picker v-model="form.note_date" type="date" style="width:100%" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item v-if="auth.isDirector || auth.isLeader">
|
||||||
|
<template #label><span class="form-label">客户经理</span></template>
|
||||||
|
<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>
|
<el-form-item>
|
||||||
<template #label>
|
<template #label>
|
||||||
<span class="form-label">分类</span>
|
<span class="form-label">分类</span>
|
||||||
|
|||||||
@@ -4,12 +4,15 @@ import { useRouter } from 'vue-router'
|
|||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { keyVisitsApi } from '@/api/keyVisits'
|
import { keyVisitsApi } from '@/api/keyVisits'
|
||||||
import { customersApi } from '@/api/customers'
|
import { customersApi } from '@/api/customers'
|
||||||
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import api from '@/api/index'
|
import api from '@/api/index'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const auth = useAuthStore()
|
||||||
const submitLoading = ref(false)
|
const submitLoading = ref(false)
|
||||||
const customers = ref<any[]>([])
|
const customers = ref<any[]>([])
|
||||||
const allUsers = ref<any[]>([])
|
const allUsers = ref<any[]>([])
|
||||||
|
const allManagers = ref<any[]>([])
|
||||||
|
|
||||||
const urgencyLevels = ['一般', '重要', '紧急']
|
const urgencyLevels = ['一般', '重要', '紧急']
|
||||||
const urgencyColors: Record<string, string> = {
|
const urgencyColors: Record<string, string> = {
|
||||||
@@ -25,6 +28,7 @@ const form = ref({
|
|||||||
planned_date: '',
|
planned_date: '',
|
||||||
planned_visitor: '',
|
planned_visitor: '',
|
||||||
visit_target: '',
|
visit_target: '',
|
||||||
|
manager_id: auth.userId || '',
|
||||||
})
|
})
|
||||||
|
|
||||||
function onVisitorsChange(val: string[]) {
|
function onVisitorsChange(val: string[]) {
|
||||||
@@ -33,6 +37,9 @@ function onVisitorsChange(val: string[]) {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await Promise.all([loadCustomers(), loadUsers()])
|
await Promise.all([loadCustomers(), loadUsers()])
|
||||||
|
if (auth.isDirector || auth.isLeader) {
|
||||||
|
api.get('/users/', { params: { role: 'manager' } }).then(r => { allManagers.value = r.data }).catch(() => {})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
async function loadCustomers(q?: string) {
|
async function loadCustomers(q?: string) {
|
||||||
@@ -88,6 +95,13 @@ async function handleSubmit() {
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item v-if="auth.isDirector || auth.isLeader">
|
||||||
|
<template #label><span class="form-label">客户经理</span></template>
|
||||||
|
<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>
|
<el-form-item>
|
||||||
<template #label><span class="form-label">紧急重要度</span></template>
|
<template #label><span class="form-label">紧急重要度</span></template>
|
||||||
<div class="urgency-grid">
|
<div class="urgency-grid">
|
||||||
|
|||||||
@@ -4,10 +4,14 @@ import { useRouter } from 'vue-router'
|
|||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { miniBusinessApi } from '@/api/miniBusiness'
|
import { miniBusinessApi } from '@/api/miniBusiness'
|
||||||
import { customersApi } from '@/api/customers'
|
import { customersApi } from '@/api/customers'
|
||||||
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
import api from '@/api/index'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const auth = useAuthStore()
|
||||||
const submitLoading = ref(false)
|
const submitLoading = ref(false)
|
||||||
const customers = ref<any[]>([])
|
const customers = ref<any[]>([])
|
||||||
|
const allManagers = ref<any[]>([])
|
||||||
|
|
||||||
const form = ref({
|
const form = ref({
|
||||||
customer_id: '',
|
customer_id: '',
|
||||||
@@ -16,9 +20,15 @@ const form = ref({
|
|||||||
follow_up_detail: '',
|
follow_up_detail: '',
|
||||||
status: '跟进中',
|
status: '跟进中',
|
||||||
expected_revenue_date: '',
|
expected_revenue_date: '',
|
||||||
|
manager_id: auth.userId || '',
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => { loadCustomers() })
|
onMounted(() => {
|
||||||
|
loadCustomers()
|
||||||
|
if (auth.isDirector || auth.isLeader) {
|
||||||
|
api.get('/users/', { params: { role: 'manager' } }).then(r => { allManagers.value = r.data }).catch(() => {})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
async function loadCustomers(q?: string) {
|
async function loadCustomers(q?: string) {
|
||||||
try {
|
try {
|
||||||
@@ -66,6 +76,13 @@ async function handleSubmit() {
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item v-if="auth.isDirector || auth.isLeader">
|
||||||
|
<template #label><span class="form-label">客户经理</span></template>
|
||||||
|
<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>
|
<el-form-item>
|
||||||
<template #label><span class="form-label">产品类型</span></template>
|
<template #label><span class="form-label">产品类型</span></template>
|
||||||
<el-input v-model="form.product_type" placeholder="例如: 云桌面、专线..." />
|
<el-input v-model="form.product_type" placeholder="例如: 云桌面、专线..." />
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ const form = ref({
|
|||||||
customer_demand: '',
|
customer_demand: '',
|
||||||
companions: [] as string[],
|
companions: [] as string[],
|
||||||
photos: [] as string[],
|
photos: [] as string[],
|
||||||
|
manager_id: auth.userId || '',
|
||||||
})
|
})
|
||||||
|
|
||||||
const timeRangeValue = ref<any>(null)
|
const timeRangeValue = ref<any>(null)
|
||||||
@@ -67,6 +68,7 @@ onMounted(async () => {
|
|||||||
customer_demand: v.customer_demand || '',
|
customer_demand: v.customer_demand || '',
|
||||||
companions: (v.companions || []).map(String),
|
companions: (v.companions || []).map(String),
|
||||||
photos: v.photos || [],
|
photos: v.photos || [],
|
||||||
|
manager_id: v.manager_id || auth.userId || '',
|
||||||
}
|
}
|
||||||
uploadedPhotos.value = v.photos || []
|
uploadedPhotos.value = v.photos || []
|
||||||
// Parse time range back into picker
|
// Parse time range back into picker
|
||||||
@@ -250,6 +252,13 @@ async function handleDelete() {
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item v-if="auth.isDirector || auth.isLeader">
|
||||||
|
<template #label><span class="form-label">客户经理</span></template>
|
||||||
|
<el-select v-model="form.manager_id" filterable placeholder="选择客户经理" style="width:100%">
|
||||||
|
<el-option v-for="m in managers" :key="m.id" :label="m.name" :value="m.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<template #label>
|
<template #label>
|
||||||
<span class="form-label">拜访日期</span>
|
<span class="form-label">拜访日期</span>
|
||||||
|
|||||||
@@ -5,19 +5,29 @@ import { ElMessage } from 'element-plus'
|
|||||||
import { todayStr } from '@/utils'
|
import { todayStr } from '@/utils'
|
||||||
import { workPlansApi } from '@/api/workPlans'
|
import { workPlansApi } from '@/api/workPlans'
|
||||||
import { customersApi } from '@/api/customers'
|
import { customersApi } from '@/api/customers'
|
||||||
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
import api from '@/api/index'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const auth = useAuthStore()
|
||||||
const submitLoading = ref(false)
|
const submitLoading = ref(false)
|
||||||
const customers = ref<any[]>([])
|
const customers = ref<any[]>([])
|
||||||
|
const allManagers = ref<any[]>([])
|
||||||
|
|
||||||
const form = ref({
|
const form = ref({
|
||||||
customer_id: '',
|
customer_id: '',
|
||||||
plan_content: '',
|
plan_content: '',
|
||||||
plan_date: todayStr(),
|
plan_date: todayStr(),
|
||||||
status: '计划中',
|
status: '计划中',
|
||||||
|
manager_id: auth.userId || '',
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => { loadCustomers() })
|
onMounted(() => {
|
||||||
|
loadCustomers()
|
||||||
|
if (auth.isDirector || auth.isLeader) {
|
||||||
|
api.get('/users/', { params: { role: 'manager' } }).then(r => { allManagers.value = r.data }).catch(() => {})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
async function loadCustomers(q?: string) {
|
async function loadCustomers(q?: string) {
|
||||||
try {
|
try {
|
||||||
@@ -65,6 +75,13 @@ async function handleSubmit() {
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item v-if="auth.isDirector || auth.isLeader">
|
||||||
|
<template #label><span class="form-label">客户经理</span></template>
|
||||||
|
<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>
|
<el-form-item>
|
||||||
<template #label><span class="form-label">工作计划</span></template>
|
<template #label><span class="form-label">工作计划</span></template>
|
||||||
<el-input v-model="form.plan_content" type="textarea" :rows="4" placeholder="描述下周工作计划..." />
|
<el-input v-model="form.plan_content" type="textarea" :rows="4" placeholder="描述下周工作计划..." />
|
||||||
|
|||||||
Reference in New Issue
Block a user