batch customer event

This commit is contained in:
Timi
2026-08-03 17:28:19 +08:00
parent 337700cfa7
commit 386d5e0613
5 changed files with 362 additions and 135 deletions
+2 -1
View File
@@ -41,7 +41,8 @@ async function rangeExport(req: RegistrationEventRecordRangeReq): Promise<Blob>
}); });
} }
async function create(req: RegistrationEventRecordCreateReq): Promise<RegistrationEventRecord> {
async function create(req: RegistrationEventRecordCreateReq): Promise<RegistrationEventRecord[]> {
return await axios.post(`${BASE_URI}/create`, req); return await axios.post(`${BASE_URI}/create`, req);
} }
+162
View File
@@ -0,0 +1,162 @@
<template>
<div class="customer-list">
<t-cell-group v-if="customerList.length">
<t-cell
v-for="(item, index) in customerList"
:key="item.id || item.code || index"
:note="item.code"
hover
@click="handleSelect(item)"
>
<template #title>
<div class="cell-title">
<span v-text="item.name || item.code || '未命名客户'" />
<gender-icon :value="item.gender" />
</div>
</template>
<template #description>
<telephone-link :value="item.telephone" placeholder="未填写电话" />
</template>
<template #leftIcon>
<div class="left-icon">
<span v-if="selectable" class="check" :class="{selected: isSelected(item)}">
<t-icon v-if="isSelected(item)" name="check" />
</span>
<t-image v-if="getCustomerPhotoURL(item)" :src="getCustomerPhotoURL(item)" :alt="item.name" fit="cover" class="avatar" />
<div v-else class="avatar fallback" v-text="(item.name || item.code || '客').slice(0, 1)" />
</div>
</template>
<template v-if="removable" #rightIcon>
<t-button theme="danger" size="extra-small" @click.stop="handleRemove(item)">移除</t-button>
</template>
</t-cell>
</t-cell-group>
<div v-else class="empty-block" v-text="emptyText" />
</div>
</template>
<script setup lang="ts">
import type { Customer } from "@/types/Customer";
import { getCustomerPhotoURL } from "@/utils/customer";
defineOptions({
name: "CustomerList"
});
interface Props {
customerList: Customer[];
selectedIdList?: string[];
selectable?: boolean;
removable?: boolean;
emptyText?: string;
}
const props = withDefaults(defineProps<Props>(), {
selectedIdList: () => [],
selectable: false,
removable: false,
emptyText: "暂无客户"
});
const emit = defineEmits<{
select: [customer: Customer];
remove: [customer: Customer];
}>();
const selectedIdSet = computed(() => new Set(props.selectedIdList));
function isSelected(customer: Customer): boolean {
return !!customer.id && selectedIdSet.value.has(customer.id);
}
function handleSelect(customer: Customer): void {
emit("select", customer);
}
function handleRemove(customer: Customer): void {
emit("remove", customer);
}
</script>
<style scoped lang="less">
.customer-list {
.cell-title {
gap: .5rem;
display: flex;
align-items: center;
}
.left-icon {
gap: .5rem;
display: flex;
align-items: center;
}
.check {
width: 1.25rem;
height: 1.25rem;
color: #fff;
display: flex;
flex: none;
border: 1px solid var(--td-component-border);
border-radius: 50%;
align-items: center;
justify-content: center;
&.selected {
border-color: var(--td-brand-color);
background: var(--td-brand-color);
}
}
.avatar {
width: 3.5rem;
height: 3.5rem;
border-radius: .5rem;
&.fallback {
color: #2563eb;
display: flex;
font-size: 1.25rem;
background: #dbeafe;
align-items: center;
font-weight: 600;
justify-content: center;
}
}
.empty-block {
color: #52607a;
margin: 1rem;
padding: 1rem;
text-align: center;
background: #f8fbff;
border-radius: 1rem;
}
}
:deep(.avatar .t-image__img) {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: inherit;
}
:global(html.theme-dark) {
.customer-list {
.check {
border-color: rgba(148, 163, 184, .4);
}
.empty-block {
color: #e2e8f0;
background: rgba(30, 41, 59, .9);
}
.avatar.fallback {
color: #93c5fd;
background: rgba(30, 64, 175, .32);
}
}
}
</style>
+86 -84
View File
@@ -1,7 +1,18 @@
<template> <template>
<t-popup v-model="popupVisible" placement="bottom"> <t-popup v-model="popupVisible" placement="bottom">
<div class="customer-selector bg-white"> <div class="customer-selector bg-white">
<div class="title-bar">
<t-button variant="text" size="small" @click="cancelSelection">取消</t-button>
<h3 class="title">选择客户</h3> <h3 class="title">选择客户</h3>
<t-button
v-if="isMultiple"
theme="primary"
variant="text"
size="small"
@click="confirmSelection"
>确定</t-button>
<span v-else class="title-placeholder" />
</div>
<div class="search-wrapper"> <div class="search-wrapper">
<t-input <t-input
v-model.trim="keyword" v-model.trim="keyword"
@@ -11,33 +22,16 @@
placeholder="姓名 / 编码 / 手机号" placeholder="姓名 / 编码 / 手机号"
/> />
</div> </div>
<div v-if="customerOptions.length" ref="resultListRef" class="result-list"> <div ref="resultListRef" class="result-list">
<t-cell-group> <customer-list
<t-cell :customer-list="customerOptions"
v-for="(item, index) in customerOptions" :selected-id-list="draftSelectedIdList"
:key="item.id || item.code || index" :selectable="isMultiple"
:note="item.code" :empty-text="loading ? '正在加载客户' : '未找到匹配客户'"
hover @select="toggleCustomer"
@click="selectCustomer(item)" />
>
<template #title>
<div class="cell-title">
<span v-text="item.name || item.code || '未命名客户'" />
<gender-icon :value="item.gender" />
</div>
</template>
<template #description>
<telephone-link :value="item.telephone" placeholder="未填写电话" />
</template>
<template #leftIcon>
<t-image v-if="getCustomerPhotoURL(item)" :src="getCustomerPhotoURL(item)" :alt="item.name" fit="cover" class="avatar" />
<div v-else class="avatar fallback" v-text="(item.name || item.code || '客').slice(0, 1)" />
</template>
</t-cell>
</t-cell-group>
<div class="load-state" v-text="loadStateText" /> <div class="load-state" v-text="loadStateText" />
</div> </div>
<div v-else class="empty-block" v-text="loading ? '正在加载客户' : '未找到匹配客户'" />
</div> </div>
</t-popup> </t-popup>
</template> </template>
@@ -45,16 +39,23 @@
<script setup lang="ts"> <script setup lang="ts">
import { Scroller } from "timi-web"; import { Scroller } from "timi-web";
import CustomerAPI from "@/api/CustomerAPI"; import CustomerAPI from "@/api/CustomerAPI";
import CustomerList from "@/components/CustomerList.vue";
import type { Customer } from "@/types/Customer"; import type { Customer } from "@/types/Customer";
import { getCustomerPhotoURL } from "@/utils/customer";
const props = defineProps<{ type SelectionMode = "single" | "multiple";
modelValue: string;
interface Props {
modelValue: string | string[];
visible: boolean; visible: boolean;
}>(); selectionMode?: SelectionMode;
}
const props = withDefaults(defineProps<Props>(), {
selectionMode: "single"
});
const emit = defineEmits<{ const emit = defineEmits<{
"update:modelValue": [value: string]; "update:modelValue": [value: string | string[]];
"update:visible": [value: boolean]; "update:visible": [value: boolean];
change: [value: string]; change: [value: string];
}>(); }>();
@@ -68,9 +69,12 @@ const pageIndex = ref(0);
const totalCount = ref(0); const totalCount = ref(0);
const customerOptions = ref<Customer[]>([]); const customerOptions = ref<Customer[]>([]);
const resultListRef = ref<HTMLElement>(); const resultListRef = ref<HTMLElement>();
const draftSelectedIdList = ref<string[]>([]);
const selectedCustomerCache = new Map<string, Customer>();
let searchToken = 0; let searchToken = 0;
let keywordTimer: number | undefined; let keywordTimer: number | undefined;
const isMultiple = computed(() => props.selectionMode === "multiple");
const loadStateText = computed(() => { const loadStateText = computed(() => {
if (loading.value) { if (loading.value) {
return "正在加载更多"; return "正在加载更多";
@@ -116,6 +120,11 @@ async function loadCustomers(): Promise<void> {
return; return;
} }
const list = result.list || []; const list = result.list || [];
for (const item of list) {
if (item.id) {
selectedCustomerCache.set(item.id, item);
}
}
customerOptions.value = customerOptions.value.concat(list); customerOptions.value = customerOptions.value.concat(list);
totalCount.value = result.total || 0; totalCount.value = result.total || 0;
pageIndex.value += 1; pageIndex.value += 1;
@@ -128,7 +137,7 @@ async function loadCustomers(): Promise<void> {
} }
async function bindScrollListener(): Promise<void> { async function bindScrollListener(): Promise<void> {
if (!props.visible || customerOptions.value.length <= 0) { if (!props.visible || 0 === customerOptions.value.length) {
Scroller.removeListener(scrollListenerName); Scroller.removeListener(scrollListenerName);
return; return;
} }
@@ -140,24 +149,58 @@ async function bindScrollListener(): Promise<void> {
}); });
} }
function selectCustomer(item: Customer): void { function getModelValueIdList(): string[] {
const value = props.modelValue;
return (Array.isArray(value) ? value : [value]).filter((item): item is string => Boolean(item));
}
function syncDraftSelection(): void {
draftSelectedIdList.value = getModelValueIdList();
}
function toggleCustomer(item: Customer): void {
const id = item.id; const id = item.id;
if (!id) { if (!id) {
return; return;
} }
selectedCustomerCache.set(id, item);
if (!isMultiple.value) {
emit("update:modelValue", id); emit("update:modelValue", id);
emit("change", id); emit("change", id);
emit("update:visible", false); emit("update:visible", false);
return;
}
if (draftSelectedIdList.value.includes(id)) {
draftSelectedIdList.value = draftSelectedIdList.value.filter(itemId => itemId !== id);
return;
}
draftSelectedIdList.value = [...draftSelectedIdList.value, id];
}
function cancelSelection(): void {
syncDraftSelection();
emit("update:visible", false);
}
function confirmSelection(): void {
const value = [...draftSelectedIdList.value];
emit("update:modelValue", value);
emit("change", value as unknown as string);
emit("update:visible", false);
} }
watch(() => props.visible, async (value) => { watch(() => props.visible, async (value) => {
if (!value) { if (!value) {
syncDraftSelection();
keyword.value = ""; keyword.value = "";
window.clearTimeout(keywordTimer); window.clearTimeout(keywordTimer);
Scroller.removeListener(scrollListenerName); Scroller.removeListener(scrollListenerName);
return; return;
} }
syncDraftSelection();
await reloadCustomers(); await reloadCustomers();
await bindScrollListener(); await bindScrollListener();
}); });
@@ -191,11 +234,22 @@ onUnmounted(() => {
border-radius: 1.25rem 1.25rem 0 0; border-radius: 1.25rem 1.25rem 0 0;
flex-direction: column; flex-direction: column;
.title-bar {
padding: 0 1rem;
display: grid;
grid-template-columns: 4rem 1fr 4rem;
align-items: center;
}
.title { .title {
margin: 0; margin: 0;
text-align: center; text-align: center;
} }
.title-placeholder {
height: 1px;
}
.search-wrapper { .search-wrapper {
z-index: 1; z-index: 1;
padding: 1rem 0 .5rem 0; padding: 1rem 0 .5rem 0;
@@ -216,12 +270,6 @@ onUnmounted(() => {
overflow: auto; overflow: auto;
min-height: 0; min-height: 0;
padding-bottom: env(safe-area-inset-bottom, 0); padding-bottom: env(safe-area-inset-bottom, 0);
.cell-title {
gap: .5rem;
display: flex;
align-items: center;
}
} }
.load-state { .load-state {
@@ -232,59 +280,13 @@ onUnmounted(() => {
} }
} }
.avatar {
width: 3.5rem;
height: 3.5rem;
border-radius: .5rem;
&.fallback {
color: #2563eb;
display: flex;
font-size: 1.25rem;
background: #dbeafe;
align-items: center;
font-weight: 600;
justify-content: center;
}
}
:deep(.avatar .t-image__img) {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: inherit;
}
.empty-block {
color: #52607a;
margin: 1rem;
padding: 1rem;
text-align: center;
background: #f8fbff;
border-radius: 1rem;
}
:global(html.theme-dark) { :global(html.theme-dark) {
.customer-selector { .customer-selector {
background: rgba(15, 23, 42, .96); background: rgba(15, 23, 42, .96);
} }
.empty-block {
color: #e2e8f0;
background: rgba(30, 41, 59, .9);
border-color: rgba(148, 163, 184, .16);
}
.load-state { .load-state {
color: #94a3b8; color: #94a3b8;
} }
.avatar {
&.fallback {
color: #93c5fd;
background: rgba(30, 64, 175, .32);
}
}
} }
</style> </style>
@@ -8,6 +8,7 @@
readonly readonly
borderless borderless
placeholder="选择或扫码客户" placeholder="选择或扫码客户"
@click="openSelectedCustomerList"
> >
<template #suffix-icon> <template #suffix-icon>
<div class="actions"> <div class="actions">
@@ -15,12 +16,12 @@
theme="primary" theme="primary"
variant="outline" variant="outline"
size="extra-small" size="extra-small"
@click="selectorVisible = true" @click.stop="selectorVisible = true"
>选择</t-button> >选择</t-button>
<t-button <t-button
theme="primary" theme="primary"
size="extra-small" size="extra-small"
@click="toggleScanner" @click.stop="toggleScanner"
> >
<span v-text="scannerVisible ? '关闭扫码' : '扫码'" /> <span v-text="scannerVisible ? '关闭扫码' : '扫码'" />
</t-button> </t-button>
@@ -37,11 +38,6 @@
@status-change="handleScanStatusChange" @status-change="handleScanStatusChange"
/> />
</div> </div>
<customer-profile-card
v-if="selectedCustomer"
class="customer-profile"
:customer="selectedCustomer"
/>
<registration-event-selector <registration-event-selector
v-model="selectedRegistrationEventIdList" v-model="selectedRegistrationEventIdList"
class="registration-event-selector" class="registration-event-selector"
@@ -92,10 +88,27 @@
</t-cell-group> </t-cell-group>
<div v-else class="empty">当前客户暂无事件记录</div> <div v-else class="empty">当前客户暂无事件记录</div>
<customer-selector <customer-selector
v-model="selectedCustomerId" v-model="selectedCustomerIdList"
v-model:visible="selectorVisible" v-model:visible="selectorVisible"
:selection-mode="isEditMode ? 'single' : 'multiple'"
@change="handleCustomerChange" @change="handleCustomerChange"
/> />
<t-popup v-model="selectedCustomerListVisible" placement="bottom">
<div class="selected-customer-popup bg-white">
<div class="popup-header">
<t-button variant="text" size="small" @click="selectedCustomerListVisible = false">关闭</t-button>
<h3>已选客户</h3>
<t-button theme="primary" variant="text" size="small" @click="openCustomerSelector">选择</t-button>
</div>
<customer-list
class="list"
:customer-list="selectedCustomerList"
:removable="!isEditMode"
empty-text="暂未选择客户"
@remove="removeSelectedCustomer"
/>
</div>
</t-popup>
<input <input
ref="recordPhotoInputRef" ref="recordPhotoInputRef"
class="file-input" class="file-input"
@@ -128,8 +141,8 @@ import CustomerAPI from "@/api/CustomerAPI";
import RegistrationEventAPI from "@/api/RegistrationEventAPI"; import RegistrationEventAPI from "@/api/RegistrationEventAPI";
import RegistrationEventRecordAPI from "@/api/RegistrationEventRecordAPI"; import RegistrationEventRecordAPI from "@/api/RegistrationEventRecordAPI";
import CodeScanner from "@/components/CodeScanner.vue"; import CodeScanner from "@/components/CodeScanner.vue";
import CustomerList from "@/components/CustomerList.vue";
import CustomerSelector from "@/components/CustomerSelector.vue"; import CustomerSelector from "@/components/CustomerSelector.vue";
import CustomerProfileCard from "@/components/CustomerProfileCard.vue";
import PhotoScroller from "@/components/PhotoScroller.vue"; import PhotoScroller from "@/components/PhotoScroller.vue";
import RegistrationEventSelector from "@/components/RegistrationEventSelector.vue"; import RegistrationEventSelector from "@/components/RegistrationEventSelector.vue";
import { useSettingStore } from "@/store/settingStore"; import { useSettingStore } from "@/store/settingStore";
@@ -150,8 +163,9 @@ const settingStore = useSettingStore();
const customerCode = ref(""); const customerCode = ref("");
const scanCode = ref(customerCode.value); const scanCode = ref(customerCode.value);
const selectorVisible = ref(false); const selectorVisible = ref(false);
const selectedCustomerId = ref(""); const selectedCustomerListVisible = ref(false);
const selectedCustomer = ref<Customer>(); const selectedCustomerIdList = ref<string[]>([]);
const selectedCustomerList = ref<Customer[]>([]);
const registrationEventList = ref<RegistrationEvent[]>([]); const registrationEventList = ref<RegistrationEvent[]>([]);
const selectedRegistrationEventIdList = ref<string[]>([]); const selectedRegistrationEventIdList = ref<string[]>([]);
const recentRecordList = ref<RegistrationEventRecord[]>([]); const recentRecordList = ref<RegistrationEventRecord[]>([]);
@@ -178,8 +192,15 @@ const routeId = computed(() => String(route.params.id || ""));
const isEditMode = computed(() => !!routeId.value); const isEditMode = computed(() => !!routeId.value);
const pageAccess = computed(() => isEditMode.value ? gaoPermissionCodes.registrationEventRecord.update : gaoPermissionCodes.registrationEventRecord.create); const pageAccess = computed(() => isEditMode.value ? gaoPermissionCodes.registrationEventRecord.update : gaoPermissionCodes.registrationEventRecord.create);
const saveButtonText = computed(() => isEditMode.value ? "保存记录" : "提交"); const saveButtonText = computed(() => isEditMode.value ? "保存记录" : "提交");
const canSubmit = computed(() => !!selectedCustomer.value?.id && 0 < selectedRegistrationEventIdList.value.length); const selectedCustomer = computed(() => selectedCustomerList.value[0]);
const customerDisplayText = computed(() => selectedCustomer.value?.name || selectedCustomer.value?.code || customerCode.value); const canSubmit = computed(() => selectedCustomerList.value.some(item => !!item.id) && 0 < selectedRegistrationEventIdList.value.length);
const customerDisplayText = computed(() => {
if (1 < selectedCustomerList.value.length) {
return `已选择 ${selectedCustomerList.value.length} 位客户`;
}
return selectedCustomer.value?.name || selectedCustomer.value?.code || customerCode.value;
});
const photoList = computed<string[]>({ const photoList = computed<string[]>({
get() { get() {
return photoItemList.value.map(item => item.url); return photoItemList.value.map(item => item.url);
@@ -215,9 +236,9 @@ async function loadRecordForEdit(): Promise<void> {
const record = await RegistrationEventRecordAPI.detail(routeId.value); const record = await RegistrationEventRecordAPI.detail(routeId.value);
editingRecord.value = record; editingRecord.value = record;
appendRecordRegistrationEvent(record); appendRecordRegistrationEvent(record);
selectedCustomer.value = record.customer; selectedCustomerList.value = record.customer ? [record.customer] : [];
customerCode.value = record.customer?.code || record.customerId || ""; customerCode.value = record.customer?.code || record.customerId || "";
selectedCustomerId.value = record.customer?.id || ""; selectedCustomerIdList.value = record.customer?.id ? [record.customer.id] : [];
selectedRegistrationEventIdList.value = record.registrationEventId ? [record.registrationEventId] : []; selectedRegistrationEventIdList.value = record.registrationEventId ? [record.registrationEventId] : [];
registeredAtText.value = record.registeredAt ? Time.formatDateTime(new Date(record.registeredAt)) : ""; registeredAtText.value = record.registeredAt ? Time.formatDateTime(new Date(record.registeredAt)) : "";
note.value = record.remark || ""; note.value = record.remark || "";
@@ -278,12 +299,36 @@ function handleScanStatusChange(value: string): void {
// ---------- 客户 ---------- // ---------- 客户 ----------
async function handleCustomerChange(id: string): Promise<void> { async function handleCustomerChange(value: string | string[]): Promise<void> {
const customer = await CustomerAPI.detail(id); selectedCustomerIdList.value = Array.isArray(value) ? value : [value];
selectedCustomer.value = customer; await loadSelectedCustomerList();
selectedCustomerId.value = customer.id || ""; await loadRecentRecordList();
customerCode.value = customer.code || ""; }
scanCode.value = customer.code || "";
async function loadSelectedCustomerList(): Promise<void> {
const customerList = await Promise.all(selectedCustomerIdList.value.map(id => CustomerAPI.detail(id)));
selectedCustomerList.value = customerList.filter(item => !!item.id);
const firstCustomer = selectedCustomer.value;
customerCode.value = firstCustomer?.code || "";
scanCode.value = firstCustomer?.code || "";
}
function openSelectedCustomerList(): void {
selectedCustomerListVisible.value = true;
}
async function openCustomerSelector(): Promise<void> {
selectedCustomerListVisible.value = false;
await nextTick();
selectorVisible.value = true;
}
async function removeSelectedCustomer(customer: Customer): Promise<void> {
if (!customer.id || isEditMode.value) {
return;
}
selectedCustomerIdList.value = selectedCustomerIdList.value.filter(id => id !== customer.id);
selectedCustomerList.value = selectedCustomerList.value.filter(item => item.id !== customer.id);
await loadRecentRecordList(); await loadRecentRecordList();
} }
@@ -297,39 +342,32 @@ async function goRecordDetail(id: string): Promise<void> {
async function loadSelectedCustomer(): Promise<void> { async function loadSelectedCustomer(): Promise<void> {
const code = customerCode.value.trim(); const code = customerCode.value.trim();
if (!code) { if (!code) {
selectedCustomer.value = undefined; selectedCustomerList.value = [];
selectedCustomerId.value = ""; selectedCustomerIdList.value = [];
recentRecordList.value = []; recentRecordList.value = [];
return; return;
} }
const customer = await CustomerAPI.nullableDetailByCode(code); const customer = await CustomerAPI.nullableDetailByCode(code);
if (!customer) { if (!customer) {
selectedCustomer.value = undefined; selectedCustomerList.value = [];
selectedCustomerId.value = ""; selectedCustomerIdList.value = [];
recentRecordList.value = []; recentRecordList.value = [];
scanStatus.value = `客户 ${code} 不存在,请先建档。`; scanStatus.value = `客户 ${code} 不存在,请先建档。`;
return; return;
} }
selectedCustomer.value = customer; if (customer.id) {
selectedCustomerId.value = customer.id || ""; selectedCustomerIdList.value = isEditMode.value
? [customer.id]
: [...new Set([...selectedCustomerIdList.value, customer.id])];
selectedCustomerList.value = isEditMode.value
? [customer]
: [...selectedCustomerList.value.filter(item => item.id !== customer.id), customer];
}
await loadRecentRecordList(); await loadRecentRecordList();
} }
async function resolveSubmitCustomer(): Promise<Customer | undefined> {
if (!validateForm()) {
return undefined;
}
if (selectedCustomer.value?.id) {
return selectedCustomer.value;
}
showToast("请先选择或扫码已有客户", "warning");
return undefined;
}
async function loadRecentRecordList(): Promise<void> { async function loadRecentRecordList(): Promise<void> {
const customerId = selectedCustomer.value?.id; const customerId = selectedCustomer.value?.id;
if (!customerId) { if (!customerId) {
@@ -399,8 +437,7 @@ function confirmRegisteredAt(): void {
} }
async function submitRecord(): Promise<void> { async function submitRecord(): Promise<void> {
const submitCustomer = await resolveSubmitCustomer(); if (!validateForm()) {
if (!submitCustomer?.id) {
return; return;
} }
@@ -411,15 +448,18 @@ async function submitRecord(): Promise<void> {
return; return;
} }
const customerIdList = selectedCustomerList.value.map(customer => customer.id).filter((item): item is string => !!item);
for (const registrationEventId of selectedRegistrationEventIdList.value) { for (const registrationEventId of selectedRegistrationEventIdList.value) {
await RegistrationEventRecordAPI.create({ await RegistrationEventRecordAPI.create({
registrationEventId, registrationEventId,
customerId: submitCustomer.id, customerIdList,
remark: note.value || "客户现场登记", remark: note.value || "客户现场登记",
registeredAt: buildRegisteredAt(), registeredAt: buildRegisteredAt(),
attachmentIdList: buildTempFileIdList() attachmentIdList: buildTempFileIdList()
}); });
} }
selectedCustomerList.value = [];
selectedCustomerIdList.value = [];
note.value = ""; note.value = "";
photoItemList.value = []; photoItemList.value = [];
await loadRecentRecordList(); await loadRecentRecordList();
@@ -432,7 +472,7 @@ async function submitRecord(): Promise<void> {
} }
function validateForm(): boolean { function validateForm(): boolean {
if (!selectedCustomer.value?.id) { if (!selectedCustomerList.value.some(item => !!item.id)) {
showToast("请先选择或扫码客户", "warning"); showToast("请先选择或扫码客户", "warning");
return false; return false;
} }
@@ -600,6 +640,28 @@ watch(customerCode, async (value) => {
} }
} }
.selected-customer-popup {
height: 70vh;
overflow: auto;
padding-top: 1rem;
border-radius: 1.25rem 1.25rem 0 0;
.popup-header {
padding: 0 1rem;
display: flex;
align-items: center;
justify-content: space-between;
h3 {
margin: 0;
}
}
.list {
margin-top: 1rem;
}
}
:global(html.theme-dark) { :global(html.theme-dark) {
.registration-record-edit { .registration-record-edit {
:deep(.form-item .t-form__label), :deep(.form-item .t-form__label),
@@ -609,7 +671,8 @@ watch(customerCode, async (value) => {
} }
.form, .form,
.record-panel { .record-panel,
.selected-customer-popup {
background: rgba(15, 23, 42, .88); background: rgba(15, 23, 42, .88);
box-shadow: 0 .75rem 2rem rgba(2, 6, 23, .32); box-shadow: 0 .75rem 2rem rgba(2, 6, 23, .32);
} }
+1 -2
View File
@@ -72,8 +72,7 @@ export type RegistrationEventRecordCalendarView = {
}; };
export type RegistrationEventRecordCreateReq = { export type RegistrationEventRecordCreateReq = {
customerId?: string; customerIdList: string[];
customerCode?: string;
registrationEventId: string; registrationEventId: string;
remark?: string; remark?: string;
registeredAt?: number; registeredAt?: number;