调整工具页面打开方式

This commit is contained in:
2022-03-20 12:41:21 +08:00
parent 63e633c452
commit fdb9d31101
23 changed files with 256 additions and 263 deletions

View File

@@ -1,5 +1,6 @@
/** /**
* @typedef {object} ToolItem * @typedef {object} ToolItem
* @property {string} name 工具名称
* @property {string} title 工具标题 * @property {string} title 工具标题
* @property {string} [desc] 工具简介 * @property {string} [desc] 工具简介
* @property {string} component 组件名称 * @property {string} component 组件名称
@@ -11,144 +12,158 @@
/** /**
* @typedef {object} ToolCategory * @typedef {object} ToolCategory
* @property {string} title 分类标题 * @property {string} title 分类标题
* @property {Object.<string, ToolItem>} list 工具列表 * @property {ToolItem[]} list 工具列表
*/ */
/** @type {Object.<string, ToolCategory>} */ /** @type {Object.<string, ToolCategory>} */
const navTools = { const navTools = {
calculation: { calculation: {
title: '计算', title: '计算',
list: { list: [
'download-time': { {
name: 'calc-download-time',
component: 'CalcDownloadTime',
title: '下载用时计算', title: '下载用时计算',
desc: '根据设定的文件大小和下载速度简单计算大约下载完成所需的时间。', desc: '根据设定的文件大小和下载速度简单计算大约下载完成所需的时间。',
component: 'CalcDownloadTime',
update: '2021-12-06', update: '2021-12-06',
version: '1', version: '1',
enabled: true enabled: true
}, },
'ratio': { {
name: 'calc-ratio',
component: 'CalcRatio',
title: '比例计算', title: '比例计算',
desc: '按设定的比例计算给出的数值所对应的数值。', desc: '按设定的比例计算给出的数值所对应的数值。',
component: 'CalcRatio',
update: '2021-11-16', update: '2021-11-16',
version: '2', version: '2',
enabled: true enabled: true
}, },
} ]
}, },
generator: { generator: {
title: '生成', title: '生成',
list: { list: [
'links': { {
name: 'gen-links',
component: 'GenLinks',
title: '生成批量下载链接', title: '生成批量下载链接',
desc: '根据设置,生成有一定规律的用于批量下载的链接。', desc: '根据设置,生成有一定规律的用于批量下载的链接。',
component: 'GenLinks',
update: '2021-11-14', update: '2021-11-14',
version: '1', version: '1',
enabled: true enabled: true
}, },
'random-str': { {
name: 'gen-random-str',
component: 'GenRandomStr',
title: '生成随机字符串', title: '生成随机字符串',
desc: '生成随机组合的字符串,可用于密码。', desc: '生成随机组合的字符串,可用于密码。',
component: 'GenRandomStr',
update: '2021-05-04', update: '2021-05-04',
version: '1', version: '1',
enabled: true enabled: true
}, },
} ]
}, },
conversion: { conversion: {
title: '转换', title: '转换',
list: { list: [
'encode-decode': { {
name: 'convert-encode-decode',
component: 'ConvertEncodeDecode',
title: '编码转换', title: '编码转换',
desc: 'HTML / URI 编码、解码', desc: 'HTML / URI 编码、解码',
component: 'ConvertEncodeDecode',
update: '2021-11-10', update: '2021-11-10',
version: '1', version: '1',
enabled: true enabled: true
}, },
'text-structure': { {
name: 'convert-text-structure',
component: 'ConvertTextStructure',
title: '文本结构转换', title: '文本结构转换',
desc: '倒序、横竖互换等', desc: '倒序、横竖互换等',
component: 'ConvertTextStructure',
update: '2021-12-06', update: '2021-12-06',
version: '1', version: '1',
enabled: true, enabled: true,
}, },
'timestamp': { {
name: 'convert-timestamp',
component: 'ConvertTimestamp',
title: 'Unix 时间戳转换', title: 'Unix 时间戳转换',
desc: '时间戳转时间 / 时间转时间戳', desc: '时间戳转时间 / 时间转时间戳',
component: 'ConvertTimestamp',
update: '2021-11-14', update: '2021-11-14',
version: '1', version: '1',
enabled: true enabled: true
}, },
} ]
}, },
minecraft: { minecraft: {
title: 'Minecraft', title: 'Minecraft',
list: { list: [
'chunk-location-calc': { {
title: 'Minecraft 区块位置计算', name: 'minecraft-chunk-location-calc',
component: 'MinecraftChunkLocationCalc', component: 'MinecraftChunkLocationCalc',
title: 'Minecraft 区块位置计算',
enabled: false enabled: false
}, },
'dynmap-renderdata-gen': { {
name: 'minecraft-dynmap-renderdata-gen',
component: 'MinecraftDynmapRenderdataGen',
title: 'Dynmap renderdata 生成', title: 'Dynmap renderdata 生成',
desc: '生成用于 Minecraft Dynmap 插件或模组的 renderdata 数据。', desc: '生成用于 Minecraft Dynmap 插件或模组的 renderdata 数据。',
component: 'MinecraftDynmapRenderdataGen',
update: '2021-12-06', update: '2021-12-06',
version: '1', version: '1',
enabled: true, enabled: true,
}, },
'uuid-converter': { {
name: 'minecraft-uuid-converter',
component: 'MinecraftUUIDConverter',
title: 'Minecraft UUID 转换', title: 'Minecraft UUID 转换',
desc: '随机生成或转换 Minecraft 的 UUID。', desc: '随机生成或转换 Minecraft 的 UUID。',
component: 'MinecraftUUIDConverter',
update: '2021-12-06', update: '2021-12-06',
version: '1', version: '1',
enabled: true enabled: true
}, },
} ]
}, },
other: { other: {
title: '其他', title: '其他',
list: { list: [
'edit-csv': { {
name: 'other-edit-csv',
component: 'OtherEditCSV',
title: 'CSV 编辑工具', title: 'CSV 编辑工具',
desc: '查看或编辑 CSV 文件', desc: '查看或编辑 CSV 文件',
component: 'OtherEditCSV',
update: '2022-03-17', update: '2022-03-17',
version: '2', version: '2',
enabled: true, enabled: true,
}, },
'new-window': { {
name: 'other-new-window',
component: 'OtherNewWindow',
title: '新窗口(小窗)中打开', title: '新窗口(小窗)中打开',
desc: '从新的小窗口中打开指定的链接(仅支持 PC 端浏览器)', desc: '从新的小窗口中打开指定的链接(仅支持 PC 端浏览器)',
component: 'OtherNewWindow',
update: '2021-05-03', update: '2021-05-03',
version: '1', version: '1',
enabled: true enabled: true
}, },
'run-js': { {
name: 'other-run-js',
component: 'OtherRunJS',
title: '执行 JavaScript', title: '执行 JavaScript',
desc: '执行简单的 JavaScript 代码片段', desc: '执行简单的 JavaScript 代码片段',
component: 'OtherRunJS',
update: '2021-06-14', update: '2021-06-14',
version: '2', version: '2',
enabled: true enabled: true
}, },
'websocket': { {
name: 'other-websocket',
component: 'OtherWebSocket',
title: 'WebSocket', title: 'WebSocket',
desc: 'WebSocket 测试工具', desc: 'WebSocket 测试工具',
component: 'OtherWebSocket',
update: '2021-11-19', update: '2021-11-19',
version: '4', version: '4',
enabled: true enabled: true
}, },
} ]
} }
}; };

