remove in customer list
This commit is contained in:
@@ -0,0 +1,151 @@
|
|||||||
|
<template>
|
||||||
|
<t-dialog
|
||||||
|
class="remove-dialog"
|
||||||
|
v-model:visible="visible"
|
||||||
|
title="删除客户"
|
||||||
|
>
|
||||||
|
<div class="content">
|
||||||
|
<div class="warning">删除后无法恢复,将同时清除以下关联数据:</div>
|
||||||
|
<ul class="items">
|
||||||
|
<li>登记事件记录及其附件,并撤销这些登记产生的积分</li>
|
||||||
|
<li>客户积分账户、标签和客户附件</li>
|
||||||
|
<li>解除该客户与其被邀请客户之间的邀请关系,被邀请客户不会被删除</li>
|
||||||
|
<li>减少该客户原介绍人的邀请客户计数</li>
|
||||||
|
<li>客户档案本身</li>
|
||||||
|
</ul>
|
||||||
|
<div class="hint">
|
||||||
|
请输入客户姓名
|
||||||
|
<span class="name red" v-text="customerName" />
|
||||||
|
以确认删除
|
||||||
|
</div>
|
||||||
|
<t-input
|
||||||
|
class="confirm-input"
|
||||||
|
v-model.trim="confirmName"
|
||||||
|
size="small"
|
||||||
|
borderless
|
||||||
|
clearable
|
||||||
|
placeholder="请输入客户姓名"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<template #cancelBtn>
|
||||||
|
<t-button
|
||||||
|
theme="default"
|
||||||
|
content="取消"
|
||||||
|
block
|
||||||
|
@click="close"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #confirmBtn>
|
||||||
|
<t-button
|
||||||
|
theme="danger"
|
||||||
|
content="删除"
|
||||||
|
block
|
||||||
|
:loading="loading"
|
||||||
|
:disabled="!canConfirm || loading"
|
||||||
|
@click="confirm"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</t-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Customer } from "@/types/Customer";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "CustomerRemoveDialog"
|
||||||
|
});
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
visible?: boolean;
|
||||||
|
customer?: Customer;
|
||||||
|
loading?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
visible: false,
|
||||||
|
loading: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
"update:visible": [visible: boolean];
|
||||||
|
confirm: [customer: Customer];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const confirmName = ref("");
|
||||||
|
const visible = computed({
|
||||||
|
get: () => props.visible,
|
||||||
|
set: (value: boolean) => emit("update:visible", value)
|
||||||
|
});
|
||||||
|
const customerName = computed(() => props.customer?.name || "");
|
||||||
|
const canConfirm = computed(() => !!customerName.value && confirmName.value === customerName.value);
|
||||||
|
|
||||||
|
watch([() => props.visible, () => props.customer], () => {
|
||||||
|
confirmName.value = "";
|
||||||
|
});
|
||||||
|
|
||||||
|
function close(): void {
|
||||||
|
visible.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirm(): void {
|
||||||
|
if (!props.customer?.id || !canConfirm.value || props.loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emit("confirm", props.customer);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.remove-dialog {
|
||||||
|
|
||||||
|
:deep(.t-dialog__footer) {
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
.warning {
|
||||||
|
color: #b42318;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.items {
|
||||||
|
gap: .5rem;
|
||||||
|
height: 9rem;
|
||||||
|
margin: .75rem 0 1rem;
|
||||||
|
display: flex;
|
||||||
|
overflow-y: auto;
|
||||||
|
line-height: 1.2;
|
||||||
|
padding-left: 1.25rem;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-input {
|
||||||
|
--td-input-vertical-padding: 10px;
|
||||||
|
|
||||||
|
border: 1px solid var(--td-component-border);
|
||||||
|
margin-top: .5rem;
|
||||||
|
border-radius: .75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html.theme-dark) {
|
||||||
|
.remove-dialog {
|
||||||
|
|
||||||
|
.warning {
|
||||||
|
color: #fda29b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-input {
|
||||||
|
background: rgba(15, 23, 42, .72);
|
||||||
|
border-color: rgba(148, 163, 184, .16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -105,53 +105,13 @@
|
|||||||
>
|
>
|
||||||
<body-area-selector :model-value="selectedAreas" readonly />
|
<body-area-selector :model-value="selectedAreas" readonly />
|
||||||
</bottom-popup>
|
</bottom-popup>
|
||||||
<t-dialog
|
<customer-remove-dialog
|
||||||
class="remove-dialog"
|
|
||||||
v-model:visible="removeDialogVisible"
|
v-model:visible="removeDialogVisible"
|
||||||
title="删除客户"
|
:customer="removeTarget"
|
||||||
>
|
:loading="isRemoving"
|
||||||
<div class="content">
|
@confirm="removeConfirmedCustomer"
|
||||||
<div class="warning">删除后无法恢复,将同时清除以下关联数据:</div>
|
|
||||||
<ul class="items">
|
|
||||||
<li>登记事件记录及其附件,并撤销这些登记产生的积分</li>
|
|
||||||
<li>客户积分账户、标签和客户附件</li>
|
|
||||||
<li>解除该客户与其被邀请客户之间的邀请关系,被邀请客户不会被删除</li>
|
|
||||||
<li>减少该客户原介绍人的邀请客户计数</li>
|
|
||||||
<li>客户档案本身</li>
|
|
||||||
</ul>
|
|
||||||
<div class="hint">
|
|
||||||
请输入客户姓名
|
|
||||||
<span class="name red" v-text="removeCustomerName" />
|
|
||||||
以确认删除
|
|
||||||
</div>
|
|
||||||
<t-input
|
|
||||||
class="confirm-input"
|
|
||||||
v-model.trim="removeConfirmName"
|
|
||||||
size="small"
|
|
||||||
borderless
|
|
||||||
clearable
|
|
||||||
placeholder="请输入客户姓名"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<template #cancelBtn>
|
|
||||||
<t-button
|
|
||||||
theme="default"
|
|
||||||
content="取消"
|
|
||||||
block
|
|
||||||
@click="closeRemoveDialog"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<template #confirmBtn>
|
|
||||||
<t-button
|
|
||||||
theme="danger"
|
|
||||||
content="删除"
|
|
||||||
block
|
|
||||||
:disabled="!canConfirmRemove"
|
|
||||||
@click="removeConfirmedCustomer"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</t-dialog>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -163,6 +123,7 @@ import CustomerAPI from "@/api/CustomerAPI";
|
|||||||
import PointAPI from "@/api/PointAPI";
|
import PointAPI from "@/api/PointAPI";
|
||||||
import TagAPI from "@/api/TagAPI";
|
import TagAPI from "@/api/TagAPI";
|
||||||
import BodyAreaSelector from "@/components/BodyAreaSelector.vue";
|
import BodyAreaSelector from "@/components/BodyAreaSelector.vue";
|
||||||
|
import CustomerRemoveDialog from "@/components/CustomerRemoveDialog.vue";
|
||||||
import CustomerProfileCard from "@/components/CustomerProfileCard.vue";
|
import CustomerProfileCard from "@/components/CustomerProfileCard.vue";
|
||||||
import PhotoScroller from "@/components/PhotoScroller.vue";
|
import PhotoScroller from "@/components/PhotoScroller.vue";
|
||||||
import TagSelector from "@/components/TagSelector.vue";
|
import TagSelector from "@/components/TagSelector.vue";
|
||||||
@@ -190,15 +151,12 @@ const userStore = useUserStore();
|
|||||||
const isRemoving = ref(false);
|
const isRemoving = ref(false);
|
||||||
const removeDialogVisible = ref(false);
|
const removeDialogVisible = ref(false);
|
||||||
const removeTarget = ref<Customer>();
|
const removeTarget = ref<Customer>();
|
||||||
const removeConfirmName = ref("");
|
|
||||||
|
|
||||||
const canReadStat = computed(() => userStore.hasPermissions(permissionCodes.stat.read));
|
const canReadStat = computed(() => userStore.hasPermissions(permissionCodes.stat.read));
|
||||||
const canReadPointAccount = computed(() => userStore.hasPermissions(permissionCodes.point.accountRead));
|
const canReadPointAccount = computed(() => userStore.hasPermissions(permissionCodes.point.accountRead));
|
||||||
const canReadTag = computed(() => userStore.hasPermissions(corePermissionCodes.tag.read));
|
const canReadTag = computed(() => userStore.hasPermissions(corePermissionCodes.tag.read));
|
||||||
const canApplyTag = computed(() => userStore.hasPermissions(corePermissionCodes.tag.apply));
|
const canApplyTag = computed(() => userStore.hasPermissions(corePermissionCodes.tag.apply));
|
||||||
const canUpdateCustomer = computed(() => userStore.hasPermissions(permissionCodes.customer.update));
|
const canUpdateCustomer = computed(() => userStore.hasPermissions(permissionCodes.customer.update));
|
||||||
const removeCustomerName = computed(() => removeTarget.value?.name || "");
|
|
||||||
const canConfirmRemove = computed(() => !!removeCustomerName.value && removeConfirmName.value === removeCustomerName.value);
|
|
||||||
|
|
||||||
// ---------- 资料 ----------
|
// ---------- 资料 ----------
|
||||||
|
|
||||||
@@ -375,25 +333,21 @@ async function confirmRemoveCustomer(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeTarget.value = currentCustomer;
|
removeTarget.value = currentCustomer;
|
||||||
removeConfirmName.value = "";
|
|
||||||
removeDialogVisible.value = true;
|
removeDialogVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeRemoveDialog(): void {
|
function closeRemoveDialog(): void {
|
||||||
removeDialogVisible.value = false;
|
removeDialogVisible.value = false;
|
||||||
removeTarget.value = undefined;
|
removeTarget.value = undefined;
|
||||||
removeConfirmName.value = "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeConfirmedCustomer(): Promise<void> {
|
async function removeConfirmedCustomer(target: Customer): Promise<void> {
|
||||||
const customerId = removeTarget.value?.id;
|
const customerId = target.id;
|
||||||
if (!customerId || !canConfirmRemove.value || isRemoving.value) {
|
if (!customerId || isRemoving.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeDialogVisible.value = false;
|
closeRemoveDialog();
|
||||||
removeTarget.value = undefined;
|
|
||||||
removeConfirmName.value = "";
|
|
||||||
await removeCustomer(customerId);
|
await removeCustomer(customerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,45 +485,6 @@ onUnmounted(() => navBarStore.clearRight());
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.remove-dialog {
|
|
||||||
|
|
||||||
:deep(.t-dialog__footer) {
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
text-align: left;
|
|
||||||
|
|
||||||
.warning {
|
|
||||||
color: #b42318;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.items {
|
|
||||||
gap: .5rem;
|
|
||||||
height: 9rem;
|
|
||||||
margin: .75rem 0 1rem;
|
|
||||||
display: flex;
|
|
||||||
overflow-y: auto;
|
|
||||||
line-height: 1.2;
|
|
||||||
padding-left: 1.25rem;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name {
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirm-input {
|
|
||||||
--td-input-vertical-padding: 10px;
|
|
||||||
|
|
||||||
border: 1px solid var(--td-component-border);
|
|
||||||
margin-top: .5rem;
|
|
||||||
border-radius: .75rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(html.theme-dark) {
|
:global(html.theme-dark) {
|
||||||
.customer-detail {
|
.customer-detail {
|
||||||
|
|
||||||
@@ -581,17 +496,6 @@ onUnmounted(() => navBarStore.clearRight());
|
|||||||
color: #94a3b8;
|
color: #94a3b8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.remove-dialog {
|
|
||||||
|
|
||||||
.warning {
|
|
||||||
color: #fda29b;
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirm-input {
|
|
||||||
background: rgba(15, 23, 42, .72);
|
|
||||||
border-color: rgba(148, 163, 184, .16);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -38,19 +38,33 @@
|
|||||||
@scrolltolower="fetch"
|
@scrolltolower="fetch"
|
||||||
>
|
>
|
||||||
<t-cell-group class="items" theme="card">
|
<t-cell-group class="items" theme="card">
|
||||||
<customer-cell
|
<t-swipe-cell
|
||||||
v-entrance-transition="index * 30"
|
v-entrance-transition="index * 30"
|
||||||
v-for="(item, index) in pageResult.list"
|
v-for="(item, index) in pageResult.list"
|
||||||
:key="item.id || item.code || index"
|
:key="item.id || item.code || index"
|
||||||
class="item"
|
class="item"
|
||||||
|
>
|
||||||
|
<customer-cell
|
||||||
|
class="cell"
|
||||||
:customer="item"
|
:customer="item"
|
||||||
arrow
|
arrow
|
||||||
@click="router.push(`/management/customers/${item.id}`)"
|
@click="toDetail(item)"
|
||||||
>
|
>
|
||||||
<template #description>
|
<template #description>
|
||||||
<telephone-link :value="item.telephone" />
|
<telephone-link :value="item.telephone" />
|
||||||
</template>
|
</template>
|
||||||
</customer-cell>
|
</customer-cell>
|
||||||
|
<template v-if="canDeleteCustomer" #right>
|
||||||
|
<t-button
|
||||||
|
class="delete"
|
||||||
|
theme="danger"
|
||||||
|
:loading="isRemoving && removingCustomerId === item.id"
|
||||||
|
@click.stop="openRemoveDialog(item)"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</t-button>
|
||||||
|
</template>
|
||||||
|
</t-swipe-cell>
|
||||||
</t-cell-group>
|
</t-cell-group>
|
||||||
<div v-if="!pageResult.list.length" class="load-state" v-text="isLoading ? '正在加载客户' : '暂无客户数据'" />
|
<div v-if="!pageResult.list.length" class="load-state" v-text="isLoading ? '正在加载客户' : '暂无客户数据'" />
|
||||||
<div v-if="pageResult.list.length" class="load-state" v-text="isLoading ? '正在加载更多' : isNoMore ? '已经到底了' : '上拉加载更多'" />
|
<div v-if="pageResult.list.length" class="load-state" v-text="isLoading ? '正在加载更多' : isNoMore ? '已经到底了' : '上拉加载更多'" />
|
||||||
@@ -60,6 +74,12 @@
|
|||||||
v-model:visible="tagSelectorVisible"
|
v-model:visible="tagSelectorVisible"
|
||||||
@change="handleTagFilterChanged"
|
@change="handleTagFilterChanged"
|
||||||
/>
|
/>
|
||||||
|
<customer-remove-dialog
|
||||||
|
v-model:visible="removeDialogVisible"
|
||||||
|
:customer="removeTarget"
|
||||||
|
:loading="isRemoving"
|
||||||
|
@confirm="removeCustomer"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<nav-bar-action-menu v-if="canShowActionMenu" :actions="actionList" />
|
<nav-bar-action-menu v-if="canShowActionMenu" :actions="actionList" />
|
||||||
</template>
|
</template>
|
||||||
@@ -69,6 +89,7 @@ import { Message } from "@/utils/message";
|
|||||||
import { Time, Toolkit, WebToolkit } from "timi-web";
|
import { Time, Toolkit, WebToolkit } from "timi-web";
|
||||||
import { type Page, type PageResult, useUserStore } from "timi-web-api";
|
import { type Page, type PageResult, useUserStore } from "timi-web-api";
|
||||||
import CustomerAPI from "@/api/CustomerAPI";
|
import CustomerAPI from "@/api/CustomerAPI";
|
||||||
|
import CustomerRemoveDialog from "@/components/CustomerRemoveDialog.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 { useNavBarStore } from "@/store/navBarStore";
|
||||||
@@ -87,9 +108,14 @@ const currentStoreStore = useCurrentStoreStore();
|
|||||||
const navBarStore = useNavBarStore();
|
const navBarStore = useNavBarStore();
|
||||||
const isRefreshing = ref(false);
|
const isRefreshing = ref(false);
|
||||||
const isExporting = ref(false);
|
const isExporting = ref(false);
|
||||||
|
const isRemoving = ref(false);
|
||||||
|
const removeDialogVisible = ref(false);
|
||||||
|
const removeTarget = ref<Customer>();
|
||||||
|
const removingCustomerId = ref("");
|
||||||
const isGlobalManager = computed(() => userStore.hasRoles(gaoRoleCodes.globalManager));
|
const isGlobalManager = computed(() => userStore.hasRoles(gaoRoleCodes.globalManager));
|
||||||
const canReadTag = computed(() => userStore.hasPermissions(corePermissionCodes.tag.read));
|
const canReadTag = computed(() => userStore.hasPermissions(corePermissionCodes.tag.read));
|
||||||
const canCreateCustomer = computed(() => userStore.hasPermissions(permissionCodes.customer.create));
|
const canCreateCustomer = computed(() => userStore.hasPermissions(permissionCodes.customer.create));
|
||||||
|
const canDeleteCustomer = computed(() => userStore.hasPermissions(permissionCodes.customer.delete));
|
||||||
const canExportCustomer = computed(() => userStore.hasPermissions(permissionCodes.customer.export));
|
const canExportCustomer = computed(() => userStore.hasPermissions(permissionCodes.customer.export));
|
||||||
const canShowActionMenu = computed(() => canCreateCustomer.value || canExportCustomer.value);
|
const canShowActionMenu = computed(() => canCreateCustomer.value || canExportCustomer.value);
|
||||||
|
|
||||||
@@ -217,6 +243,44 @@ async function toCreate(): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function toDetail(customer: Customer): Promise<void> {
|
||||||
|
if (!customer.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await router.push(`/management/customers/${customer.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- 删除客户 ----------
|
||||||
|
|
||||||
|
function openRemoveDialog(customer: Customer): void {
|
||||||
|
if (!customer.id || isRemoving.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
removeTarget.value = customer;
|
||||||
|
removeDialogVisible.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeCustomer(customer: Customer): Promise<void> {
|
||||||
|
const customerId = customer.id;
|
||||||
|
if (!customerId || isRemoving.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isRemoving.value = true;
|
||||||
|
removingCustomerId.value = customerId;
|
||||||
|
removeDialogVisible.value = false;
|
||||||
|
removeTarget.value = undefined;
|
||||||
|
try {
|
||||||
|
await CustomerAPI.remove(customerId);
|
||||||
|
pageResult.list = pageResult.list.filter(item => item.id !== customerId);
|
||||||
|
pageResult.total = Math.max(0, pageResult.total - 1);
|
||||||
|
Message.success("客户已删除");
|
||||||
|
} finally {
|
||||||
|
isRemoving.value = false;
|
||||||
|
removingCustomerId.value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- 导出 ----------
|
// ---------- 导出 ----------
|
||||||
|
|
||||||
async function doExport(): Promise<void> {
|
async function doExport(): Promise<void> {
|
||||||
@@ -282,11 +346,10 @@ async function doExport(): Promise<void> {
|
|||||||
.list-wrapper {
|
.list-wrapper {
|
||||||
padding: 1rem 0;
|
padding: 1rem 0;
|
||||||
|
|
||||||
.items {
|
.delete {
|
||||||
|
height: 100%;
|
||||||
.item {
|
padding: 0 1rem;
|
||||||
padding: 10px;
|
border-radius: 0;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user