This commit is contained in:
zlwu committed 2026-07-12 12:04:43 +08:00
1 parent 7b1d4c8729
commit ffc89d9b57
75 files changed
+12263 -7950

No files matched your search

+140 -6
View File
@@ -2,7 +2,7 @@
<el-dialog
title="选择客户"
v-model="dialogVisible"
width="1280px"
width="1500px"
align-center
class="dialog-form-layout"
modal-class="order-module-dialog-mask"
@@ -39,6 +39,7 @@
<div class="panel dialog-list-panel">
<div class="list-body list-wrap dialog-list-body-only">
<el-table
ref="tableRef"
v-loading="loading"
:data="customerList"
class="list-table"
@@ -48,7 +49,13 @@
:max-height="400"
>
<el-table-column type="index" label="序号" min-width="60" align="center" header-align="center" />
<el-table-column label="客户名称" prop="customerName" min-width="230" align="center" header-align="center" :show-overflow-tooltip="true" />
<el-table-column label="客户名称" prop="customerName" min-width="200" align="center" header-align="center" :show-overflow-tooltip="true" />
<el-table-column label="客户简称" prop="shortName" min-width="120" align="center" header-align="center" :show-overflow-tooltip="true">
<template #default="scope">
<span v-if="hasShortName(scope.row)">{{ scope.row.shortName }}</span>
<el-tag v-else type="warning" size="small">未填写</el-tag>
</template>
</el-table-column>
<el-table-column label="客户类型" prop="customerType" min-width="150" align="center" header-align="center">
<template #default="scope">
<DictTag dict-type="crm_customer_type" :value="scope.row.customerType" />
@@ -72,6 +79,13 @@
</template>
</el-table-column>
<el-table-column label="负责人" prop="ownerUserName" min-width="100" align="center" header-align="center" :show-overflow-tooltip="true" />
<el-table-column label="操作" fixed="right" min-width="120" align="center" header-align="center" class-name="col-actions">
<template #default="scope">
<el-button link type="primary" @click.stop="handleEditShortName(scope.row)">
<el-icon><Edit /></el-icon>{{ hasShortName(scope.row) ? '编辑简称' : '填写简称' }}
</el-button>
</template>
</el-table-column>
</el-table>
<ListPagination
@@ -90,6 +104,7 @@
<div class="selected-customer">
<el-descriptions :column="2" border>
<el-descriptions-item label="客户名称">{{ currentCustomer.customerName }}</el-descriptions-item>
<el-descriptions-item label="客户简称">{{ currentCustomer.shortName }}</el-descriptions-item>
<el-descriptions-item label="客户类型">
<DictTag dict-type="crm_customer_type" :value="currentCustomer.customerType" />
</el-descriptions-item>
@@ -110,7 +125,55 @@
<el-button class="btn-ghost" @click="handleClose">
<el-icon><Close /></el-icon>取消
</el-button>
<el-button class="btn-main" type="primary" @click="handleConfirm" :disabled="!currentCustomer">
<el-button
class="btn-main"
type="primary"
@click="handleConfirm"
:disabled="!currentCustomer || !hasShortName(currentCustomer)"
>
<el-icon><Check /></el-icon>确定
</el-button>
</div>
</template>
</el-dialog>
<!-- 填写/编辑客户简称 -->
<el-dialog
v-model="shortNameDialogVisible"
:title="hasShortName(shortNameForm) ? '编辑客户简称' : '填写客户简称'"
width="480px"
align-center
class="dialog-form-layout"
modal-class="order-module-dialog-mask"
:close-on-click-modal="false"
append-to-body
>
<el-form
ref="shortNameFormRef"
:model="shortNameForm"
:rules="shortNameRules"
label-position="top"
class="crm-dialog-form"
>
<el-form-item label="客户名称">
<el-input :model-value="shortNameForm.customerName" disabled />
</el-form-item>
<el-form-item label="客户简称" prop="shortName">
<el-input
v-model="shortNameForm.shortName"
placeholder="请输入客户简称"
maxlength="100"
show-word-limit
clearable
/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button class="btn-ghost" @click="shortNameDialogVisible = false">
<el-icon><Close /></el-icon>取消
</el-button>
<el-button class="btn-main" type="primary" :loading="shortNameSaving" @click="handleSaveShortName">
<el-icon><Check /></el-icon>确定
</el-button>
</div>
@@ -122,10 +185,10 @@
import { useDictStore } from '@/store/modules/dict'
import DictTag from '@/components/DictTag/index.vue'
import ListPagination from '@/components/ListPagination/index.vue'
import { Check, Close, Refresh, Search } from '@element-plus/icons-vue'
import { Check, Close, Edit, Refresh, Search } from '@element-plus/icons-vue'
import { ref, watch } from 'vue'
import type { ElFormInstance } from 'element-plus'
import { fetchCustomerList } from '@/api/crm/customer'
import type { ElFormInstance, FormItemRule } from 'element-plus'
import { fetchCustomerList, fetchUpdateCustomer } from '@/api/crm/customer'
const dictStore = useDictStore()
@@ -167,6 +230,21 @@ const loading = ref(false)
const total = ref(0)
const currentCustomer = ref<any | null>(null)
const queryFormRef = ref<ElFormInstance>()
const tableRef = ref<any>()
const shortNameDialogVisible = ref(false)
const shortNameSaving = ref(false)
const shortNameFormRef = ref<ElFormInstance>()
const shortNameForm = ref({
id: '' as string | number,
customerName: '',
shortName: ''
})
const shortNameRules: Record<string, FormItemRule[]> = {
shortName: [{ required: true, message: '客户简称不能为空', trigger: 'blur' }]
}
const hasShortName = (row?: { shortName?: string } | null) => Boolean(String(row?.shortName || '').trim())
const getList = async () => {
loading.value = true
@@ -213,10 +291,66 @@ watchDebounced(
)
const handleCurrentChange = (row: any) => {
if (!row) {
currentCustomer.value = null
return
}
if (!hasShortName(row)) {
ElMessage.warning('该客户尚未填写简称,请先填写客户简称后再选择')
tableRef.value?.setCurrentRow(undefined)
currentCustomer.value = null
return
}
currentCustomer.value = row
}
const handleEditShortName = (row: any) => {
shortNameForm.value = {
id: row.id,
customerName: row.customerName || '',
shortName: row.shortName || ''
}
shortNameDialogVisible.value = true
nextTick(() => shortNameFormRef.value?.clearValidate())
}
const handleSaveShortName = async () => {
const valid = await shortNameFormRef.value?.validate().catch(() => false)
if (!valid) return
shortNameSaving.value = true
try {
const trimmedShortName = shortNameForm.value.shortName.trim()
await fetchUpdateCustomer({
id: shortNameForm.value.id,
shortName: trimmedShortName
})
ElMessage.success('客户简称保存成功')
const targetId = shortNameForm.value.id
const updatedRow = customerList.value.find((item) => item.id === targetId)
if (updatedRow) {
updatedRow.shortName = trimmedShortName
}
if (currentCustomer.value?.id === targetId) {
currentCustomer.value = { ...currentCustomer.value, shortName: trimmedShortName }
}
shortNameDialogVisible.value = false
} catch (error) {
console.error('保存客户简称失败:', error)
ElMessage.error('保存客户简称失败')
} finally {
shortNameSaving.value = false
}
}
const handleConfirm = () => {
if (!currentCustomer.value) return
if (!hasShortName(currentCustomer.value)) {
ElMessage.warning('该客户尚未填写简称,请先填写客户简称后再选择')
return
}
emit('confirm', currentCustomer.value)
handleClose()
}
@@ -54,6 +54,9 @@
</span>
</div>
</template>
<span v-if="getCategoryLabel(file)" class="attachment-preview__category">
{{ getCategoryLabel(file) }}
</span>
</div>
<button
v-if="compact && !disabled"
@@ -165,6 +168,8 @@ const props = withDefaults(
disabled?: boolean
/** 紧凑模式:小缩略图,只读时隐藏文件名与操作栏 */
compact?: boolean
/** 附件 URL → 分类名称,用于查看态在缩略图上展示分类标签 */
categoryLabels?: Record<string, string>
}>(),
{
modelValue: '',
@@ -173,7 +178,8 @@ const props = withDefaults(
accept: 'image/*,video/*,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.zip,.rar',
hint: '支持图片、视频、PDF、Word、Excel等文件',
disabled: false,
compact: false
compact: false,
categoryLabels: () => ({})
}
)
@@ -269,6 +275,11 @@ function getFileExtension(fileName: string) {
return fileName.substring(lastDotIndex + 1).toUpperCase()
}
function getCategoryLabel(file: AttachmentItem) {
const label = props.categoryLabels?.[file.fileUrl]
return label ? String(label).trim() : ''
}
function canPreview(file: AttachmentItem) {
return Boolean(file.fileUrl) && (isImageFile(file) || isVideoFile(file) || isPdfFile(file))
}
@@ -420,6 +431,7 @@ defineExpose({ hasUploading })
}
.attachment-item {
flex: 0 0 auto;
width: 140px;
border: 1px solid #e2e8f0;
border-radius: 8px;
@@ -438,6 +450,7 @@ defineExpose({ hasUploading })
}
.attachment-preview {
position: relative;
width: 100%;
height: 96px;
display: flex;
@@ -490,6 +503,21 @@ defineExpose({ hasUploading })
color: #1e293b;
}
.attachment-preview__category {
position: absolute;
right: 0;
bottom: 0;
left: 0;
z-index: 1;
padding: 2px 4px;
background: rgba(15, 23, 42, 0.62);
color: #fff;
font-size: 11px;
line-height: 1.3;
text-align: center;
word-break: keep-all;
}
.attachment-footer {
display: flex;
flex-direction: column;
@@ -565,7 +593,8 @@ defineExpose({ hasUploading })
}
:deep(.el-upload) {
display: block;
display: inline-flex;
flex: 0 0 auto;
}
.attachment-upload--compact {
@@ -204,7 +204,7 @@
调整运输时现场链接将轮换更新,旧现场链接将失效。
</template>
<template v-else-if="showDibangHint">
勾选后将按产品进行地磅单生成(当月已有则复用),并在地磅出入库管理中产生对应记录。
勾选后将按产品进行地磅单生成(当月已有则复用);入库单日期取送达时间,出库单日期取取货时间,未填时回退计划日期。
</template>
</p>
</div>
@@ -1,12 +1,12 @@
<template>
<div class="view-kv-table-cell">
<el-tag v-if="field.tagType" :type="field.tagType" size="small">{{
<el-tag v-if="field.tagType" :type="field.tagType" size="small" class="view-kv-tag">{{
formatViewValue(field.value)
}}</el-tag>
<ul v-else-if="field.items?.length" class="view-plain-list">
<li v-for="(item, idx) in field.items" :key="idx">{{ item }}</li>
</ul>
<span v-else :class="{ 'money-display': field.money, 'muted-display': isMuted }">{{
<span v-else :class="{ 'money-display': field.money, 'danger-display': field.danger, 'muted-display': isMuted }">{{
formatViewValue(field.value)
}}</span>
<el-button
@@ -49,7 +49,7 @@
const onFieldView = inject<((field: ViewField) => void) | undefined>(VIEW_KV_FIELD_VIEW_KEY)
const isMuted = computed(
() => !props.field.money && formatViewValue(props.field.value) === '—'
() => !props.field.money && !props.field.danger && formatViewValue(props.field.value) === '—'
)
const showViewBtn = computed(() => {
@@ -91,6 +91,10 @@
gap: 6px;
}
.view-kv-tag {
font-weight: 600;
}
.view-kv-field-link {
margin: 0;
padding: 0 4px;
@@ -112,4 +116,9 @@
color: var(--text-muted, #64748b);
font-weight: 600;
}
.danger-display {
color: #dc2626;
font-weight: 600;
}
</style>
@@ -18,6 +18,8 @@ export interface ViewField {
fullSpan?: boolean
tagType?: TagType
money?: boolean
/** 红色加粗强调展示(如结算方式等关键业务字段) */
danger?: boolean
/** 值后展示「查看」按钮,点击时 ViewKvTable 抛出 field-view */
viewTarget?: 'customer' | 'opportunity' | 'quote'
viewId?: string | number | null