239 lines
7.2 KiB
Vue
239 lines
7.2 KiB
Vue
<script lang="ts" setup>
|
||
import type { ButtonInstance, ScrollbarInstance } from 'element-plus';
|
||
|
||
import type { AppLink } from './data';
|
||
|
||
import { nextTick, ref } from 'vue';
|
||
|
||
import { getUrlNumberValue } from '@vben/utils';
|
||
|
||
import { ElScrollbar } from 'element-plus';
|
||
|
||
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<ScrollbarInstance>(); // 右侧滚动条
|
||
const groupTitleRefs = ref<HTMLInputElement[]>([]); // 分组标题引用列表
|
||
const groupScrollbar = ref<ScrollbarInstance>(); // 分组滚动条
|
||
const groupBtnRefs = ref<ButtonInstance[]>([]); // 分组引用列表
|
||
|
||
const detailSelectDialog = ref<{
|
||
id?: number;
|
||
type?: APP_LINK_TYPE_ENUM;
|
||
visible: boolean;
|
||
}>({
|
||
visible: false,
|
||
id: undefined,
|
||
type: undefined,
|
||
}); // 详情选择对话框
|
||
|
||
/** 打开弹窗 */
|
||
const dialogVisible = ref(false);
|
||
const 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 还没生成,导致滚动失败
|
||
nextTick(() => handleGroupSelected(group.name));
|
||
}
|
||
};
|
||
defineExpose({ open });
|
||
|
||
/** 处理 APP 链接选中 */
|
||
const 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.visible = true;
|
||
detailSelectDialog.value.type = appLink.type;
|
||
// 返显
|
||
detailSelectDialog.value.id =
|
||
getUrlNumberValue(
|
||
'id',
|
||
`http://127.0.0.1${activeAppLink.value.path}`,
|
||
) || undefined;
|
||
break;
|
||
}
|
||
default: {
|
||
break;
|
||
}
|
||
}
|
||
};
|
||
|
||
function handleSubmit() {
|
||
dialogVisible.value = false;
|
||
emit('change', activeAppLink.value.path);
|
||
emit('appLinkChange', activeAppLink.value);
|
||
}
|
||
|
||
/**
|
||
* 处理右侧链接列表滚动
|
||
* @param {object} param0 滚动事件参数
|
||
* @param {number} param0.scrollTop 滚动条的位置
|
||
*/
|
||
function handleScroll({ scrollTop }: { scrollTop: number }) {
|
||
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?.setScrollTop(titleRef.offsetTop);
|
||
}
|
||
}
|
||
|
||
/** 自动滚动分组按钮,确保分组按钮保持在可视区域内 */
|
||
function scrollToGroupBtn(group: string) {
|
||
const groupBtn = groupBtnRefs.value
|
||
.map((btn: ButtonInstance) => btn.ref)
|
||
.find((ref: HTMLButtonElement | undefined) => ref?.textContent === group);
|
||
if (groupBtn) {
|
||
groupScrollbar.value?.setScrollTop(groupBtn.offsetTop);
|
||
}
|
||
}
|
||
|
||
/** 是否为相同的链接(不比较参数,只比较链接) */
|
||
function isSameLink(link1: string, link2: string) {
|
||
return link2 ? link1.split('?')[0] === link2.split('?')[0] : false;
|
||
}
|
||
|
||
/** 处理详情选择 */
|
||
function handleProductCategorySelected(id: number) {
|
||
// TODO @AI:这里有点问题;
|
||
const url = new URL(activeAppLink.value.path, 'http://127.0.0.1');
|
||
// 修改 id 参数
|
||
url.searchParams.set('id', `${id}`);
|
||
// 排除域名
|
||
activeAppLink.value.path = `${url.pathname}${url.search}`;
|
||
// 关闭对话框
|
||
detailSelectDialog.value.visible = false;
|
||
// 重置 id
|
||
detailSelectDialog.value.id = undefined;
|
||
}
|
||
</script>
|
||
<template>
|
||
<el-dialog v-model="dialogVisible" title="选择链接" width="65%">
|
||
<div class="flex h-[500px] gap-2">
|
||
<!-- 左侧分组列表 -->
|
||
<ElScrollbar
|
||
wrap-class="h-full"
|
||
ref="groupScrollbar"
|
||
view-class="flex flex-col"
|
||
>
|
||
<el-button
|
||
v-for="(group, groupIndex) in APP_LINK_GROUP_LIST"
|
||
:key="groupIndex"
|
||
class="ml-0 mr-4 w-[90px] justify-start"
|
||
:class="[{ active: activeGroup === group.name }]"
|
||
ref="groupBtnRefs"
|
||
:text="activeGroup !== group.name"
|
||
:type="activeGroup === group.name ? 'primary' : 'default'"
|
||
@click="handleGroupSelected(group.name)"
|
||
>
|
||
{{ group.name }}
|
||
</el-button>
|
||
</ElScrollbar>
|
||
<!-- 右侧链接列表 -->
|
||
<ElScrollbar
|
||
class="h-full flex-1"
|
||
@scroll="handleScroll"
|
||
ref="linkScrollbar"
|
||
>
|
||
<div
|
||
v-for="(group, groupIndex) in APP_LINK_GROUP_LIST"
|
||
:key="groupIndex"
|
||
>
|
||
<!-- 分组标题 -->
|
||
<div class="font-bold" ref="groupTitleRefs">{{ group.name }}</div>
|
||
<!-- 链接列表 -->
|
||
<el-tooltip
|
||
v-for="(appLink, appLinkIndex) in group.links"
|
||
:key="appLinkIndex"
|
||
:content="appLink.path"
|
||
placement="bottom"
|
||
:show-after="300"
|
||
>
|
||
<el-button
|
||
class="mb-2 ml-0 mr-2"
|
||
:type="
|
||
isSameLink(appLink.path, activeAppLink.path)
|
||
? 'primary'
|
||
: 'default'
|
||
"
|
||
@click="handleAppLinkSelected(appLink)"
|
||
>
|
||
{{ appLink.name }}
|
||
</el-button>
|
||
</el-tooltip>
|
||
</div>
|
||
</ElScrollbar>
|
||
</div>
|
||
<!-- 底部对话框操作按钮 -->
|
||
<template #footer>
|
||
<el-button type="primary" @click="handleSubmit">确 定</el-button>
|
||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
<el-dialog v-model="detailSelectDialog.visible" title="" width="50%">
|
||
<el-form class="min-h-[200px]">
|
||
<el-form-item
|
||
label="选择分类"
|
||
v-if="
|
||
detailSelectDialog.type === APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST
|
||
"
|
||
>
|
||
<ProductCategorySelect
|
||
v-model="detailSelectDialog.id"
|
||
:parent-id="0"
|
||
@update:model-value="handleProductCategorySelected"
|
||
/>
|
||
</el-form-item>
|
||
</el-form>
|
||
</el-dialog>
|
||
</template>
|
||
<style lang="scss" scoped>
|
||
:deep(.el-button + .el-button) {
|
||
margin-left: 0 !important;
|
||
}
|
||
</style>
|