Files
yunzerwebsiteallinone/backend/src/views/apps/erp/supplier/index.vue
T
2026-09-28 23:16:26 +08:00

378 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="supplier-page">
<div class="page-header">
<div>
<h2>供应商管理</h2>
<p>管理企业供应商信息,支持多维度筛选与全生命周期追踪</p>
</div>
<div class="header-actions">
<el-button type="primary" :icon="Plus" @click="openCreate">新增供应商</el-button>
<el-button type="success" :icon="Cpu" @click="smartAddVisible = true">智能添加</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: 220px"
@keyup.enter="handleSearch"
/>
</el-form-item>
<el-form-item label="供应商类型">
<el-select v-model="filters.supplier_type" clearable placeholder="全部" style="width: 140px">
<el-option v-for="item in typeOptions" :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: 130px">
<el-option v-for="item in levelOptions" :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 statusOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="数据状态">
<el-select v-model="filters.is_draft" clearable placeholder="全部" style="width: 110px">
<el-option label="正式" value="0" />
<el-option label="草稿" value="1" />
</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 prop="supplier_name" label="供应商名称" min-width="180" show-overflow-tooltip>
<template #default="{ row }">
<div class="name-cell-wrapper">
<span class="name-cell name-link" @click="openDetail(row)">{{ row.supplier_name }}</span>
<el-tag v-if="row.is_draft === 1" type="warning" size="small" effect="plain">草稿</el-tag>
<!-- 同一家单位可能既是供应商又是客户(已建客户档案) -->
<el-tag v-if="row.also_customer" type="warning" size="small" effect="plain">也是客户</el-tag>
</div>
</template>
</el-table-column>
<el-table-column prop="supplier_type" label="类型" width="120" align="center">
<template #default="{ row }">
<el-tag :type="supplierTypeTag(row.supplier_type)" size="small">
{{ supplierTypeText(row.supplier_type) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="supplier_level" label="等级" width="110" align="center">
<template #default="{ row }">
<el-tag :type="supplierLevelTag(row.supplier_level)" size="small" effect="plain">
{{ supplierLevelText(row.supplier_level) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="contact_person" label="联系人" width="100" />
<el-table-column prop="contact_phone" label="联系电话" width="130" />
<el-table-column prop="contact_email" label="邮箱" min-width="160" show-overflow-tooltip />
<el-table-column prop="industry" label="行业" width="100" show-overflow-tooltip />
<el-table-column prop="status" label="状态" width="80" 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 prop="update_time" label="更新时间" width="160" align="center">
<template #default="{ row }">
{{ formatDateTime(row.update_time) }}
</template>
</el-table-column>
<el-table-column label="操作" width="300" align="center" fixed="right">
<template #default="{ row }">
<el-button link type="primary" size="small" @click="openContactBook(row)">通讯录</el-button>
<el-button link type="primary" size="small" @click="openEdit(row)">编辑</el-button>
<el-button link type="success" size="small" @click="handleConvertCustomer(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>
</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="fetchList"
@current-change="fetchList"
/>
</div>
</div>
<SupplierEdit
v-model:visible="editVisible"
:edit-data="currentEditData"
@success="fetchList"
/>
<SupplierDetail
v-model:visible="detailVisible"
:data="detailData"
@convert="handleConvertCustomer"
/>
<ContactBook
v-model:visible="contactBookVisible"
:company-type="'supplier'"
:company-id="currentCompany?.id"
:company-name="currentCompany?.supplier_name"
/>
<SmartAddCompany
v-model:visible="smartAddVisible"
type="supplier"
@success="fetchList"
/>
</div>
</template>
<script setup>
import { computed, ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Search, Refresh, Cpu } from '@element-plus/icons-vue'
import { getSupplierList, deleteSupplier, toggleSupplierStatus, convertSupplierToCustomer } from '@/api/supplier'
import SupplierEdit from './components/edit.vue'
import SupplierDetail from './components/detail.vue'
import ContactBook from '../components/contactBook.vue'
import SmartAddCompany from '../components/smartAddCompany.vue'
import { useSupplierDict, formatDateTime } from './dict'
import { useDictStore } from '@/stores/dict'
// 类型/等级/状态的文案与标签色统一取自数据字典
const { supplierTypeText, supplierTypeTag, supplierLevelText, supplierLevelTag, statusText, statusTag } = useSupplierDict()
const dictStore = useDictStore()
const typeOptions = computed(() => dictStore.getSelectOptions('erp_supplier_type'))
const levelOptions = computed(() => dictStore.getSelectOptions('common_object_level'))
const statusOptions = computed(() => dictStore.getSelectOptions('common_status'))
const loading = ref(false)
const tableData = ref([])
const editVisible = ref(false)
const currentEditData = ref(null)
const smartAddVisible = ref(false)
const detailVisible = ref(false)
const detailData = ref(null)
const contactBookVisible = ref(false)
const currentCompany = ref(null)
const filters = reactive({
keyword: '',
supplier_type: '',
supplier_level: '',
status: '',
is_draft: ''
})
const pagination = reactive({
page: 1,
pageSize: 20,
total: 0
})
onMounted(() => {
fetchList()
})
async function fetchList() {
loading.value = true
try {
const res = await getSupplierList({
page: pagination.page,
pageSize: pagination.pageSize,
...filters
})
if (res.code === 200) {
tableData.value = res.data.list || []
pagination.total = res.data.total || 0
}
} catch (e) {
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 = ''
filters.is_draft = ''
pagination.page = 1
fetchList()
}
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
}
function openContactBook(row) {
currentCompany.value = { ...row }
contactBookVisible.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 toggleSupplierStatus(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 deleteSupplier(row.id)
ElMessage.success('删除成功')
fetchList()
} catch (e) {
if (e !== 'cancel') {
ElMessage.error(e.message || '删除失败')
}
}
}
/**
* 供应商转客户:把供应商的企业资料与联系人复制一套到客户库。
* 同一家单位可能既是供应商又是客户,原供应商记录保留;客户库已有同名企业时只补同步联系人,不重复建档。
*/
async function handleConvertCustomer(row) {
try {
await ElMessageBox.confirm(
`将供应商「${row.supplier_name}」的企业资料与联系人复制一套到客户库?原供应商记录会保留,可在客户管理中继续完善。`,
'转为客户',
{ type: 'warning', confirmButtonText: '确认转化' }
)
} catch (e) {
return // 取消
}
try {
const res = await convertSupplierToCustomer(row.id)
const data = res?.data || {}
const c = data.contacts || {}
ElMessage.success(
data.existed
? `客户库已有同名企业,已同步联系人(新增 ${c.inserted || 0}、补充 ${c.merged || 0}),未重复建档`
: `已转为客户,并同步联系人(新增 ${c.inserted || 0})`
)
fetchList()
} catch (e) {
ElMessage.error(e.message || '转化失败')
}
}
</script>
<style lang="less" scoped>
.supplier-page {
padding: 20px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 20px;
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: 4px;
margin-bottom: 16px;
border: 1px solid var(--el-border-color-lighter);
}
.table-container {
background: var(--el-bg-color);
padding: 16px;
border-radius: 4px;
border: 1px solid var(--el-border-color-lighter);
}
.name-cell {
font-weight: 500;
color: var(--el-text-color-primary);
}
.name-cell-wrapper {
display: flex;
align-items: center;
gap: 8px;
}
.name-link {
cursor: pointer;
color: var(--el-color-primary);
&:hover {
text-decoration: underline;
}
}
.pagination {
margin-top: 16px;
display: flex;
justify-content: flex-end;
}
</style>