优化项目管理
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
<el-tag v-else-if="party?.ref_name" size="small" type="info" effect="plain">
|
||||
{{ party.ref_type === 2 ? "供应商" : "客户" }}
|
||||
</el-tag>
|
||||
<span v-if="locked" class="party-company">{{ tenantName }}</span>
|
||||
<span v-if="locked" class="party-company">{{ party?.ref_name || tenantName }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 关联主体:当前租户方锁定,其余模糊搜索客户库 / 供应商库 -->
|
||||
@@ -62,13 +62,30 @@
|
||||
<!-- <div v-if="party.ref_name" class="picked-name">已关联:{{ party.ref_name }}</div> -->
|
||||
</template>
|
||||
|
||||
<!-- 签约人 -->
|
||||
<!-- 签约人:本公司方从组织架构人员中选择,其余方从关联企业联系人中选择,均自动带出联系电话;也支持手动输入 -->
|
||||
<div class="party-row signer-row">
|
||||
<el-input
|
||||
<el-select
|
||||
v-model="party.signer_name"
|
||||
placeholder="签约人姓名"
|
||||
maxlength="30"
|
||||
/>
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
clearable
|
||||
:loading="signerLoading"
|
||||
:placeholder="contactPlaceholder"
|
||||
@change="handleSignerPick"
|
||||
>
|
||||
<el-option
|
||||
v-for="c in contacts"
|
||||
:key="c.id"
|
||||
:label="c.contact_name"
|
||||
:value="c.contact_name"
|
||||
>
|
||||
<span class="option-name">{{ c.contact_name }}</span>
|
||||
<span class="option-sub">
|
||||
{{ [c.position || c.department, signerPhoneOf(c)].filter(Boolean).join(" · ") }}
|
||||
</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
<el-input v-model="party.signer_phone" placeholder="联系电话" maxlength="20" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -78,6 +95,8 @@
|
||||
import { ref, computed, watch } from "vue";
|
||||
import { Plus } from "@element-plus/icons-vue";
|
||||
import { getCrmCustomerList, getCrmSupplierList } from "@/api/crm";
|
||||
import { listContacts } from "@/api/contact";
|
||||
import { erpOrganizationApi } from "@/api/organization";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
|
||||
const props = defineProps({
|
||||
@@ -102,6 +121,83 @@ const party = computed(() => props.modelValue);
|
||||
const searching = ref(false);
|
||||
const options = ref([]);
|
||||
|
||||
/* ------------------------------ 签约人:人员联动 ------------------------------ */
|
||||
|
||||
/** 是否为本公司方:锁定方,或 ref_type=0(本公司) */
|
||||
const isOwnCompany = computed(() => props.locked || Number(party.value?.ref_type) === 0);
|
||||
|
||||
const contacts = ref([]);
|
||||
const contactLoading = ref(false);
|
||||
/** 本公司人员:来自租户组织架构(与组织架构模块同源) */
|
||||
const ownEmployees = ref([]);
|
||||
const ownLoading = ref(false);
|
||||
|
||||
/** 签约人下拉加载态:本公司方取员工加载态,其余取联系人加载态 */
|
||||
const signerLoading = computed(() => (isOwnCompany.value ? ownLoading.value : contactLoading.value));
|
||||
|
||||
/** 联系人电话:手机优先,其次座机 */
|
||||
const signerPhoneOf = (c) => c?.mobile || c?.phone || "";
|
||||
|
||||
const contactPlaceholder = computed(() => {
|
||||
if (isOwnCompany.value) return "选择本公司人员(也可输入)";
|
||||
return party.value?.ref_id ? "选择签约人(也可输入)" : "请先选择客户 / 供应商";
|
||||
});
|
||||
|
||||
/** 拉取本公司人员(在职员工),映射为与联系人一致的字段结构,便于复用下拉渲染 */
|
||||
async function fetchOwnEmployees() {
|
||||
ownLoading.value = true;
|
||||
try {
|
||||
const res = await erpOrganizationApi.getEmployeeList({ status: 1 });
|
||||
const list = Array.isArray(res) ? res : res?.data || [];
|
||||
ownEmployees.value = (Array.isArray(list) ? list : []).map((e) => ({
|
||||
id: `emp_${e.id}`,
|
||||
contact_name: e.name || e.account || "",
|
||||
position: e.position || "",
|
||||
department: e.department_name || "",
|
||||
mobile: e.phone || "",
|
||||
phone: "",
|
||||
}));
|
||||
} catch {
|
||||
ownEmployees.value = [];
|
||||
} finally {
|
||||
ownLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 拉取签约人候选:本公司方取组织架构人员,其余取关联企业联系人(客户 / 供应商) */
|
||||
async function fetchContacts() {
|
||||
if (isOwnCompany.value) {
|
||||
if (!ownEmployees.value.length) await fetchOwnEmployees();
|
||||
contacts.value = ownEmployees.value;
|
||||
return;
|
||||
}
|
||||
const refID = party.value?.ref_id;
|
||||
if (!refID) {
|
||||
contacts.value = [];
|
||||
return;
|
||||
}
|
||||
contactLoading.value = true;
|
||||
try {
|
||||
const res = await listContacts({
|
||||
related_type: Number(party.value.ref_type) === 2 ? 2 : 1,
|
||||
related_id: refID,
|
||||
page: 1,
|
||||
pageSize: 100,
|
||||
});
|
||||
contacts.value = res?.data?.list || [];
|
||||
} catch (e) {
|
||||
contacts.value = [];
|
||||
} finally {
|
||||
contactLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 选择签约人:自动带出该联系人的联系电话 */
|
||||
function handleSignerPick(name) {
|
||||
const hit = contacts.value.find((c) => c.contact_name === name);
|
||||
if (hit) party.value.signer_phone = signerPhoneOf(hit);
|
||||
}
|
||||
|
||||
const refTypeText = computed(() =>
|
||||
Number(party.value.ref_type) === 2 ? "供应商" : "客户"
|
||||
);
|
||||
@@ -121,7 +217,11 @@ const ensureOption = () => {
|
||||
|
||||
watch(
|
||||
() => party.value?.ref_id,
|
||||
() => ensureOption(),
|
||||
() => {
|
||||
ensureOption();
|
||||
// 关联企业变化时同步刷新其联系人,供签约人下拉选择
|
||||
fetchContacts();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
@@ -160,14 +260,20 @@ const handlePick = (id) => {
|
||||
const handleClear = () => {
|
||||
party.value.ref_id = null;
|
||||
party.value.ref_name = "";
|
||||
party.value.signer_name = "";
|
||||
party.value.signer_phone = "";
|
||||
options.value = [];
|
||||
contacts.value = [];
|
||||
};
|
||||
|
||||
/** 丙/丁方切换客户 / 供应商来源时清空已选 */
|
||||
const handleRefTypeChange = () => {
|
||||
party.value.ref_id = null;
|
||||
party.value.ref_name = "";
|
||||
party.value.signer_name = "";
|
||||
party.value.signer_phone = "";
|
||||
options.value = [];
|
||||
contacts.value = [];
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user