优化
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import '@/styles/adminDensity.css'
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { FileUp, MessageSquareText, Pencil, Trash2 } from '@lucide/vue'
|
||||
import { FileUp } from '@lucide/vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
import AdminPage from '@/shared/components/AdminPage.vue'
|
||||
@@ -84,6 +85,7 @@ const dialogOpen = ref(false)
|
||||
const detailLoading = ref(false)
|
||||
const saving = ref(false)
|
||||
const uploading = ref(false)
|
||||
const fileInput = ref<HTMLInputElement | null>(null)
|
||||
const detail = ref<CustomerVisitDetail | null>(null)
|
||||
const form = reactive({ selectedInboundOrderId: '', visitResult: '', customerFeedback: '', remark: '', attachments: [] as CustomerVisitAttachment[] })
|
||||
|
||||
@@ -155,20 +157,16 @@ onMounted(load)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AdminPage>
|
||||
<AdminPage class="admin-density">
|
||||
<ListPageLayout aria-label="客户回访列表">
|
||||
<template #toolbar>
|
||||
<section class="visit-summary surface-panel" aria-label="回访统计">
|
||||
<div><strong>{{ summary.visitable }}</strong><span>可回访订单</span></div>
|
||||
<div><strong>{{ summary.pending }}</strong><span>待回访</span></div>
|
||||
<div><strong>{{ summary.completed }}</strong><span>已完成回访</span></div>
|
||||
</section>
|
||||
<dl class="visit-summary density-detail-overview" aria-label="回访统计"><div><dt>可回访订单</dt><dd>{{ summary.visitable }}</dd></div><div><dt>待回访</dt><dd>{{ summary.pending }}</dd></div><div><dt>已完成回访</dt><dd>{{ summary.completed }}</dd></div></dl>
|
||||
<QueryToolbar v-model="filters" class="surface-panel" :fields="fields" :loading="loading" ariaLabel="客户回访筛选" preference-key="overseas-customer-visits-query" :search-on-reset="false" @search="search" @reset="reset" />
|
||||
</template>
|
||||
<template v-if="error" #state><LoadErrorState title="客户回访列表加载失败" :message="error.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState></template>
|
||||
<section v-if="!error" class="surface-panel table-panel">
|
||||
<DataTable v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :loading="loading" :total="total" preference-key="overseas-customer-visits" row-key="id" empty-text="暂无可回访派送单" @update:page="load()" @update:page-size="page = 1; load()">
|
||||
<template #actions="{ row }"><el-button v-if="access.can(permission.customerVisitEdit)" size="small" type="primary" plain @click="openVisit(row as CustomerVisitRow)"><component :is="row.status === 'COMPLETED' ? Pencil : MessageSquareText" :size="15" />{{ row.status === 'COMPLETED' ? '再次回访' : '回访' }}</el-button></template>
|
||||
<template #actions="{ row }"><el-button v-if="access.can(permission.customerVisitEdit)" class="density-action" data-tone="neutral" size="small" @click="openVisit(row as CustomerVisitRow)">{{ row.status === 'COMPLETED' ? '再次回访' : '回访' }}</el-button></template>
|
||||
<template #status="{ row }"><StatusBadge :status="row.status === 'COMPLETED' ? '已回访' : '待回访'" /></template>
|
||||
<template #pagination-left>共 {{ total }} 条回访记录</template>
|
||||
</DataTable>
|
||||
@@ -176,15 +174,15 @@ onMounted(load)
|
||||
</ListPageLayout>
|
||||
</AdminPage>
|
||||
|
||||
<AppDialog v-model="dialogOpen" title="客户回访" width="min(920px, calc(100vw - 28px))" confirm-text="保存回访" :loading="detailLoading" :confirm-loading="saving" :confirm-disabled="uploading" @confirm="submit">
|
||||
<AppDialog class="admin-density-dialog" v-model="dialogOpen" title="客户回访" width="min(920px, calc(100vw - 28px))" confirm-text="保存回访" :loading="detailLoading" :confirm-loading="saving" :confirm-disabled="uploading" @confirm="submit">
|
||||
<template v-if="detail">
|
||||
<section class="visit-order-summary">
|
||||
<div><span>派送单号</span><strong>{{ detail.visit.deliveryNo }}</strong></div>
|
||||
<div><span>客户</span><strong>{{ detail.visit.customerName || '—' }}</strong></div>
|
||||
<div><span>完成时间</span><strong>{{ formatOverseasDateTime(detail.visit.deliveryCompletedAt) }}</strong></div>
|
||||
<div><span>目的地</span><strong>{{ detail.visit.destination || '—' }}</strong></div>
|
||||
</section>
|
||||
<el-form label-position="top" class="visit-form">
|
||||
<dl class="visit-order-summary density-fields">
|
||||
<div><dt>派送单号</dt><dd>{{ detail.visit.deliveryNo }}</dd></div>
|
||||
<div><dt>客户</dt><dd>{{ detail.visit.customerName || '—' }}</dd></div>
|
||||
<div><dt>完成时间</dt><dd>{{ formatOverseasDateTime(detail.visit.deliveryCompletedAt) }}</dd></div>
|
||||
<div><dt>目的地</dt><dd>{{ detail.visit.destination || '—' }}</dd></div>
|
||||
</dl>
|
||||
<el-form label-position="top" class="visit-form density-form">
|
||||
<el-form-item label="选择本次回访的入仓单" required>
|
||||
<div class="inbound-options">
|
||||
<label v-for="option in detail.inboundOrders" :key="option.inboundOrderId" class="inbound-option" :class="{ selected: form.selectedInboundOrderId === option.inboundOrderId }">
|
||||
@@ -197,10 +195,10 @@ onMounted(load)
|
||||
<el-form-item label="客户反馈"><el-input v-model="form.customerFeedback" type="textarea" :rows="4" maxlength="2000" show-word-limit placeholder="记录客户对派送服务的反馈" /></el-form-item>
|
||||
<el-form-item label="备注"><el-input v-model="form.remark" type="textarea" :rows="3" maxlength="1000" show-word-limit placeholder="补充后续跟进事项" /></el-form-item>
|
||||
<el-form-item label="回访资料">
|
||||
<label class="file-picker" :class="{ disabled: uploading }"><FileUp :size="16" />{{ uploading ? '上传中...' : '选择文件' }}<input type="file" multiple :disabled="uploading" accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.jpg,.jpeg,.png,.webp" @change="selectFiles" /></label>
|
||||
<el-button :disabled="uploading" :loading="uploading" @click="fileInput?.click()"><FileUp :size="16" />{{ uploading ? '上传中...' : '选择文件' }}</el-button><input ref="fileInput" hidden type="file" multiple :disabled="uploading" accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.jpg,.jpeg,.png,.webp" @change="selectFiles" />
|
||||
<span class="file-tip">支持 PDF、Office、JPG、PNG、WEBP,单个不超过20MB</span>
|
||||
<div v-if="form.attachments.length" class="attachment-list">
|
||||
<div v-for="file in form.attachments" :key="file.fileId"><span>{{ file.fileName }}</span><small>{{ fileSize(file.sizeBytes) }}</small><el-button text type="danger" aria-label="移除资料" @click="removeAttachment(file.fileId)"><Trash2 :size="15" /></el-button></div>
|
||||
<div v-for="file in form.attachments" :key="file.fileId"><span>{{ file.fileName }}</span><small>{{ fileSize(file.sizeBytes) }}</small><el-button class="density-action" data-tone="danger" size="small" aria-label="移除资料" @click="removeAttachment(file.fileId)">移除</el-button></div>
|
||||
</div>
|
||||
<div v-else class="attachment-empty">暂未上传回访资料</div>
|
||||
</el-form-item>
|
||||
@@ -210,19 +208,15 @@ onMounted(load)
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.visit-summary { display: flex; gap: 12px; padding: 12px; }
|
||||
.visit-summary > div { display: flex; align-items: baseline; gap: 8px; min-width: 140px; padding: 8px 14px; border: 1px solid var(--cc-border-color); border-radius: 8px; }
|
||||
.visit-summary strong { font-size: 22px; color: var(--cc-text-primary); } .visit-summary span { color: var(--cc-text-secondary); }
|
||||
.visit-order-summary { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px 28px; padding: 18px; margin-bottom: 18px; border: 1px solid var(--cc-border-color); border-radius: 8px; background: var(--cc-bg-muted); }
|
||||
.visit-order-summary div { display: grid; gap: 5px; } .visit-order-summary span { color: var(--cc-text-secondary); } .visit-order-summary strong { color: var(--cc-text-primary); }
|
||||
.inbound-options { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; width: 100%; }
|
||||
.inbound-option { display: grid; gap: 6px; padding: 12px; border: 1px solid var(--cc-border-color); border-radius: 8px; cursor: pointer; color: var(--cc-text-secondary); }
|
||||
.visit-order-reference { display: flex; min-width: 0; align-items: baseline; gap: 3px; }
|
||||
.visit-summary { flex-wrap: wrap; }
|
||||
.visit-order-summary.density-fields { grid-template-columns: repeat(2, minmax(0, 1fr)); margin-bottom: var(--admin-space-5); }
|
||||
.inbound-options { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--admin-space-3); width: 100%; }
|
||||
.inbound-option { display: grid; min-width: 0; gap: var(--admin-space-2); padding: var(--admin-space-3); border: 1px solid var(--cc-border); border-radius: var(--admin-radius); cursor: pointer; color: var(--cc-muted); overflow-wrap: anywhere; }
|
||||
.visit-order-reference { display: flex; min-width: 0; align-items: baseline; gap: var(--admin-space-1); }
|
||||
.inbound-option.selected { border-color: var(--el-color-primary); background: var(--el-color-primary-light-9); }
|
||||
.file-picker { display: inline-flex; align-items: center; gap: 6px; padding: 8px 12px; color: var(--el-color-primary); border: 1px solid var(--el-color-primary); border-radius: 6px; cursor: pointer; }
|
||||
.file-picker input { display: none; } .file-picker.disabled { opacity: .6; cursor: not-allowed; } .file-tip { margin-left: 12px; color: var(--cc-text-secondary); }
|
||||
.attachment-list, .attachment-empty { width: 100%; margin-top: 12px; }
|
||||
.attachment-list > div { display: grid; grid-template-columns: 1fr auto auto; align-items: center; gap: 10px; padding: 8px 10px; border-bottom: 1px solid var(--cc-border-color); }
|
||||
.attachment-list small, .attachment-empty { color: var(--cc-text-secondary); }
|
||||
@media (max-width: 720px) { .visit-summary { flex-wrap: wrap; } .visit-order-summary, .inbound-options { grid-template-columns: 1fr; } .file-tip { display: block; margin: 8px 0 0; } }
|
||||
.file-tip { margin-left: var(--admin-space-3); color: var(--cc-muted); font-size: var(--admin-font-caption); }
|
||||
.attachment-list, .attachment-empty { width: 100%; margin-top: var(--admin-space-3); }
|
||||
.attachment-list > div { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; align-items: center; gap: var(--admin-space-3); padding: var(--admin-space-2) 0; border-bottom: 1px solid var(--cc-border); overflow-wrap: anywhere; }
|
||||
.attachment-list small, .attachment-empty { color: var(--cc-muted); font-size: var(--admin-font-caption); }
|
||||
@media (max-width: 720px) { .visit-summary { flex-wrap: wrap; } .visit-order-summary.density-fields, .inbound-options { grid-template-columns: 1fr; } .file-tip { display: block; margin: 8px 0 0; } }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user