临时更新

This commit is contained in:
2026-09-10 20:18:11 +08:00
parent e340d9a7d1
commit 26eaf1eddb
11 changed files with 4212 additions and 0 deletions
@@ -0,0 +1,285 @@
<template>
<div class="crm-page">
<div class="page-header">
<div>
<h2>联系人管理</h2>
<p>统一管理客户与供应商的对接人,支持设置主联系人</p>
</div>
<div class="header-actions">
<el-button :icon="Refresh" @click="fetchList">刷新</el-button>
<el-button type="primary" :icon="Plus" @click="openCreate">新增联系人</el-button>
</div>
</div>
<div class="filter-bar">
<el-form :inline="true" :model="filters" @submit.prevent>
<el-form-item>
<el-input
v-model="filters.keyword"
clearable
placeholder="搜索姓名 / 手机号 / 邮箱"
:prefix-icon="Search"
style="width: 240px"
@keyup.enter="handleSearch"
/>
</el-form-item>
<el-form-item label="关联类型">
<el-select v-model="filters.related_type" clearable placeholder="全部" style="width: 120px">
<el-option
v-for="item in RELATED_TYPE_OPTIONS"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="主联系人">
<el-select v-model="filters.is_primary" clearable placeholder="全部" style="width: 110px">
<el-option label="是" :value="1" />
<el-option label="否" :value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="handleSearch">查询</el-button>
<el-button :icon="Refresh" @click="resetFilters">重置</el-button>
</el-form-item>
</el-form>
</div>
<div class="table-container" v-loading="loading">
<el-table :data="tableData" stripe border>
<el-table-column type="index" label="#" width="56" align="center" />
<el-table-column label="姓名" min-width="140" show-overflow-tooltip>
<template #default="{ row }">
<div class="name-cell-wrapper">
<span class="name-cell">{{ row.contact_name }}</span>
<el-tag v-if="Number(row.is_primary) === 1" type="danger" size="small" effect="plain">
主联系人
</el-tag>
</div>
</template>
</el-table-column>
<el-table-column label="性别" width="70" align="center">
<template #default="{ row }">{{ genderText(row.gender) }}</template>
</el-table-column>
<el-table-column prop="mobile" label="手机号" width="130" />
<el-table-column prop="phone" label="座机" width="130" show-overflow-tooltip />
<el-table-column prop="email" label="邮箱" min-width="170" show-overflow-tooltip />
<el-table-column label="关联类型" width="100" align="center">
<template #default="{ row }">
<el-tag size="small" :type="Number(row.related_type) === 1 ? 'primary' : 'warning'">
{{ relatedTypeText(row.related_type) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="关联对象" min-width="180" show-overflow-tooltip>
<template #default="{ row }">{{ row.related_name || '-' }}</template>
</el-table-column>
<el-table-column prop="department" label="部门" width="120" show-overflow-tooltip />
<el-table-column prop="position" label="职位" width="130" show-overflow-tooltip />
<el-table-column label="更新时间" width="160" align="center">
<template #default="{ row }">{{ formatDateTime(row.update_time) }}</template>
</el-table-column>
<el-table-column label="操作" width="220" align="center" fixed="right">
<template #default="{ row }">
<el-button link type="primary" size="small" @click="openEdit(row)">编辑</el-button>
<el-button
v-if="Number(row.is_primary) !== 1"
link
type="primary"
size="small"
@click="handleSetPrimary(row)"
>
设为主联系人
</el-button>
<el-button link type="danger" size="small" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
<template #empty>
<el-empty description="暂无联系人" :image-size="80" />
</template>
</el-table>
<div class="pagination">
<el-pagination
v-model:current-page="pagination.page"
v-model:page-size="pagination.pageSize"
:total="pagination.total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSearch"
@current-change="fetchList"
/>
</div>
</div>
<ContactEdit
v-model:visible="editVisible"
:edit-data="currentEditData"
@success="fetchList"
/>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { Plus, Search, Refresh } from "@element-plus/icons-vue";
import { listContacts, updateContact, deleteContact } from "@/api/contact";
import ContactEdit from "./components/edit.vue";
import {
RELATED_TYPE_OPTIONS,
genderText,
relatedTypeText,
formatDateTime,
} from "../dict";
const loading = ref(false);
const tableData = ref([]);
const editVisible = ref(false);
const currentEditData = ref(null);
const filters = reactive({
keyword: "",
related_type: "",
is_primary: "",
});
const pagination = reactive({
page: 1,
pageSize: 20,
total: 0,
});
onMounted(() => {
fetchList();
});
async function fetchList() {
loading.value = true;
try {
const res = await listContacts({
page: pagination.page,
pageSize: pagination.pageSize,
...filters,
});
const data = res?.data || {};
const list = Array.isArray(data) ? data : data.list || [];
tableData.value = list;
pagination.total = data.total || list.length;
} catch (e) {
tableData.value = [];
pagination.total = 0;
ElMessage.error(e.message || "查询失败");
} finally {
loading.value = false;
}
}
function handleSearch() {
pagination.page = 1;
fetchList();
}
function resetFilters() {
filters.keyword = "";
filters.related_type = "";
filters.is_primary = "";
handleSearch();
}
function openCreate() {
currentEditData.value = null;
editVisible.value = true;
}
function openEdit(row) {
currentEditData.value = { ...row };
editVisible.value = true;
}
async function handleSetPrimary(row) {
try {
await updateContact({ ...row, is_primary: 1 });
ElMessage.success("已设为主联系人");
fetchList();
} catch (e) {
ElMessage.error(e.message || "操作失败");
}
}
async function handleDelete(row) {
try {
await ElMessageBox.confirm(
`确定要删除联系人「${row.contact_name}」吗?删除后不可恢复。`,
"删除确认",
{ type: "warning" }
);
await deleteContact({ id: row.id });
ElMessage.success("删除成功");
fetchList();
} catch (e) {
if (e !== "cancel") {
ElMessage.error(e.message || "删除失败");
}
}
}
</script>
<style lang="less" scoped>
.crm-page {
padding: 20px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 16px;
h2 {
margin: 0 0 6px 0;
font-size: 20px;
font-weight: 600;
color: var(--el-text-color-primary);
}
p {
margin: 0;
font-size: 13px;
color: var(--el-text-color-secondary);
}
}
.filter-bar {
background: var(--el-bg-color);
padding: 16px 16px 0;
border-radius: 6px;
margin-bottom: 12px;
border: 1px solid var(--el-border-color-lighter);
}
.table-container {
background: var(--el-bg-color);
padding: 16px;
border-radius: 6px;
border: 1px solid var(--el-border-color-lighter);
}
.name-cell-wrapper {
display: flex;
align-items: center;
gap: 8px;
}
.name-cell {
font-weight: 500;
color: var(--el-text-color-primary);
}
.pagination {
margin-top: 16px;
display: flex;
justify-content: flex-end;
}
</style>