临时更新
This commit is contained in:
@@ -0,0 +1,533 @@
|
||||
<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.supplier_type" clearable placeholder="全部" style="width: 150px">
|
||||
<el-option
|
||||
v-for="item in SUPPLIER_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.supplier_level" clearable placeholder="全部" style="width: 140px">
|
||||
<el-option
|
||||
v-for="item in levelOptions('supplier')"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="filters.status" clearable placeholder="全部" style="width: 110px">
|
||||
<el-option
|
||||
v-for="item in STATUS_OPTIONS"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</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 row-key="id">
|
||||
<el-table-column label="供应商名称" min-width="200" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<span class="name-link" @click="openDetail(row)">{{ row.supplier_name }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="类型" width="140" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="typeTag(row.supplier_type)" size="small">
|
||||
{{ typeText('supplier', row.supplier_type) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="等级" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="levelTag(row.supplier_level)" size="small" effect="plain">
|
||||
{{ levelText('supplier', row.supplier_level) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="industry" label="行业" width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="contact_person" label="联系人" width="100" />
|
||||
<el-table-column prop="contact_phone" label="联系电话" width="130" />
|
||||
<el-table-column label="合作期限" width="200" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ formatDate(row.register_time) }} ~ {{ formatDate(row.expire_time) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusTag(row.status)" size="small">{{ statusText(row.status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="更新时间" width="160" align="center">
|
||||
<template #default="{ row }">{{ formatDateTime(row.update_time) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="260" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" size="small" @click="openEdit(row)">编辑</el-button>
|
||||
<el-button link type="primary" size="small" @click="openContact(row)">联系人</el-button>
|
||||
<el-button link type="primary" size="small" @click="handleToggleStatus(row)">
|
||||
{{ row.status === '1' ? '禁用' : '启用' }}
|
||||
</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>
|
||||
|
||||
<SupplierEdit
|
||||
v-model:visible="editVisible"
|
||||
:edit-data="currentEditData"
|
||||
@success="fetchList"
|
||||
/>
|
||||
|
||||
<!-- 详情抽屉 -->
|
||||
<el-drawer
|
||||
v-model="detailVisible"
|
||||
:title="detailData?.supplier_name || '供应商详情'"
|
||||
direction="rtl"
|
||||
size="560px"
|
||||
>
|
||||
<div v-if="detailData" class="detail-content">
|
||||
<el-descriptions title="基本信息" :column="2" border size="small">
|
||||
<el-descriptions-item label="供应商名称" :span="2">
|
||||
{{ detailData.supplier_name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="供应商类型">
|
||||
{{ typeText('supplier', detailData.supplier_type) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="供应商等级">
|
||||
{{ levelText('supplier', detailData.supplier_level) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="所属行业">{{ detailData.industry || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="statusTag(detailData.status)" size="small">
|
||||
{{ statusText(detailData.status) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="合作开始">{{ formatDate(detailData.register_time) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="合作到期">{{ formatDate(detailData.expire_time) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="供应商地址" :span="2">
|
||||
{{ detailData.address || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ formatDateTime(detailData.create_time) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">{{ formatDateTime(detailData.update_time) }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-descriptions
|
||||
title="联系信息"
|
||||
:column="2"
|
||||
border
|
||||
size="small"
|
||||
style="margin-top: 20px"
|
||||
>
|
||||
<el-descriptions-item label="联系人">{{ detailData.contact_person || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话">{{ detailData.contact_phone || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系邮箱" :span="2">
|
||||
{{ detailData.contact_email || '-' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-descriptions
|
||||
title="开票信息"
|
||||
:column="2"
|
||||
border
|
||||
size="small"
|
||||
style="margin-top: 20px"
|
||||
>
|
||||
<el-descriptions-item label="发票抬头" :span="2">
|
||||
{{ detailData.invoice_title || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="纳税人识别号">{{ detailData.tax_number || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="注册电话">{{ detailData.registered_phone || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="注册地址" :span="2">
|
||||
{{ detailData.registered_address || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="开户行">{{ detailData.bank_name || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="银行账号">{{ detailData.bank_account || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-descriptions title="备注" :column="1" border size="small" style="margin-top: 20px">
|
||||
<el-descriptions-item label="备注">{{ detailData.remark || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</el-drawer>
|
||||
|
||||
<!-- 联系人抽屉 -->
|
||||
<el-drawer
|
||||
v-model="contactVisible"
|
||||
:title="`${currentRow?.supplier_name || '供应商'} - 联系人`"
|
||||
direction="rtl"
|
||||
size="820px"
|
||||
>
|
||||
<div class="contact-drawer">
|
||||
<div class="drawer-toolbar">
|
||||
<el-button type="primary" :icon="Plus" size="small" @click="openContactCreate">
|
||||
新增联系人
|
||||
</el-button>
|
||||
<span class="count-tip">共 {{ contactList.length }} 位联系人</span>
|
||||
</div>
|
||||
<el-table :data="contactList" v-loading="contactLoading" stripe border size="small">
|
||||
<el-table-column label="姓名" width="140">
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.contact_name }}</span>
|
||||
<el-tag
|
||||
v-if="Number(row.is_primary) === 1"
|
||||
type="danger"
|
||||
size="small"
|
||||
effect="plain"
|
||||
style="margin-left: 6px"
|
||||
>
|
||||
主联系人
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="mobile" label="手机号" width="130" />
|
||||
<el-table-column prop="email" label="邮箱" min-width="180" show-overflow-tooltip />
|
||||
<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="120" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" size="small" @click="openContactEdit(row)">编辑</el-button>
|
||||
<el-button link type="danger" size="small" @click="handleContactDelete(row)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #empty>
|
||||
<el-empty description="暂无联系人" :image-size="60" />
|
||||
</template>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-drawer>
|
||||
|
||||
<ContactEdit
|
||||
v-model:visible="contactEditVisible"
|
||||
:edit-data="contactEditData"
|
||||
:default-related="contactRelated"
|
||||
@success="loadContacts"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { Plus, Search, Refresh } from "@element-plus/icons-vue";
|
||||
import {
|
||||
getCrmSupplierList,
|
||||
deleteCrmSupplier,
|
||||
toggleCrmSupplierStatus,
|
||||
} from "@/api/crm";
|
||||
import { listContacts, deleteContact } from "@/api/contact";
|
||||
import SupplierEdit from "./components/edit.vue";
|
||||
import ContactEdit from "../contact/components/edit.vue";
|
||||
import {
|
||||
SUPPLIER_TYPE_OPTIONS,
|
||||
STATUS_OPTIONS,
|
||||
levelOptions,
|
||||
typeText,
|
||||
typeTag,
|
||||
levelText,
|
||||
levelTag,
|
||||
statusText,
|
||||
statusTag,
|
||||
formatDate,
|
||||
formatDateTime,
|
||||
} from "../dict";
|
||||
|
||||
const loading = ref(false);
|
||||
const tableData = ref([]);
|
||||
const editVisible = ref(false);
|
||||
const currentEditData = ref(null);
|
||||
const detailVisible = ref(false);
|
||||
const detailData = ref(null);
|
||||
|
||||
const contactVisible = ref(false);
|
||||
const contactLoading = ref(false);
|
||||
const contactList = ref([]);
|
||||
const currentRow = ref(null);
|
||||
const contactEditVisible = ref(false);
|
||||
const contactEditData = ref(null);
|
||||
|
||||
const filters = reactive({
|
||||
keyword: "",
|
||||
supplier_type: "",
|
||||
supplier_level: "",
|
||||
status: "",
|
||||
});
|
||||
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
const contactRelated = computed(() =>
|
||||
currentRow.value
|
||||
? {
|
||||
related_type: 2,
|
||||
related_id: currentRow.value.id,
|
||||
related_name: currentRow.value.supplier_name,
|
||||
}
|
||||
: null
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
fetchList();
|
||||
});
|
||||
|
||||
async function fetchList() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getCrmSupplierList({
|
||||
page: pagination.page,
|
||||
pageSize: pagination.pageSize,
|
||||
...filters,
|
||||
});
|
||||
tableData.value = res?.data?.list || [];
|
||||
pagination.total = res?.data?.total || 0;
|
||||
} 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.supplier_type = "";
|
||||
filters.supplier_level = "";
|
||||
filters.status = "";
|
||||
handleSearch();
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
currentEditData.value = null;
|
||||
editVisible.value = true;
|
||||
}
|
||||
|
||||
function openEdit(row) {
|
||||
currentEditData.value = { ...row };
|
||||
editVisible.value = true;
|
||||
}
|
||||
|
||||
function openDetail(row) {
|
||||
detailData.value = { ...row };
|
||||
detailVisible.value = true;
|
||||
}
|
||||
|
||||
async function handleToggleStatus(row) {
|
||||
const newStatus = row.status === "1" ? "0" : "1";
|
||||
const action = newStatus === "1" ? "启用" : "禁用";
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定要${action}供应商「${row.supplier_name}」吗?`, "提示", {
|
||||
type: "warning",
|
||||
});
|
||||
await toggleCrmSupplierStatus(row.id, newStatus);
|
||||
ElMessage.success(`${action}成功`);
|
||||
fetchList();
|
||||
} catch (e) {
|
||||
if (e !== "cancel") {
|
||||
ElMessage.error(e.message || "操作失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete(row) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要删除供应商「${row.supplier_name}」吗?删除后不可恢复。`,
|
||||
"删除确认",
|
||||
{ type: "warning" }
|
||||
);
|
||||
await deleteCrmSupplier(row.id);
|
||||
ElMessage.success("删除成功");
|
||||
fetchList();
|
||||
} catch (e) {
|
||||
if (e !== "cancel") {
|
||||
ElMessage.error(e.message || "删除失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------ 联系人 ------------------------------ */
|
||||
async function openContact(row) {
|
||||
currentRow.value = { ...row };
|
||||
contactVisible.value = true;
|
||||
loadContacts();
|
||||
}
|
||||
|
||||
async function loadContacts() {
|
||||
if (!currentRow.value) return;
|
||||
contactLoading.value = true;
|
||||
try {
|
||||
const res = await listContacts({ related_type: 2, related_id: currentRow.value.id });
|
||||
const data = res?.data || {};
|
||||
contactList.value = Array.isArray(data) ? data : data.list || [];
|
||||
} catch (e) {
|
||||
contactList.value = [];
|
||||
ElMessage.error(e.message || "联系人查询失败");
|
||||
} finally {
|
||||
contactLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function openContactCreate() {
|
||||
contactEditData.value = null;
|
||||
contactEditVisible.value = true;
|
||||
}
|
||||
|
||||
function openContactEdit(row) {
|
||||
contactEditData.value = { ...row };
|
||||
contactEditVisible.value = true;
|
||||
}
|
||||
|
||||
async function handleContactDelete(row) {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定删除联系人「${row.contact_name}」吗?`, "删除确认", {
|
||||
type: "warning",
|
||||
});
|
||||
await deleteContact({ id: row.id });
|
||||
ElMessage.success("删除成功");
|
||||
loadContacts();
|
||||
} 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);
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.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-link {
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
color: var(--el-color-primary);
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.contact-drawer {
|
||||
.drawer-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.count-tip {
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user