fix submit button position
This commit is contained in:
@@ -30,7 +30,6 @@
|
|||||||
:empty-text="loading ? '正在加载客户' : '未找到匹配客户'"
|
:empty-text="loading ? '正在加载客户' : '未找到匹配客户'"
|
||||||
@select="toggleCustomer"
|
@select="toggleCustomer"
|
||||||
/>
|
/>
|
||||||
<div class="load-state" v-text="loadStateText" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</t-popup>
|
</t-popup>
|
||||||
@@ -75,13 +74,6 @@ let searchToken = 0;
|
|||||||
let keywordTimer: number | undefined;
|
let keywordTimer: number | undefined;
|
||||||
|
|
||||||
const isMultiple = computed(() => props.selectionMode === "multiple");
|
const isMultiple = computed(() => props.selectionMode === "multiple");
|
||||||
const loadStateText = computed(() => {
|
|
||||||
if (loading.value) {
|
|
||||||
return "正在加载更多";
|
|
||||||
}
|
|
||||||
|
|
||||||
return noMore.value ? `共 ${totalCount.value} 位客户` : "上拉加载更多";
|
|
||||||
});
|
|
||||||
|
|
||||||
const popupVisible = computed({
|
const popupVisible = computed({
|
||||||
get() {
|
get() {
|
||||||
|
|||||||
@@ -147,18 +147,6 @@
|
|||||||
accept="image/*"
|
accept="image/*"
|
||||||
@change="handlePhotoSelected"
|
@change="handlePhotoSelected"
|
||||||
>
|
>
|
||||||
<t-button
|
|
||||||
theme="primary"
|
|
||||||
class="save"
|
|
||||||
:disabled="!canSubmit"
|
|
||||||
:loading="isSubmitting"
|
|
||||||
@click="doSubmit"
|
|
||||||
>
|
|
||||||
<template #icon>
|
|
||||||
<t-icon name="check" />
|
|
||||||
</template>
|
|
||||||
保存
|
|
||||||
</t-button>
|
|
||||||
<customer-selector
|
<customer-selector
|
||||||
v-model="introducerCustomerId"
|
v-model="introducerCustomerId"
|
||||||
v-model:visible="selectorVisible"
|
v-model:visible="selectorVisible"
|
||||||
@@ -203,6 +191,7 @@ import PhotoScroller from "@/components/PhotoScroller.vue";
|
|||||||
import TagSelector from "@/components/TagSelector.vue";
|
import TagSelector from "@/components/TagSelector.vue";
|
||||||
import CustomerTagCard from "@/pages/customer/component/CustomerTagCard.vue";
|
import CustomerTagCard from "@/pages/customer/component/CustomerTagCard.vue";
|
||||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||||
|
import { useNavBarStore } from "@/store/navBarStore";
|
||||||
import { useSettingStore } from "@/store/settingStore";
|
import { useSettingStore } from "@/store/settingStore";
|
||||||
import type { BodyAreaValue, Customer, CustomerAttachmentType, EditableCustomerPhoto } from "@/types/Customer";
|
import type { BodyAreaValue, Customer, CustomerAttachmentType, EditableCustomerPhoto } from "@/types/Customer";
|
||||||
import type { Tag } from "@/types/Tag";
|
import type { Tag } from "@/types/Tag";
|
||||||
@@ -222,8 +211,10 @@ const router = useRouter();
|
|||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const settingStore = useSettingStore();
|
const settingStore = useSettingStore();
|
||||||
const currentStoreStore = useCurrentStoreStore();
|
const currentStoreStore = useCurrentStoreStore();
|
||||||
|
const navBarStore = useNavBarStore();
|
||||||
const isCreateMode = computed(() => route.name === "CustomerCreate");
|
const isCreateMode = computed(() => route.name === "CustomerCreate");
|
||||||
const savePermissionCode = computed(() => isCreateMode.value ? permissionCodes.customer.create : permissionCodes.customer.update);
|
const savePermissionCode = computed(() => isCreateMode.value ? permissionCodes.customer.create : permissionCodes.customer.update);
|
||||||
|
const hasCustomerDeletePermission = computed(() => userStore.hasPermissions(permissionCodes.customer.delete));
|
||||||
const isGlobalManager = computed(() => userStore.hasRoles(gaoRoleCodes.globalManager));
|
const isGlobalManager = computed(() => userStore.hasRoles(gaoRoleCodes.globalManager));
|
||||||
|
|
||||||
let latestDecodedText = "";
|
let latestDecodedText = "";
|
||||||
@@ -457,6 +448,14 @@ async function loadDetail() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
onMounted(loadDetail);
|
onMounted(loadDetail);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
navBarStore.clearRight();
|
||||||
|
if (userStore.hasPermissions(savePermissionCode.value)) {
|
||||||
|
navBarStore.setRightText("保存", "var(--td-brand-color)", doSubmit);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
watch(() => route.fullPath, loadDetail);
|
watch(() => route.fullPath, loadDetail);
|
||||||
|
|
||||||
// ---------- 年龄 ----------
|
// ---------- 年龄 ----------
|
||||||
@@ -524,6 +523,23 @@ function normalizeAttachmentType(attachType?: string): CustomerAttachmentType {
|
|||||||
return "PHOTO";
|
return "PHOTO";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validateCustomerPhotoList(): boolean {
|
||||||
|
if (hasCustomerDeletePermission.value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const attachTypeSet = new Set(photoItemList.value.map(item => item.attachType));
|
||||||
|
if (!attachTypeSet.has("PHOTO")) {
|
||||||
|
Toast.warning("请上传客户照片");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!attachTypeSet.has("POINT_CARD")) {
|
||||||
|
Toast.warning("请上传积分卡照片");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
async function handlePhotoSelected(event: Event): Promise<void> {
|
async function handlePhotoSelected(event: Event): Promise<void> {
|
||||||
const input = event.target as HTMLInputElement;
|
const input = event.target as HTMLInputElement;
|
||||||
const file = input.files?.[0];
|
const file = input.files?.[0];
|
||||||
@@ -552,10 +568,20 @@ async function handlePhotoSelected(event: Event): Promise<void> {
|
|||||||
const isSubmitting = ref(false);
|
const isSubmitting = ref(false);
|
||||||
|
|
||||||
async function doSubmit(): Promise<void> {
|
async function doSubmit(): Promise<void> {
|
||||||
if (!formData.storeId) {
|
if (isSubmitting.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!canSubmit.value) {
|
||||||
|
if (!formData.name?.trim()) {
|
||||||
|
Toast.warning("请填写姓名");
|
||||||
|
return;
|
||||||
|
}
|
||||||
Toast.warning("请选择门店");
|
Toast.warning("请选择门店");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!validateCustomerPhotoList()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
isSubmitting.value = true;
|
isSubmitting.value = true;
|
||||||
try {
|
try {
|
||||||
@@ -594,6 +620,10 @@ async function doSubmit(): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
navBarStore.clearRight();
|
||||||
|
});
|
||||||
|
|
||||||
function buildAttachmentList(): Attachment[] {
|
function buildAttachmentList(): Attachment[] {
|
||||||
return photoItemList.value.flatMap(item => {
|
return photoItemList.value.flatMap(item => {
|
||||||
if (item.attachment) {
|
if (item.attachment) {
|
||||||
@@ -660,12 +690,6 @@ function buildAttachmentList(): Attachment[] {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.save {
|
|
||||||
left: 1rem;
|
|
||||||
right: 1rem;
|
|
||||||
bottom: calc(.75rem + env(safe-area-inset-bottom, 0));
|
|
||||||
position: fixed;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(html.theme-dark) {
|
:global(html.theme-dark) {
|
||||||
|
|||||||
@@ -96,17 +96,6 @@
|
|||||||
</template>
|
</template>
|
||||||
导出
|
导出
|
||||||
</t-button>
|
</t-button>
|
||||||
<t-button
|
|
||||||
v-can="permissionCodes.customer.create"
|
|
||||||
theme="primary"
|
|
||||||
class="create"
|
|
||||||
@click="toCreate"
|
|
||||||
>
|
|
||||||
<template #icon>
|
|
||||||
<t-icon name="add" />
|
|
||||||
</template>
|
|
||||||
添加
|
|
||||||
</t-button>
|
|
||||||
</div>
|
</div>
|
||||||
<tag-selector
|
<tag-selector
|
||||||
v-model="selectedTagIdList"
|
v-model="selectedTagIdList"
|
||||||
@@ -124,6 +113,7 @@ import CustomerAPI from "@/api/CustomerAPI";
|
|||||||
import GenderIcon from "@/components/GenderIcon.vue";
|
import GenderIcon from "@/components/GenderIcon.vue";
|
||||||
import TagSelector from "@/components/TagSelector.vue";
|
import TagSelector from "@/components/TagSelector.vue";
|
||||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||||
|
import { useNavBarStore } from "@/store/navBarStore";
|
||||||
import type { Customer } from "@/types/Customer";
|
import type { Customer } from "@/types/Customer";
|
||||||
import type { Tag } from "@/types/Tag";
|
import type { Tag } from "@/types/Tag";
|
||||||
import { getCustomerPhotoURL } from "@/utils/customer";
|
import { getCustomerPhotoURL } from "@/utils/customer";
|
||||||
@@ -136,6 +126,7 @@ defineOptions({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const currentStoreStore = useCurrentStoreStore();
|
const currentStoreStore = useCurrentStoreStore();
|
||||||
|
const navBarStore = useNavBarStore();
|
||||||
const isRefreshing = ref(false);
|
const isRefreshing = ref(false);
|
||||||
const isExporting = ref(false);
|
const isExporting = ref(false);
|
||||||
const isGlobalManager = computed(() => userStore.hasRoles(gaoRoleCodes.globalManager));
|
const isGlobalManager = computed(() => userStore.hasRoles(gaoRoleCodes.globalManager));
|
||||||
@@ -239,6 +230,13 @@ async function loadData(): Promise<void> {
|
|||||||
|
|
||||||
onMounted(loadData);
|
onMounted(loadData);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
navBarStore.clearRight();
|
||||||
|
if (userStore.hasPermissions(permissionCodes.customer.create)) {
|
||||||
|
navBarStore.setRightText("添加", "var(--td-brand-color)", toCreate);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
watch(keyword, async () => {
|
watch(keyword, async () => {
|
||||||
window.clearTimeout(keywordTimer);
|
window.clearTimeout(keywordTimer);
|
||||||
keywordTimer = window.setTimeout(async () => {
|
keywordTimer = window.setTimeout(async () => {
|
||||||
@@ -247,6 +245,7 @@ watch(keyword, async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
navBarStore.clearRight();
|
||||||
window.clearTimeout(keywordTimer);
|
window.clearTimeout(keywordTimer);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -371,9 +370,6 @@ async function doExport(): Promise<void> {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.create {
|
|
||||||
flex: 2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -167,9 +167,6 @@
|
|||||||
multiple
|
multiple
|
||||||
@change="handleRecordPhotoSelected"
|
@change="handleRecordPhotoSelected"
|
||||||
>
|
>
|
||||||
<t-button theme="primary" class="save" :disabled="!canSubmit" :loading="isSaving" @click="submitRecord">
|
|
||||||
<span v-text="saveButtonText" />
|
|
||||||
</t-button>
|
|
||||||
</div>
|
</div>
|
||||||
<t-popup v-model="registeredAtPickerVisible" placement="bottom">
|
<t-popup v-model="registeredAtPickerVisible" placement="bottom">
|
||||||
<t-date-time-picker
|
<t-date-time-picker
|
||||||
@@ -197,6 +194,7 @@ import CustomerProfileCard from "@/components/CustomerProfileCard.vue";
|
|||||||
import CustomerSelector from "@/components/CustomerSelector.vue";
|
import CustomerSelector from "@/components/CustomerSelector.vue";
|
||||||
import PhotoScroller from "@/components/PhotoScroller.vue";
|
import PhotoScroller from "@/components/PhotoScroller.vue";
|
||||||
import EventSelector from "@/components/EventSelector.vue";
|
import EventSelector from "@/components/EventSelector.vue";
|
||||||
|
import { useNavBarStore } from "@/store/navBarStore";
|
||||||
import { useSettingStore } from "@/store/settingStore";
|
import { useSettingStore } from "@/store/settingStore";
|
||||||
import type { Customer } from "@/types/Customer";
|
import type { Customer } from "@/types/Customer";
|
||||||
import type { EventItem } from "@/types/EventItem";
|
import type { EventItem } from "@/types/EventItem";
|
||||||
@@ -216,6 +214,7 @@ const router = useRouter();
|
|||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const settingStore = useSettingStore();
|
const settingStore = useSettingStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
const navBarStore = useNavBarStore();
|
||||||
|
|
||||||
type EditablePhoto = {
|
type EditablePhoto = {
|
||||||
attachment?: Partial<Attachment>;
|
attachment?: Partial<Attachment>;
|
||||||
@@ -590,9 +589,10 @@ const note = ref("");
|
|||||||
const isSaving = ref(false);
|
const isSaving = ref(false);
|
||||||
const editingRecord = ref<EventRecord>();
|
const editingRecord = ref<EventRecord>();
|
||||||
|
|
||||||
const canSubmit = computed(() => selectedCustomerList.value.some(item => !!item.id) && 0 < selectedEventIdList.value.length);
|
|
||||||
|
|
||||||
async function submitRecord(): Promise<void> {
|
async function submitRecord(): Promise<void> {
|
||||||
|
if (isSaving.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!validateForm()) {
|
if (!validateForm()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -731,6 +731,17 @@ async function loadRecordForEdit(): Promise<void> {
|
|||||||
|
|
||||||
onMounted(loadPageData);
|
onMounted(loadPageData);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
navBarStore.clearRight();
|
||||||
|
if (userStore.hasPermissions(pageAccess.value)) {
|
||||||
|
navBarStore.setRightText(saveButtonText.value, "var(--td-brand-color)", submitRecord);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
navBarStore.clearRight();
|
||||||
|
});
|
||||||
|
|
||||||
watch(selectedEventIdList, async () => {
|
watch(selectedEventIdList, async () => {
|
||||||
await loadSourceValueRule();
|
await loadSourceValueRule();
|
||||||
}, { deep: true });
|
}, { deep: true });
|
||||||
@@ -833,13 +844,6 @@ watch(customerCode, async (value) => {
|
|||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.save {
|
|
||||||
left: 1rem;
|
|
||||||
right: 1rem;
|
|
||||||
bottom: calc(.75rem + env(safe-area-inset-bottom, 0));
|
|
||||||
position: fixed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-input {
|
.file-input {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,7 +126,6 @@
|
|||||||
multiple
|
multiple
|
||||||
@change="handleRecordPhotoSelected"
|
@change="handleRecordPhotoSelected"
|
||||||
>
|
>
|
||||||
<t-button theme="primary" class="save" :disabled="!canSubmit" :loading="saving" @click="saveRecord">提交</t-button>
|
|
||||||
</div>
|
</div>
|
||||||
<customer-selector
|
<customer-selector
|
||||||
v-model="selectedCustomerId"
|
v-model="selectedCustomerId"
|
||||||
@@ -154,6 +153,7 @@ import { loadSourceValueConfig, type SourceValueConfig } from "@/utils/point";
|
|||||||
import { compressUploadImage, compressUploadImageList } from "@/utils/imageCompression";
|
import { compressUploadImage, compressUploadImageList } from "@/utils/imageCompression";
|
||||||
import EventSelector from "@/components/EventSelector.vue";
|
import EventSelector from "@/components/EventSelector.vue";
|
||||||
import EventRecordAPI from "@/api/EventRecordAPI.ts";
|
import EventRecordAPI from "@/api/EventRecordAPI.ts";
|
||||||
|
import { useNavBarStore } from "@/store/navBarStore";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "QuickEntryPage"
|
name: "QuickEntryPage"
|
||||||
@@ -162,7 +162,8 @@ defineOptions({
|
|||||||
const settingStore = useSettingStore();
|
const settingStore = useSettingStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
const navBarStore = useNavBarStore();
|
||||||
|
const hasCustomerDeletePermission = computed(() => userStore.hasPermissions(permissionCodes.customer.delete));
|
||||||
|
|
||||||
const eventList = ref<EventItem[]>([]);
|
const eventList = ref<EventItem[]>([]);
|
||||||
|
|
||||||
@@ -243,7 +244,6 @@ function validateNumberValueList(): boolean {
|
|||||||
|
|
||||||
const note = ref("");
|
const note = ref("");
|
||||||
const saving = ref(false);
|
const saving = ref(false);
|
||||||
const canSubmit = computed(() => !!inputCode.value && !!customerName.value.trim() && !customerPhotoUploading.value && !recordPhotoUploading.value);
|
|
||||||
|
|
||||||
let latestDecodedText = "";
|
let latestDecodedText = "";
|
||||||
|
|
||||||
@@ -539,6 +539,23 @@ function normalizeCustomerAttachmentType(attachType?: string): CustomerAttachmen
|
|||||||
return "PHOTO";
|
return "PHOTO";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validateCustomerPhotoList(): boolean {
|
||||||
|
if (hasCustomerDeletePermission.value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const attachTypeSet = new Set(customerPhotoItemList.value.map(item => item.attachType));
|
||||||
|
if (!attachTypeSet.has("PHOTO")) {
|
||||||
|
showToast("请上传客户照片", "warning");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!attachTypeSet.has("POINT_CARD")) {
|
||||||
|
showToast("请上传积分卡照片", "warning");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
async function handleCustomerPhotoSelected(event: Event): Promise<void> {
|
async function handleCustomerPhotoSelected(event: Event): Promise<void> {
|
||||||
const input = event.target as HTMLInputElement;
|
const input = event.target as HTMLInputElement;
|
||||||
const file = input.files?.[0];
|
const file = input.files?.[0];
|
||||||
@@ -611,6 +628,13 @@ async function handleRecordPhotoSelected(event: Event): Promise<void> {
|
|||||||
// ---------- 提交 ----------
|
// ---------- 提交 ----------
|
||||||
|
|
||||||
async function saveRecord(): Promise<void> {
|
async function saveRecord(): Promise<void> {
|
||||||
|
if (saving.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (customerPhotoUploading.value || recordPhotoUploading.value) {
|
||||||
|
showToast("照片正在上传,请稍候", "warning");
|
||||||
|
return;
|
||||||
|
}
|
||||||
const customerCode = validateForm();
|
const customerCode = validateForm();
|
||||||
if (!customerCode) {
|
if (!customerCode) {
|
||||||
return;
|
return;
|
||||||
@@ -704,6 +728,9 @@ function validateForm(): string {
|
|||||||
showToast("请填写客户姓名", "warning");
|
showToast("请填写客户姓名", "warning");
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
if (!validateCustomerPhotoList()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
if (!validateNumberValueList()) {
|
if (!validateNumberValueList()) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -729,9 +756,17 @@ function showToast(message: string, theme: "success" | "warning" = "success"): v
|
|||||||
// ---------- 初始化 ----------
|
// ---------- 初始化 ----------
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
navBarStore.clearRight();
|
||||||
|
if (userStore.hasPermissions(permissionCodes.eventRecord.create)) {
|
||||||
|
navBarStore.setRightText("保存", "var(--td-brand-color)", saveRecord);
|
||||||
|
}
|
||||||
eventList.value = await EventAPI.listValid();
|
eventList.value = await EventAPI.listValid();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
navBarStore.clearRight();
|
||||||
|
});
|
||||||
|
|
||||||
watch(selectedEventIdList, async () => {
|
watch(selectedEventIdList, async () => {
|
||||||
await loadSourceValueRule();
|
await loadSourceValueRule();
|
||||||
}, { deep: true });
|
}, { deep: true });
|
||||||
@@ -797,12 +832,6 @@ watch(scanCode, (value) => {
|
|||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.save {
|
|
||||||
left: 1rem;
|
|
||||||
right: 1rem;
|
|
||||||
bottom: calc(.75rem + env(safe-area-inset-bottom, 0));
|
|
||||||
position: fixed;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(html.theme-dark) {
|
:global(html.theme-dark) {
|
||||||
|
|||||||
Reference in New Issue
Block a user