2024-08-26 14:07:57 +08:00
|
|
|
<template>
|
2024-09-01 01:10:10 +08:00
|
|
|
<div class="nav-view flex-col">
|
2024-09-01 17:11:14 +08:00
|
|
|
|
2024-08-31 19:37:01 +08:00
|
|
|
<div class="app-view-header">
|
2024-09-01 17:11:14 +08:00
|
|
|
<div>{{ NAV_MODULE_TITLE }}</div>
|
|
|
|
<div class="placeholder"></div>
|
|
|
|
<n-button
|
|
|
|
type="primary"
|
|
|
|
size="small"
|
|
|
|
:circle="true"
|
|
|
|
:secondary="true"
|
|
|
|
@click="showHelp"
|
|
|
|
>
|
|
|
|
<span class="mdi mdi-help"></span>
|
|
|
|
</n-button>
|
2024-08-31 19:37:01 +08:00
|
|
|
</div>
|
2024-09-01 17:11:14 +08:00
|
|
|
|
2024-09-01 01:10:10 +08:00
|
|
|
<div class="app-view-content">
|
2024-09-01 17:11:14 +08:00
|
|
|
<n-layout
|
|
|
|
class="page-layout"
|
|
|
|
:has-sider="true"
|
|
|
|
>
|
|
|
|
|
|
|
|
<!-- 左 -->
|
2024-09-01 01:10:10 +08:00
|
|
|
<n-layout-sider
|
|
|
|
class="left-aside"
|
|
|
|
collapse-mode="width"
|
2024-09-01 17:11:20 +08:00
|
|
|
show-trigger="arrow-circle"
|
2024-09-01 01:10:10 +08:00
|
|
|
:bordered="true"
|
2024-12-01 17:02:24 +08:00
|
|
|
:collapsed="storeNavView.isAsideCollapsed.value"
|
2024-09-01 01:10:10 +08:00
|
|
|
:collapsed-width="64"
|
|
|
|
:native-scrollbar="false"
|
2024-09-01 17:11:14 +08:00
|
|
|
:scrollbar-props="{ trigger: 'hover' }"
|
2024-09-01 01:10:10 +08:00
|
|
|
:width="240"
|
2024-12-01 17:02:24 +08:00
|
|
|
@collapse="storeNavView.isAsideCollapsed.value = true"
|
|
|
|
@expand="storeNavView.isAsideCollapsed.value = false"
|
2024-09-01 01:10:10 +08:00
|
|
|
>
|
|
|
|
<n-menu
|
|
|
|
v-model:value="navLinksTitle"
|
|
|
|
mode="vertical"
|
2024-12-01 17:02:24 +08:00
|
|
|
:collapsed="storeNavView.isAsideCollapsed.value"
|
2024-09-01 01:10:10 +08:00
|
|
|
:collapsed-icon-size="24"
|
|
|
|
:collapsed-width="64"
|
|
|
|
:icon-size="24"
|
|
|
|
:indent="24"
|
|
|
|
:options="navLinksCategory"
|
|
|
|
:on-update:value="handleSelectCategory"
|
|
|
|
/>
|
|
|
|
</n-layout-sider>
|
2024-09-01 17:11:14 +08:00
|
|
|
|
|
|
|
<!-- 右 -->
|
2024-09-01 17:11:20 +08:00
|
|
|
<n-layout class="right-content">
|
|
|
|
<div class="right-content-header">
|
|
|
|
<n-input-group>
|
|
|
|
<n-select
|
2024-12-01 17:02:24 +08:00
|
|
|
v-model:value="storeNavView.searchType.value"
|
2024-09-01 17:11:20 +08:00
|
|
|
class="search-type"
|
|
|
|
:options="searchTypes"
|
|
|
|
/>
|
|
|
|
<n-input
|
|
|
|
v-model:value="searchKeyword"
|
|
|
|
class="search-input"
|
|
|
|
placeholder="搜索链接项"
|
|
|
|
:clearable="true"
|
|
|
|
/>
|
|
|
|
</n-input-group>
|
|
|
|
</div>
|
|
|
|
<div class="right-content-body">
|
|
|
|
<n-scrollbar trigger="none">
|
|
|
|
<n-tree
|
|
|
|
children-field="children"
|
|
|
|
key-field="_key"
|
|
|
|
label-field="title"
|
|
|
|
:cancelable="false"
|
|
|
|
:block-line="true"
|
|
|
|
:block-node="true"
|
|
|
|
:data="navLinksCurr"
|
|
|
|
:filter="treeDataFilter"
|
|
|
|
:pattern="searchKeyword"
|
|
|
|
:render-label="renderTreeLabel"
|
|
|
|
:selectable="true"
|
|
|
|
:show-irrelevant-nodes="false"
|
|
|
|
/>
|
|
|
|
</n-scrollbar>
|
2024-09-01 01:10:10 +08:00
|
|
|
</div>
|
|
|
|
</n-layout>
|
2024-09-01 17:11:14 +08:00
|
|
|
|
2024-09-01 01:10:10 +08:00
|
|
|
</n-layout>
|
2024-08-31 19:37:01 +08:00
|
|
|
</div>
|
2024-09-01 17:11:14 +08:00
|
|
|
|
|
|
|
<!-- 链接详情 -->
|
|
|
|
<n-drawer
|
|
|
|
v-model:show="detailDrawer.show"
|
|
|
|
class="nav-url-detail-drawer"
|
|
|
|
placement="right"
|
|
|
|
>
|
|
|
|
<n-drawer-content
|
|
|
|
title="链接详情"
|
|
|
|
:closable="true"
|
|
|
|
>
|
|
|
|
<n-descriptions
|
|
|
|
label-placement="top"
|
|
|
|
:column="1"
|
|
|
|
>
|
|
|
|
<n-descriptions-item label="ID">
|
2024-09-01 17:11:20 +08:00
|
|
|
{{ detailDrawer.data._key || '-' }}
|
2024-09-01 17:11:14 +08:00
|
|
|
</n-descriptions-item>
|
|
|
|
<n-descriptions-item label="链接标题">
|
2024-09-01 17:11:20 +08:00
|
|
|
{{ detailDrawer.data.title || '-' }}
|
|
|
|
</n-descriptions-item>
|
|
|
|
<n-descriptions-item label="链接简介">
|
|
|
|
{{ detailDrawer.data.desc || '-' }}
|
2024-09-01 17:11:14 +08:00
|
|
|
</n-descriptions-item>
|
|
|
|
<n-descriptions-item label="链接地址">
|
2024-09-01 17:11:20 +08:00
|
|
|
{{ detailDrawer.data.url || '-' }}
|
2024-09-01 17:11:14 +08:00
|
|
|
</n-descriptions-item>
|
|
|
|
<n-descriptions-item label="更新日期">
|
2024-09-01 17:11:20 +08:00
|
|
|
{{ detailDrawer.data.date || '-' }}
|
2024-09-01 17:11:14 +08:00
|
|
|
</n-descriptions-item>
|
|
|
|
<n-descriptions-item label="是否有效">
|
|
|
|
{{ detailDrawer.data.isInvalid ? '否' : '是' }}
|
|
|
|
</n-descriptions-item>
|
|
|
|
</n-descriptions>
|
|
|
|
</n-drawer-content>
|
|
|
|
</n-drawer>
|
|
|
|
|
2024-08-31 19:37:01 +08:00
|
|
|
</div>
|
2024-08-26 14:07:57 +08:00
|
|
|
</template>
|
|
|
|
|
2024-09-01 01:10:10 +08:00
|
|
|
<script lang="jsx" setup>
|
|
|
|
import {
|
2024-09-01 17:11:20 +08:00
|
|
|
NButton, NDescriptions, NDescriptionsItem,
|
|
|
|
NDrawer, NDrawerContent, NInput, NInputGroup,
|
|
|
|
NLayout, NLayoutSider, NLi, NMenu,
|
|
|
|
NScrollbar, NSelect, NTree, NUl,
|
2024-09-01 01:10:10 +08:00
|
|
|
} from 'naive-ui';
|
|
|
|
|
|
|
|
import {
|
2024-09-01 17:11:14 +08:00
|
|
|
reactive, shallowRef, onBeforeMount,
|
2024-09-01 01:10:10 +08:00
|
|
|
} from 'vue';
|
|
|
|
|
2024-08-31 19:37:01 +08:00
|
|
|
import {
|
|
|
|
NAV_MODULE_TITLE,
|
|
|
|
} from '@/config/modules';
|
2024-09-01 01:10:10 +08:00
|
|
|
|
2024-09-01 18:01:04 +08:00
|
|
|
import {
|
2024-12-01 17:02:24 +08:00
|
|
|
storeNavView,
|
|
|
|
} from '@/assets/js/local-storage';
|
2024-09-01 18:01:04 +08:00
|
|
|
|
2024-09-01 17:11:14 +08:00
|
|
|
import {
|
|
|
|
$dialog, $message,
|
|
|
|
} from '@/assets/js/naive-ui';
|
|
|
|
|
2024-09-01 01:10:10 +08:00
|
|
|
import {
|
|
|
|
formatNavLinks,
|
|
|
|
} from '@/assets/js/nav-links';
|
|
|
|
|
2024-09-01 18:01:04 +08:00
|
|
|
import {
|
|
|
|
useLocalStorage,
|
|
|
|
} from '@vueuse/core';
|
|
|
|
|
2024-09-01 17:11:14 +08:00
|
|
|
/** 链接详情 */
|
|
|
|
const detailDrawer = reactive({
|
|
|
|
|
|
|
|
/** @type {NavLinkItem | null} */
|
|
|
|
data: null,
|
|
|
|
|
|
|
|
/** @type {boolean} */
|
|
|
|
show: false,
|
|
|
|
|
|
|
|
});
|
|
|
|
|
2024-09-01 01:10:10 +08:00
|
|
|
/** 完整的链接列表 */
|
2024-09-01 17:11:20 +08:00
|
|
|
const navLinksAll = formatNavLinks(true);
|
2024-09-01 01:10:10 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @desc 链接分类列表
|
|
|
|
* @type { import('naive-ui').MenuOption[] }
|
|
|
|
*/
|
|
|
|
const navLinksCategory = navLinksAll.map((item) => {
|
|
|
|
return {
|
|
|
|
icon: () => <span class={item.icon}></span>,
|
|
|
|
key: item.title,
|
|
|
|
label: item.title,
|
|
|
|
_data: item,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @desc 当前显示的链接列表
|
|
|
|
* @type {VueRef<NavLinkItem[]>}
|
|
|
|
*/
|
|
|
|
const navLinksCurr = shallowRef([]);
|
|
|
|
|
|
|
|
/** 当前显示的链接分类标题 */
|
|
|
|
const navLinksTitle = shallowRef('');
|
|
|
|
|
2024-09-01 17:11:20 +08:00
|
|
|
/** 搜索关键词 */
|
|
|
|
const searchKeyword = shallowRef('');
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @desc 搜索类型列表
|
|
|
|
* @type { import('naive-ui').SelectOption[] }
|
|
|
|
*/
|
|
|
|
const searchTypes = [
|
|
|
|
{ label: '全部', value: 'all' },
|
|
|
|
{ label: '标题', value: 'title' },
|
|
|
|
{ label: '链接', value: 'url' },
|
|
|
|
{ label: '简介', value: 'desc' },
|
|
|
|
];
|
|
|
|
|
2024-09-01 01:10:10 +08:00
|
|
|
/**
|
|
|
|
* @description 切换链接列表
|
|
|
|
* @param {NavLinkItem} [data] 链接分类信息
|
|
|
|
*/
|
|
|
|
function changeList(data = null) {
|
|
|
|
|
2024-09-08 21:01:30 +08:00
|
|
|
let useData = null;
|
|
|
|
let storedKey = '';
|
|
|
|
|
|
|
|
if (data) {
|
|
|
|
useData = data;
|
|
|
|
} else {
|
2024-12-01 17:02:24 +08:00
|
|
|
storedKey = storeNavView.currentCategory.value;
|
2024-09-08 21:01:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (storedKey) {
|
|
|
|
useData = navLinksAll.find((item) => {
|
|
|
|
return item.title === storedKey;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!useData) {
|
|
|
|
useData = navLinksAll[0] || null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (useData) {
|
2024-12-01 17:02:24 +08:00
|
|
|
storeNavView.currentCategory.value = useData.title;
|
2024-09-08 21:01:30 +08:00
|
|
|
navLinksCurr.value = useData.children;
|
|
|
|
navLinksTitle.value = useData.title;
|
|
|
|
} else {
|
2024-12-01 17:02:24 +08:00
|
|
|
storeNavView.currentCategory.value = '';
|
2024-09-08 21:01:30 +08:00
|
|
|
navLinksCurr.value = [];
|
|
|
|
navLinksTitle.value = '';
|
|
|
|
}
|
|
|
|
|
2024-09-01 01:10:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @description 处理选择链接分类
|
|
|
|
* @param {string} key
|
|
|
|
* @param {NavLinkItem} item
|
|
|
|
*/
|
|
|
|
function handleSelectCategory(key, item) {
|
|
|
|
changeList(item._data);
|
|
|
|
}
|
|
|
|
|
2024-09-01 17:11:14 +08:00
|
|
|
/**
|
|
|
|
* @description 打开链接
|
2024-09-01 17:43:04 +08:00
|
|
|
* @param {NavLinkItem} data
|
2024-09-01 17:11:14 +08:00
|
|
|
*/
|
2024-09-01 17:43:04 +08:00
|
|
|
function openURL(data) {
|
|
|
|
if (data.isInvalid) {
|
|
|
|
$message.warning('链接已失效,仅支持查看详情');
|
|
|
|
} else if (data.showOnly) {
|
|
|
|
$message.warning('该链接不支持直接打开,请在链接详情中复制后手动打开');
|
|
|
|
} else if (data.url) {
|
|
|
|
window.open(data.url, '_blank');
|
2024-09-01 17:11:14 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-01 01:10:10 +08:00
|
|
|
/**
|
|
|
|
* @description 树形数据节点内容渲染函数
|
|
|
|
* @param {object} info
|
|
|
|
* @param {NavLinkItem} info.option
|
|
|
|
*/
|
|
|
|
function renderTreeLabel(info) {
|
2024-09-01 17:11:14 +08:00
|
|
|
|
|
|
|
let data = info.option;
|
|
|
|
let isURL = !data.children;
|
|
|
|
|
|
|
|
let open = () => {
|
2024-09-01 17:43:04 +08:00
|
|
|
isURL && openURL(data);
|
2024-09-01 17:11:14 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
let show = () => {
|
|
|
|
isURL && toggleDetailDrawer(true, data);
|
|
|
|
};
|
|
|
|
|
2024-09-01 17:50:12 +08:00
|
|
|
return <div
|
|
|
|
class="item-wrapper"
|
|
|
|
onContextmenu={show}
|
|
|
|
onClick={open}
|
|
|
|
>
|
|
|
|
<div class="item-title">
|
|
|
|
<span>{ data.title }</span>
|
|
|
|
{ data.desc ? <span> - { data.desc }</span> : ''}
|
|
|
|
</div>
|
2024-09-01 17:11:14 +08:00
|
|
|
<div class="item-url">{ data.url }</div>
|
|
|
|
</div>;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/** 显示说明 */
|
|
|
|
function showHelp() {
|
|
|
|
$dialog.create({
|
|
|
|
content: () => {
|
|
|
|
return <NUl>
|
|
|
|
<NLi>点击目录项左侧按钮以展开目录。</NLi>
|
|
|
|
<NLi>PC 端右键或移动端长按链接项以显示详情。</NLi>
|
|
|
|
</NUl>
|
|
|
|
},
|
|
|
|
title: '操作说明',
|
|
|
|
type: 'default',
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-09-01 17:11:20 +08:00
|
|
|
/**
|
|
|
|
* @description 处理搜索
|
|
|
|
* @param {string} pattern
|
|
|
|
* @param {NavLinkItem} node
|
|
|
|
*/
|
|
|
|
function treeDataFilter(pattern = '', node = null) {
|
|
|
|
|
|
|
|
if (pattern === '') {
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
pattern = pattern.toLowerCase();
|
|
|
|
}
|
|
|
|
|
2024-12-01 17:02:24 +08:00
|
|
|
let type = storeNavView.searchType.value;
|
2024-09-01 17:11:20 +08:00
|
|
|
|
|
|
|
let desc = String(node.desc).toLowerCase();
|
|
|
|
let title = String(node.title).toLowerCase();
|
|
|
|
let url = String(node.url).toLowerCase();
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case 'all':
|
|
|
|
return (
|
|
|
|
title.includes(pattern) ||
|
|
|
|
url.includes(pattern) ||
|
|
|
|
desc.includes(pattern)
|
|
|
|
);
|
|
|
|
case 'desc':
|
|
|
|
return desc.includes(pattern);
|
|
|
|
case 'title':
|
|
|
|
return title.includes(pattern);
|
|
|
|
case 'url':
|
|
|
|
return url.includes(pattern);
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2024-09-01 17:11:14 +08:00
|
|
|
/**
|
|
|
|
* @description 切换链接详情显示隐藏
|
|
|
|
* @param {boolean} show
|
|
|
|
* @param {NavLinkItem} data
|
|
|
|
*/
|
|
|
|
function toggleDetailDrawer(show = false, data = null) {
|
|
|
|
if (show) {
|
|
|
|
detailDrawer.data = data;
|
|
|
|
}
|
|
|
|
detailDrawer.show = show;
|
2024-09-01 01:10:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
onBeforeMount(() => {
|
|
|
|
changeList(null);
|
|
|
|
});
|
2024-08-26 14:07:57 +08:00
|
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="less" scoped>
|
2024-09-01 01:10:10 +08:00
|
|
|
.page-layout {
|
|
|
|
width: 100%;
|
|
|
|
height: 100%;
|
|
|
|
}
|
|
|
|
|
|
|
|
.left-aside {
|
|
|
|
:deep(.n-menu-item-content__icon) {
|
|
|
|
color: var(--color-text-2);
|
|
|
|
}
|
|
|
|
|
|
|
|
:deep(.n-menu-item-content-header) {
|
|
|
|
color: var(--color-text-2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.right-content {
|
|
|
|
width: 0;
|
|
|
|
height: 100%;
|
|
|
|
|
2024-09-01 17:11:20 +08:00
|
|
|
:deep(> div) {
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
2024-09-01 01:10:10 +08:00
|
|
|
width: 100%;
|
|
|
|
height: 100%;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-01 17:11:20 +08:00
|
|
|
.right-content-header,
|
|
|
|
.right-content-body {
|
2024-09-01 01:10:10 +08:00
|
|
|
width: 100%;
|
2024-09-01 17:11:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
.right-content-header {
|
|
|
|
// 注:右侧 padding 2px 防止输入框聚焦样式显示不全
|
|
|
|
padding: 10px 2px 0 16px;
|
|
|
|
|
|
|
|
.search-type {
|
|
|
|
width: 80px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.search-input {
|
|
|
|
flex-grow: 1;
|
|
|
|
width: 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.right-content-body {
|
|
|
|
flex-grow: 1;
|
|
|
|
padding: 16px 2px 0 16px;
|
|
|
|
height: 0;
|
2024-09-01 01:10:10 +08:00
|
|
|
|
|
|
|
:deep(.n-tree-node) {
|
|
|
|
align-items: center;
|
|
|
|
}
|
|
|
|
|
2024-09-01 17:11:14 +08:00
|
|
|
:deep(.n-tree-node-content) {
|
|
|
|
color: var(--color-text-2);
|
|
|
|
line-height: 1.5;
|
|
|
|
}
|
|
|
|
|
2024-09-01 17:11:20 +08:00
|
|
|
:deep(.n-tree-node-content__text) {
|
|
|
|
border-bottom: none;
|
|
|
|
}
|
|
|
|
|
2024-09-01 17:11:14 +08:00
|
|
|
:deep(.n-tree-node-indent > div) {
|
|
|
|
width: 16px !important;
|
|
|
|
}
|
|
|
|
|
|
|
|
:deep(.n-tree-node--selected) {
|
|
|
|
.n-tree-node-content,
|
|
|
|
.n-tree-node-switcher__icon {
|
|
|
|
color: var(--color-primary);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
:deep(.n-tree-node-switcher__icon) {
|
|
|
|
width: 1em;
|
|
|
|
height: 1em;
|
|
|
|
color: var(--color-text-2);
|
|
|
|
font-size: 20px;
|
|
|
|
}
|
|
|
|
|
|
|
|
:deep(.item-wrapper) {
|
2024-09-01 01:10:10 +08:00
|
|
|
display: flex;
|
2024-09-01 17:11:14 +08:00
|
|
|
flex-direction: column;
|
|
|
|
justify-content: center;
|
|
|
|
width: 100%;
|
|
|
|
height: 48px;
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
|
|
> div {
|
|
|
|
width: 100%;
|
|
|
|
overflow: hidden;
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
:deep(.item-url) {
|
|
|
|
opacity: 0.5;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|
|
|
|
|
|
|
|
<style lang="less">
|
|
|
|
.nav-url-detail-drawer {
|
|
|
|
width: 100% !important;
|
|
|
|
max-width: 320px;
|
|
|
|
|
|
|
|
.n-descriptions-table-content {
|
|
|
|
user-select: text;
|
2024-09-01 01:10:10 +08:00
|
|
|
}
|
|
|
|
}
|
2024-08-26 14:07:57 +08:00
|
|
|
</style>
|