|
@@ -122,12 +122,19 @@
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
<el-table-column label="项目角色" align="center" width="120">
|
|
<el-table-column label="项目角色" align="center" width="120">
|
|
|
<template #default="scope">
|
|
<template #default="scope">
|
|
|
- {{ scope.row.projectRole || scope.row.role || '' }}
|
|
|
|
|
|
|
+ <dict-tag :options="projectRoleOptions" :value="scope.row.projectRole" />
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
- <el-table-column label="是否关键人" align="center" width="110">
|
|
|
|
|
|
|
+ <el-table-column label="公关情况" align="center" width="150" show-overflow-tooltip>
|
|
|
<template #default="scope">
|
|
<template #default="scope">
|
|
|
- <span>{{ (scope.row.isKeyPerson || scope.row.keyPerson) ? '是' : '否' }}</span>
|
|
|
|
|
|
|
+ {{ scope.row.prStatus }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="是否关键人" align="center" width="100">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <el-tag :type="scope.row.isKeyPerson == 1 ? 'danger' : 'info'">
|
|
|
|
|
+ {{ scope.row.isKeyPerson == 1 ? '是' : '否' }}
|
|
|
|
|
+ </el-tag>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
<el-table-column label="操作" align="center" width="140" fixed="right">
|
|
<el-table-column label="操作" align="center" width="140" fixed="right">
|
|
@@ -270,212 +277,230 @@
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
|
|
|
- <!-- 编辑项目联系人抽屉 -->
|
|
|
|
|
- <el-drawer
|
|
|
|
|
- title="编辑项目联系人"
|
|
|
|
|
- v-model="editContactVisible"
|
|
|
|
|
- size="80%"
|
|
|
|
|
- append-to-body
|
|
|
|
|
- destroy-on-close
|
|
|
|
|
- class="edit-contact-drawer"
|
|
|
|
|
- >
|
|
|
|
|
- <el-form :model="editContactForm" ref="editContactRef" label-width="100px" size="default">
|
|
|
|
|
- <!-- 基本信息 -->
|
|
|
|
|
- <div class="form-section">
|
|
|
|
|
- <div class="form-section-title">基本信息</div>
|
|
|
|
|
- <el-row :gutter="20">
|
|
|
|
|
- <el-col :span="8">
|
|
|
|
|
- <el-form-item label="姓名" prop="contactName" required>
|
|
|
|
|
- <el-input v-model="editContactForm.contactName" placeholder="请输入姓名" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="8">
|
|
|
|
|
- <el-form-item label="联系人类型" prop="roleId" required>
|
|
|
|
|
- <el-radio-group v-model="editContactForm.roleId">
|
|
|
|
|
- <el-radio value="1">公司职员</el-radio>
|
|
|
|
|
- <el-radio value="2">关系资源人</el-radio>
|
|
|
|
|
- </el-radio-group>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="8">
|
|
|
|
|
- <el-form-item label="性别" prop="gender" required>
|
|
|
|
|
- <el-radio-group v-model="editContactForm.gender">
|
|
|
|
|
- <el-radio value="0">男</el-radio>
|
|
|
|
|
- <el-radio value="1">女</el-radio>
|
|
|
|
|
- </el-radio-group>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- </el-row>
|
|
|
|
|
- <el-row :gutter="20">
|
|
|
|
|
- <el-col :span="8">
|
|
|
|
|
- <el-form-item label="年龄" prop="age">
|
|
|
|
|
- <el-input-number v-model="editContactForm.age" :min="0" :max="150" style="width:100%" controls-position="right" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="8">
|
|
|
|
|
- <el-form-item label="籍贯" prop="nativePlace">
|
|
|
|
|
- <el-input v-model="editContactForm.nativePlace" placeholder="请输入籍贯" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="8">
|
|
|
|
|
- <el-form-item label="生日" prop="birthday">
|
|
|
|
|
- <el-date-picker v-model="editContactForm.birthday" type="date" placeholder="选择日期" style="width:100%" value-format="YYYY-MM-DD" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- </el-row>
|
|
|
|
|
- <el-row :gutter="20">
|
|
|
|
|
- <el-col :span="24">
|
|
|
|
|
- <el-form-item label="描述" prop="remark">
|
|
|
|
|
- <el-input v-model="editContactForm.remark" type="textarea" :rows="3" placeholder="请输入内容" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- </el-row>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <!-- 编辑/新建项目联系人抽屉 (完全对齐商机模块) -->
|
|
|
|
|
+ <el-drawer v-model="projectContactDrawerOpen" direction="rtl" size="80%" destroy-on-close :with-header="false">
|
|
|
|
|
+ <div class="drawer-header-compact" style="height: 40px; padding: 0 16px; border-bottom: 1px solid #e2e8f0; display: flex; justify-content: space-between; align-items: center; background: #fff;">
|
|
|
|
|
+ <span style="font-size: 15px; font-weight: 600; color: #333;">{{ projectContactForm.id ? '编辑项目联系人' : '新建项目联系人' }}</span>
|
|
|
|
|
+ <el-icon @click="projectContactDrawerOpen = false" style="cursor: pointer; font-size: 18px; color: #94a3b8;"><Close /></el-icon>
|
|
|
|
|
+ </div>
|
|
|
|
|
|
|
|
- <!-- 办公信息 -->
|
|
|
|
|
- <div class="form-section">
|
|
|
|
|
- <div class="form-section-title">办公信息</div>
|
|
|
|
|
- <el-row :gutter="20">
|
|
|
|
|
- <el-col :span="8">
|
|
|
|
|
- <el-form-item label="在职状态" prop="status" required>
|
|
|
|
|
- <el-radio-group v-model="editContactForm.status">
|
|
|
|
|
- <el-radio value="0">在职</el-radio>
|
|
|
|
|
- <el-radio value="1">离职</el-radio>
|
|
|
|
|
- </el-radio-group>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="8">
|
|
|
|
|
- <el-form-item label="手机号码" prop="phone" required>
|
|
|
|
|
- <el-input v-model="editContactForm.phone" placeholder="请输入手机号码" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="8">
|
|
|
|
|
- <el-form-item label="部门" prop="deptName" required>
|
|
|
|
|
- <el-input v-model="editContactForm.deptName" placeholder="请输入部门" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- </el-row>
|
|
|
|
|
- <el-row :gutter="20">
|
|
|
|
|
- <el-col :span="8">
|
|
|
|
|
- <el-form-item label="职位" prop="roleName" required>
|
|
|
|
|
- <el-input v-model="editContactForm.roleName" placeholder="请输入职位" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="8">
|
|
|
|
|
- <el-form-item label="办公座机" prop="officePhone">
|
|
|
|
|
- <el-input v-model="editContactForm.officePhone" placeholder="请输入办公座机" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="8">
|
|
|
|
|
- </el-col>
|
|
|
|
|
- </el-row>
|
|
|
|
|
- <el-row :gutter="20">
|
|
|
|
|
- <el-col :span="24">
|
|
|
|
|
- <el-form-item label="办公地址" prop="addressDetail">
|
|
|
|
|
- <el-input v-model="editContactForm.addressDetail" placeholder="请输入详细办公地址" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- </el-row>
|
|
|
|
|
- <el-row :gutter="20">
|
|
|
|
|
- <el-col :span="24">
|
|
|
|
|
- <el-form-item label="工作内容" prop="workContent">
|
|
|
|
|
- <el-input v-model="editContactForm.workContent" type="textarea" :rows="3" placeholder="请输入工作内容" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- </el-row>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <div class="drawer-body-custom" style="padding: 0; overflow-y: auto; height: calc(100% - 110px);">
|
|
|
|
|
+ <el-form :model="projectContactForm" :rules="projectContactRules" ref="projectContactFormRef" label-width="100px" class="no-bold-label">
|
|
|
|
|
+ <!-- 基本信息 -->
|
|
|
|
|
+ <div class="form-section-group">
|
|
|
|
|
+ <div class="section-group-title">基本信息</div>
|
|
|
|
|
+ <div class="section-group-content">
|
|
|
|
|
+ <el-row :gutter="20">
|
|
|
|
|
+ <el-col :span="8">
|
|
|
|
|
+ <el-form-item label="姓名" prop="contactName">
|
|
|
|
|
+ <el-input v-model="projectContactForm.contactName" placeholder="请输入" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="8">
|
|
|
|
|
+ <el-form-item label="联系人类型" prop="type">
|
|
|
|
|
+ <el-radio-group v-model="projectContactForm.type">
|
|
|
|
|
+ <el-radio value="1">公司职员</el-radio>
|
|
|
|
|
+ <el-radio value="2">关系资源人</el-radio>
|
|
|
|
|
+ </el-radio-group>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="8">
|
|
|
|
|
+ <el-form-item label="性别" prop="gender">
|
|
|
|
|
+ <el-radio-group v-model="projectContactForm.gender">
|
|
|
|
|
+ <el-radio value="0">男</el-radio>
|
|
|
|
|
+ <el-radio value="1">女</el-radio>
|
|
|
|
|
+ </el-radio-group>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+ <el-row :gutter="20">
|
|
|
|
|
+ <el-col :span="8">
|
|
|
|
|
+ <el-form-item label="年龄" prop="age">
|
|
|
|
|
+ <el-input v-model="projectContactForm.age" placeholder="请输入" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="8">
|
|
|
|
|
+ <el-form-item label="籍贯" prop="nativePlace">
|
|
|
|
|
+ <el-input v-model="projectContactForm.nativePlace" placeholder="请输入" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="8">
|
|
|
|
|
+ <el-form-item label="生日" prop="birthday">
|
|
|
|
|
+ <el-date-picker v-model="projectContactForm.birthday" type="date" placeholder="请选择" style="width: 100%" value-format="YYYY-MM-DD" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+ <el-row :gutter="20">
|
|
|
|
|
+ <el-col :span="24">
|
|
|
|
|
+ <el-form-item label="描述" prop="remark">
|
|
|
|
|
+ <el-input v-model="projectContactForm.remark" type="textarea" :rows="2" maxlength="200" show-word-limit placeholder="请输入" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
|
|
|
- <!-- 项目决策 -->
|
|
|
|
|
- <div class="form-section">
|
|
|
|
|
- <div class="form-section-title">项目决策</div>
|
|
|
|
|
- <el-row :gutter="20">
|
|
|
|
|
- <el-col :span="8">
|
|
|
|
|
- <el-form-item label="项目角色" prop="projectRole">
|
|
|
|
|
- <el-select v-model="editContactForm.projectRole" placeholder="请选择" style="width:100%" clearable>
|
|
|
|
|
- <el-option
|
|
|
|
|
- v-for="dict in projectRoleOptions"
|
|
|
|
|
- :key="dict.value"
|
|
|
|
|
- :label="dict.label"
|
|
|
|
|
- :value="dict.value"
|
|
|
|
|
- />
|
|
|
|
|
- </el-select>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="8">
|
|
|
|
|
- <el-form-item label="是否关键人" prop="isKeyPerson">
|
|
|
|
|
- <el-select v-model="editContactForm.isKeyPerson" placeholder="请选择" style="width:100%">
|
|
|
|
|
- <el-option label="是" :value="1" />
|
|
|
|
|
- <el-option label="否" :value="0" />
|
|
|
|
|
- </el-select>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="8">
|
|
|
|
|
- </el-col>
|
|
|
|
|
- </el-row>
|
|
|
|
|
- <el-row :gutter="20">
|
|
|
|
|
- <el-col :span="24">
|
|
|
|
|
- <el-form-item label="公关情况" prop="prStatus">
|
|
|
|
|
- <el-input v-model="editContactForm.prStatus" type="textarea" :rows="3" placeholder="请输入内容" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- </el-row>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <!-- 办公信息 -->
|
|
|
|
|
+ <div class="form-section-group">
|
|
|
|
|
+ <div class="section-group-title">办公信息</div>
|
|
|
|
|
+ <div class="section-group-content">
|
|
|
|
|
+ <el-row :gutter="20">
|
|
|
|
|
+ <el-col :span="8">
|
|
|
|
|
+ <el-form-item label="在职状态" prop="jobStatus">
|
|
|
|
|
+ <el-radio-group v-model="projectContactForm.jobStatus">
|
|
|
|
|
+ <el-radio value="1">在职</el-radio>
|
|
|
|
|
+ <el-radio value="2">离职</el-radio>
|
|
|
|
|
+ </el-radio-group>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="8">
|
|
|
|
|
+ <el-form-item label="手机号码" prop="phone">
|
|
|
|
|
+ <el-input v-model="projectContactForm.phone" placeholder="请输入" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="8">
|
|
|
|
|
+ <el-form-item label="部门" prop="deptName">
|
|
|
|
|
+ <el-input v-model="projectContactForm.deptName" placeholder="请输入" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+ <el-row :gutter="20">
|
|
|
|
|
+ <el-col :span="8">
|
|
|
|
|
+ <el-form-item label="职位" prop="position">
|
|
|
|
|
+ <el-input v-model="projectContactForm.position" placeholder="请输入" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="8">
|
|
|
|
|
+ <el-form-item label="办公座机" prop="officePhone">
|
|
|
|
|
+ <el-input v-model="projectContactForm.officePhone" placeholder="请输入" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+ <el-row :gutter="20">
|
|
|
|
|
+ <el-col :span="16">
|
|
|
|
|
+ <el-form-item label="办公地址" prop="addressDetail">
|
|
|
|
|
+ <div style="display: flex; gap: 10px; width: 100%">
|
|
|
|
|
+ <el-cascader
|
|
|
|
|
+ v-model="projectContactForm.officeRegion"
|
|
|
|
|
+ ref="officeRegionCascader"
|
|
|
|
|
+ :options="areaOptions"
|
|
|
|
|
+ :props="{ label: 'areaName', value: 'id', children: 'children' }"
|
|
|
|
|
+ placeholder="请选择省/市/县"
|
|
|
|
|
+ style="width: 220px"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-input v-model="projectContactForm.addressDetail" placeholder="请输入详细地址" style="flex: 1" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+ <el-row :gutter="20">
|
|
|
|
|
+ <el-col :span="24">
|
|
|
|
|
+ <el-form-item label="工作内容" prop="jobContent">
|
|
|
|
|
+ <el-input v-model="projectContactForm.jobContent" type="textarea" :rows="2" maxlength="200" show-word-limit placeholder="请输入" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
|
|
|
- <!-- 家庭信息 -->
|
|
|
|
|
- <div class="form-section">
|
|
|
|
|
- <div class="form-section-title">家庭信息</div>
|
|
|
|
|
- <el-row :gutter="20">
|
|
|
|
|
- <el-col :span="24">
|
|
|
|
|
- <el-form-item label="家庭住址" prop="homeAddress">
|
|
|
|
|
- <el-input v-model="editContactForm.homeAddress" placeholder="请输入详细家庭地址" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- </el-row>
|
|
|
|
|
- <el-row :gutter="20">
|
|
|
|
|
- <el-col :span="8">
|
|
|
|
|
- <el-form-item label="家庭情况" prop="familyStatus">
|
|
|
|
|
- <el-input v-model="editContactForm.familyStatus" placeholder="请输入家庭情况" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="8">
|
|
|
|
|
- <el-form-item label="爱好" prop="hobby">
|
|
|
|
|
- <el-input v-model="editContactForm.hobby" placeholder="请输入爱好" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="8">
|
|
|
|
|
- <el-form-item label="性格特征" prop="character">
|
|
|
|
|
- <el-input v-model="editContactForm.character" placeholder="请输入性格特征" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- </el-row>
|
|
|
|
|
- <el-row :gutter="20">
|
|
|
|
|
- <el-col :span="8">
|
|
|
|
|
- <el-form-item label="是否抽烟" prop="isSmoking">
|
|
|
|
|
- <el-radio-group v-model="editContactForm.isSmoking">
|
|
|
|
|
- <el-radio value="1">是</el-radio>
|
|
|
|
|
- <el-radio value="0">否</el-radio>
|
|
|
|
|
- </el-radio-group>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="8">
|
|
|
|
|
- <el-form-item label="是否喝酒" prop="isDrinking">
|
|
|
|
|
- <el-radio-group v-model="editContactForm.isDrinking">
|
|
|
|
|
- <el-radio value="1">是</el-radio>
|
|
|
|
|
- <el-radio value="0">否</el-radio>
|
|
|
|
|
- </el-radio-group>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="8">
|
|
|
|
|
- </el-col>
|
|
|
|
|
- </el-row>
|
|
|
|
|
- </div>
|
|
|
|
|
- </el-form>
|
|
|
|
|
- <template #footer>
|
|
|
|
|
- <div style="flex: auto; text-align: right; padding: 10px 20px; border-top: 1px solid #f0f0f0;">
|
|
|
|
|
- <el-button type="primary" @click="submitEditContact">保 存</el-button>
|
|
|
|
|
- <el-button @click="editContactVisible = false">取 消</el-button>
|
|
|
|
|
- </div>
|
|
|
|
|
- </template>
|
|
|
|
|
|
|
+ <!-- 项目决策 -->
|
|
|
|
|
+ <div class="form-section-group">
|
|
|
|
|
+ <div class="section-group-title">项目决策</div>
|
|
|
|
|
+ <div class="section-group-content">
|
|
|
|
|
+ <el-row :gutter="20">
|
|
|
|
|
+ <el-col :span="8">
|
|
|
|
|
+ <el-form-item label="项目角色" prop="projectRole">
|
|
|
|
|
+ <el-select v-model="projectContactForm.projectRole" style="width: 100%" placeholder="请选择" filterable>
|
|
|
|
|
+ <el-option v-for="item in projectRoleOptions" :key="item.value" :label="item.label" :value="parseInt(item.value)" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="8">
|
|
|
|
|
+ <el-form-item label="是否关键人" prop="isKeyPerson">
|
|
|
|
|
+ <el-select v-model="projectContactForm.isKeyPerson" style="width: 100%" placeholder="请选择">
|
|
|
|
|
+ <el-option label="是" :value="1" />
|
|
|
|
|
+ <el-option label="否" :value="0" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+ <el-row :gutter="20">
|
|
|
|
|
+ <el-col :span="24">
|
|
|
|
|
+ <el-form-item label="公关情况" prop="prStatus">
|
|
|
|
|
+ <el-input v-model="projectContactForm.prStatus" type="textarea" :rows="2" maxlength="500" show-word-limit placeholder="请输入详细公关情况" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 家庭信息 -->
|
|
|
|
|
+ <div class="form-section-group">
|
|
|
|
|
+ <div class="section-group-title">家庭信息</div>
|
|
|
|
|
+ <div class="section-group-content">
|
|
|
|
|
+ <el-row :gutter="20">
|
|
|
|
|
+ <el-col :span="24">
|
|
|
|
|
+ <el-form-item label="家庭住址" prop="homeAddressDetail">
|
|
|
|
|
+ <div style="display: flex; gap: 10px; width: 100%">
|
|
|
|
|
+ <el-cascader
|
|
|
|
|
+ v-model="projectContactForm.homeRegion"
|
|
|
|
|
+ ref="homeRegionCascader"
|
|
|
|
|
+ :options="areaOptions"
|
|
|
|
|
+ :props="{ label: 'areaName', value: 'id', children: 'children' }"
|
|
|
|
|
+ placeholder="请选择省/市/县"
|
|
|
|
|
+ style="width: 220px"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-input v-model="projectContactForm.homeAddressDetail" placeholder="请输入详细地址" style="flex: 1" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+ <el-row :gutter="20">
|
|
|
|
|
+ <el-col :span="8">
|
|
|
|
|
+ <el-form-item label="家庭情况" prop="familyStatus">
|
|
|
|
|
+ <el-input v-model="projectContactForm.familyStatus" placeholder="请输入家庭情况" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="8">
|
|
|
|
|
+ <el-form-item label="爱好" prop="hobby">
|
|
|
|
|
+ <el-input v-model="projectContactForm.hobby" placeholder="请输入爱好" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="8">
|
|
|
|
|
+ <el-form-item label="性格特征" prop="characterTrait">
|
|
|
|
|
+ <el-input v-model="projectContactForm.characterTrait" placeholder="请输入性格特征" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+ <el-row :gutter="20">
|
|
|
|
|
+ <el-col :span="8">
|
|
|
|
|
+ <el-form-item label="是否抽烟" prop="isSmoke">
|
|
|
|
|
+ <el-radio-group v-model="projectContactForm.isSmoke">
|
|
|
|
|
+ <el-radio value="1">是</el-radio>
|
|
|
|
|
+ <el-radio value="0">否</el-radio>
|
|
|
|
|
+ </el-radio-group>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="8">
|
|
|
|
|
+ <el-form-item label="是否喝酒" prop="isDrink">
|
|
|
|
|
+ <el-radio-group v-model="projectContactForm.isDrink">
|
|
|
|
|
+ <el-radio value="1">是</el-radio>
|
|
|
|
|
+ <el-radio value="0">否</el-radio>
|
|
|
|
|
+ </el-radio-group>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="drawer-footer-standard" style="padding: 15px 20px; border-top: 1px solid #f0f0f0; text-align: right;">
|
|
|
|
|
+ <el-button @click="projectContactDrawerOpen = false">取 消</el-button>
|
|
|
|
|
+ <el-button type="primary" @click="submitProjectContact" :loading="contactSubmitting">确 定</el-button>
|
|
|
|
|
+ </div>
|
|
|
</el-drawer>
|
|
</el-drawer>
|
|
|
|
|
|
|
|
<!-- 联系人详情预览弹窗 -->
|
|
<!-- 联系人详情预览弹窗 -->
|
|
@@ -503,6 +528,22 @@
|
|
|
<el-descriptions-item label="办公地址">{{ contactForm.addressDetail }}</el-descriptions-item>
|
|
<el-descriptions-item label="办公地址">{{ contactForm.addressDetail }}</el-descriptions-item>
|
|
|
</el-descriptions>
|
|
</el-descriptions>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <div class="preview-section" style="margin-top: 20px;">
|
|
|
|
|
+ <div class="preview-title">项目决策</div>
|
|
|
|
|
+ <el-descriptions :column="2" border>
|
|
|
|
|
+ <el-descriptions-item label="项目角色">
|
|
|
|
|
+ <dict-tag :options="projectRoleOptions" :value="contactForm.projectRole" />
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="是否关键人">
|
|
|
|
|
+ <el-tag :type="contactForm.isKeyPerson == 1 ? 'danger' : 'info'">
|
|
|
|
|
+ {{ contactForm.isKeyPerson == 1 ? '是' : '否' }}
|
|
|
|
|
+ </el-tag>
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="公关情况" :span="2">
|
|
|
|
|
+ {{ contactForm.prStatus }}
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ </el-descriptions>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
<template #footer>
|
|
<template #footer>
|
|
|
<el-button @click="contactDetailVisible = false">关 闭</el-button>
|
|
<el-button @click="contactDetailVisible = false">关 闭</el-button>
|
|
@@ -514,13 +555,15 @@
|
|
|
<script setup>
|
|
<script setup>
|
|
|
import { ref, reactive, watch, getCurrentInstance, toRefs } from 'vue';
|
|
import { ref, reactive, watch, getCurrentInstance, toRefs } from 'vue';
|
|
|
import { getLeads, updateLeads } from '@/api/saleManage/leads/index';
|
|
import { getLeads, updateLeads } from '@/api/saleManage/leads/index';
|
|
|
-import { listContact, updateContact, delContact } from '@/api/customer/crmContact';
|
|
|
|
|
-import { getContactPerson } from "@/api/customer/contactPerson";
|
|
|
|
|
|
|
+import { listContact, addContact, updateContact, delContact, getContact } from '@/api/customer/crmContact';
|
|
|
import { getSalesResultAnalyzeByObjectNo, addSalesResultAnalyze, updateSalesResultAnalyze } from '@/api/saleManage/leads/salesResultAnalyze';
|
|
import { getSalesResultAnalyzeByObjectNo, addSalesResultAnalyze, updateSalesResultAnalyze } from '@/api/saleManage/leads/salesResultAnalyze';
|
|
|
import { listByIds } from '@/api/system/oss/index';
|
|
import { listByIds } from '@/api/system/oss/index';
|
|
|
|
|
+import { listProvinceWithCities } from "@/api/customer/addressArea";
|
|
|
|
|
+import { listCustomerInfo } from "@/api/customer/customerInfo/index";
|
|
|
import { globalHeaders } from '@/utils/request';
|
|
import { globalHeaders } from '@/utils/request';
|
|
|
import BusinessActivity from '../../common/businessActivity.vue';
|
|
import BusinessActivity from '../../common/businessActivity.vue';
|
|
|
import { Close, Edit, Plus, Upload, CircleCheck, ArrowDown } from '@element-plus/icons-vue';
|
|
import { Close, Edit, Plus, Upload, CircleCheck, ArrowDown } from '@element-plus/icons-vue';
|
|
|
|
|
+import { onMounted } from 'vue';
|
|
|
|
|
|
|
|
const props = defineProps({
|
|
const props = defineProps({
|
|
|
modelValue: Boolean,
|
|
modelValue: Boolean,
|
|
@@ -553,9 +596,54 @@ const selectedContacts = ref([]);
|
|
|
const contactDetailVisible = ref(false);
|
|
const contactDetailVisible = ref(false);
|
|
|
const contactDetailLoading = ref(false);
|
|
const contactDetailLoading = ref(false);
|
|
|
const contactForm = ref({});
|
|
const contactForm = ref({});
|
|
|
-const editContactVisible = ref(false);
|
|
|
|
|
-const editContactLoading = ref(false);
|
|
|
|
|
-const editContactForm = ref({});
|
|
|
|
|
|
|
+const areaOptions = ref([]);
|
|
|
|
|
+const projectContactDrawerOpen = ref(false);
|
|
|
|
|
+const projectContactFormRef = ref(null);
|
|
|
|
|
+const officeRegionCascader = ref(null);
|
|
|
|
|
+const homeRegionCascader = ref(null);
|
|
|
|
|
+const contactSubmitting = ref(false);
|
|
|
|
|
+const currentId = ref(props.id);
|
|
|
|
|
+const projectContactForm = reactive({
|
|
|
|
|
+ id: undefined,
|
|
|
|
|
+ customerId: undefined,
|
|
|
|
|
+ contactName: '',
|
|
|
|
|
+ type: '1',
|
|
|
|
|
+ gender: '0',
|
|
|
|
|
+ age: null,
|
|
|
|
|
+ nativePlace: '',
|
|
|
|
|
+ birthday: null,
|
|
|
|
|
+ remark: null,
|
|
|
|
|
+ jobStatus: '1',
|
|
|
|
|
+ phone: '',
|
|
|
|
|
+ deptName: '',
|
|
|
|
|
+ position: '',
|
|
|
|
|
+ officePhone: '',
|
|
|
|
|
+ officeRegion: [],
|
|
|
|
|
+ provincialCityCounty: '',
|
|
|
|
|
+ addressDetail: '',
|
|
|
|
|
+ jobContent: null,
|
|
|
|
|
+ projectRole: null,
|
|
|
|
|
+ isKeyPerson: 0,
|
|
|
|
|
+ prStatus: null,
|
|
|
|
|
+ familyStatus: null,
|
|
|
|
|
+ hobby: null,
|
|
|
|
|
+ characterTrait: null,
|
|
|
|
|
+ isSmoke: '0',
|
|
|
|
|
+ isDrink: '0',
|
|
|
|
|
+ homeRegion: [],
|
|
|
|
|
+ homeAddressDetail: null
|
|
|
|
|
+});
|
|
|
|
|
+const projectContactRules = {
|
|
|
|
|
+ contactName: [{ required: true, message: '姓名不能为空', trigger: 'blur' }],
|
|
|
|
|
+ age: [{ required: true, message: '年龄不能为空', trigger: 'blur' }],
|
|
|
|
|
+ deptName: [{ required: true, message: '部门不能为空', trigger: 'blur' }],
|
|
|
|
|
+ phone: [
|
|
|
|
|
+ { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的11位手机号码', trigger: 'blur' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ officePhone: [
|
|
|
|
|
+ { pattern: /^1[3-9]\d{9}$/, message: '座机号请输入正确的11位手机号码', trigger: 'blur' }
|
|
|
|
|
+ ]
|
|
|
|
|
+};
|
|
|
const proxy = getCurrentInstance().proxy;
|
|
const proxy = getCurrentInstance().proxy;
|
|
|
const {
|
|
const {
|
|
|
LXRJE0001: projectRoleOptions,
|
|
LXRJE0001: projectRoleOptions,
|
|
@@ -570,26 +658,99 @@ watch(() => props.modelValue, (val) => {
|
|
|
}, { immediate: true });
|
|
}, { immediate: true });
|
|
|
|
|
|
|
|
watch(() => visible.value, (val) => {
|
|
watch(() => visible.value, (val) => {
|
|
|
- emit('update:modelValue', val);
|
|
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
-watch(() => [props.id, visible.value], ([newId, isVisible]) => {
|
|
|
|
|
- if (isVisible) {
|
|
|
|
|
- // 加载字典
|
|
|
|
|
|
|
+ if (val) {
|
|
|
loadDicts();
|
|
loadDicts();
|
|
|
- // 优先使用列表传入的基础数据,实现秒开体验
|
|
|
|
|
|
|
+ loadAreaOptions();
|
|
|
if (props.infoData && Object.keys(props.infoData).length > 0) {
|
|
if (props.infoData && Object.keys(props.infoData).length > 0) {
|
|
|
form.value = { ...props.infoData };
|
|
form.value = { ...props.infoData };
|
|
|
}
|
|
}
|
|
|
- if (newId) {
|
|
|
|
|
- getDetail(newId);
|
|
|
|
|
|
|
+ const targetId = props.id || currentId.value;
|
|
|
|
|
+ if (targetId) {
|
|
|
|
|
+ getDetail(targetId);
|
|
|
}
|
|
}
|
|
|
|
|
+ // 强制触发一次地址加载
|
|
|
|
|
+ loadAreaOptions();
|
|
|
}
|
|
}
|
|
|
-}, { immediate: true });
|
|
|
|
|
|
|
+ emit('update:modelValue', val);
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const open = (id, options = {}) => {
|
|
|
|
|
+ visible.value = true;
|
|
|
|
|
+ activeTab.value = options.activeTab || 'info';
|
|
|
|
|
+ currentId.value = id;
|
|
|
|
|
+ // 这种情况下,watch 也会触发,但我们可以显式调用以防万一
|
|
|
|
|
+ if (id) {
|
|
|
|
|
+ getDetail(id);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (options.autoCreateContact) {
|
|
|
|
|
+ // 延迟确保详情加载和抽屉组件就绪
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ handleNewProjectContact();
|
|
|
|
|
+ }, 500);
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
const loadDicts = () => {
|
|
const loadDicts = () => {
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+const loadAreaOptions = () => {
|
|
|
|
|
+ listProvinceWithCities().then(res => {
|
|
|
|
|
+ // 接口返回的是分页对象 res.rows
|
|
|
|
|
+ const list = res.rows || [];
|
|
|
|
|
+ if (list.length > 0) {
|
|
|
|
|
+ // 这里的 props 映射是 { label: 'areaName', value: 'id', children: 'children' }
|
|
|
|
|
+ // 我们通过 handleTree 构建树形结构
|
|
|
|
|
+ areaOptions.value = handleTree(list, "id", "parentId");
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+/** 构造树型结构数据 */
|
|
|
|
|
+function handleTree(data, id, parentId, children) {
|
|
|
|
|
+ let config = {
|
|
|
|
|
+ id: id || 'id',
|
|
|
|
|
+ parentId: parentId || 'parentId',
|
|
|
|
|
+ childrenList: children || 'children'
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ var childrenListMap = {};
|
|
|
|
|
+ var nodeIds = {};
|
|
|
|
|
+ var tree = [];
|
|
|
|
|
+
|
|
|
|
|
+ for (let d of data) {
|
|
|
|
|
+ let pId = d[config.parentId];
|
|
|
|
|
+ if (childrenListMap[pId] == null) {
|
|
|
|
|
+ childrenListMap[pId] = [];
|
|
|
|
|
+ }
|
|
|
|
|
+ nodeIds[d[config.id]] = d;
|
|
|
|
|
+ childrenListMap[pId].push(d);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for (let d of data) {
|
|
|
|
|
+ let pId = d[config.parentId];
|
|
|
|
|
+ if (nodeIds[pId] == null) {
|
|
|
|
|
+ tree.push(d);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for (let t of tree) {
|
|
|
|
|
+ adaptToChildrenList(t);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function adaptToChildrenList(o) {
|
|
|
|
|
+ if (childrenListMap[o[config.id]] !== null) {
|
|
|
|
|
+ o[config.childrenList] = childrenListMap[o[config.id]];
|
|
|
|
|
+ }
|
|
|
|
|
+ if (o[config.childrenList]) {
|
|
|
|
|
+ for (let c of o[config.childrenList]) {
|
|
|
|
|
+ adaptToChildrenList(c);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return tree;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
const getDetail = (id) => {
|
|
const getDetail = (id) => {
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
loading.value = true;
|
|
loading.value = true;
|
|
@@ -598,9 +759,25 @@ const getDetail = (id) => {
|
|
|
const data = res.data || res;
|
|
const data = res.data || res;
|
|
|
if (data && typeof data === 'object') {
|
|
if (data && typeof data === 'object') {
|
|
|
form.value = data;
|
|
form.value = data;
|
|
|
- loadProjectContacts();
|
|
|
|
|
- loadAnalysisData();
|
|
|
|
|
- loadOssFiles();
|
|
|
|
|
|
|
+ // 如果数据中已经带了 customerId,直接作为 realCustomerId
|
|
|
|
|
+ if (data.customerId) {
|
|
|
|
|
+ form.value.realCustomerId = data.customerId;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 通过客户编号查询真实的客户 ID (作为补充校验)
|
|
|
|
|
+ if (data.customerNo) {
|
|
|
|
|
+ listCustomerInfo({ customerNo: data.customerNo }).then(res => {
|
|
|
|
|
+ if (res.rows && res.rows.length > 0) {
|
|
|
|
|
+ form.value.realCustomerId = res.rows[0].id;
|
|
|
|
|
+ }
|
|
|
|
|
+ loadProjectContacts();
|
|
|
|
|
+ loadAnalysisData();
|
|
|
|
|
+ loadOssFiles();
|
|
|
|
|
+ });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ loadProjectContacts();
|
|
|
|
|
+ loadAnalysisData();
|
|
|
|
|
+ loadOssFiles();
|
|
|
|
|
+ }
|
|
|
} else {
|
|
} else {
|
|
|
proxy.$modal.msgWarning("未获取到详情数据");
|
|
proxy.$modal.msgWarning("未获取到详情数据");
|
|
|
}
|
|
}
|
|
@@ -618,9 +795,12 @@ const findUserName = (id) => {
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const loadProjectContacts = () => {
|
|
const loadProjectContacts = () => {
|
|
|
- if (!form.value.id) return;
|
|
|
|
|
contactLoading.value = true;
|
|
contactLoading.value = true;
|
|
|
- listContact({ platformCode: String(form.value.id) }).then(res => {
|
|
|
|
|
|
|
+ // 按照客户 ID 和项目编号查询(即便 realCustomerId 为空,后端也支持通过 projectNo 进行备注匹配回显)
|
|
|
|
|
+ listContact({
|
|
|
|
|
+ customerId: form.value.realCustomerId || form.value.customerId,
|
|
|
|
|
+ projectNo: form.value.projectNo
|
|
|
|
|
+ }).then(res => {
|
|
|
contactList.value = res.rows || [];
|
|
contactList.value = res.rows || [];
|
|
|
}).finally(() => {
|
|
}).finally(() => {
|
|
|
contactLoading.value = false;
|
|
contactLoading.value = false;
|
|
@@ -734,10 +914,24 @@ const handleContactCommand = (command) => {
|
|
|
if (command === 'associate') {
|
|
if (command === 'associate') {
|
|
|
openAssociateDialog();
|
|
openAssociateDialog();
|
|
|
} else if (command === 'create') {
|
|
} else if (command === 'create') {
|
|
|
- proxy.$modal.msgInfo("新建联系人功能开发中...");
|
|
|
|
|
|
|
+ handleNewProjectContact();
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+const handleNewProjectContact = () => {
|
|
|
|
|
+ Object.assign(projectContactForm, {
|
|
|
|
|
+ id: undefined,
|
|
|
|
|
+ customerId: form.value.customerId,
|
|
|
|
|
+ contactName: '', type: '1', gender: '0', age: null, deptName: '',
|
|
|
|
|
+ position: '', projectRole: null, isKeyPerson: 0, jobStatus: '1', phone: '',
|
|
|
|
|
+ nativePlace: '', birthday: null, remark: null, officePhone: '', officeRegion: [], provincialCityCounty: '', addressDetail: '',
|
|
|
|
|
+ jobContent: null, prStatus: null,
|
|
|
|
|
+ familyStatus: null, hobby: null, characterTrait: null, isSmoke: '0', isDrink: '0',
|
|
|
|
|
+ homeRegion: [], homeAddressDetail: null
|
|
|
|
|
+ });
|
|
|
|
|
+ projectContactDrawerOpen.value = true;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
// 打开关联联系人弹窗
|
|
// 打开关联联系人弹窗
|
|
|
const openAssociateDialog = () => {
|
|
const openAssociateDialog = () => {
|
|
|
if (!form.value.customerName) {
|
|
if (!form.value.customerName) {
|
|
@@ -770,11 +964,12 @@ const submitAssociate = async () => {
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
proxy.$modal.loading("正在关联...");
|
|
proxy.$modal.loading("正在关联...");
|
|
|
- // 遍历选中的联系人,更新其 platformCode 为当前线索 ID
|
|
|
|
|
const promises = selectedContacts.value.map(contact => {
|
|
const promises = selectedContacts.value.map(contact => {
|
|
|
return updateContact({
|
|
return updateContact({
|
|
|
...contact,
|
|
...contact,
|
|
|
- platformCode: String(form.value.id)
|
|
|
|
|
|
|
+ customerId: form.value.realCustomerId || form.value.customerId || contact.customerId,
|
|
|
|
|
+ platformCode: form.value.projectNo || '',
|
|
|
|
|
+ projectNo: form.value.projectNo || ''
|
|
|
});
|
|
});
|
|
|
});
|
|
});
|
|
|
|
|
|
|
@@ -791,11 +986,11 @@ const submitAssociate = async () => {
|
|
|
|
|
|
|
|
// 查看联系人详情
|
|
// 查看联系人详情
|
|
|
const viewContactDetail = (row) => {
|
|
const viewContactDetail = (row) => {
|
|
|
- const contactId = row.contactId || row.id;
|
|
|
|
|
|
|
+ const contactId = row.id;
|
|
|
if (!contactId) return;
|
|
if (!contactId) return;
|
|
|
contactDetailVisible.value = true;
|
|
contactDetailVisible.value = true;
|
|
|
contactDetailLoading.value = true;
|
|
contactDetailLoading.value = true;
|
|
|
- getContactPerson(contactId).then(res => {
|
|
|
|
|
|
|
+ getContact(contactId).then(res => {
|
|
|
contactForm.value = res.data || {};
|
|
contactForm.value = res.data || {};
|
|
|
}).finally(() => {
|
|
}).finally(() => {
|
|
|
contactDetailLoading.value = false;
|
|
contactDetailLoading.value = false;
|
|
@@ -805,41 +1000,172 @@ const viewContactDetail = (row) => {
|
|
|
const handleEditContact = (row) => {
|
|
const handleEditContact = (row) => {
|
|
|
const contactId = row.contactId || row.id;
|
|
const contactId = row.contactId || row.id;
|
|
|
if (!contactId) return;
|
|
if (!contactId) return;
|
|
|
- editContactLoading.value = true;
|
|
|
|
|
- getContactPerson(contactId).then(res => {
|
|
|
|
|
- editContactForm.value = res.data || {};
|
|
|
|
|
- // 确保单选框的值是字符串,以便正确匹配 radio
|
|
|
|
|
- if (editContactForm.value.roleId) editContactForm.value.roleId = String(editContactForm.value.roleId);
|
|
|
|
|
- if (editContactForm.value.gender) editContactForm.value.gender = String(editContactForm.value.gender);
|
|
|
|
|
- if (editContactForm.value.status) editContactForm.value.status = String(editContactForm.value.status);
|
|
|
|
|
- if (editContactForm.value.isSmoking) editContactForm.value.isSmoking = String(editContactForm.value.isSmoking);
|
|
|
|
|
- if (editContactForm.value.isDrinking) editContactForm.value.isDrinking = String(editContactForm.value.isDrinking);
|
|
|
|
|
- editContactVisible.value = true;
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 重置表单,确保不残留上次的数据
|
|
|
|
|
+ Object.assign(projectContactForm, {
|
|
|
|
|
+ id: undefined,
|
|
|
|
|
+ customerId: undefined,
|
|
|
|
|
+ contactName: '', type: '1', gender: '0', age: null, deptName: '',
|
|
|
|
|
+ position: '', projectRole: null, isKeyPerson: 0, jobStatus: '1', phone: '',
|
|
|
|
|
+ nativePlace: '', birthday: null, remark: null, officePhone: '', officeRegion: [], provincialCityCounty: '', addressDetail: '',
|
|
|
|
|
+ jobContent: null, prStatus: null,
|
|
|
|
|
+ familyStatus: null, hobby: null, characterTrait: null, isSmoke: '0', isDrink: '0',
|
|
|
|
|
+ homeRegion: [], homeAddressDetail: null
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 请求详情数据
|
|
|
|
|
+ proxy.$modal.loading("正在获取详情...");
|
|
|
|
|
+ getContact(contactId).then(res => {
|
|
|
|
|
+ const data = res.data || {};
|
|
|
|
|
+ if (!data.id) {
|
|
|
|
|
+ proxy.$modal.msgError("获取联系人详情失败");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 赋值回显
|
|
|
|
|
+ projectContactForm.id = data.id;
|
|
|
|
|
+ projectContactForm.customerId = data.customerId;
|
|
|
|
|
+ projectContactForm.contactName = data.contactName;
|
|
|
|
|
+ projectContactForm.gender = String(data.gender || '0');
|
|
|
|
|
+ projectContactForm.deptName = data.deptName;
|
|
|
|
|
+ projectContactForm.position = data.position;
|
|
|
|
|
+ projectContactForm.projectRole = data.projectRole !== null && data.projectRole !== undefined ? Number(data.projectRole) : null;
|
|
|
|
|
+ projectContactForm.isKeyPerson = data.isKeyPerson !== null && data.isKeyPerson !== undefined ? Number(data.isKeyPerson) : 0;
|
|
|
|
|
+ projectContactForm.prStatus = data.prStatus;
|
|
|
|
|
+ projectContactForm.phone = data.phone;
|
|
|
|
|
+ projectContactForm.type = String(data.type || '1');
|
|
|
|
|
+ projectContactForm.jobStatus = String(data.jobStatus || '1');
|
|
|
|
|
+ projectContactForm.age = data.age;
|
|
|
|
|
+ projectContactForm.nativePlace = data.nativePlace;
|
|
|
|
|
+ projectContactForm.birthday = data.birthday;
|
|
|
|
|
+ projectContactForm.remark = data.remark;
|
|
|
|
|
+ projectContactForm.officePhone = data.officePhone;
|
|
|
|
|
+ projectContactForm.addressDetail = data.addressDetail;
|
|
|
|
|
+ projectContactForm.jobContent = data.jobContent;
|
|
|
|
|
+ projectContactForm.familyStatus = data.familyStatus;
|
|
|
|
|
+ projectContactForm.hobby = data.hobby;
|
|
|
|
|
+ projectContactForm.characterTrait = data.characterTrait;
|
|
|
|
|
+ projectContactForm.isSmoke = String(data.isSmoke || '0');
|
|
|
|
|
+ projectContactForm.isDrink = String(data.isDrink || '0');
|
|
|
|
|
+ projectContactForm.homeAddressDetail = data.homeAddressDetail;
|
|
|
|
|
+
|
|
|
|
|
+ // 区域级联处理 (由于办公地址数据库存的是代码 String,而家庭住址存的是 ID Long,这里需要做特殊转换)
|
|
|
|
|
+ const findIdByCode = (code) => {
|
|
|
|
|
+ if (!code) return null;
|
|
|
|
|
+ // 在扁平的原始列表中查找(由于 loadAreaOptions 是异步的,这里从 areaOptions 树中递归找或者从接口缓存中找更稳)
|
|
|
|
|
+ // 简单处理:我们通过深度优先搜索在已有的树中查找
|
|
|
|
|
+ const findInTree = (nodes, targetCode) => {
|
|
|
|
|
+ for (const node of nodes) {
|
|
|
|
|
+ if (String(node.areaCode) === String(targetCode)) return node.id;
|
|
|
|
|
+ if (node.children) {
|
|
|
|
|
+ const found = findInTree(node.children, targetCode);
|
|
|
|
|
+ if (found) return found;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ };
|
|
|
|
|
+ return findInTree(areaOptions.value, code);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const officeArr = [];
|
|
|
|
|
+ if (data.addressProvince) {
|
|
|
|
|
+ // 如果是代码,转为 ID
|
|
|
|
|
+ const pid = /^\d+$/.test(data.addressProvince) && data.addressProvince.length < 5 ? data.addressProvince : findIdByCode(data.addressProvince);
|
|
|
|
|
+ if (pid) officeArr.push(Number(pid));
|
|
|
|
|
+ }
|
|
|
|
|
+ if (data.addressCity) {
|
|
|
|
|
+ const cid = /^\d+$/.test(data.addressCity) && data.addressCity.length < 7 ? data.addressCity : findIdByCode(data.addressCity);
|
|
|
|
|
+ if (cid) officeArr.push(Number(cid));
|
|
|
|
|
+ }
|
|
|
|
|
+ if (data.addressCounty) {
|
|
|
|
|
+ const aid = /^\d+$/.test(data.addressCounty) && data.addressCounty.length < 9 ? data.addressCounty : findIdByCode(data.addressCounty);
|
|
|
|
|
+ if (aid) officeArr.push(Number(aid));
|
|
|
|
|
+ }
|
|
|
|
|
+ projectContactForm.officeRegion = officeArr;
|
|
|
|
|
+
|
|
|
|
|
+ const homeArr = [];
|
|
|
|
|
+ if (data.homeProvinceId) homeArr.push(Number(data.homeProvinceId));
|
|
|
|
|
+ if (data.homeCityId) homeArr.push(Number(data.homeCityId));
|
|
|
|
|
+ if (data.homeAreaId) homeArr.push(Number(data.homeAreaId));
|
|
|
|
|
+ projectContactForm.homeRegion = homeArr;
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 全部数据就绪后再打开抽屉
|
|
|
|
|
+ projectContactDrawerOpen.value = true;
|
|
|
}).finally(() => {
|
|
}).finally(() => {
|
|
|
- editContactLoading.value = false;
|
|
|
|
|
|
|
+ proxy.$modal.closeLoading();
|
|
|
});
|
|
});
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-const submitEditContact = () => {
|
|
|
|
|
- proxy.$refs.editContactRef.validate(valid => {
|
|
|
|
|
|
|
+const submitProjectContact = () => {
|
|
|
|
|
+ projectContactFormRef.value.validate(async (valid) => {
|
|
|
if (valid) {
|
|
if (valid) {
|
|
|
- proxy.$modal.loading("正在保存...");
|
|
|
|
|
- updateContact(editContactForm.value).then(() => {
|
|
|
|
|
- proxy.$modal.closeLoading();
|
|
|
|
|
- proxy.$modal.msgSuccess("修改成功");
|
|
|
|
|
- editContactVisible.value = false;
|
|
|
|
|
|
|
+ contactSubmitting.value = true;
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 获取省市区名称
|
|
|
|
|
+ const checkedNodes = officeRegionCascader.value?.getCheckedNodes();
|
|
|
|
|
+ let provincialCityCounty = '';
|
|
|
|
|
+ if (checkedNodes && checkedNodes.length > 0) {
|
|
|
|
|
+ provincialCityCounty = checkedNodes[0].pathLabels.join("");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取选中的 AreaCode 列表 (用于办公地址)
|
|
|
|
|
+ const findCodeById = (id) => {
|
|
|
|
|
+ const findInTree = (nodes, targetId) => {
|
|
|
|
|
+ for (const node of nodes) {
|
|
|
|
|
+ if (Number(node.id) === Number(targetId)) return node.areaCode;
|
|
|
|
|
+ if (node.children) {
|
|
|
|
|
+ const found = findInTree(node.children, targetId);
|
|
|
|
|
+ if (found) return found;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ };
|
|
|
|
|
+ return findInTree(areaOptions.value, id);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const payload = {
|
|
|
|
|
+ ...projectContactForm,
|
|
|
|
|
+ roleId: projectContactForm.type === '1' ? 1 : 2,
|
|
|
|
|
+ // 优先使用异步查询到的 ID,其次使用主表 ID,最后使用表单内的 ID
|
|
|
|
|
+ customerId: form.value.realCustomerId || form.value.customerId || projectContactForm.customerId,
|
|
|
|
|
+ platformCode: form.value.projectNo || '',
|
|
|
|
|
+ customerName: form.value.customerName || '',
|
|
|
|
|
+ customerNo: form.value.customerNo || '',
|
|
|
|
|
+ projectNo: form.value.projectNo || '',
|
|
|
|
|
+ // age 字段转换
|
|
|
|
|
+ age: projectContactForm.age ? parseInt(projectContactForm.age, 10) : null,
|
|
|
|
|
+ // 办公地址存储为代码 (String)
|
|
|
|
|
+ addressProvince: projectContactForm.officeRegion?.[0] ? findCodeById(projectContactForm.officeRegion[0]) : null,
|
|
|
|
|
+ addressCity: projectContactForm.officeRegion?.[1] ? findCodeById(projectContactForm.officeRegion[1]) : null,
|
|
|
|
|
+ addressCounty: projectContactForm.officeRegion?.[2] ? findCodeById(projectContactForm.officeRegion[2]) : null,
|
|
|
|
|
+ provincialCityCounty: provincialCityCounty,
|
|
|
|
|
+ // 家庭地址存储为 ID (Long)
|
|
|
|
|
+ homeProvinceId: projectContactForm.homeRegion?.[0] || null,
|
|
|
|
|
+ homeCityId: projectContactForm.homeRegion?.[1] || null,
|
|
|
|
|
+ homeAreaId: projectContactForm.homeRegion?.[2] || null,
|
|
|
|
|
+ };
|
|
|
|
|
+ if (projectContactForm.id) {
|
|
|
|
|
+ await updateContact(payload);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ await addContact(payload);
|
|
|
|
|
+ }
|
|
|
|
|
+ proxy.$modal.msgSuccess("保存成功");
|
|
|
|
|
+ projectContactDrawerOpen.value = false;
|
|
|
loadProjectContacts();
|
|
loadProjectContacts();
|
|
|
- }).catch(() => {
|
|
|
|
|
- proxy.$modal.closeLoading();
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ // 显示真实错误,避免静默失败
|
|
|
|
|
+ proxy.$modal.msgError(e?.message || "保存失败,请检查控制台");
|
|
|
|
|
+ console.error("保存联系人失败:", e);
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ contactSubmitting.value = false;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const handleDeleteContact = (row) => {
|
|
const handleDeleteContact = (row) => {
|
|
|
- proxy.$modal.confirm('确认要删除该联系人与本项目的关联吗?').then(() => {
|
|
|
|
|
- // 这里其实是取消关联,将 platformCode 置空
|
|
|
|
|
- return updateContact({ ...row, platformCode: '' });
|
|
|
|
|
|
|
+ proxy.$modal.confirm('确认要删除该联系人吗?').then(() => {
|
|
|
|
|
+ return delContact(row.id);
|
|
|
}).then(() => {
|
|
}).then(() => {
|
|
|
proxy.$modal.msgSuccess("删除成功");
|
|
proxy.$modal.msgSuccess("删除成功");
|
|
|
loadProjectContacts();
|
|
loadProjectContacts();
|
|
@@ -862,6 +1188,12 @@ const handleDeleteFile = (row) => {
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
};
|
|
};
|
|
|
|
|
+
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ loadAreaOptions();
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+defineExpose({ open });
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
<style scoped lang="scss">
|
|
@@ -1002,13 +1334,38 @@ const handleDeleteFile = (row) => {
|
|
|
padding: 0 10px;
|
|
padding: 0 10px;
|
|
|
.preview-title {
|
|
.preview-title {
|
|
|
font-size: 14px;
|
|
font-size: 14px;
|
|
|
- font-weight: 500;
|
|
|
|
|
|
|
+ font-weight: normal;
|
|
|
color: #409eff;
|
|
color: #409eff;
|
|
|
margin-bottom: 12px;
|
|
margin-bottom: 12px;
|
|
|
padding-left: 8px;
|
|
padding-left: 8px;
|
|
|
border-left: 3px solid #409eff;
|
|
border-left: 3px solid #409eff;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+.form-section-group {
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+ .section-group-title {
|
|
|
|
|
+ padding: 8px 15px;
|
|
|
|
|
+ background-color: #f8fbff;
|
|
|
|
|
+ color: #409eff;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ margin-bottom: 15px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .section-group-content { padding: 0 15px; }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.no-bold-label {
|
|
|
|
|
+ :deep(.el-form-item__label) { font-weight: normal !important; color: #666; }
|
|
|
|
|
+ :deep(.el-form-item) { margin-bottom: 18px !important; }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.drawer-body-custom {
|
|
|
|
|
+ &::-webkit-scrollbar {
|
|
|
|
|
+ display: none;
|
|
|
|
|
+ }
|
|
|
|
|
+ -ms-overflow-style: none;
|
|
|
|
|
+ scrollbar-width: none;
|
|
|
|
|
+}
|
|
|
:deep(.el-descriptions__label) {
|
|
:deep(.el-descriptions__label) {
|
|
|
width: 100px;
|
|
width: 100px;
|
|
|
background-color: #f8f9fb !important;
|
|
background-color: #f8f9fb !important;
|