完善供应商和客户功能
This commit is contained in:
parent
aae2fe1e14
commit
7e4ac8fe67
@ -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 }
|
||||
})
|
||||
}
|
||||
|
||||
@ -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>
|
||||
<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 />
|
||||
|
||||
<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="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 ?? ''
|
||||
|
||||
@ -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>
|
||||
<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 />
|
||||
|
||||
<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="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>
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"server/models"
|
||||
@ -35,9 +36,25 @@ func (c *ContactController) List() {
|
||||
|
||||
// Add POST /api/crm/contact/add
|
||||
func (c *ContactController) Add() {
|
||||
// Debug: print the raw request body
|
||||
body := string(c.Ctx.Input.RequestBody)
|
||||
fmt.Printf("Received request body: %s\n", body)
|
||||
|
||||
// Try unmarshaling to a map first to see the raw data
|
||||
var rawData map[string]interface{}
|
||||
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &rawData); err != nil {
|
||||
fmt.Printf("JSON unmarshal error (raw): %v\n", err)
|
||||
c.Data["json"] = map[string]interface{}{"code": 1, "message": "请求参数格式错误", "debug": body}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
fmt.Printf("Raw data: %+v\n", rawData)
|
||||
|
||||
// Now try unmarshaling to the struct
|
||||
var m models.Contact
|
||||
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &m); err != nil {
|
||||
c.Data["json"] = map[string]interface{}{"code": 1, "message": "请求参数格式错误"}
|
||||
fmt.Printf("JSON unmarshal error (struct): %v\n", err)
|
||||
c.Data["json"] = map[string]interface{}{"code": 1, "message": "请求参数格式错误", "debug": body, "error": err.Error()}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import (
|
||||
"server/services"
|
||||
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
)
|
||||
|
||||
type CustomerController struct {
|
||||
@ -67,67 +68,67 @@ func (c *CustomerController) Add() {
|
||||
|
||||
// Edit POST /api/crm/customer/edit body: {id, ...}
|
||||
func (c *CustomerController) Edit() {
|
||||
var body map[string]interface{}
|
||||
_ = json.Unmarshal(c.Ctx.Input.RequestBody, &body)
|
||||
id, _ := body["id"].(string)
|
||||
if id == "" {
|
||||
// 也允许前端直接在JSON中传 id 字段,下面会再从结构体取
|
||||
}
|
||||
var m models.Customer
|
||||
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &m); err != nil {
|
||||
// 回退:从通用map中提取已知字段,避免类型不匹配导致失败
|
||||
if body == nil {
|
||||
_ = json.Unmarshal(c.Ctx.Input.RequestBody, &body)
|
||||
}
|
||||
toStr := func(v interface{}) string {
|
||||
switch t := v.(type) {
|
||||
case nil:
|
||||
return ""
|
||||
case string:
|
||||
return t
|
||||
case json.Number:
|
||||
return t.String()
|
||||
default:
|
||||
return fmt.Sprint(v)
|
||||
}
|
||||
}
|
||||
m = models.Customer{
|
||||
Id: toStr(body["id"]),
|
||||
TenantId: toStr(body["tenant_id"]),
|
||||
CustomerName: toStr(body["customer_name"]),
|
||||
CustomerType: toStr(body["customer_type"]),
|
||||
ContactPerson: toStr(body["contact_person"]),
|
||||
ContactPhone: toStr(body["contact_phone"]),
|
||||
ContactEmail: toStr(body["contact_email"]),
|
||||
CustomerLevel: toStr(body["customer_level"]),
|
||||
Industry: toStr(body["industry"]),
|
||||
Address: toStr(body["address"]),
|
||||
Status: toStr(body["status"]),
|
||||
Remark: toStr(body["remark"]),
|
||||
}
|
||||
if m.Id == "" {
|
||||
if id == "" {
|
||||
c.Data["json"] = map[string]interface{}{"code": 1, "message": "id不能为空"}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
m.Id = id
|
||||
}
|
||||
}
|
||||
if m.Id == "" {
|
||||
if id == "" {
|
||||
c.Data["json"] = map[string]interface{}{"code": 1, "message": "id不能为空"}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
m.Id = id
|
||||
}
|
||||
if err := services.UpdateCustomer(&m); err != nil {
|
||||
c.Data["json"] = map[string]interface{}{"code": 1, "message": err.Error()}
|
||||
} else {
|
||||
c.Data["json"] = map[string]interface{}{"code": 0, "message": "ok"}
|
||||
}
|
||||
c.ServeJSON()
|
||||
var body map[string]interface{}
|
||||
_ = json.Unmarshal(c.Ctx.Input.RequestBody, &body)
|
||||
id, _ := body["id"].(string)
|
||||
if id == "" {
|
||||
// 也允许前端直接在JSON中传 id 字段,下面会再从结构体取
|
||||
}
|
||||
var m models.Customer
|
||||
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &m); err != nil {
|
||||
// 回退:从通用map中提取已知字段,避免类型不匹配导致失败
|
||||
if body == nil {
|
||||
_ = json.Unmarshal(c.Ctx.Input.RequestBody, &body)
|
||||
}
|
||||
toStr := func(v interface{}) string {
|
||||
switch t := v.(type) {
|
||||
case nil:
|
||||
return ""
|
||||
case string:
|
||||
return t
|
||||
case json.Number:
|
||||
return t.String()
|
||||
default:
|
||||
return fmt.Sprint(v)
|
||||
}
|
||||
}
|
||||
m = models.Customer{
|
||||
Id: toStr(body["id"]),
|
||||
TenantId: toStr(body["tenant_id"]),
|
||||
CustomerName: toStr(body["customer_name"]),
|
||||
CustomerType: toStr(body["customer_type"]),
|
||||
ContactPerson: toStr(body["contact_person"]),
|
||||
ContactPhone: toStr(body["contact_phone"]),
|
||||
ContactEmail: toStr(body["contact_email"]),
|
||||
CustomerLevel: toStr(body["customer_level"]),
|
||||
Industry: toStr(body["industry"]),
|
||||
Address: toStr(body["address"]),
|
||||
Status: toStr(body["status"]),
|
||||
Remark: toStr(body["remark"]),
|
||||
}
|
||||
if m.Id == "" {
|
||||
if id == "" {
|
||||
c.Data["json"] = map[string]interface{}{"code": 1, "message": "id不能为空"}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
m.Id = id
|
||||
}
|
||||
}
|
||||
if m.Id == "" {
|
||||
if id == "" {
|
||||
c.Data["json"] = map[string]interface{}{"code": 1, "message": "id不能为空"}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
m.Id = id
|
||||
}
|
||||
if err := services.UpdateCustomer(&m); err != nil {
|
||||
c.Data["json"] = map[string]interface{}{"code": 1, "message": err.Error()}
|
||||
} else {
|
||||
c.Data["json"] = map[string]interface{}{"code": 0, "message": "ok"}
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// Delete POST /api/crm/customer/delete body: {id, tenantId}
|
||||
@ -153,3 +154,36 @@ func (c *CustomerController) Delete() {
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// 更新客户开票信息
|
||||
func (c *CustomerController) UpdateInvoice() {
|
||||
var body struct {
|
||||
Id string `json:"id"`
|
||||
TenantId string `json:"tenantId"`
|
||||
}
|
||||
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &body); err != nil {
|
||||
c.Data["json"] = map[string]interface{}{"code": 1, "message": "请求参数格式错误"}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
if body.Id == "" {
|
||||
c.Data["json"] = map[string]interface{}{"code": 1, "message": "id不能为空"}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
// Extract invoice fields from the request payload
|
||||
var payload map[string]interface{}
|
||||
_ = json.Unmarshal(c.Ctx.Input.RequestBody, &payload)
|
||||
params := orm.Params{}
|
||||
for _, key := range []string{"invoice_title", "tax_number", "bank_name", "bank_account", "registered_address", "registered_phone"} {
|
||||
if v, ok := payload[key]; ok {
|
||||
params[key] = v
|
||||
}
|
||||
}
|
||||
if err := services.UpdateInvoice(body.Id, params); err != nil {
|
||||
c.Data["json"] = map[string]interface{}{"code": 1, "message": err.Error()}
|
||||
} else {
|
||||
c.Data["json"] = map[string]interface{}{"code": 0, "message": "ok"}
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import (
|
||||
"server/services"
|
||||
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
)
|
||||
|
||||
type SupplierController struct {
|
||||
@ -30,12 +31,18 @@ func (c *SupplierController) List() {
|
||||
}
|
||||
|
||||
func (c *SupplierController) Detail() {
|
||||
id := c.GetString("id")
|
||||
if id == "" {
|
||||
idStr := c.GetString("id")
|
||||
if idStr == "" {
|
||||
c.Data["json"] = map[string]interface{}{"code": 1, "message": "id不能为空"}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
id, errConv := strconv.ParseInt(idStr, 10, 64)
|
||||
if errConv != nil {
|
||||
c.Data["json"] = map[string]interface{}{"code": 1, "message": "id格式不正确"}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
m, err := services.GetSupplier(id)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{"code": 1, "message": err.Error()}
|
||||
@ -63,20 +70,27 @@ func (c *SupplierController) Add() {
|
||||
func (c *SupplierController) Edit() {
|
||||
var body map[string]interface{}
|
||||
_ = json.Unmarshal(c.Ctx.Input.RequestBody, &body)
|
||||
id, _ := body["id"].(string)
|
||||
var idInt int64
|
||||
if v, ok := body["id"].(string); ok && v != "" {
|
||||
if n, err := strconv.ParseInt(v, 10, 64); err == nil {
|
||||
idInt = n
|
||||
}
|
||||
} else if v2, ok2 := body["id"].(float64); ok2 {
|
||||
idInt = int64(v2)
|
||||
}
|
||||
var m models.Supplier
|
||||
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &m); err != nil {
|
||||
c.Data["json"] = map[string]interface{}{"code": 1, "message": "请求参数格式错误"}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
if m.Id == "" {
|
||||
if id == "" {
|
||||
if m.Id == 0 {
|
||||
if idInt == 0 {
|
||||
c.Data["json"] = map[string]interface{}{"code": 1, "message": "id不能为空"}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
m.Id = id
|
||||
m.Id = idInt
|
||||
}
|
||||
if err := services.UpdateSupplier(&m); err != nil {
|
||||
c.Data["json"] = map[string]interface{}{"code": 1, "message": err.Error()}
|
||||
@ -101,7 +115,43 @@ func (c *SupplierController) Delete() {
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
if err := services.SoftDeleteSupplier(body.Id); err != nil {
|
||||
id, errConv := strconv.ParseInt(body.Id, 10, 64)
|
||||
if errConv != nil {
|
||||
c.Data["json"] = map[string]interface{}{"code": 1, "message": "id格式不正确"}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
if err := services.SoftDeleteSupplier(id); err != nil {
|
||||
c.Data["json"] = map[string]interface{}{"code": 1, "message": err.Error()}
|
||||
} else {
|
||||
c.Data["json"] = map[string]interface{}{"code": 0, "message": "ok"}
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// 更新供应商开票信息
|
||||
func (c *SupplierController) UpdateInvoice() {
|
||||
var payload map[string]interface{}
|
||||
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &payload); err != nil {
|
||||
c.Data["json"] = map[string]interface{}{"code": 1, "message": "请求参数格式错误"}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
idVal, ok := payload["id"]
|
||||
if !ok || idVal == "" {
|
||||
c.Data["json"] = map[string]interface{}{"code": 1, "message": "id不能为空"}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
// Prepare params for service: include id and invoice fields
|
||||
params := orm.Params{}
|
||||
params["id"] = idVal
|
||||
for _, key := range []string{"invoice_title", "tax_number", "bank_name", "bank_account", "registered_address", "registered_phone"} {
|
||||
if v, exists := payload[key]; exists {
|
||||
params[key] = v
|
||||
}
|
||||
}
|
||||
if err := services.UpdateSupplierInvoice(params); err != nil {
|
||||
c.Data["json"] = map[string]interface{}{"code": 1, "message": err.Error()}
|
||||
} else {
|
||||
c.Data["json"] = map[string]interface{}{"code": 0, "message": "ok"}
|
||||
|
||||
@ -593,6 +593,12 @@ CREATE TABLE `yz_tenant_crm_customer` (
|
||||
`expire_time` date DEFAULT NULL COMMENT '合作到期日期(无到期则为空)',
|
||||
`status` varchar(20) NOT NULL DEFAULT '1' COMMENT '客户状态(0-禁用/1-正常/2-冻结/3-已注销)',
|
||||
`remark` text COMMENT '客户备注',
|
||||
`invoice_title` varchar(100) DEFAULT '' COMMENT '发票抬头',
|
||||
`tax_number` varchar(50) DEFAULT '' COMMENT '纳税人识别号',
|
||||
`bank_name` varchar(100) DEFAULT '' COMMENT '开户行名称',
|
||||
`bank_account` varchar(50) DEFAULT '' COMMENT '开户行账号',
|
||||
`registered_address` varchar(255) DEFAULT '' COMMENT '注册地址',
|
||||
`registered_phone` varchar(50) DEFAULT '' COMMENT '注册电话',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`delete_time` datetime DEFAULT NULL COMMENT '删除时间',
|
||||
@ -627,6 +633,12 @@ CREATE TABLE `yz_tenant_crm_supplier` (
|
||||
`expire_time` date DEFAULT NULL COMMENT '合作到期日期',
|
||||
`status` varchar(20) NOT NULL DEFAULT '1' COMMENT '供应商状态(0-禁用/1-正常/2-冻结/3-已注销)',
|
||||
`remark` text COMMENT '供应商备注',
|
||||
`invoice_title` varchar(100) DEFAULT '' COMMENT '发票抬头',
|
||||
`tax_number` varchar(50) DEFAULT '' COMMENT '纳税人识别号',
|
||||
`bank_name` varchar(100) DEFAULT '' COMMENT '开户行名称',
|
||||
`bank_account` varchar(50) DEFAULT '' COMMENT '开户行账号',
|
||||
`registered_address` varchar(255) DEFAULT '' COMMENT '注册地址',
|
||||
`registered_phone` varchar(50) DEFAULT '' COMMENT '注册电话',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`delete_time` datetime DEFAULT NULL COMMENT '删除时间',
|
||||
|
||||
@ -8,24 +8,28 @@ import (
|
||||
|
||||
// Contact 联系人模型(对应表 yz_tenant_crm_contact)
|
||||
type Contact struct {
|
||||
Id int64 `orm:"pk;auto" json:"id"`
|
||||
TenantId string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
|
||||
RelatedType int `orm:"column(related_type)" json:"related_type"` // 1=客户 2=供应商
|
||||
RelatedId string `orm:"column(related_id);size(64)" json:"related_id"`
|
||||
ContactName string `orm:"column(contact_name);size(50)" json:"contact_name"`
|
||||
Gender int8 `orm:"column(gender);null" json:"gender"`
|
||||
Mobile string `orm:"column(mobile);size(20);null" json:"mobile"`
|
||||
Phone string `orm:"column(phone);size(20);null" json:"phone"`
|
||||
Email string `orm:"column(email);size(100);null" json:"email"`
|
||||
Position string `orm:"column(position);size(50);null" json:"position"`
|
||||
Department string `orm:"column(department);size(50);null" json:"department"`
|
||||
IsPrimary int8 `orm:"column(is_primary);null" json:"is_primary"`
|
||||
Remark string `orm:"column(remark);size(500);null" json:"remark"`
|
||||
CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);type(datetime);auto_now" json:"update_time"`
|
||||
CreateBy int64 `orm:"column(create_by);null" json:"create_by"`
|
||||
UpdateBy int64 `orm:"column(update_by);null" json:"update_by"`
|
||||
IsDeleted int8 `orm:"column(is_deleted);null" json:"is_deleted"`
|
||||
Id int64 `orm:"pk;auto" json:"id,omitempty"`
|
||||
TenantId string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
|
||||
RelatedType int `orm:"column(related_type)" json:"related_type"` // 1=客户 2=供应商
|
||||
RelatedId string `orm:"column(related_id);size(64)" json:"related_id"`
|
||||
ContactName string `orm:"column(contact_name);size(50)" json:"contact_name"`
|
||||
Gender int8 `orm:"column(gender);null" json:"gender"`
|
||||
Phone1 string `orm:"column(phone1);size(20);null" json:"phone1"`
|
||||
Phone2 string `orm:"column(phone2);size(20);null" json:"phone2"`
|
||||
Phone3 string `orm:"column(phone3);size(20);null" json:"phone3"`
|
||||
Qq string `orm:"column(qq);size(255);null" json:"qq"`
|
||||
Wechat string `orm:"column(wechat);size(255);null" json:"wechat"`
|
||||
Email string `orm:"column(email);size(100);null" json:"email"`
|
||||
Position string `orm:"column(position);size(50);null" json:"position"`
|
||||
Department string `orm:"column(department);size(50);null" json:"department"`
|
||||
IsPrimary int8 `orm:"column(is_primary);null" json:"is_primary"`
|
||||
HomeAddress string `orm:"column(home_address);size(65535);null" json:"home_address"`
|
||||
Remark string `orm:"column(remark);size(65535);null" json:"remark"`
|
||||
CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time,omitempty"`
|
||||
UpdateTime time.Time `orm:"column(update_time);type(datetime);auto_now" json:"update_time,omitempty"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time,omitempty"`
|
||||
CreateBy int64 `orm:"column(create_by);null" json:"create_by"`
|
||||
UpdateBy int64 `orm:"column(update_by);null" json:"update_by"`
|
||||
}
|
||||
|
||||
func (t *Contact) TableName() string {
|
||||
|
||||
@ -22,6 +22,12 @@ type Customer struct {
|
||||
ExpireTime *time.Time `orm:"column(expire_time);null;type(date)" json:"expire_time"`
|
||||
Status string `orm:"column(status);size(20)" json:"status"`
|
||||
Remark string `orm:"column(remark);type(text);null" json:"remark"`
|
||||
InvoiceTitle string `orm:"column(invoice_title);size(100);null" json:"invoice_title"`
|
||||
TaxNumber string `orm:"column(tax_number);size(50);null" json:"tax_number"`
|
||||
BankName string `orm:"column(bank_name);size(100);null" json:"bank_name"`
|
||||
BankAccount string `orm:"column(bank_account);size(50);null" json:"bank_account"`
|
||||
RegisteredAddress string `orm:"column(registered_address);size(255);null" json:"registered_address"`
|
||||
RegisteredPhone string `orm:"column(registered_phone);size(50);null" json:"registered_phone"`
|
||||
CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);type(datetime);auto_now" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);null;type(datetime)" json:"delete_time"`
|
||||
|
||||
@ -8,23 +8,29 @@ import (
|
||||
|
||||
// Supplier 供应商模型(对应表 yz_tenant_crm_supplier)
|
||||
type Supplier struct {
|
||||
Id string `orm:"pk;size(36)" json:"id"`
|
||||
TenantId string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
|
||||
SupplierName string `orm:"column(supplier_name);size(100)" json:"supplier_name"`
|
||||
SupplierType string `orm:"column(supplier_type);size(20)" json:"supplier_type"`
|
||||
ContactPerson string `orm:"column(contact_person);size(50)" json:"contact_person"`
|
||||
ContactPhone string `orm:"column(contact_phone);size(20)" json:"contact_phone"`
|
||||
ContactEmail string `orm:"column(contact_email);size(100);null" json:"contact_email"`
|
||||
SupplierLevel string `orm:"column(supplier_level);size(20);null" json:"supplier_level"`
|
||||
Industry string `orm:"column(industry);size(50);null" json:"industry"`
|
||||
Address string `orm:"column(address);size(255);null" json:"address"`
|
||||
RegisterTime *time.Time `orm:"column(register_time);null;type(date)" json:"register_time"`
|
||||
ExpireTime *time.Time `orm:"column(expire_time);null;type(date)" json:"expire_time"`
|
||||
Status string `orm:"column(status);size(20)" json:"status"`
|
||||
Remark string `orm:"column(remark);type(text);null" json:"remark"`
|
||||
CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);type(datetime);auto_now" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);null;type(datetime)" json:"delete_time"`
|
||||
Id int64 `orm:"pk;auto" json:"id"`
|
||||
TenantId string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
|
||||
SupplierName string `orm:"column(supplier_name);size(100)" json:"supplier_name"`
|
||||
SupplierType string `orm:"column(supplier_type);size(20)" json:"supplier_type"`
|
||||
ContactPerson string `orm:"column(contact_person);size(50)" json:"contact_person"`
|
||||
ContactPhone string `orm:"column(contact_phone);size(20)" json:"contact_phone"`
|
||||
ContactEmail string `orm:"column(contact_email);size(100);null" json:"contact_email"`
|
||||
SupplierLevel string `orm:"column(supplier_level);size(20);null" json:"supplier_level"`
|
||||
Industry string `orm:"column(industry);size(50);null" json:"industry"`
|
||||
Address string `orm:"column(address);size(255);null" json:"address"`
|
||||
RegisterTime *time.Time `orm:"column(register_time);null;type(date)" json:"register_time"`
|
||||
ExpireTime *time.Time `orm:"column(expire_time);null;type(date)" json:"expire_time"`
|
||||
Status string `orm:"column(status);size(20)" json:"status"`
|
||||
Remark string `orm:"column(remark);type(text);null" json:"remark"`
|
||||
InvoiceTitle string `orm:"column(invoice_title);size(255);null" json:"invoice_title"`
|
||||
TaxNumber string `orm:"column(tax_number);size(20);null" json:"tax_number"`
|
||||
BankName string `orm:"column(bank_name);size(50);null" json:"bank_name"`
|
||||
BankAccount string `orm:"column(bank_account);size(50);null" json:"bank_account"`
|
||||
RegisteredAddress string `orm:"column(registered_address);size(255);null" json:"registered_address"`
|
||||
RegisteredPhone string `orm:"column(registered_phone);size(20);null" json:"registered_phone"`
|
||||
CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);type(datetime);auto_now" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);null;type(datetime)" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (t *Supplier) TableName() string {
|
||||
|
||||
@ -335,6 +335,7 @@ func init() {
|
||||
beego.Router("/api/crm/customer/add", &controllers.CustomerController{}, "post:Add")
|
||||
beego.Router("/api/crm/customer/edit", &controllers.CustomerController{}, "post:Edit")
|
||||
beego.Router("/api/crm/customer/delete", &controllers.CustomerController{}, "post:Delete")
|
||||
beego.Router("/api/crm/customer/update-invoice", &controllers.CustomerController{}, "post:UpdateInvoice")
|
||||
|
||||
// CRM 供应商路由
|
||||
beego.Router("/api/crm/supplier/list", &controllers.SupplierController{}, "get:List")
|
||||
@ -342,6 +343,7 @@ func init() {
|
||||
beego.Router("/api/crm/supplier/add", &controllers.SupplierController{}, "post:Add")
|
||||
beego.Router("/api/crm/supplier/edit", &controllers.SupplierController{}, "post:Edit")
|
||||
beego.Router("/api/crm/supplier/delete", &controllers.SupplierController{}, "post:Delete")
|
||||
beego.Router("/api/crm/supplier/update-invoice", &controllers.SupplierController{}, "post:UpdateInvoice")
|
||||
|
||||
// CRM 联系人路由
|
||||
beego.Router("/api/crm/contact/list", &controllers.ContactController{}, "get:List")
|
||||
|
||||
@ -2,15 +2,17 @@ package services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"server/models"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
"server/models"
|
||||
)
|
||||
|
||||
func ListContacts(tenantId string, relatedType int, relatedId string) ([]models.Contact, error) {
|
||||
o := orm.NewOrm()
|
||||
var list []models.Contact
|
||||
qs := o.QueryTable(new(models.Contact)).Filter("tenant_id", tenantId).Filter("related_type", relatedType).Filter("related_id", relatedId).Filter("is_deleted", 0)
|
||||
qs := o.QueryTable(new(models.Contact)).Filter("tenant_id", tenantId).Filter("related_type", relatedType).Filter("related_id", relatedId).Filter("delete_time__isnull", true)
|
||||
_, err := qs.All(&list)
|
||||
return list, err
|
||||
}
|
||||
@ -20,10 +22,6 @@ func CreateContact(m *models.Contact) error {
|
||||
return errors.New("缺少必填参数")
|
||||
}
|
||||
o := orm.NewOrm()
|
||||
// 若设为主联系人,则先清理同归属其他主联系人
|
||||
if m.IsPrimary == 1 {
|
||||
_, _ = o.QueryTable(new(models.Contact)).Filter("tenant_id", m.TenantId).Filter("related_type", m.RelatedType).Filter("related_id", m.RelatedId).Filter("is_deleted", 0).Update(orm.Params{"is_primary": 0})
|
||||
}
|
||||
_, err := o.Insert(m)
|
||||
return err
|
||||
}
|
||||
@ -33,16 +31,17 @@ func UpdateContact(m *models.Contact) error {
|
||||
return errors.New("id不能为空")
|
||||
}
|
||||
o := orm.NewOrm()
|
||||
// 若设为主联系人,则先清理同归属其他主联系人
|
||||
if m.IsPrimary == 1 {
|
||||
// 需要获取原始记录以得到 tenant/related
|
||||
var origin models.Contact
|
||||
origin.Id = m.Id
|
||||
if err := o.Read(&origin); err == nil {
|
||||
_, _ = o.QueryTable(new(models.Contact)).Filter("tenant_id", origin.TenantId).Filter("related_type", origin.RelatedType).Filter("related_id", origin.RelatedId).Filter("id__ne", m.Id).Filter("is_deleted", 0).Update(orm.Params{"is_primary": 0})
|
||||
}
|
||||
existing := models.Contact{Id: m.Id}
|
||||
if err := o.Read(&existing); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := o.Update(m, "contact_name", "gender", "mobile", "phone", "email", "position", "department", "is_primary", "remark")
|
||||
if existing.TenantId != m.TenantId {
|
||||
return errors.New("租户不匹配")
|
||||
}
|
||||
if existing.DeleteTime != nil {
|
||||
return errors.New("记录已删除")
|
||||
}
|
||||
_, err := o.Update(m, "contact_name", "gender", "phone1", "phone2", "phone3", "qq", "wechat", "email", "position", "department", "home_address", "remark", "is_primary")
|
||||
return err
|
||||
}
|
||||
|
||||
@ -58,7 +57,11 @@ func DeleteContact(id int64, tenantId string) error {
|
||||
if m.TenantId != tenantId {
|
||||
return errors.New("租户不匹配")
|
||||
}
|
||||
m.IsDeleted = 1
|
||||
_, err := o.Update(&m, "is_primary", "is_deleted")
|
||||
if m.DeleteTime != nil {
|
||||
return errors.New("记录已删除")
|
||||
}
|
||||
now := time.Now()
|
||||
m.DeleteTime = &now
|
||||
_, err := o.Update(&m, "delete_time")
|
||||
return err
|
||||
}
|
||||
|
||||
@ -1,79 +1,89 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"time"
|
||||
"time"
|
||||
|
||||
"server/models"
|
||||
"server/models"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
)
|
||||
|
||||
// ListCustomers 获取客户列表(可选租户过滤、关键词搜索、状态筛选、分页)
|
||||
func ListCustomers(tenantId, keyword, status string, page, pageSize int) (list []*models.Customer, total int64, err error) {
|
||||
o := orm.NewOrm()
|
||||
qs := o.QueryTable(new(models.Customer)).Filter("delete_time__isnull", true)
|
||||
if tenantId != "" {
|
||||
qs = qs.Filter("tenant_id", tenantId)
|
||||
}
|
||||
if keyword != "" {
|
||||
cond := orm.NewCondition()
|
||||
cond1 := cond.Or("customer_name__icontains", keyword).
|
||||
Or("contact_person__icontains", keyword).
|
||||
Or("contact_phone__icontains", keyword)
|
||||
qs = qs.SetCond(cond1)
|
||||
}
|
||||
if status != "" {
|
||||
qs = qs.Filter("status", status)
|
||||
}
|
||||
o := orm.NewOrm()
|
||||
qs := o.QueryTable(new(models.Customer)).Filter("delete_time__isnull", true)
|
||||
if tenantId != "" {
|
||||
qs = qs.Filter("tenant_id", tenantId)
|
||||
}
|
||||
if keyword != "" {
|
||||
cond := orm.NewCondition()
|
||||
cond1 := cond.Or("customer_name__icontains", keyword).
|
||||
Or("contact_person__icontains", keyword).
|
||||
Or("contact_phone__icontains", keyword)
|
||||
qs = qs.SetCond(cond1)
|
||||
}
|
||||
if status != "" {
|
||||
qs = qs.Filter("status", status)
|
||||
}
|
||||
|
||||
total, err = qs.Count()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 {
|
||||
pageSize = 10
|
||||
}
|
||||
offset := (page - 1) * pageSize
|
||||
_, err = qs.OrderBy("-create_time").Limit(pageSize, offset).All(&list)
|
||||
return
|
||||
total, err = qs.Count()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 {
|
||||
pageSize = 10
|
||||
}
|
||||
offset := (page - 1) * pageSize
|
||||
_, err = qs.OrderBy("-create_time").Limit(pageSize, offset).All(&list)
|
||||
return
|
||||
}
|
||||
|
||||
// GetCustomer 通过ID获取客户
|
||||
func GetCustomer(id string) (*models.Customer, error) {
|
||||
o := orm.NewOrm()
|
||||
m := models.Customer{Id: id}
|
||||
if err := o.Read(&m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &m, nil
|
||||
o := orm.NewOrm()
|
||||
m := models.Customer{Id: id}
|
||||
if err := o.Read(&m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
// CreateCustomer 新增客户
|
||||
func CreateCustomer(m *models.Customer) error {
|
||||
o := orm.NewOrm()
|
||||
_, err := o.Insert(m)
|
||||
return err
|
||||
o := orm.NewOrm()
|
||||
_, err := o.Insert(m)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateCustomer 更新客户(全量更新)
|
||||
func UpdateCustomer(m *models.Customer, cols ...string) error {
|
||||
o := orm.NewOrm()
|
||||
if len(cols) == 0 {
|
||||
_, err := o.Update(m)
|
||||
return err
|
||||
}
|
||||
_, err := o.Update(m, cols...)
|
||||
return err
|
||||
o := orm.NewOrm()
|
||||
if len(cols) == 0 {
|
||||
_, err := o.Update(m)
|
||||
return err
|
||||
}
|
||||
_, err := o.Update(m, cols...)
|
||||
return err
|
||||
}
|
||||
|
||||
// SoftDeleteCustomer 软删除客户
|
||||
func SoftDeleteCustomer(id string) error {
|
||||
o := orm.NewOrm()
|
||||
now := time.Now()
|
||||
m := models.Customer{Id: id, DeleteTime: &now}
|
||||
_, err := o.Update(&m, "delete_time")
|
||||
return err
|
||||
o := orm.NewOrm()
|
||||
now := time.Now()
|
||||
m := models.Customer{Id: id, DeleteTime: &now}
|
||||
_, err := o.Update(&m, "delete_time")
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新客户开票信息
|
||||
func UpdateInvoice(id string, params orm.Params) error {
|
||||
o := orm.NewOrm()
|
||||
m := &models.Customer{Id: id}
|
||||
if _, err := o.QueryTable(m).Filter("id", id).Update(params); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1,79 +1,116 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"time"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"server/models"
|
||||
"server/models"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
)
|
||||
|
||||
// ListSuppliers 获取供应商列表(可选租户过滤、关键词搜索、状态筛选、分页)
|
||||
func ListSuppliers(tenantId, keyword, status string, page, pageSize int) (list []*models.Supplier, total int64, err error) {
|
||||
o := orm.NewOrm()
|
||||
qs := o.QueryTable(new(models.Supplier)).Filter("delete_time__isnull", true)
|
||||
if tenantId != "" {
|
||||
qs = qs.Filter("tenant_id", tenantId)
|
||||
}
|
||||
if keyword != "" {
|
||||
cond := orm.NewCondition()
|
||||
cond1 := cond.Or("supplier_name__icontains", keyword).
|
||||
Or("contact_person__icontains", keyword).
|
||||
Or("contact_phone__icontains", keyword)
|
||||
qs = qs.SetCond(cond1)
|
||||
}
|
||||
if status != "" {
|
||||
qs = qs.Filter("status", status)
|
||||
}
|
||||
o := orm.NewOrm()
|
||||
qs := o.QueryTable(new(models.Supplier)).Filter("delete_time__isnull", true)
|
||||
if tenantId != "" {
|
||||
qs = qs.Filter("tenant_id", tenantId)
|
||||
}
|
||||
if keyword != "" {
|
||||
cond := orm.NewCondition()
|
||||
cond1 := cond.Or("supplier_name__icontains", keyword).
|
||||
Or("contact_person__icontains", keyword).
|
||||
Or("contact_phone__icontains", keyword)
|
||||
qs = qs.SetCond(cond1)
|
||||
}
|
||||
if status != "" {
|
||||
qs = qs.Filter("status", status)
|
||||
}
|
||||
|
||||
total, err = qs.Count()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 {
|
||||
pageSize = 10
|
||||
}
|
||||
offset := (page - 1) * pageSize
|
||||
_, err = qs.OrderBy("-create_time").Limit(pageSize, offset).All(&list)
|
||||
return
|
||||
total, err = qs.Count()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 {
|
||||
pageSize = 10
|
||||
}
|
||||
offset := (page - 1) * pageSize
|
||||
_, err = qs.OrderBy("-create_time").Limit(pageSize, offset).All(&list)
|
||||
return
|
||||
}
|
||||
|
||||
// GetSupplier 通过ID获取供应商
|
||||
func GetSupplier(id string) (*models.Supplier, error) {
|
||||
o := orm.NewOrm()
|
||||
m := models.Supplier{Id: id}
|
||||
if err := o.Read(&m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &m, nil
|
||||
func GetSupplier(id int64) (*models.Supplier, error) {
|
||||
o := orm.NewOrm()
|
||||
m := models.Supplier{Id: id}
|
||||
if err := o.Read(&m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
// CreateSupplier 新增供应商
|
||||
func CreateSupplier(m *models.Supplier) error {
|
||||
o := orm.NewOrm()
|
||||
_, err := o.Insert(m)
|
||||
return err
|
||||
o := orm.NewOrm()
|
||||
_, err := o.Insert(m)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateSupplier 更新供应商(全量更新)
|
||||
func UpdateSupplier(m *models.Supplier, cols ...string) error {
|
||||
o := orm.NewOrm()
|
||||
if len(cols) == 0 {
|
||||
_, err := o.Update(m)
|
||||
return err
|
||||
}
|
||||
_, err := o.Update(m, cols...)
|
||||
return err
|
||||
o := orm.NewOrm()
|
||||
if len(cols) == 0 {
|
||||
_, err := o.Update(m)
|
||||
return err
|
||||
}
|
||||
_, err := o.Update(m, cols...)
|
||||
return err
|
||||
}
|
||||
|
||||
// SoftDeleteSupplier 软删除供应商
|
||||
func SoftDeleteSupplier(id string) error {
|
||||
o := orm.NewOrm()
|
||||
now := time.Now()
|
||||
m := models.Supplier{Id: id, DeleteTime: &now}
|
||||
_, err := o.Update(&m, "delete_time")
|
||||
return err
|
||||
func SoftDeleteSupplier(id int64) error {
|
||||
o := orm.NewOrm()
|
||||
now := time.Now()
|
||||
m := models.Supplier{Id: id, DeleteTime: &now}
|
||||
_, err := o.Update(&m, "delete_time")
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新供应商开票信息
|
||||
func UpdateSupplierInvoice(params orm.Params) error {
|
||||
o := orm.NewOrm()
|
||||
// Extract id from params
|
||||
idVal, ok := params["id"]
|
||||
if !ok || idVal == "" {
|
||||
return fmt.Errorf("id is required")
|
||||
}
|
||||
var id int64
|
||||
switch v := idVal.(type) {
|
||||
case int64:
|
||||
id = v
|
||||
case int:
|
||||
id = int64(v)
|
||||
case float64:
|
||||
id = int64(v)
|
||||
case string:
|
||||
if v == "" {
|
||||
return fmt.Errorf("id is required")
|
||||
}
|
||||
n, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid id: %v", err)
|
||||
}
|
||||
id = n
|
||||
default:
|
||||
return fmt.Errorf("invalid id type")
|
||||
}
|
||||
delete(params, "id") // remove id from update fields
|
||||
if _, err := o.QueryTable(new(models.Supplier)).Filter("id", id).Update(params); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user