| | |
| | | <div class="left-panel"> |
| | | <div class="left-header"> |
| | | <div class="left-title">地区</div> |
| | | <el-button type="primary" |
| | | size="small" |
| | | @click="openAddRegionDialog">新增地区</el-button> |
| | | <div class="left-actions"> |
| | | <el-button type="primary" |
| | | size="small" |
| | | @click="openAddRegionDialog">新增</el-button> |
| | | <el-button size="small" |
| | | @click="openEditRegionDialog">修改</el-button> |
| | | <el-button type="danger" |
| | | plain |
| | | size="small" |
| | | @click="handleDeleteRegion">删除</el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="left-search"> |
| | |
| | | animated /> |
| | | <template v-else> |
| | | <div class="region-item" |
| | | :class="{ active: selectedRegion === '' }" |
| | | :class="{ active: selectedRegionId === 0 }" |
| | | @click="selectRegion('')">全部</div> |
| | | <div v-for="item in filteredRegions" |
| | | :key="item.__key" |
| | | class="region-item" |
| | | :class="{ active: selectedRegion === item.regionName }" |
| | | @click="selectRegion(item.regionName)" |
| | | :title="item.regionName">{{ item.regionName }}</div> |
| | | <div v-if="filteredRegions.length === 0" |
| | | <el-tree ref="regionTreeRef" |
| | | :data="regionTreeData" |
| | | node-key="id" |
| | | :props="regionTreeProps" |
| | | :filter-node-method="filterRegionNode" |
| | | highlight-current |
| | | default-expand-all |
| | | :expand-on-click-node="false" |
| | | @node-click="handleRegionNodeClick" /> |
| | | <div v-if="regionTreeData.length === 0" |
| | | class="empty-tip">暂无地区</div> |
| | | </template> |
| | | </div> |
| | |
| | | @close="closeAddRegionDialog"> |
| | | <el-form :model="addRegionForm" |
| | | label-width="90px"> |
| | | <el-form-item label="上级地区"> |
| | | <el-cascader v-model="addRegionForm.parentPath" |
| | | :options="regionTreeData" |
| | | :props="regionCascaderProps" |
| | | clearable |
| | | filterable |
| | | placeholder="不选则为顶级地区" /> |
| | | </el-form-item> |
| | | <el-form-item label="地区名称"> |
| | | <el-input v-model="addRegionForm.regionName" |
| | | <el-input v-model="addRegionForm.regionsName" |
| | | placeholder="请输入" |
| | | clearable /> |
| | | </el-form-item> |
| | |
| | | <el-button type="primary" |
| | | @click="submitAddRegion">确认</el-button> |
| | | <el-button @click="closeAddRegionDialog">取消</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <el-dialog v-model="editRegionDialogVisible" |
| | | title="修改地区" |
| | | width="420px" |
| | | @close="closeEditRegionDialog"> |
| | | <el-form :model="editRegionForm" |
| | | label-width="90px"> |
| | | <el-form-item label="上级地区"> |
| | | <el-input :value="editRegionParentLabel" |
| | | disabled /> |
| | | </el-form-item> |
| | | <el-form-item label="地区名称"> |
| | | <el-input v-model="editRegionForm.regionsName" |
| | | placeholder="请输入" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitEditRegion">确认</el-button> |
| | | <el-button @click="closeEditRegionDialog">取消</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客户地区:" |
| | | prop="regions"> |
| | | <el-cascader v-model="formRegionPath" |
| | | :options="regionTreeData" |
| | | :props="regionCascaderProps" |
| | | clearable |
| | | filterable |
| | | style="width: 100%" |
| | | placeholder="请选择" |
| | | @change="handleFormRegionChange" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客户地址:" |
| | | prop="companyAddress"> |
| | | <el-input v-model="form.companyAddress" |
| | |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客户地区:" |
| | | prop="regions"> |
| | | <el-input v-model="form.regions" |
| | | placeholder="请输入" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">公司电话:</span> |
| | | <span class="info-label">客户电话:</span> |
| | | <span class="info-value">{{ detailForm.companyPhone }}</span> |
| | | </div> |
| | | </el-col> |
| | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">公司地址:</span> |
| | | <span class="info-label">客户地区:</span> |
| | | <span class="info-value">{{ detailForm.regionsName || detailForm.regions }}</span> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">客户地址:</span> |
| | | <span class="info-value">{{ detailForm.companyAddress }}</span> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">银行基本户:</span> |
| | | <span class="info-value">{{ detailForm.basicBankAccount }}</span> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">银行账号:</span> |
| | | <span class="info-value">{{ detailForm.bankAccount }}</span> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">开户行号:</span> |
| | | <span class="info-value">{{ detailForm.bankCode }}</span> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">联系人:</span> |
| | | <span class="info-value">{{ detailForm.contactPerson }}</span> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">联系电话:</span> |
| | | <span class="info-value">{{ detailForm.contactPhone }}</span> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">维护人:</span> |
| | | <span class="info-value">{{ detailForm.maintainer }}</span> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">维护时间:</span> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { onMounted, ref, reactive, getCurrentInstance, toRefs, computed } from "vue"; |
| | | import { onMounted, ref, reactive, getCurrentInstance, toRefs, computed, watch } from "vue"; |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { |
| | | addCustomer, |
| | |
| | | getCustomer, |
| | | listCustomer, |
| | | listCustomerRegions, |
| | | addCustomerRegions, |
| | | updateCustomerRegions, |
| | | delCustomerRegions, |
| | | updateCustomer, |
| | | // addCustomerFollow, |
| | | // updateCustomerFollow, |
| | |
| | | const detailDialogVisible = ref(false); |
| | | const detailForm = reactive({ |
| | | customerName: "", |
| | | regionsName: "", |
| | | regions: "", |
| | | customerType: "", |
| | | taxpayerIdentificationNumber: "", |
| | | companyPhone: "", |
| | |
| | | }, |
| | | { |
| | | label: "客户地区", |
| | | prop: "regions", |
| | | prop: "regionsName", |
| | | width: 120, |
| | | }, |
| | | { |
| | |
| | | customerName: "", |
| | | customerType: "", |
| | | regions: "", |
| | | regionsId: "", |
| | | }, |
| | | form: { |
| | | customerName: "", |
| | | taxpayerIdentificationNumber: "", |
| | | companyAddress: "", |
| | | regions: "", |
| | | regionsId: "", |
| | | regionsld: "", |
| | | companyPhone: "", |
| | | contactPerson: "", |
| | | contactPhone: "", |
| | |
| | | { required: true, message: "请输入", trigger: "blur" }, |
| | | ], |
| | | companyAddress: [{ required: true, message: "请输入", trigger: "blur" }], |
| | | regions: [{ required: true, message: "请选择客户地区", trigger: "change" }], |
| | | companyPhone: [{ required: true, message: "请输入", trigger: "blur" }], |
| | | // contactPerson: [{ required: true, message: "请输入", trigger: "blur" }], |
| | | // contactPhone: [{ required: true, message: "请输入", trigger: "blur" }], |
| | |
| | | const { searchForm, form, rules } = toRefs(data); |
| | | |
| | | // 左侧地区栏 |
| | | const regionTreeRef = ref(); |
| | | const regionsLoading = ref(false); |
| | | const regions = ref([]); |
| | | const regionTreeData = ref([]); |
| | | const regionKeyword = ref(""); |
| | | const selectedRegion = ref(""); // '' 表示全部 |
| | | const selectedRegionId = ref(0); // 0 表示全部 |
| | | const selectedRegionNode = ref(null); |
| | | const formRegionPath = ref([]); |
| | | const addRegionDialogVisible = ref(false); |
| | | const addRegionForm = reactive({ regionName: "" }); |
| | | const editRegionDialogVisible = ref(false); |
| | | const addRegionForm = reactive({ parentPath: [], regionsName: "" }); |
| | | const editRegionForm = reactive({ id: undefined, parentId: 0, regionsName: "" }); |
| | | const regionTreeProps = { label: "label", children: "children" }; |
| | | const regionCascaderProps = { |
| | | value: "id", |
| | | label: "label", |
| | | children: "children", |
| | | checkStrictly: true, |
| | | emitPath: true, |
| | | }; |
| | | const regionNodeMap = computed(() => { |
| | | const map = new Map(); |
| | | const walk = list => { |
| | | (list || []).forEach(node => { |
| | | map.set(node.id, node); |
| | | walk(node.children || []); |
| | | }); |
| | | }; |
| | | walk(regionTreeData.value); |
| | | return map; |
| | | }); |
| | | const editRegionParentLabel = computed(() => { |
| | | if (!editRegionForm.parentId) return "顶级地区"; |
| | | return regionNodeMap.value.get(editRegionForm.parentId)?.label || "未知"; |
| | | }); |
| | | |
| | | const normalizeRegionItem = (raw, index) => { |
| | | if (typeof raw === "string") { |
| | | return { regionName: raw, __key: `s_${raw}_${index}`, __local: false }; |
| | | } |
| | | const name = raw?.regionName ?? raw?.regions ?? raw?.name ?? raw?.label ?? ""; |
| | | return { ...raw, regionName: name, __key: raw?.id ?? `o_${name}_${index}` }; |
| | | const normalizeRegionTree = list => { |
| | | return (list || []).map(item => ({ |
| | | ...item, |
| | | label: item.label || item.regionsName || "", |
| | | regionsName: item.regionsName || item.label || "", |
| | | children: normalizeRegionTree(item.children || []), |
| | | })); |
| | | }; |
| | | |
| | | const fetchRegions = async () => { |
| | |
| | | try { |
| | | const res = await listCustomerRegions({}); |
| | | const list = res?.data ?? res?.rows ?? res ?? []; |
| | | regions.value = Array.isArray(list) |
| | | ? list.map(normalizeRegionItem).filter(i => i.regionName) |
| | | : []; |
| | | regionTreeData.value = Array.isArray(list) ? normalizeRegionTree(list) : []; |
| | | } catch (e) { |
| | | console.error("地区查询失败:", e); |
| | | regions.value = []; |
| | | regionTreeData.value = []; |
| | | } finally { |
| | | regionsLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const filteredRegions = computed(() => { |
| | | const kw = (regionKeyword.value || "").trim(); |
| | | if (!kw) return regions.value; |
| | | return regions.value.filter(r => (r.regionName || "").includes(kw)); |
| | | }); |
| | | const filterRegionNode = (value, data) => { |
| | | if (!value) return true; |
| | | return (data.label || "").includes(value); |
| | | }; |
| | | |
| | | const selectRegion = regionName => { |
| | | selectedRegion.value = regionName ?? ""; |
| | | searchForm.value.regions = selectedRegion.value || ""; |
| | | selectedRegionId.value = 0; |
| | | selectedRegionNode.value = null; |
| | | searchForm.value.regions = regionName || ""; |
| | | searchForm.value.regionsId = ""; |
| | | handleQuery(); |
| | | }; |
| | | const handleRegionNodeClick = data => { |
| | | selectedRegionId.value = data.id; |
| | | selectedRegionNode.value = data; |
| | | searchForm.value.regions = data.regionsName || data.label || ""; |
| | | searchForm.value.regionsId = data.id; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | const openAddRegionDialog = () => { |
| | | addRegionForm.regionName = ""; |
| | | addRegionForm.parentPath = []; |
| | | addRegionForm.regionsName = ""; |
| | | addRegionDialogVisible.value = true; |
| | | }; |
| | | const closeAddRegionDialog = () => { |
| | | addRegionDialogVisible.value = false; |
| | | addRegionForm.regionName = ""; |
| | | addRegionForm.parentPath = []; |
| | | addRegionForm.regionsName = ""; |
| | | }; |
| | | const submitAddRegion = () => { |
| | | const name = (addRegionForm.regionName || "").trim(); |
| | | const submitAddRegion = async () => { |
| | | const name = (addRegionForm.regionsName || "").trim(); |
| | | if (!name) return proxy.$modal.msgWarning("请输入地区名称"); |
| | | const exists = regions.value.some(r => r.regionName === name); |
| | | if (!exists) { |
| | | regions.value.unshift({ |
| | | regionName: name, |
| | | __key: `local_${Date.now()}`, |
| | | __local: true, |
| | | }); |
| | | } |
| | | proxy.$modal.msgWarning("地区新增接口未提供,已本地新增(刷新后失效)"); |
| | | const parentPath = addRegionForm.parentPath || []; |
| | | const parentId = parentPath.length ? parentPath[parentPath.length - 1] : 0; |
| | | await addCustomerRegions({ parentId, regionsName: name }); |
| | | proxy.$modal.msgSuccess("新增成功"); |
| | | await fetchRegions(); |
| | | closeAddRegionDialog(); |
| | | }; |
| | | |
| | | const openEditRegionDialog = () => { |
| | | if (!selectedRegionNode.value || selectedRegionId.value === 0) { |
| | | return proxy.$modal.msgWarning("请先选择要修改的地区"); |
| | | } |
| | | editRegionForm.id = selectedRegionNode.value.id; |
| | | editRegionForm.parentId = selectedRegionNode.value.parentId || 0; |
| | | editRegionForm.regionsName = |
| | | selectedRegionNode.value.regionsName || selectedRegionNode.value.label || ""; |
| | | editRegionDialogVisible.value = true; |
| | | }; |
| | | const closeEditRegionDialog = () => { |
| | | editRegionDialogVisible.value = false; |
| | | editRegionForm.id = undefined; |
| | | editRegionForm.parentId = 0; |
| | | editRegionForm.regionsName = ""; |
| | | }; |
| | | const submitEditRegion = async () => { |
| | | const name = (editRegionForm.regionsName || "").trim(); |
| | | if (!name) return proxy.$modal.msgWarning("请输入地区名称"); |
| | | await updateCustomerRegions({ |
| | | id: editRegionForm.id, |
| | | parentId: editRegionForm.parentId, |
| | | regionsName: name, |
| | | }); |
| | | proxy.$modal.msgSuccess("修改成功"); |
| | | await fetchRegions(); |
| | | closeEditRegionDialog(); |
| | | }; |
| | | const handleDeleteRegion = () => { |
| | | if (!selectedRegionNode.value || selectedRegionId.value === 0) { |
| | | return proxy.$modal.msgWarning("请先选择要删除的地区"); |
| | | } |
| | | ElMessageBox.confirm("删除后不可恢复,是否继续?", "删除地区", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }) |
| | | .then(async () => { |
| | | await delCustomerRegions(selectedRegionNode.value.id); |
| | | proxy.$modal.msgSuccess("删除成功"); |
| | | selectRegion(""); |
| | | await fetchRegions(); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | const handleFormRegionChange = value => { |
| | | const ids = value || []; |
| | | if (!ids.length) { |
| | | form.value.regions = ""; |
| | | form.value.regionsId = ""; |
| | | form.value.regionsld = ""; |
| | | return; |
| | | } |
| | | const lastId = ids[ids.length - 1]; |
| | | form.value.regions = regionNodeMap.value.get(lastId)?.regionsName || ""; |
| | | form.value.regionsId = lastId; |
| | | form.value.regionsld = lastId; |
| | | }; |
| | | const findRegionPathByName = (tree, targetName, parentPath = []) => { |
| | | for (const item of tree || []) { |
| | | const currentPath = [...parentPath, item.id]; |
| | | if ((item.regionsName || item.label) === targetName) { |
| | | return currentPath; |
| | | } |
| | | const childResult = findRegionPathByName(item.children || [], targetName, currentPath); |
| | | if (childResult.length) return childResult; |
| | | } |
| | | return []; |
| | | }; |
| | | const addNewContact = () => { |
| | | formYYs.value.contactList.push({ |
| | |
| | | const openForm = (type, row) => { |
| | | operationType.value = type; |
| | | form.value = {}; |
| | | formRegionPath.value = []; |
| | | form.value.regionsId = ""; |
| | | form.value.regionsld = ""; |
| | | form.value.maintainer = userStore.nickName; |
| | | formYYs.value.contactList = [ |
| | | { |
| | |
| | | if (type === "edit") { |
| | | getCustomer(row.id).then(res => { |
| | | form.value = { ...res.data }; |
| | | formRegionPath.value = findRegionPathByName( |
| | | regionTreeData.value, |
| | | form.value.regions || "" |
| | | ); |
| | | const selectedRegionId = |
| | | formRegionPath.value.length > 0 |
| | | ? formRegionPath.value[formRegionPath.value.length - 1] |
| | | : ""; |
| | | form.value.regionsId = form.value.regionsId || selectedRegionId; |
| | | form.value.regionsld = form.value.regionsld || form.value.regionsId || selectedRegionId; |
| | | formYYs.value.contactList = res.data.contactPerson |
| | | .split(",") |
| | | .map((item, index) => { |
| | |
| | | form.value.contactPhone = formYYs.value.contactList |
| | | .map(item => item.contactPhone) |
| | | .join(","); |
| | | if (!form.value.regionsId && formRegionPath.value.length) { |
| | | form.value.regionsId = formRegionPath.value[formRegionPath.value.length - 1]; |
| | | } |
| | | form.value.regionsld = form.value.regionsId || ""; |
| | | addCustomer(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("提交成功"); |
| | | closeDia(); |
| | |
| | | form.value.contactPhone = formYYs.value.contactList |
| | | .map(item => item.contactPhone) |
| | | .join(","); |
| | | if (!form.value.regionsId && formRegionPath.value.length) { |
| | | form.value.regionsId = formRegionPath.value[formRegionPath.value.length - 1]; |
| | | } |
| | | form.value.regionsld = form.value.regionsId || ""; |
| | | updateCustomer(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("提交成功"); |
| | | closeDia(); |
| | |
| | | // 关闭弹框 |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | formRegionPath.value = []; |
| | | dialogFormVisible.value = false; |
| | | }; |
| | | // 导出 |
| | |
| | | onMounted(() => { |
| | | fetchRegions(); |
| | | getList(); |
| | | }); |
| | | |
| | | watch(regionKeyword, value => { |
| | | regionTreeRef.value?.filter((value || "").trim()); |
| | | }); |
| | | </script> |
| | | |
| | |
| | | color: #303133; |
| | | } |
| | | |
| | | .left-actions { |
| | | display: flex; |
| | | gap: 6px; |
| | | } |
| | | |
| | | .left-actions :deep(.el-button) { |
| | | margin-left: 0; |
| | | padding: 5px 10px; |
| | | } |
| | | |
| | | .left-search { |
| | | margin-bottom: 10px; |
| | | } |
| | |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .left-list :deep(.el-tree) { |
| | | background: transparent; |
| | | } |
| | | |
| | | .left-list :deep(.el-tree-node__content) { |
| | | height: 30px; |
| | | border-radius: 6px; |
| | | } |
| | | |
| | | .left-list :deep(.el-tree-node__content:hover) { |
| | | background: #f5f7fa; |
| | | } |
| | | |
| | | .empty-tip { |
| | | padding: 16px 10px; |
| | | color: #909399; |