新增合同管理相关功能
This commit is contained in:
@@ -0,0 +1,236 @@
|
||||
<template>
|
||||
<div class="party-select" :class="{ own: locked }">
|
||||
<div class="party-head">
|
||||
<span class="party-role">{{ roleLabel }}</span>
|
||||
<el-tag v-if="locked" size="small" type="primary" effect="light">本公司</el-tag>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<!-- 关联主体:当前租户方锁定,其余模糊搜索客户库 / 供应商库 -->
|
||||
<template v-if="!locked">
|
||||
<div class="party-row">
|
||||
<el-select
|
||||
v-if="allowChooseRefType"
|
||||
v-model="party.ref_type"
|
||||
class="ref-type-select"
|
||||
size="default"
|
||||
@change="handleRefTypeChange"
|
||||
>
|
||||
<el-option label="客户" :value="1" />
|
||||
<el-option label="供应商" :value="2" />
|
||||
</el-select>
|
||||
|
||||
<el-select
|
||||
v-model="party.ref_id"
|
||||
class="ref-select"
|
||||
filterable
|
||||
remote
|
||||
clearable
|
||||
reserve-keyword
|
||||
:loading="searching"
|
||||
:remote-method="handleSearch"
|
||||
:placeholder="`搜索${refTypeText}名称`"
|
||||
@change="handlePick"
|
||||
@clear="handleClear"
|
||||
@visible-change="handleDropdownOpen"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:key="item.id"
|
||||
:label="item.label"
|
||||
:value="item.id"
|
||||
>
|
||||
<span class="option-name">{{ item.label }}</span>
|
||||
<span class="option-sub">{{ item.sub }}</span>
|
||||
</el-option>
|
||||
<template #footer>
|
||||
<div class="select-footer">
|
||||
<el-button link type="primary" :icon="Plus" @click="emit('create')">
|
||||
新建{{ refTypeText }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-select>
|
||||
|
||||
<el-tooltip :content="`没有找到?点击新建${refTypeText}`" placement="top">
|
||||
<el-button :icon="Plus" @click="emit('create')" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div v-if="party.ref_name" class="picked-name">已关联:{{ party.ref_name }}</div>
|
||||
</template>
|
||||
|
||||
<!-- 签约人 -->
|
||||
<div class="party-row signer-row">
|
||||
<el-input
|
||||
v-model="party.signer_name"
|
||||
placeholder="签约人姓名"
|
||||
maxlength="30"
|
||||
/>
|
||||
<el-input v-model="party.signer_phone" placeholder="联系电话" maxlength="20" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch } from "vue";
|
||||
import { Plus } from "@element-plus/icons-vue";
|
||||
import { getCrmCustomerList, getCrmSupplierList } from "@/api/crm";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
|
||||
const props = defineProps({
|
||||
/** 参与方对象:{ role, ref_type, ref_id, ref_name, signer_name, signer_phone } */
|
||||
modelValue: { type: Object, required: true },
|
||||
/** 甲方 / 乙方 / 丙方 / 丁方 */
|
||||
roleLabel: { type: String, default: "" },
|
||||
/** 是否为当前租户方(锁定为本公司) */
|
||||
locked: { type: Boolean, default: false },
|
||||
/** 丙/丁方允许在客户 / 供应商库之间切换来源 */
|
||||
allowChooseRefType: { type: Boolean, default: false },
|
||||
});
|
||||
|
||||
const emit = defineEmits(["create"]);
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const tenantName = computed(() => authStore.user?.tenant_name || "本公司");
|
||||
|
||||
/** 参与方对象由父组件持有,这里直接读写其字段 */
|
||||
const party = computed(() => props.modelValue);
|
||||
|
||||
const searching = ref(false);
|
||||
const options = ref([]);
|
||||
|
||||
const refTypeText = computed(() =>
|
||||
Number(party.value.ref_type) === 2 ? "供应商" : "客户"
|
||||
);
|
||||
|
||||
/** 初次打开按已有名称兜底展示,避免回显成 id */
|
||||
const ensureOption = () => {
|
||||
if (party.value?.ref_id && party.value?.ref_name) {
|
||||
const exists = options.value.some((i) => String(i.id) === String(party.value.ref_id));
|
||||
if (!exists) {
|
||||
options.value = [
|
||||
{ id: party.value.ref_id, label: party.value.ref_name, sub: "" },
|
||||
...options.value,
|
||||
];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => party.value?.ref_id,
|
||||
() => ensureOption(),
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const remoteSearch = async (keyword) => {
|
||||
searching.value = true;
|
||||
try {
|
||||
const api =
|
||||
Number(party.value.ref_type) === 2 ? getCrmSupplierList : getCrmCustomerList;
|
||||
const res = await api({ keyword, page: 1, pageSize: 50 });
|
||||
const list = res?.data?.list || [];
|
||||
options.value = list.map((item) => ({
|
||||
id: item.id,
|
||||
label: item.customer_name || item.supplier_name || item.name || `#${item.id}`,
|
||||
sub: item.contact_person || item.industry || "",
|
||||
}));
|
||||
ensureOption();
|
||||
} catch {
|
||||
options.value = [];
|
||||
} finally {
|
||||
searching.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearch = (keyword) => remoteSearch(keyword || "");
|
||||
|
||||
/** 下拉首次展开时加载全量候选 */
|
||||
const handleDropdownOpen = (visible) => {
|
||||
if (visible && options.value.length === 0) remoteSearch("");
|
||||
};
|
||||
|
||||
const handlePick = (id) => {
|
||||
const hit = options.value.find((i) => String(i.id) === String(id));
|
||||
party.value.ref_name = hit?.label || "";
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
party.value.ref_id = null;
|
||||
party.value.ref_name = "";
|
||||
options.value = [];
|
||||
};
|
||||
|
||||
/** 丙/丁方切换客户 / 供应商来源时清空已选 */
|
||||
const handleRefTypeChange = () => {
|
||||
party.value.ref_id = null;
|
||||
party.value.ref_name = "";
|
||||
options.value = [];
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.party-select {
|
||||
padding: 14px 16px;
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 8px;
|
||||
background: var(--el-fill-color-extra-light);
|
||||
transition: border-color 0.2s, background 0.2s;
|
||||
|
||||
/* 本公司方:主色浅底突出显示 */
|
||||
&.own {
|
||||
border-color: var(--el-color-primary-light-5);
|
||||
background: var(--el-color-primary-light-9);
|
||||
}
|
||||
|
||||
.party-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.party-role {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.party-company {
|
||||
margin-left: auto;
|
||||
font-size: 13px;
|
||||
color: var(--el-color-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.party-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
&.signer-row {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.ref-type-select {
|
||||
width: 96px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ref-select {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.picked-name {
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.select-footer {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user