Files
frontend/apps/web-antd/src/views/mall/promotion/components/app-link-input/app-link-select-dialog.vue

247 lines
7.1 KiB
Vue

<script lang="ts" setup>
import type { AppLink } from './data';
import { nextTick, ref } from 'vue';
import { getUrlNumberValue } from '@vben/utils';
import { Button, Form, FormItem, Modal, Tooltip } from 'ant-design-vue';
import ProductCategorySelect from '#/views/mall/product/category/components/product-category-select.vue';
import { APP_LINK_GROUP_LIST, APP_LINK_TYPE_ENUM } from './data';
/** APP 链接选择弹框 */
defineOptions({ name: 'AppLinkSelectDialog' });
const emit = defineEmits<{
appLinkChange: [appLink: AppLink];
change: [link: string];
}>();
const activeGroup = ref(APP_LINK_GROUP_LIST[0]?.name); // 选中的分组,默认选中第一个
const activeAppLink = ref({} as AppLink); // 选中的 APP 链接
const linkScrollbar = ref<HTMLDivElement>(); // 右侧滚动条
const groupTitleRefs = ref<HTMLInputElement[]>([]); // 分组标题引用列表
const groupScrollbar = ref<HTMLDivElement>(); // 分组滚动条
const groupBtnRefs = ref<HTMLButtonElement[]>([]); // 分组引用列表
const detailSelectDialog = ref<{
id?: number;
type?: APP_LINK_TYPE_ENUM;
visible: boolean;
}>({
visible: false,
id: undefined,
type: undefined,
}); // 详情选择对话框
const dialogVisible = ref(false);
defineExpose({ open });
/** 打开弹窗 */
async function open(link: string) {
activeAppLink.value.path = link;
dialogVisible.value = true;
// 滚动到当前的链接
const group = APP_LINK_GROUP_LIST.find((group) =>
group.links.some((linkItem) => {
const sameLink = isSameLink(linkItem.path, link);
if (sameLink) {
activeAppLink.value = { ...linkItem, path: link };
}
return sameLink;
}),
);
if (group) {
// 使用 nextTick 的原因:可能 Dom 还没生成,导致滚动失败
await nextTick();
handleGroupSelected(group.name);
}
}
/** 处理 APP 链接选中 */
function handleAppLinkSelected(appLink: AppLink) {
if (!isSameLink(appLink.path, activeAppLink.value.path)) {
activeAppLink.value = appLink;
}
switch (appLink.type) {
case APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST: {
detailSelectDialog.value.type = appLink.type;
// 返显
detailSelectDialog.value.id =
getUrlNumberValue(
'id',
`http://127.0.0.1${activeAppLink.value.path}`,
) || undefined;
detailSelectDialog.value.visible = true;
break;
}
default: {
break;
}
}
}
/** 处理确认提交 */
function handleSubmit() {
emit('change', activeAppLink.value.path);
emit('appLinkChange', activeAppLink.value);
dialogVisible.value = false;
}
/**
* 处理右侧链接列表滚动
*
* @param {object} param0 滚动事件参数
* @param {number} param0.scrollTop 滚动条的位置
*/
function handleScroll(event: Event) {
const scrollTop = (event.target as HTMLDivElement).scrollTop;
const titleEl = groupTitleRefs.value.find((titleEl: HTMLInputElement) => {
// 获取标题的位置信息
const { offsetHeight, offsetTop } = titleEl;
// 判断标题是否在可视范围内
return scrollTop >= offsetTop && scrollTop < offsetTop + offsetHeight;
});
// 只需处理一次
if (titleEl && activeGroup.value !== titleEl.textContent) {
activeGroup.value = titleEl.textContent || '';
// 同步左侧的滚动条位置
scrollToGroupBtn(activeGroup.value);
}
}
/** 处理分组选中 */
function handleGroupSelected(group: string) {
activeGroup.value = group;
const titleRef = groupTitleRefs.value.find(
(item: HTMLInputElement) => item.textContent === group,
);
if (titleRef && linkScrollbar.value) {
// 滚动分组标题
linkScrollbar.value.scrollTop = titleRef.offsetTop;
}
}
/** 自动滚动分组按钮,确保分组按钮保持在可视区域内 */
function scrollToGroupBtn(group: string) {
const groupBtn = groupBtnRefs.value.find(
(ref: HTMLButtonElement | undefined) => ref?.textContent === group,
);
if (groupBtn && groupScrollbar.value) {
groupScrollbar.value.scrollTop = groupBtn.offsetTop;
}
}
/** 是否为相同的链接(不比较参数,只比较链接) */
function isSameLink(link1: string, link2: string) {
return link2 ? link1.split('?')[0] === link2.split('?')[0] : false;
}
/** 处理详情选择 */
function handleProductCategorySelected(id: number) {
// 生成 activeAppLink
const url = new URL(activeAppLink.value.path, 'http://127.0.0.1');
url.searchParams.set('id', `${id}`);
activeAppLink.value.path = `${url.pathname}${url.search}`;
// 关闭对话框,并重置 id
detailSelectDialog.value.visible = false;
detailSelectDialog.value.id = undefined;
}
</script>
<template>
<Modal
v-model:open="dialogVisible"
title="选择链接"
width="65%"
@ok="handleSubmit"
>
<div class="flex h-[500px] gap-2">
<div class="flex flex-col">
<!-- 左侧分组列表 -->
<div
class="h-full overflow-y-auto border-r border-gray-200 pr-2"
ref="groupScrollbar"
>
<Button
v-for="(group, groupIndex) in APP_LINK_GROUP_LIST"
:key="groupIndex"
class="!ml-0 mb-1 mr-4 !justify-start"
:class="[{ active: activeGroup === group.name }]"
ref="groupBtnRefs"
:type="activeGroup === group.name ? 'primary' : 'default'"
:ghost="activeGroup !== group.name"
@click="handleGroupSelected(group.name)"
>
{{ group.name }}
</Button>
</div>
</div>
<!-- 右侧链接列表 -->
<div
class="h-full flex-1 overflow-y-auto pl-2"
@scroll="handleScroll"
ref="linkScrollbar"
>
<div
v-for="(group, groupIndex) in APP_LINK_GROUP_LIST"
:key="groupIndex"
class="mb-4 border-b border-gray-100 pb-4 last:mb-0 last:border-b-0"
>
<!-- 分组标题 -->
<div class="mb-2 font-bold" ref="groupTitleRefs">
{{ group.name }}
</div>
<!-- 链接列表 -->
<Tooltip
v-for="(appLink, appLinkIndex) in group.links"
:key="appLinkIndex"
:title="appLink.path"
placement="bottom"
:mouse-enter-delay="0.3"
>
<Button
class="mb-2 ml-0 mr-2"
:type="
isSameLink(appLink.path, activeAppLink.path)
? 'primary'
: 'default'
"
@click="handleAppLinkSelected(appLink)"
>
{{ appLink.name }}
</Button>
</Tooltip>
</div>
</div>
</div>
</Modal>
<Modal v-model:open="detailSelectDialog.visible" title="选择分类" width="65%">
<Form class="min-h-[200px]">
<FormItem
label="选择分类"
v-if="
detailSelectDialog.type === APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST
"
>
<ProductCategorySelect
v-model="detailSelectDialog.id"
:parent-id="0"
@update:model-value="handleProductCategorySelected"
/>
</FormItem>
</Form>
</Modal>
</template>
<style lang="scss" scoped>
:deep(.ant-btn + .ant-btn) {
margin-left: 0 !important;
}
</style>