完善供应商和客户功能

This commit is contained in:
2025-11-25 17:35:33 +08:00
parent aae2fe1e14
commit 7e4ac8fe67
20 changed files with 1524 additions and 485 deletions
+15
View File
@@ -70,3 +70,18 @@ export function deleteCustomer(id, tenantId) {
data: { id, tenantId }
})
}
/**
* 更新开票信息
* @param {number|string} id 客户ID
* @param {Object} data 更新的数据
* @param {number} data.tenantId 租户ID
* @returns {Promise}
*/
export function updateInvoice(id, data) {
return request({
url: '/api/crm/customer/update-invoice',
method: 'post',
data: { id, ...data }
})
}
+17 -1
View File
@@ -35,10 +35,11 @@ export function getSupplier(id) {
* @returns {Promise}
*/
export function createSupplier(data) {
const { id, ...payload } = data
return request({
url: '/api/crm/supplier/add',
method: 'post',
data
data: payload
})
}
@@ -70,3 +71,18 @@ export function deleteSupplier(id, tenantId) {
data: { id, tenantId }
})
}
/**
* 更新供应商开票信息
* @param {number|string} id 供应商ID
* @param {Object} data 更新的数据
* @param {number} data.tenantId 租户ID
* @returns {Promise}
*/
export function updateSupplierInvoice(id, data) {
return request({
url: '/api/crm/supplier/update-invoice',
method: 'post',
data: { id, ...data }
})
}
@@ -1,60 +1,180 @@
<template>
<el-drawer
v-model="visible"
title="联系人管理"
size="600px"
:close-on-click-modal="false"
>
<el-drawer v-model="visible" size="900px" :close-on-click-modal="false">
<template #header>
<div class="drawer-header">
<div class="header-left">
<el-icon class="header-icon">
<User />
</el-icon>
<span class="header-title">联系人管理</span>
</div>
</div>
</template>
<div class="contact-container">
<el-button type="primary" @click="handleAdd" style="margin-bottom: 16px">
新增联系人
</el-button>
<el-table :data="contactList" border>
<el-table-column prop="name" label="姓名" />
<el-table-column prop="phone" label="电话" />
<el-table-column prop="email" label="邮箱" />
<el-table-column prop="position" label="职位" />
<el-table-column label="操作" width="150">
<template #default="{ row }">
<el-button link type="primary" @click="handleEdit(row)">编辑</el-button>
<el-button link type="danger" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="header-right">
<el-button type="primary" @click="handleAdd">
<el-icon>
<Plus />
</el-icon>
新增联系人
</el-button>
<el-button @click="fetchList">
<el-icon>
<Refresh />
</el-icon>
刷新
</el-button>
</div>
<el-divider />
<div class="contact-cards">
<div v-for="contact in contactList" :key="contact.id" class="contact-card">
<div class="card-header">
<div class="contact-name">
<el-icon class="name-icon">
<User />
</el-icon>
{{ contact.name }}
<span class="gender-tag">{{ getGenderText(contact.gender) }}</span>
<el-tag v-if="contact.is_primary" type="success" size="small" style="margin-left: 8px">主联系人</el-tag>
</div>
<div class="card-actions">
<el-button link type="primary" size="small" @click="handleEdit(contact)">编辑</el-button>
<el-button link type="danger" size="small" @click="handleDelete(contact)">删除</el-button>
</div>
</div>
<div class="card-body">
<div class="info-grid">
<div class="info-row">
<span class="info-label">电话1:</span>
<span>{{ contact.phone1 || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">电话2:</span>
<span>{{ contact.phone2 || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">电话3:</span>
<span>{{ contact.phone3 || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">邮箱:</span>
<span>{{ contact.email || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">职位:</span>
<span>{{ contact.position || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">部门:</span>
<span>{{ contact.department || '-' }}</span>
</div>
</div>
<div class="address-row">
<span class="info-label">家庭住址:</span>
<span>{{ contact.address || '-' }}</span>
</div>
<div class="address-row">
<span class="info-label">备注:</span>
<span>{{ contact.remark || '-' }}</span>
</div>
</div>
</div>
<div v-if="contactList.length === 0" class="empty-state">
<el-empty description="暂无联系人" />
</div>
</div>
</div>
<el-dialog
v-model="dialogVisible"
:title="isEdit ? '编辑联系人' : '新增联系人'"
width="500px"
>
<el-dialog v-model="dialogVisible" :title="isEdit ? '编辑联系人' : '新增联系人'" width="700px">
<el-form :model="form" :rules="rules" ref="formRef" label-width="80px">
<el-form-item label="姓名" prop="name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="电话" prop="phone">
<el-input v-model="form.phone" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" />
</el-form-item>
<el-form-item label="职位" prop="position">
<el-input v-model="form.position" />
</el-form-item>
<el-form-item label="性别" prop="gender">
<el-select v-model="form.gender" placeholder="请选择性别" clearable style="width: 100%">
<el-option label="未知" :value="0" />
<el-option label="男" :value="1" />
<el-option label="女" :value="2" />
</el-select>
</el-form-item>
<el-form-item label="部门" prop="department">
<el-input v-model="form.department" />
</el-form-item>
<el-form-item label="主联系人" prop="is_primary">
<el-switch v-model="form.is_primary" :active-value="1" :inactive-value="0" />
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="姓名" prop="name">
<el-input v-model="form.name" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="电话1" prop="phone1">
<el-input v-model="form.phone1" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="电话2" prop="phone2">
<el-input v-model="form.phone2" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="电话3" prop="phone3">
<el-input v-model="form.phone3" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="QQ" prop="qq">
<el-input v-model="form.qq" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="微信" prop="wechat">
<el-input v-model="form.wechat" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="性别" prop="gender">
<el-select v-model="form.gender" placeholder="请选择性别" clearable style="width: 100%">
<el-option label="未知" :value="0" />
<el-option label="男" :value="1" />
<el-option label="女" :value="2" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="部门" prop="department">
<el-input v-model="form.department" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="职位" prop="position">
<el-input v-model="form.position" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="家庭住址" prop="address">
<el-input v-model="form.address" type="textarea" :rows="2" placeholder="请输入家庭住址" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注信息" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="主联系人" prop="is_primary">
<el-switch v-model="form.is_primary" :active-value="1" :inactive-value="0" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
@@ -67,6 +187,7 @@
<script setup>
import { ref, computed, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { User, Phone, Message, OfficeBuilding, School, Plus, Refresh } from '@element-plus/icons-vue'
import { listContacts, createContact, updateContact, deleteContact } from '@/api/contact.js'
const props = defineProps({
@@ -89,11 +210,17 @@ const formRef = ref(null)
const form = ref({
id: null,
name: '',
phone: '',
phone1: '',
phone2: '',
phone3: '',
qq: '',
wechat: '',
email: '',
position: '',
gender: 0,
department: '',
address: '',
remark: '',
is_primary: 0,
})
@@ -103,10 +230,14 @@ const rules = {
function handleAdd() {
isEdit.value = false
form.value = { id: null, name: '', phone: '', email: '', position: '' }
form.value = { id: null, name: '', phone1: '', phone2: '', phone3: '', qq: '', wechat: '', email: '', position: '', gender: 0, department: '', address: '', remark: '', is_primary: 0 }
dialogVisible.value = true
}
function handleRefresh() {
fetchList()
}
function handleEdit(row) {
isEdit.value = true
form.value = { ...row }
@@ -121,10 +252,18 @@ function getTenantId() {
const tid = u.tenant_id || u.tenantId || ''
return tid != null && tid !== undefined ? String(tid) : ''
}
} catch (e) {}
} catch (e) { }
return ''
}
function getGenderText(gender) {
switch (gender) {
case 1: return '男'
case 2: return '女'
default: return '未知'
}
}
async function fetchList() {
const tenant_id = getTenantId()
if (!props.model?.id || !tenant_id) return
@@ -135,11 +274,17 @@ async function fetchList() {
contactList.value = (resp.data || []).map(it => ({
id: it.id,
name: it.contact_name,
phone: it.mobile || it.phone || '',
phone1: it.phone1 || '',
phone2: it.phone2 || '',
phone3: it.phone3 || '',
qq: it.qq || '',
wechat: it.wechat || '',
email: it.email || '',
position: it.position || '',
gender: Number(it.gender || 0),
department: it.department || '',
address: it.home_address || '',
remark: it.remark || '',
is_primary: Number(it.is_primary || 0),
_raw: it,
}))
@@ -165,13 +310,19 @@ async function onSubmit() {
const base = {
tenant_id,
related_type: 1,
related_id: props.model?.id,
related_id: String(props.model?.id),
contact_name: form.value.name,
mobile: form.value.phone,
phone1: form.value.phone1,
phone2: form.value.phone2,
phone3: form.value.phone3,
qq: form.value.qq,
wechat: form.value.wechat,
email: form.value.email,
position: form.value.position,
gender: form.value.gender,
department: form.value.department,
home_address: form.value.address,
remark: form.value.remark,
is_primary: form.value.is_primary,
}
if (isEdit.value) {
@@ -198,4 +349,144 @@ watch(() => props.model, (m) => {
.contact-container {
padding: 0 20px;
}
.contact-cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 16px;
}
.contact-card {
border: 1px solid #e4e7ed;
border-radius: 8px;
padding: 16px;
background: #fff;
transition: all 0.3s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.contact-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
}
.contact-name {
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
font-weight: 600;
color: #303133;
}
.gender-tag {
font-size: 12px;
padding: 2px 6px;
background-color: #f0f9ff;
color: #1e40af;
border-radius: 4px;
font-weight: normal;
}
.name-icon {
margin-right: 8px;
color: #409eff;
}
.card-actions {
display: flex;
gap: 8px;
}
.card-body {
display: flex;
flex-direction: column;
gap: 12px;
}
.info-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px 16px;
}
.info-row {
display: flex;
align-items: center;
font-size: 14px;
color: #606266;
gap: 8px;
}
.info-label {
font-weight: 500;
color: #303133;
min-width: 50px;
}
.address-row {
display: flex;
align-items: flex-start;
font-size: 14px;
color: #606266;
gap: 8px;
padding-top: 8px;
border-top: 1px solid #f0f0f0;
}
.address-row .info-label {
min-width: 70px;
}
.empty-state {
grid-column: 1 / -1;
display: flex;
justify-content: center;
align-items: center;
padding: 40px 0;
}
.tools {
display: flex;
justify-content: space-between;
}
.drawer-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 0 16px 0;
border-bottom: 1px solid #e4e7ed;
}
.header-left {
display: flex;
align-items: center;
gap: 8px;
}
.header-icon {
font-size: 18px;
color: #409eff;
}
.header-title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
.header-right {
display: flex;
gap: 8px;
}
</style>
@@ -1,10 +1,15 @@
<template>
<el-drawer
v-model="visible"
title="开票信息"
size="600px"
:close-on-click-modal="false"
>
<template #header>
<div style="display: flex; align-items: center;">
<span>开票信息</span>
<el-button @click="copyAll" type="primary" style="margin-left: 20px;">一键复制</el-button>
</div>
</template>
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
<el-form-item label="发票抬头" prop="invoice_title">
<el-input v-model="form.invoice_title" placeholder="请输入发票抬头" />
@@ -24,6 +29,12 @@
<el-form-item label="注册电话" prop="registered_phone">
<el-input v-model="form.registered_phone" placeholder="请输入注册电话" />
</el-form-item>
<el-divider />
<!-- 智能识别框 -->
<el-form-item label="智能识别">
<el-input v-model="smartText" rows="6" type="textarea" placeholder="请粘贴包含关键信息的文本" />
<el-button type="primary" style="margin-top: 10px;" @click="smartRecognize">智能识别</el-button>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="onCancel">取消</el-button>
@@ -36,6 +47,7 @@
import { ref, reactive, computed, watch } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
import { ElMessage } from 'element-plus'
import { updateInvoice } from '@/api/customer'
const props = defineProps<{ modelValue: boolean, model?: any }>()
const emit = defineEmits(['update:modelValue', 'saved'])
@@ -47,6 +59,7 @@ const visible = computed({
const formRef = ref<FormInstance>()
const submitting = ref(false)
const smartText = ref('')
const form = reactive({
invoice_title: '',
tax_number: '',
@@ -75,12 +88,37 @@ function onCancel() {
visible.value = false
}
function getTenantId(): string {
let v: any = props.model?.tenantId ?? props.model?.tenant_id
if (!v) {
const keys = ['userinfo', 'userInfo', 'user']
let s = ''
for (const k of keys) {
s = localStorage.getItem(k) || sessionStorage.getItem(k) || ''
if (s) break
}
if (s) {
try {
const u = JSON.parse(s)
v = u?.tenantId ?? u?.tenant_id ?? u?.tenant?.id
?? u?.user?.tenantId ?? u?.user?.tenant_id ?? u?.user?.tenant?.id
?? u?.userInfo?.tenantId ?? u?.userInfo?.tenant_id ?? u?.userInfo?.tenant?.id
} catch {}
}
// Fallback for testing: replace with your real tenant id or remove this line
if (!v) v = '1'
}
return (v ?? '').toString()
}
async function onSubmit() {
if (!formRef.value) return
await formRef.value.validate()
try {
submitting.value = true
// TODO: 调用保存开票信息的API
const tenantId = getTenantId()
const tenant_id = tenantId
await updateInvoice(props.model.id, { ...form, tenantId, tenant_id })
ElMessage.success('保存成功')
emit('saved')
visible.value = false
@@ -89,6 +127,51 @@ async function onSubmit() {
}
}
function copyAll() {
const lines = [
`名称:${form.invoice_title}`,
`纳税人识别号:${form.tax_number}`,
`地址:${form.registered_address}`,
`电话:${form.registered_phone}`,
`开户行:${form.bank_name}`,
`账号:${form.bank_account}`
]
const text = lines.join('\n')
navigator.clipboard.writeText(text).then(() => {
ElMessage.success('已复制到剪贴板')
}).catch(() => {
ElMessage.error('复制失败')
})
}
function smartRecognize() {
const text = smartText.value || ''
const lines = text.split(/\r?\n/).map(l => l.trim()).filter(Boolean)
if (!lines.length) {
ElMessage.warning('请先粘贴要识别的内容')
return
}
const map: Record<string, string> = {}
const re = /^(名称|纳税人识别号|地址|电话|开户行|账号)\s*[:]\s*(.+)$/
for (const line of lines) {
const m = line.match(re)
if (m) {
map[m[1]] = m[2].trim()
}
}
if (Object.keys(map).length === 0) {
ElMessage.warning('未识别到有效字段')
return
}
if (map['名称']) form.invoice_title = map['名称']
if (map['纳税人识别号']) form.tax_number = map['纳税人识别号'].replace(/\s+/g, '')
if (map['地址']) form.registered_address = map['地址']
if (map['电话']) form.registered_phone = map['电话'].replace(/\s*-\s*/g, '-')
if (map['开户行']) form.bank_name = map['开户行']
if (map['账号']) form.bank_account = map['账号'].replace(/\s+/g, '')
ElMessage.success('识别完成,已填充表单')
}
watch(() => props.model, (m) => {
if (m) {
form.invoice_title = m.invoice_title ?? ''
+3 -3
View File
@@ -18,9 +18,9 @@
<el-table :data="customerList" v-loading="loading" stripe border style="width: 100%">
<el-table-column prop="name" label="客户名称" width="220" />
<el-table-column prop="contact" label="联系人" width="100" />
<el-table-column prop="phone" label="联系电话" min-width="80" />
<el-table-column prop="email" label="邮箱" min-width="100" />
<el-table-column prop="address" label="地址" show-overflow-tooltip />
<el-table-column prop="phone" label="联系电话" min-width="120" />
<el-table-column prop="email" label="邮箱" min-width="150" />
<el-table-column prop="address" label="地址" min-width="150" show-overflow-tooltip />
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.status === 1 ? 'success' : 'info'">
@@ -1,60 +1,188 @@
<template>
<el-drawer
v-model="visible"
title="联系人管理"
size="600px"
:close-on-click-modal="false"
>
<el-drawer v-model="visible" size="900px" :close-on-click-modal="false">
<template #header>
<div class="drawer-header">
<div class="header-left">
<el-icon class="header-icon">
<User />
</el-icon>
<span class="header-title">联系人管理</span>
</div>
</div>
</template>
<div class="contact-container">
<el-button type="primary" @click="handleAdd" style="margin-bottom: 16px">
新增联系人
</el-button>
<el-table :data="contactList" border>
<el-table-column prop="name" label="姓名" />
<el-table-column prop="phone" label="电话" />
<el-table-column prop="email" label="邮箱" />
<el-table-column prop="position" label="职位" />
<el-table-column label="操作" width="150">
<template #default="{ row }">
<el-button link type="primary" @click="handleEdit(row)">编辑</el-button>
<el-button link type="danger" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="header-right">
<el-button type="primary" @click="handleAdd">
<el-icon>
<Plus />
</el-icon>
新增联系人
</el-button>
<el-button @click="fetchList">
<el-icon>
<Refresh />
</el-icon>
刷新
</el-button>
</div>
<el-divider />
<div class="contact-cards">
<div v-for="contact in contactList" :key="contact.id" class="contact-card">
<div class="card-header">
<div class="contact-name">
<el-icon class="name-icon">
<User />
</el-icon>
{{ contact.name }}
<span class="gender-tag">{{ getGenderText(contact.gender) }}</span>
<el-tag v-if="contact.is_primary" type="success" size="small" style="margin-left: 8px">主联系人</el-tag>
</div>
<div class="card-actions">
<el-button link type="primary" size="small" @click="handleEdit(contact)">编辑</el-button>
<el-button link type="danger" size="small" @click="handleDelete(contact)">删除</el-button>
</div>
</div>
<div class="card-body">
<div class="info-grid">
<div class="info-row">
<span class="info-label">电话1:</span>
<span>{{ contact.phone1 || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">电话2:</span>
<span>{{ contact.phone2 || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">电话3:</span>
<span>{{ contact.phone3 || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">邮箱:</span>
<span>{{ contact.email || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">QQ:</span>
<span>{{ contact.qq || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">微信:</span>
<span>{{ contact.wechat || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">职位:</span>
<span>{{ contact.position || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">部门:</span>
<span>{{ contact.department || '-' }}</span>
</div>
</div>
<div class="address-row">
<span class="info-label">家庭住址:</span>
<span>{{ contact.address || '-' }}</span>
</div>
<div class="address-row">
<span class="info-label">备注:</span>
<span>{{ contact.remark || '-' }}</span>
</div>
</div>
</div>
<div v-if="contactList.length === 0" class="empty-state">
<el-empty description="暂无联系人" />
</div>
</div>
</div>
<el-dialog
v-model="dialogVisible"
:title="isEdit ? '编辑联系人' : '新增联系人'"
width="500px"
>
<el-dialog v-model="dialogVisible" :title="isEdit ? '编辑联系人' : '新增联系人'" width="700px">
<el-form :model="form" :rules="rules" ref="formRef" label-width="80px">
<el-form-item label="姓名" prop="name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="电话" prop="phone">
<el-input v-model="form.phone" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" />
</el-form-item>
<el-form-item label="职位" prop="position">
<el-input v-model="form.position" />
</el-form-item>
<el-form-item label="性别" prop="gender">
<el-select v-model="form.gender" placeholder="请选择性别" clearable style="width: 100%">
<el-option label="未知" :value="0" />
<el-option label="男" :value="1" />
<el-option label="女" :value="2" />
</el-select>
</el-form-item>
<el-form-item label="部门" prop="department">
<el-input v-model="form.department" />
</el-form-item>
<el-form-item label="主联系人" prop="is_primary">
<el-switch v-model="form.is_primary" :active-value="1" :inactive-value="0" />
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="姓名" prop="name">
<el-input v-model="form.name" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="电话1" prop="phone1">
<el-input v-model="form.phone1" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="电话2" prop="phone2">
<el-input v-model="form.phone2" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="电话3" prop="phone3">
<el-input v-model="form.phone3" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="QQ" prop="qq">
<el-input v-model="form.qq" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="微信" prop="wechat">
<el-input v-model="form.wechat" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="性别" prop="gender">
<el-select v-model="form.gender" placeholder="请选择性别" clearable style="width: 100%">
<el-option label="未知" :value="0" />
<el-option label="男" :value="1" />
<el-option label="女" :value="2" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="部门" prop="department">
<el-input v-model="form.department" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="职位" prop="position">
<el-input v-model="form.position" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="家庭住址" prop="address">
<el-input v-model="form.address" type="textarea" :rows="2" placeholder="请输入家庭住址" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注信息" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="主联系人" prop="is_primary">
<el-switch v-model="form.is_primary" :active-value="1" :inactive-value="0" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
@@ -67,6 +195,7 @@
<script setup>
import { ref, computed, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { User, Phone, Message, ChatDotRound, OfficeBuilding, School, Plus, Refresh } from '@element-plus/icons-vue'
import { listContacts, createContact, updateContact, deleteContact } from '@/api/contact.js'
const props = defineProps({
@@ -89,11 +218,17 @@ const formRef = ref(null)
const form = ref({
id: null,
name: '',
phone: '',
phone1: '',
phone2: '',
phone3: '',
qq: '',
wechat: '',
email: '',
position: '',
gender: 0,
department: '',
address: '',
remark: '',
is_primary: 0,
})
@@ -103,10 +238,14 @@ const rules = {
function handleAdd() {
isEdit.value = false
form.value = { id: null, name: '', phone: '', email: '', position: '' }
form.value = { id: null, name: '', phone1: '', phone2: '', phone3: '', qq: '', wechat: '', email: '', position: '', gender: 0, department: '', address: '', remark: '', is_primary: 0 }
dialogVisible.value = true
}
function handleRefresh() {
fetchList()
}
function handleEdit(row) {
isEdit.value = true
form.value = { ...row }
@@ -121,10 +260,18 @@ function getTenantId() {
const tid = u.tenant_id || u.tenantId || ''
return tid != null && tid !== undefined ? String(tid) : ''
}
} catch (e) {}
} catch (e) { }
return ''
}
function getGenderText(gender) {
switch (gender) {
case 1: return '男'
case 2: return '女'
default: return '未知'
}
}
async function fetchList() {
const tenant_id = getTenantId()
if (!props.model?.id || !tenant_id) return
@@ -135,11 +282,17 @@ async function fetchList() {
contactList.value = (resp.data || []).map(it => ({
id: it.id,
name: it.contact_name,
phone: it.mobile || it.phone || '',
phone1: it.phone1 || '',
phone2: it.phone2 || '',
phone3: it.phone3 || '',
qq: it.qq || '',
wechat: it.wechat || '',
email: it.email || '',
position: it.position || '',
gender: Number(it.gender || 0),
department: it.department || '',
address: it.home_address || '',
remark: it.remark || '',
is_primary: Number(it.is_primary || 0),
_raw: it,
}))
@@ -165,13 +318,19 @@ async function onSubmit() {
const base = {
tenant_id,
related_type: 2,
related_id: props.model?.id,
related_id: String(props.model?.id),
contact_name: form.value.name,
mobile: form.value.phone,
phone1: form.value.phone1,
phone2: form.value.phone2,
phone3: form.value.phone3,
qq: form.value.qq,
wechat: form.value.wechat,
email: form.value.email,
position: form.value.position,
gender: form.value.gender,
department: form.value.department,
home_address: form.value.address,
remark: form.value.remark,
is_primary: form.value.is_primary,
}
if (isEdit.value) {
@@ -198,4 +357,146 @@ watch(() => props.model, (m) => {
.contact-container {
padding: 0 20px;
}
.contact-cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 16px;
}
.contact-card {
border: 1px solid #e4e7ed;
border-radius: 8px;
padding: 16px;
background: #fff;
transition: all 0.3s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.contact-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
}
.contact-name {
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
font-weight: 600;
color: #303133;
}
.gender-tag {
font-size: 12px;
padding: 2px 6px;
background-color: #f0f9ff;
color: #1e40af;
border-radius: 4px;
font-weight: normal;
}
.name-icon {
margin-right: 8px;
color: #409eff;
}
.card-actions {
display: flex;
gap: 8px;
}
.card-body {
display: flex;
flex-direction: column;
gap: 12px;
}
.info-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px 16px;
}
.info-row {
display: flex;
align-items: center;
font-size: 14px;
color: #606266;
gap: 8px;
}
.info-label {
font-weight: 500;
color: #303133;
min-width: 50px;
}
.address-row {
display: flex;
align-items: flex-start;
font-size: 14px;
color: #606266;
gap: 8px;
padding-top: 8px;
border-top: 1px solid #f0f0f0;
}
.address-row .info-label {
min-width: 70px;
}
.empty-state {
grid-column: 1 / -1;
display: flex;
justify-content: center;
align-items: center;
padding: 40px 0;
}
.tools {
display: flex;
justify-content: space-between;
}
.drawer-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 0 16px 0;
border-bottom: 1px solid #e4e7ed;
}
.header-left {
display: flex;
align-items: center;
gap: 8px;
}
.header-icon {
font-size: 18px;
color: #409eff;
}
.header-title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
.header-right {
display: flex;
gap: 8px;
}
</style>
@@ -1,16 +1,16 @@
<template>
<el-drawer v-model="visible" :title="title" size="40%" destroy-on-close>
<el-descriptions :column="2" border v-if="model">
<el-descriptions-item label="供应商名称">{{ model.name || '-' }}</el-descriptions-item>
<el-descriptions-item label="联系人">{{ model.contact || '-' }}</el-descriptions-item>
<el-descriptions-item label="电话">{{ model.phone || '-' }}</el-descriptions-item>
<el-descriptions-item label="邮箱">{{ model.email || '-' }}</el-descriptions-item>
<el-descriptions-item label="供应商类型">{{ getLabel(supplierTypeOptions, model.supplier_type) }}</el-descriptions-item>
<el-descriptions-item label="供应商等级">{{ getLabel(supplierLevelOptions, model.supplier_level) }}</el-descriptions-item>
<el-descriptions-item label="所属行业">{{ getLabel(industryOptions, model.industry) }}</el-descriptions-item>
<el-descriptions-item label="供应商名称">{{ model.supplier_name || '-' }}</el-descriptions-item>
<el-descriptions-item label="联系人">{{ model.contact_person || '-' }}</el-descriptions-item>
<el-descriptions-item label="电话">{{ model.contact_phone || '-' }}</el-descriptions-item>
<el-descriptions-item label="邮箱">{{ model.contact_email || '-' }}</el-descriptions-item>
<el-descriptions-item label="供应商类型">{{ getDictLabel('supplier_type', model.supplier_type) }}</el-descriptions-item>
<el-descriptions-item label="供应商等级">{{ getDictLabel('supplier_level', model.supplier_level) }}</el-descriptions-item>
<el-descriptions-item label="所属行业">{{ getDictLabel('industry', model.industry) }}</el-descriptions-item>
<el-descriptions-item label="供应商状态">
<el-tag :type="(model.status===1||model.status==='1') ? 'success' : 'info'">
{{ getLabel(supplierStatusOptions, String(model.status ?? '')) || ((model.status===1||model.status==='1') ? '正常' : '停用') }}
{{ getDictLabel('supplier_status', model.status) || ((model.status===1||model.status==='1') ? '正常' : '停用') }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="地址" :span="2">{{ model.address || '-' }}</el-descriptions-item>
@@ -33,39 +33,68 @@ const visible = computed({
set: (v: boolean) => emit('update:modelValue', v)
})
const title = computed(() => `供应商详情${props.model?.name ? ' - ' + props.model.name : ''}`)
const title = computed(() => `供应商详情${props.model?.supplier_name ? ' - ' + props.model.supplier_name : ''}`)
const supplierTypeOptions = ref<{label:string,value:string}[]>([])
const supplierLevelOptions = ref<{label:string,value:string}[]>([])
const industryOptions = ref<{label:string,value:string}[]>([])
const supplierStatusOptions = ref<{label:string,value:string}[]>([])
// 字典数据存储
const dictData = ref<Record<string, Array<{label: string, value: string}>>>({})
function mapDictItems(items: any[]) {
return (items || []).map(it => ({ label: it.dict_label, value: String(it.dict_value) }))
}
function getLabel(options: {label:string,value:string}[], value: any) {
const val = value != null && value !== undefined ? String(value) : ''
// 获取字典标签
function getDictLabel(dictCode: string, value: any) {
if (!value || value === '' || value === null || value === undefined) return '-'
const options = dictData.value[dictCode] || []
if (!options.length) return String(value)
const val = String(value)
const found = options.find(o => o.value === val)
return found ? found.label : '-'
return found ? found.label : val
}
// 加载字典数据
async function loadDictOptions() {
const [typeRes, levelRes, industryRes, statusRes] = await Promise.all([
request({ url: '/api/dict/items/code/supplier_type', method: 'get' }),
request({ url: '/api/dict/items/code/supplier_level', method: 'get' }),
request({ url: '/api/dict/items/code/industry', method: 'get' }),
request({ url: '/api/dict/items/code/supplier_status', method: 'get' }),
])
const getData = (r:any) => (r && r.data && (r.data.data || r.data)) || []
supplierTypeOptions.value = mapDictItems(getData(typeRes))
supplierLevelOptions.value = mapDictItems(getData(levelRes))
industryOptions.value = mapDictItems(getData(industryRes))
supplierStatusOptions.value = mapDictItems(getData(statusRes))
try {
const dictCodes = ['supplier_type', 'supplier_level', 'industry', 'supplier_status']
const promises = dictCodes.map(code =>
request({ url: `/api/dict/items/code/${code}`, method: 'get' })
.then((res: any) => {
console.log(`${code} 字典响应:`, res)
return { code, res }
})
.catch((err: any) => {
console.error(`${code} 字典加载失败:`, err)
return { code, res: null }
})
)
const results = await Promise.all(promises)
results.forEach(({ code, res }: { code: string, res: any }) => {
if (res && (res.success === true || res.code === 0) && res.data) {
const items = Array.isArray(res.data) ? res.data : (res.data.data || [])
dictData.value[code] = items.map((item: any) => ({
label: item.dict_label || item.label || item.name,
value: String(item.dict_value || item.value || item.id)
}))
console.log(`${code} 字典数据处理完成:`, dictData.value[code])
} else {
dictData.value[code] = []
console.warn(`${code} 字典数据为空或格式错误:`, res)
}
})
console.log('所有字典数据:', dictData.value)
} catch (error) {
console.error('加载字典数据失败:', error)
}
}
// 监听抽屉打开,加载字典数据
watch(() => visible.value, (v) => {
if (v) loadDictOptions()
})
if (v) {
loadDictOptions()
}
}, { immediate: true })
</script>
<style scoped>
@@ -1,10 +1,11 @@
<template>
<el-drawer
v-model="visible"
title="开票信息"
size="600px"
:close-on-click-modal="false"
>
<el-drawer v-model="visible" size="600px" :close-on-click-modal="false">
<template #header>
<div style="display: flex; align-items: center;">
<span>开票信息</span>
<el-button @click="copyAll" type="primary" style="margin-left: 20px;">一键复制</el-button>
</div>
</template>
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
<el-form-item label="发票抬头" prop="invoice_title">
<el-input v-model="form.invoice_title" placeholder="请输入发票抬头" />
@@ -24,6 +25,12 @@
<el-form-item label="注册电话" prop="registered_phone">
<el-input v-model="form.registered_phone" placeholder="请输入注册电话" />
</el-form-item>
<el-divider />
<!-- 智能识别框 -->
<el-form-item label="智能识别">
<el-input v-model="smartText" rows="6" type="textarea" placeholder="请粘贴包含关键信息的文本" />
<el-button type="primary" style="margin-top: 10px;" @click="smartRecognize">智能识别</el-button>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="onCancel">取消</el-button>
@@ -36,6 +43,8 @@
import { ref, reactive, computed, watch } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
import { ElMessage } from 'element-plus'
// @ts-ignore
import { updateSupplierInvoice } from '@/api/supplier'
const props = defineProps<{ modelValue: boolean, model?: any }>()
const emit = defineEmits(['update:modelValue', 'saved'])
@@ -47,6 +56,7 @@ const visible = computed({
const formRef = ref<FormInstance>()
const submitting = ref(false)
const smartText = ref('')
const form = reactive({
invoice_title: '',
tax_number: '',
@@ -75,12 +85,41 @@ function onCancel() {
visible.value = false
}
function getTenantId(): string {
let v: any = props.model?.tenantId ?? props.model?.tenant_id
if (!v) {
const keys = ['userinfo', 'userInfo', 'user']
let s = ''
for (const k of keys) {
s = localStorage.getItem(k) || sessionStorage.getItem(k) || ''
if (s) break
}
if (s) {
try {
const u = JSON.parse(s)
v = u?.tenantId ?? u?.tenant_id ?? u?.tenant?.id
?? u?.user?.tenantId ?? u?.user?.tenant_id ?? u?.user?.tenant?.id
?? u?.userInfo?.tenantId ?? u?.userInfo?.tenant_id ?? u?.userInfo?.tenant?.id
} catch {}
}
}
return (v ?? '').toString()
}
async function onSubmit() {
if (!formRef.value) return
await formRef.value.validate()
try {
submitting.value = true
// TODO: 调用保存开票信息的API
const id = props.model?.id
if (!id) {
ElMessage.warning('请先保存供应商基本信息,再编辑开票信息')
return
}
const tenantId = getTenantId()
const tenant_id = tenantId
await updateSupplierInvoice(id, { ...form, tenantId, tenant_id })
ElMessage.success('保存成功')
emit('saved')
visible.value = false
@@ -89,6 +128,110 @@ async function onSubmit() {
}
}
function copyAll() {
const lines = [
`名称:${form.invoice_title}`,
`纳税人识别号:${form.tax_number}`,
`地址:${form.registered_address}`,
`电话:${form.registered_phone}`,
`开户行:${form.bank_name}`,
`账号:${form.bank_account}`
]
const text = lines.join('\n')
navigator.clipboard.writeText(text).then(() => {
ElMessage.success('已复制到剪贴板')
}).catch(() => {
ElMessage.error('复制失败')
})
}
function smartRecognize() {
const text = smartText.value || ''
const lines = text.split(/\r?\n/).map(l => l.trim()).filter(Boolean)
if (!lines.length) {
ElMessage.warning('请先粘贴要识别的内容')
return
}
// 模糊识别,支持多种关键词格式
const map: Record<string, string> = {}
const patterns = [
// 标准格式:名称:xxx
/^(名称|纳税人识别号|识别号|地址|电话|开户行|账号|开票企业名称|开票地址|开票电话|开户账号)\s*[:]\s*(.+)$/,
// 紧凑格式:名称 xxx
/^(名称|纳税人识别号|识别号|地址|电话|开户行|账号|开票企业名称|开票地址|开票电话|开户账号)\s+(.+)$/,
// 带空格后缀:名称 xxx
/^(名称|纳税人识别号|识别号|地址|电话|开户行|账号|开票企业名称|开票地址|开票电话|开户账号)\s+(.+)$/,
// 单行连续格式:开票企业名称 江苏赛鹏信息技术有限公司 纳税人识别号 91320700MA1WPW853X...
/(开票企业名称|纳税人识别号|开票地址|开票电话|开户行|开户账号)\s+([^\s]+(?:\s[^\s]+)*?)(?=\s+(?:开票企业名称|纳税人识别号|开票地址|开票电话|开户行|开户账号)\s+|$)/
]
for (const line of lines) {
// 特殊处理单行连续格式
if (line.includes('开票企业名称') || line.includes('纳税人识别号')) {
const singleLinePattern = /(开票企业名称|纳税人识别号|开票地址|开票电话|开户行|开户账号)\s+([^\s]+(?:\s[^\s]+)*?)(?=\s+(?:开票企业名称|纳税人识别号|开票地址|开票电话|开户行|开户账号)\s+|$)/g
let match
while ((match = singleLinePattern.exec(line)) !== null) {
const key = match[1]
let value = match[2].trim()
value = value.replace(/\s+$/, '')
map[key] = value
}
continue
}
for (const pattern of patterns) {
const m = line.match(pattern)
if (m) {
const key = m[1]
let value = m[2].trim()
// 去掉末尾可能的空格
value = value.replace(/\s+$/, '')
map[key] = value
break
}
}
}
// 字段映射
const fieldMap: Record<string, keyof typeof form> = {
'名称': 'invoice_title',
'开票企业名称': 'invoice_title',
'纳税人识别号': 'tax_number',
'识别号': 'tax_number',
'地址': 'registered_address',
'开票地址': 'registered_address',
'电话': 'registered_phone',
'开票电话': 'registered_phone',
'开户行': 'bank_name',
'账号': 'bank_account',
'开户账号': 'bank_account'
}
if (Object.keys(map).length === 0) {
ElMessage.warning('未识别到有效字段')
return
}
// 填充表单
let filledCount = 0
for (const [key, value] of Object.entries(map)) {
const field = fieldMap[key]
if (field) {
// 清理空格
const cleanValue = value.replace(/\s+/g, '')
if (field === 'registered_phone') {
form[field] = value.replace(/\s*-\s*/g, '-')
} else {
form[field] = cleanValue
}
filledCount++
}
}
ElMessage.success(`识别完成,已填充 ${filledCount} 个字段`)
}
watch(() => props.model, (m) => {
if (m) {
form.invoice_title = m.invoice_title ?? ''
@@ -103,5 +246,4 @@ watch(() => props.model, (m) => {
}, { immediate: true, deep: true })
</script>
<style scoped>
</style>
<style scoped></style>
+75 -94
View File
@@ -27,70 +27,15 @@
border
style="width: 100%"
>
<el-table-column prop="name" label="供应商名称" width="180" />
<el-table-column prop="contact" label="联系人" width="120" />
<el-table-column prop="phone" label="联系电话" width="150" />
<el-table-column prop="email" label="邮箱" width="200" />
<el-table-column prop="supplier_name" label="供应商名称" width="220" />
<el-table-column prop="contact_person" label="联系人" width="140" />
<el-table-column prop="contact_phone" label="联系电话" width="160" />
<el-table-column prop="contact_email" label="邮箱" width="220" />
<el-table-column prop="address" label="地址" show-overflow-tooltip />
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.status === 1 ? 'success' : 'info'">
{{ row.status === 1 ? '正常' : '停用' }}
function handleContactView(row) {
currentRow.value = { ...row }
contactVisible.value = true
getSupplier(row.id).then((res) => {
const resp = (res && typeof res.code !== 'undefined') ? res : (res && res.data ? res.data : res)
if (resp && resp.code === 0 && resp.data) {
const m = resp.data
currentRow.value = {
id: m.id,
name: m.supplier_name || m.name || '',
contact: m.contact_person || m.contact || '',
phone: m.contact_phone || m.phone || '',
email: m.contact_email || m.email || '',
address: m.address || '',
status: typeof m.status === 'number' ? m.status : (m.status === '0' ? 0 : 1),
supplier_type: m.supplier_type,
supplier_level: m.supplier_level,
industry: m.industry,
_raw: m,
}
}
})
}
function handleInvoiceView(row) {
currentRow.value = { ...row }
invoiceVisible.value = true
getSupplier(row.id).then((res) => {
const resp = (res && typeof res.code !== 'undefined') ? res : (res && res.data ? res.data : res)
if (resp && resp.code === 0 && resp.data) {
const m = resp.data
currentRow.value = {
id: m.id,
name: m.supplier_name || m.name || '',
contact: m.contact_person || m.contact || '',
phone: m.contact_phone || m.phone || '',
email: m.contact_email || m.email || '',
address: m.address || '',
status: typeof m.status === 'number' ? m.status : (m.status === '0' ? 0 : 1),
supplier_type: m.supplier_type,
supplier_level: m.supplier_level,
industry: m.industry,
// 开票相关(如果后端返回则透传给抽屉)
invoice_title: m.invoice_title,
tax_number: m.tax_number,
bank_name: m.bank_name,
bank_account: m.bank_account,
registered_address: m.registered_address,
registered_phone: m.registered_phone,
_raw: m,
}
}
})
}
<el-tag :type="row.status === '1' ? 'success' : 'info'">
{{ row.status === '1' ? '正常' : '停用' }}
</el-tag>
</template>
</el-table-column>
@@ -161,38 +106,19 @@ const invoiceVisible = ref(false);
const isEdit = ref(false);
const currentRow = ref(null);
function fetchCustomerList() {
loading.value = true;
const params = {
keyword: searchQuery.value,
page: currentPage.value,
pageSize: pageSize.value,
async function fetchCustomerList() {
loading.value = true
try {
const { data } = await listSuppliers({
keyword: searchQuery.value,
page: currentPage.value,
pageSize: pageSize.value,
})
customerList.value = data.list || []
total.value = data.total || 0
} finally {
loading.value = false
}
listSuppliers(params)
.then((res) => {
const resp = res && res.data ? res.data : res
if (resp && resp.code === 0 && resp.data) {
const rows = resp.data.list || []
total.value = resp.data.total || 0
customerList.value = rows.map((m) => ({
id: m.id,
name: m.supplier_name || m.name || '',
contact: m.contact_person || m.contact || '',
phone: m.contact_phone || m.phone || '',
email: m.contact_email || m.email || '',
address: m.address || '',
status: typeof m.status === 'number' ? m.status : (m.status === '0' ? 0 : 1),
_raw: m,
}))
} else {
customerList.value = []
total.value = 0
if (resp && resp.message) ElMessage.error(resp.message)
}
})
.finally(() => {
loading.value = false;
})
}
function handleAdd() {
@@ -208,7 +134,8 @@ function handleEdit(row) {
}
function handleView(row) {
// 拉取最新详情再展示
currentRow.value = { ...row }
detailVisible.value = true
getSupplier(row.id).then((res) => {
const resp = res && res.data ? res.data : res
if (resp && resp.code === 0 && resp.data) {
@@ -223,7 +150,61 @@ function handleView(row) {
status: typeof m.status === 'number' ? m.status : (m.status === '0' ? 0 : 1),
_raw: m,
}
detailVisible.value = true
}
})
}
function handleContactView(row) {
currentRow.value = { ...row }
contactVisible.value = true
getSupplier(row.id).then((res) => {
const resp = res && res.data ? res.data : res
if (resp && resp.code === 0 && resp.data) {
const m = resp.data
currentRow.value = {
id: m.id,
name: m.supplier_name || m.name || '',
contact: m.contact_person || m.contact || '',
phone: m.contact_phone || m.phone || '',
email: m.contact_email || m.email || '',
address: m.address || '',
status: typeof m.status === 'number' ? m.status : (m.status === '0' ? 0 : 1),
supplier_type: m.supplier_type,
supplier_level: m.supplier_level,
industry: m.industry,
_raw: m,
}
}
})
}
function handleInvoiceView(row) {
currentRow.value = { ...row }
invoiceVisible.value = true
getSupplier(row.id).then((res) => {
const resp = res && res.data ? res.data : res
if (resp && resp.code === 0 && resp.data) {
const m = resp.data
currentRow.value = {
id: m.id,
name: m.supplier_name || m.name || '',
contact: m.contact_person || m.contact || '',
phone: m.contact_phone || m.phone || '',
email: m.contact_email || m.email || '',
address: m.address || '',
status: typeof m.status === 'number' ? m.status : (m.status === '0' ? 0 : 1),
supplier_type: m.supplier_type,
supplier_level: m.supplier_level,
industry: m.industry,
// 开票相关(如果后端返回则透传给抽屉)
invoice_title: m.invoice_title,
tax_number: m.tax_number,
bank_name: m.bank_name,
bank_account: m.bank_account,
registered_address: m.registered_address,
registered_phone: m.registered_phone,
_raw: m,
}
}
})
}