View File

@@ -64,14 +64,10 @@ export default {
* 返回主页 * 返回主页
*/ */
backToHome() { backToHome() {
var routeName = 'Home'; const routeName = 'Home';
if (this.$route.name != routeName) { if (this.$route.name != routeName) {
this.$router.push({ this.$router.push({ name: routeName });
name: routeName
}).then(() => {
window.location.reload();
});
} else { } else {
this.$message({ this.$message({
duration: 2000, duration: 2000,

View File

@@ -2,7 +2,8 @@ import Vue from 'vue';
import VueRouter from 'vue-router'; import VueRouter from 'vue-router';
import routes from './routes'; import routes from './routes';
import config from '../assets/js/config'; import config from '@/assets/js/config';
import utils from '@/assets/js/utils';
Vue.use(VueRouter); Vue.use(VueRouter);
@@ -23,6 +24,7 @@ router.afterEach((to) => {
if (to.meta.loadingBar) { if (to.meta.loadingBar) {
config.loading.subPage = false; config.loading.subPage = false;
} }
utils.changeTitle(to.meta.title);
}); });
export default router; export default router;

View File

@@ -1,10 +1,13 @@
import tools from './tools';
/** @type { import('vue-router').RouteConfig[] } */ /** @type { import('vue-router').RouteConfig[] } */
const routes = [ const routes = [
{ {
path: '/', path: '/',
name: 'Home', name: 'Home',
meta: { meta: {
loadingBar: true loadingBar: true,
title: '主页',
}, },
component: () => import( component: () => import(
/* webpackChunkName: 'home-view' */ /* webpackChunkName: 'home-view' */
@@ -15,28 +18,33 @@ const routes = [
path: '/tools', path: '/tools',
name: 'Tools', name: 'Tools',
meta: { meta: {
loadingBar: true loadingBar: true,
title: '小工具',
}, },
component: () => import( component: () => import(
/* webpackChunkName: 'tools-view' */ /* webpackChunkName: 'tools-view' */
'@/views/ToolsView.vue' '@/views/ToolsView.vue'
)
},
{
path: '/tool',
name: 'Tool',
meta: {
loadingBar: true,
title: '小工具',
},
component: () => import(
/* webpackChunkName: 'tools-detail' */
'@/views/ToolsDetail.vue'
), ),
children: [ children: tools
{
path: '/tools/:category/:name',
name: 'ToolsDetail',
component: () => import(
/* webpackChunkName: 'tools-detail' */
'@/views/ToolsDetail.vue'
)
}
]
}, },
{ {
path: '/settings', path: '/settings',
name: 'Settings', name: 'Settings',
meta: { meta: {
loadingBar: true loadingBar: true,
title: '设置',
}, },
component: () => import( component: () => import(
/* webpackChunkName: 'settings-view' */ /* webpackChunkName: 'settings-view' */
@@ -47,7 +55,8 @@ const routes = [
path: '/about', path: '/about',
name: 'About', name: 'About',
meta: { meta: {
loadingBar: true loadingBar: true,
title: '关于',
}, },
component: () => import( component: () => import(
/* webpackChunkName: 'about-view' */ /* webpackChunkName: 'about-view' */

40
src/router/tools.js Normal file
View File

@@ -0,0 +1,40 @@
import navTools from '@/assets/js/navTools';
/** @type { import('vue-router').RouteConfig[] } */
const routes = [];
// 提取工具列表信息,添加到路由
for (let categoryName in navTools) {
const toolList = (navTools[categoryName].list || []);
toolList.forEach((toolItem) => {
const {
name: toolName = null,
component = null,
title = '未知',
update = '',
version = '',
} = toolItem;
if (toolName === null || component === null) {
console.warn('部分小工具未设置工具名称或组件名称。');
return;
}
routes.push({
path: `/tool/${toolName}`,
name: `Tool${component}`,
meta: {
loadingBar: true,
title,
update,
version,
},
component: () => import(`@/views/Tools/${component}.vue`)
});
});
}
export default routes;

View File

@@ -43,15 +43,9 @@
<script> <script>
export default { export default {
name: 'AboutView', name: 'AboutView',
beforeRouteEnter(to, from, next) {
next(vm => {
vm.utils.changeTitle('关于');
});
},
data() { data() {
return { return {
navLinks: this.$root.navLinks, navLinks: this.$root.navLinks,
utils: this.$root.utils,
info: { info: {
github: 'https://github.com/Frost-ZX/frost-navigation' github: 'https://github.com/Frost-ZX/frost-navigation'
} }

View File

@@ -237,11 +237,6 @@ export default {
components: { components: {
IconElement IconElement
}, },
beforeRouteEnter(to, from, next) {
next(vm => {
vm.utils.changeTitle();
});
},
data() { data() {
return { return {
config: this.$root.config.storage, config: this.$root.config.storage,

View File

@@ -49,15 +49,9 @@
<script> <script>
export default { export default {
name: 'SettingsView', name: 'SettingsView',
beforeRouteEnter(to, from, next) {
next(vm => {
vm.utils.changeTitle('设置');
});
},
data() { data() {
return { return {
config: this.$root.config.storage, config: this.$root.config.storage,
utils: this.$root.utils
} }
}, },
methods: { methods: {

View File

@@ -1,70 +1,145 @@
<template> <template>
<div class="tools-detail"> <div class="tools-detail">
<component :is="toolPage" />
<div class="tools-header">
<!-- 标题 -->
<div class="title">
<span>{{ routeMeta.title }}</span>
<span
v-show="Boolean(routeMeta.version)"
>[{{ routeMeta.version }}]</span>
<span
v-show="Boolean(routeMeta.update)"
>[{{ routeMeta.update }}]</span>
</div>
<!-- 在新标签页打开 -->
<el-tooltip content="在新标签页中打开" placement="left">
<div
class="btn el-icon-copy-document"
@click="openNewTab()"
></div>
</el-tooltip>
<!-- 关闭 -->
<el-tooltip content="关闭工具" placement="left">
<div
class="btn el-icon-close"
@click="close()"
></div>
</el-tooltip>
</div>
<div class="tools-content">
<router-view></router-view>
</div>
</div> </div>
</template> </template>
<script> <script>
import navTools from '@/assets/js/navTools.js';
export default { export default {
name: 'ToolsDetail', name: 'ToolsDetail',
beforeRouteEnter(to, from, next) {
next(vm => {
const { params, query } = vm.$route;
const { category: cCategory, name: cName } = params;
const componentName = vm.toolList[cCategory]['list'][cName].component;
var loading = null;
console.log('[打开工具]', { params, query });
// 异步,防止找不到 target
setTimeout(() => {
// 开启 Loading
loading = vm.$loading({
background: '#FFF',
lock: true,
// Loading 需要覆盖的 DOM 节点
target: '.drawer-full .el-drawer__body'
});
}, 0);
vm.toolPage = (() => {
// 动态引入组件
const component = import(`@/components/Tools/${componentName}.vue`);
Promise.all([component]).then(() => {
setTimeout(() => {
// 关闭 Loading
loading.close();
}, 200);
});
return component;
});
});
},
data() { data() {
return { return {
utils: this.$root.utils,
toolList: navTools,
toolPage: null,
} }
}, },
computed: {
/** 路由 meta 信息 */
routeMeta() {
return (this.$route.meta || {});
},
},
methods: {
/** 关闭工具 */
close() {
this.$confirm('是否关闭?').then(() => {
// 返回工具页面
this.$router.push({
name: 'Tools'
});
}).catch(() => { });
},
/** 在新标签页打开工具 */
openNewTab() {
const url = window.location.href;
window.open(url, '_blank');
},
},
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.tools-detail { .tools-detail {
display: flex; display: flex;
align-items: flex-start; align-items: center;
justify-content: center; flex-direction: column;
position: fixed;
top: 0;
left: 0;
z-index: 1000;
width: 100%;
height: 100%;
background-color: #FFF;
}
.tools-header {
display: flex;
align-items: center;
position: relative;
z-index: 20;
padding: 1rem;
width: 100%;
box-shadow: 0 0.5rem 1rem -0.5rem rgba(0, 0, 0, 0.2);
background-color: @colorPrimary;
color: #FFF;
.title {
flex-grow: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
span {
margin-right: 0.25em;
}
}
.btn {
flex-shrink: 0;
margin-left: 0.5em;
cursor: pointer;
}
}
.tools-content {
flex-grow: 1;
position: relative;
z-index: 10;
width: 100%;
height: 0;
overflow-y: auto;
} }
/deep/ .tool-page { /deep/ .tool-page {
margin: 0 auto;
padding: 1rem 2rem;
width: 100%; width: 100%;
height: auto;
max-width: 60rem; max-width: 60rem;
background-color: #FFF;
> div { > div {
> .title { > .title {

View File

@@ -1,6 +1,5 @@
<template> <template>
<el-container class="tools"> <el-container class="tools">
<div class="wrapper"> <div class="wrapper">
<!-- 工具分类 --> <!-- 工具分类 -->
@@ -15,10 +14,10 @@
<!-- 工具项 --> <!-- 工具项 -->
<div <div
v-for="(toolItem, toolKey) in categoryItem.list" v-for="toolItem in categoryItem.list"
:key="toolKey" :key="toolItem.name"
:class="['tool-item', 'shadow-2', { disabled: !toolItem.enabled }]" :class="['tool-item', 'shadow-2', { disabled: !toolItem.enabled }]"
@click="detailOpen(categoryKey, toolKey)" @click="openTool(`Tool${toolItem.component || 'Unknown'}`, toolItem.enabled)"
> >
<div class="item-title limit-line-1">{{ toolItem.title }}</div> <div class="item-title limit-line-1">{{ toolItem.title }}</div>
<div class="item-content limit-line-3">{{ toolItem.desc || '无简介' }}</div> <div class="item-content limit-line-3">{{ toolItem.desc || '无简介' }}</div>
@@ -27,32 +26,6 @@
</div> </div>
</div> </div>
<!-- 弹出层 -->
<el-drawer
custom-class="drawer-full"
direction="btt"
size="100%"
:append-to-body="true"
:destroy-on-close="true"
:title="detail.title"
:visible.sync="detail.show"
:before-close="detailClose"
>
<!-- 标题区域 -->
<div slot="title" class="header">
<span class="title">{{ detail.title }}</span>
<el-tooltip content="在新标签页中打开本工具" placement="left">
<i class="btn el-icon-copy-document" @click="detailOpenNewTab()"></i>
</el-tooltip>
</div>
<!-- 内容区域 -->
<router-view></router-view>
</el-drawer>
</el-container> </el-container>
</template> </template>
@@ -61,116 +34,30 @@ import navTools from '@/assets/js/navTools.js';
export default { export default {
name: 'ToolsView', name: 'ToolsView',
beforeRouteEnter(to, from, next) {
next(vm => {
const { name: rName, params: rParams } = vm.$route;
// 判断进入的路由
if (rName === 'ToolsDetail') {
// [工具内容页面]
vm.detailOpen(rParams.category, rParams.name);
} else {
// [工具列表页面]
vm.utils.changeTitle('小工具');
}
});
},
data() { data() {
return { return {
utils: this.$root.utils,
toolList: navTools, toolList: navTools,
detail: {
show: false,
title: ''
},
} }
}, },
methods: { methods: {
/** /**
* 关闭工具 * @description 打开工具
* @param {string} routeName 路由名称
* @param {boolean} isEnabled 是否已启用
*/ */
detailClose(done) { openTool(routeName, isEnabled = false) {
this.$confirm('是否关闭?').then(() => {
// 关闭 drawer
done();
// 返回工具页面
this.$router.push({
name: 'Tools'
}).then(() => {
// 更新标题
this.utils.changeTitle('小工具');
});
}).catch(() => { });
},
/** if (isEnabled) {
* 打开工具 this.$router.push({ name: routeName });
* } else {
* @param {string} toolCatrgory 工具分类
* @param {string} toolName 工具名称
*/
detailOpen(toolCatrgory, toolName) {
const errMsg = `无法打开该工具(分类:${toolCatrgory} 名称:${toolName}`;
try {
var info = this.toolList[toolCatrgory]['list'][toolName];
if (info === undefined) {
throw new Error(errMsg);
}
} catch (err) {
console.warn('[打开工具]', err);
this.$message({ this.$message({
message: errMsg, duration: 3000,
type: 'error' message: '该工具正在开发中,暂时无法使用。',
type: 'warning',
}); });
return;
} }
// 禁用
if (!info.enabled) {
this.$message({
message: '该工具未启用',
type: 'warning'
});
return;
}
const {
title: iTitle,
version: iVersion,
update: iUpdate,
} = info;
// 路由跳转
// 注:当前路由相同时也进行跳转
this.$router.push({
name: 'ToolsDetail',
params: {
category: toolCatrgory,
name: toolName
}
}).catch((err) => {
console.log('[打开工具]', err.name);
}).finally(() => {
// 更新标题
this.utils.changeTitle(iTitle);
this.detail.title = `${iTitle} [${iVersion || '未知'}][${iUpdate || '未知'}]`;
// 显示 drawer
this.detail.show = true;
});
},
/**
* 打开工具(新标签页)
*/
detailOpenNewTab() {
const url = window.location.href;
window.open(url, '_blank');
}, },
}, },
@@ -245,18 +132,4 @@ export default {
} }
} }
} }
.drawer-full {
.header {
display: flex;
align-items: center;
justify-content: space-between;
.btn {
margin: 0 0.5rem;
font-size: inherit;
cursor: pointer;
}
}
}
</style> </style>