初始化项目

This commit is contained in:
jimmy committed 2025-05-10 11:47:15 +08:00
commit 709ca30f12
121 files changed
+12315

No files matched your search

+195
View File
@@ -0,0 +1,195 @@
<script setup lang="ts">
import { useRoute, useRouter } from 'vue-router'
import PathUtils from '@/utils/pathUtils'
import StringUtils from '@/utils/stringUtils'
import type { subRoute, subRouteList } from '@/types/devIndex'
import app from '@/constants/app'
import { ref, onMounted } from 'vue'
const router = useRouter()
// 是否展示生成路由
const isShowGenerateRouter = ref(false)
// 添加拖拽相关状态
const isDragging = ref(false)
const navPosition = ref({ x: 10, y: 10 }) // 默认位置
const dragOffset = ref({ x: 0, y: 0 })
const navRef = ref(null) // 用于获取导航栏元素
// region 获取路由列表
// 自动获取路由列表
const routes = {
...import.meta.glob('@/views/*.vue'),
...import.meta.glob('@/views/dev/*.vue'),
}
const route = useRoute()
// 子路由列表
const subRouteLists: subRouteList = []
// 遍历路由page列表进行路由渲染
Object.keys(routes).map((path) => {
if ('.vue' === PathUtils.extname(path).trim()) {
const fileName = PathUtils.basename(path).split('.')[0]
const s = StringUtils.toCustomCase(fileName).toLowerCase().split('-')[0]
const items = {
displayName: fileName,
path: `/${s}`, // 生成路径
}
subRouteLists.push(items)
}
return ''
})
/**
* 列表路径是否激活
* @param path
*/
const isActive = (path: string) => {
return route.path.includes(path)
}
/**
* 列表点击调准
*/
function listClickJump(item: subRoute) {
console.log(item.path)
router.push({ path: item.path })
}
// endregion
// region 开发和生产状态确认
const nodeEnv: string = import.meta.env.MODE
console.log(nodeEnv)
if (nodeEnv !== undefined && !StringUtils.isBlank(nodeEnv) && nodeEnv === app.DEVELOPMENT) {
app.IS_PRODUCTION = false
isShowGenerateRouter.value = app.IS_SHOW_GENERATE_ROUTER
}
// endregion
// 拖拽相关函数
function startDrag(event) {
if (event.target.closest('.drag-handle')) {
isDragging.value = true
// 计算鼠标与元素左上角的偏移量
const navElement = navRef.value
const rect = navElement.getBoundingClientRect()
dragOffset.value = {
x: event.clientX - rect.left,
y: event.clientY - rect.top
}
// 阻止默认事件,防止文本选择等
event.preventDefault()
}
}
function onDrag(event) {
if (isDragging.value) {
// 计算新位置
navPosition.value = {
x: event.clientX - dragOffset.value.x,
y: event.clientY - dragOffset.value.y
}
event.preventDefault()
}
}
function endDrag() {
isDragging.value = false
}
// 添加全局事件监听
onMounted(() => {
window.addEventListener('mousemove', onDrag)
window.addEventListener('mouseup', endDrag)
// 清理函数
return () => {
window.removeEventListener('mousemove', onDrag)
window.removeEventListener('mouseup', endDrag)
}
})
</script>
<template>
<div class="main-container">
<!-- 可拖动的导航栏 -->
<div
v-if="isShowGenerateRouter"
ref="navRef"
class="floating-navigation"
:style="{ left: `${navPosition.x}px`, top: `${navPosition.y}px` }"
@mousedown="startDrag"
>
<div class="drag-handle">
<span>≡</span> <!-- 拖动图标 -->
</div>
<ul class="nav-list">
<li v-for="(subRoute, index) in subRouteLists" :key="index">
<v-btn :type="isActive(subRoute.path) ? 'primary' : ''" @click="listClickJump(subRoute)">
{{ subRoute.displayName }}
</v-btn>
</li>
</ul>
</div>
<!-- 展示主要内容-->
<div class="main-show">
<router-view />
</div>
</div>
</template>
<style scoped lang="scss">
.main-container {
position: relative;
width: 100%;
height: 100%;
.floating-navigation {
z-index: 9999; // 确保在最上层
position: fixed; // 改为固定定位,不随滚动条滚动
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
padding: 5px;
min-width: 300px;
cursor: move; // 鼠标显示为可移动状态
.drag-handle {
padding: 5px;
text-align: center;
border-bottom: 1px solid #eee;
font-size: 18px;
cursor: move;
span {
display: inline-block;
padding: 0 10px;
}
}
.nav-list {
display: flex;
flex-wrap: wrap; // 允许内容换行
justify-content: flex-start;
list-style: none;
padding: 5px;
margin: 0;
li {
margin: 3px;
text-align: center;
}
}
}
.main-show {
width: 100%;
height: 100%;
}
}
</style>
+86
View File
@@ -0,0 +1,86 @@
/* color palette from <https://github.com/vuejs/theme> */
:root {
--vt-c-white: #ffffff;
--vt-c-white-soft: #f8f8f8;
--vt-c-white-mute: #f2f2f2;
--vt-c-black: #181818;
--vt-c-black-soft: #222222;
--vt-c-black-mute: #282828;
--vt-c-indigo: #2c3e50;
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
--vt-c-text-light-1: var(--vt-c-indigo);
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
--vt-c-text-dark-1: var(--vt-c-white);
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
}
/* semantic color variables for this project */
:root {
--color-background: var(--vt-c-white);
--color-background-soft: var(--vt-c-white-soft);
--color-background-mute: var(--vt-c-white-mute);
--color-border: var(--vt-c-divider-light-2);
--color-border-hover: var(--vt-c-divider-light-1);
--color-heading: var(--vt-c-text-light-1);
--color-text: var(--vt-c-text-light-1);
--section-gap: 160px;
}
@media (prefers-color-scheme: dark) {
:root {
--color-background: var(--vt-c-black);
--color-background-soft: var(--vt-c-black-soft);
--color-background-mute: var(--vt-c-black-mute);
--color-border: var(--vt-c-divider-dark-2);
--color-border-hover: var(--vt-c-divider-dark-1);
--color-heading: var(--vt-c-text-dark-1);
--color-text: var(--vt-c-text-dark-2);
}
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
font-weight: normal;
}
body {
min-height: 100vh;
color: var(--color-text);
background: var(--color-background);
transition:
color 0.5s,
background-color 0.5s;
line-height: 1.6;
font-family:
Inter,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Fira Sans',
'Droid Sans',
'Helvetica Neue',
sans-serif;
font-size: 15px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

After

Width:  |  Height:  |  Size: 276 B

+1
View File
@@ -0,0 +1 @@
@import './base.css';
+21
View File
@@ -0,0 +1,21 @@
/**
* Time:2025/5/3 19:12 15
* Name:index.ts
* Path:src/commands
* ProjectName:net-src
* Author:charlatans
*
* Il n'ya qu'un héroïsme au monde :
* c'est de voir le monde tel qu'il est et de l'aimer.
*/
import { invoke } from '@tauri-apps/api/core'
import { greetCommand } from '@/constants/command'
/**
* 问候
* @param name
*/
export function greet(name: string) {
return invoke<string>(greetCommand, { name })
}
+199
View File
@@ -0,0 +1,199 @@
<script setup lang="ts">
import { type Ref, ref } from 'vue'
import { baseInvoke } from '@/utils/commandUtil.ts'
import type { CommandType } from '@/types/command'
import { logN } from '@/utils/logHelper/logUtils.ts'
// 传递参数
const props = defineProps({
pro: Array<CommandType>,
})
// 使用 ref 使数据变成响应式数据
const modules: Ref<CommandType[]> = ref([...(props.pro || [])])
/**
* 指令触发
* @param module
*/
const invokeCommand = async (module: CommandType) => {
const params = Object.fromEntries(module.params.map((param) => [param.name, param.value]))
logN.success('前端发送的参数', module.name, params)
try {
const s = baseInvoke(module.name, params)
s.then((res) => {
logN.warning('后端返回的参数', module.name, res)
module.result = JSON.stringify(res, null, 2)
}).catch((err) => {
console.error(err)
module.result = `Error: ${err.message}`
})
} catch (error) {
console.error(err)
module.result = `Error: ${error}`
}
}
</script>
<template>
<div class="container">
<div class="modules-grid">
<div v-for="(module, index) in modules" :key="index" class="module-card">
<h2 class="module-title">{{ module.name }}</h2>
<p class="module-description">{{ module.description }}</p>
<div class="params-container">
<div
v-for="(param, paramIndex) in module.params"
:key="paramIndex"
class="param-item"
>
<label :for="`${module.name}-${param.name}`" class="param-label">
{{ param.label }}
</label>
<input
v-if="param.type === 'text'"
:id="`${module.name}-${param.name}`"
v-model="param.value"
:placeholder="param.placeholder"
type="text"
class="text-input"
/>
<input
v-if="param.type === 'checkbox'"
:id="`${module.name}-${param.name}`"
v-model="param.value"
type="checkbox"
class="checkbox-input"
/>
</div>
</div>
<button
@click="invokeCommand(module)"
class="action-button"
>
触发 {{ module.name }}
</button>
<div v-if="module.result" class="result-container">
<h3 class="result-title">结果</h3>
<pre class="result-content">{{ module.result }}</pre>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.container {
padding: 1.5rem; // p-6
font-family: sans-serif;
.modules-grid {
display: flex;
flex-wrap: wrap;
gap: 1.5rem; // gap-6
width: 100%;
justify-content: center;
.module-card {
width: 100%;
padding: 1rem; // p-4
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); // shadow-md
border-radius: 0.375rem; // rounded-md
.module-title {
font-size: 1.125rem; // text-lg
font-weight: 600; // font-semibold
margin-bottom: 0.5rem; // mb-2
user-select: text; /* 允许选中 */
pointer-events: auto; /* 确保可以响应鼠标操作 */
}
.module-description {
color: #718096; // text-gray-600
margin-bottom: 1rem; // mb-4
}
.params-container {
width: 100%;
display: flex;
flex-direction: row;
.param-item {
display: flex;
flex-direction: column;
gap: 0.25rem; // space-y-1
width: 100%;
margin: 0.5rem; // m-2
.param-label {
display: block;
font-weight: 700; // font-bold
color: #f56565; // text-red-400
}
.text-input {
width: 100%;
padding: 0.5rem 0.75rem; // px-3 py-2
border: 1px solid #e2e8f0; // border border-gray-300
border-radius: 0.375rem; // rounded-md
&:focus {
outline: none;
border-color: #4299e1;
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5);
}
}
.checkbox-input {
border-radius: 0.25rem; // rounded
&:checked {
background-color: #4299e1;
}
}
}
}
.action-button {
margin-top: 1rem; // mt-4
width: 100%;
padding: 0.5rem 0; // py-2
background-color: #2b6cb0; // bg-blue-700
color: white;
border-radius: 0.375rem; // rounded-md
border: none;
cursor: pointer;
&:hover {
background-color: #3182ce; // bg-blue-600
}
}
.result-container {
margin-top: 1rem; // mt-4
padding: 0.75rem; // p-3
background-color: #f7fafc; // bg-gray-100
border: 1px solid #e2e8f0;
border-radius: 0.375rem; // rounded-md
.result-title {
font-weight: 600; // font-semibold
margin-bottom: 0.5rem; // mb-2
}
.result-content {
font-size: 0.875rem; // text-sm
white-space: pre-wrap;
user-select: text; /* 允许选中 */
pointer-events: auto; /* 确保可以响应鼠标操作 */
}
}
}
}
}
</style>
+41
View File
@@ -0,0 +1,41 @@
<script setup lang="ts">
defineProps<{
msg: string
}>()
</script>
<template>
<div class="greetings">
<h1 class="green">{{ msg }}</h1>
<h3>
You’ve successfully created a project with
<a href="https://vite.dev/" target="_blank" rel="noopener">Vite</a> +
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>. What's next?
</h3>
</div>
</template>
<style scoped>
h1 {
font-weight: 500;
font-size: 2.6rem;
position: relative;
top: -10px;
}
h3 {
font-size: 1.2rem;
}
.greetings h1,
.greetings h3 {
text-align: center;
}
@media (min-width: 1024px) {
.greetings h1,
.greetings h3 {
text-align: left;
}
}
</style>
+94
View File
@@ -0,0 +1,94 @@
<script setup lang="ts">
import WelcomeItem from './WelcomeItem.vue'
import DocumentationIcon from './icons/IconDocumentation.vue'
import ToolingIcon from './icons/IconTooling.vue'
import EcosystemIcon from './icons/IconEcosystem.vue'
import CommunityIcon from './icons/IconCommunity.vue'
import SupportIcon from './icons/IconSupport.vue'
const openReadmeInEditor = () => fetch('/__open-in-editor?file=README.md')
</script>
<template>
<WelcomeItem>
<template #icon>
<DocumentationIcon />
</template>
<template #heading>Documentation</template>
Vue’s
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
provides you with all information you need to get started.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<ToolingIcon />
</template>
<template #heading>Tooling</template>
This project is served and bundled with
<a href="https://vite.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The
recommended IDE setup is
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a>
+
<a href="https://github.com/vuejs/language-tools" target="_blank" rel="noopener">Vue - Official</a>. If
you need to test your components and web pages, check out
<a href="https://vitest.dev/" target="_blank" rel="noopener">Vitest</a>
and
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a>
/
<a href="https://playwright.dev/" target="_blank" rel="noopener">Playwright</a>.
<br />
More instructions are available in
<a href="javascript:void(0)" @click="openReadmeInEditor"><code>README.md</code></a
>.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<EcosystemIcon />
</template>
<template #heading>Ecosystem</template>
Get official tools and libraries for your project:
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
<a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If
you need more resources, we suggest paying
<a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
a visit.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<CommunityIcon />
</template>
<template #heading>Community</template>
Got stuck? Ask your question on
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>
(our official Discord server), or
<a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener"
>StackOverflow</a
>. You should also follow the official
<a href="https://bsky.app/profile/vuejs.org" target="_blank" rel="noopener">@vuejs.org</a>
Bluesky account or the
<a href="https://x.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
X account for latest news in the Vue world.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<SupportIcon />
</template>
<template #heading>Support Vue</template>
As an independent project, Vue relies on community backing for its sustainability. You can help
us by
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
</WelcomeItem>
</template>
+87
View File
@@ -0,0 +1,87 @@
<template>
<div class="item">
<i>
<slot name="icon"></slot>
</i>
<div class="details">
<h3>
<slot name="heading"></slot>
</h3>
<slot></slot>
</div>
</div>
</template>
<style scoped>
.item {
margin-top: 2rem;
display: flex;
position: relative;
}
.details {
flex: 1;
margin-left: 1rem;
}
i {
display: flex;
place-items: center;
place-content: center;
width: 32px;
height: 32px;
color: var(--color-text);
}
h3 {
font-size: 1.2rem;
font-weight: 500;
margin-bottom: 0.4rem;
color: var(--color-heading);
}
@media (min-width: 1024px) {
.item {
margin-top: 0;
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
}
i {
top: calc(50% - 25px);
left: -26px;
position: absolute;
border: 1px solid var(--color-border);
background: var(--color-background);
border-radius: 8px;
width: 50px;
height: 50px;
}
.item:before {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
bottom: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:after {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
top: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:first-of-type:before {
display: none;
}
.item:last-of-type:after {
display: none;
}
}
</style>
@@ -0,0 +1,11 @@
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import HelloWorld from '../HelloWorld.vue'
describe('HelloWorld', () => {
it('renders properly', () => {
const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } })
expect(wrapper.text()).toContain('Hello Vitest')
})
})
@@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
/>
</svg>
</template>
@@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
<path
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
/>
</svg>
</template>
@@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
<path
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
/>
</svg>
</template>
@@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
/>
</svg>
</template>
@@ -0,0 +1,19 @@
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
class="iconify iconify--mdi"
width="24"
height="24"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 24 24"
>
<path
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
fill="currentColor"
></path>
</svg>
</template>
+33
View File
@@ -0,0 +1,33 @@
/**
* Time:2024/8/30 下午2:21 50
* Name:app.ts
* Path:src/constants
* ProjectName:utopia-front-vue
* Author:charlatans
*
* Il n'ya qu'un héroïsme au monde :
* c'est de voir le monde tel qu'il est et de l'aimer.
*/
const app = {
/**
* 生产
*/
PRODUCTION: 'production',
/**
* 开发
*/
DEVELOPMENT: 'development',
/**
* 是否是生产环境【默认是】
*/
IS_PRODUCTION: true,
/**
* 是否展示生成路由
*/
IS_SHOW_GENERATE_ROUTER: true
}
export default app
+15
View File
@@ -0,0 +1,15 @@
/**
* Time:2025/5/3 19:12 58
* Name:command.ts
* Path:src/constants
* ProjectName:net-src
* Author:charlatans
*
* Il n'ya qu'un héroïsme au monde :
* c'est de voir le monde tel qu'il est et de l'aimer.
*/
/**
* 问候
*/
export const greetCommand = 'greet'
+59
View File
@@ -0,0 +1,59 @@
/**
* Time:2024/8/30 下午2:28 44
* Name:logUtil.ts
* Path:src/constants
* ProjectName:utopia-front-vue
* Author:charlatans
*
* Il n'ya qu'un héroïsme au monde :
* c'est de voir le monde tel qu'il est et de l'aimer.
*/
/**
* 日志级别
*/
const level = {
error: 0,
warn: 1,
info: 2,
http: 3,
verbose: 4,
debug: 5,
silly: 6
}
/**
* 日志工具类常量
*/
const logUtil = {
/**
* 日志级别
*/
level: level,
ERROR: 'error',
WARN: 'warn',
INFO: 'info',
HTTP: 'http',
VERBOSE: 'verbose',
DEBUG: 'debug',
SILLY: 'silly'
}
export type Color =
'primary'
| 'success'
| 'info'
| 'warning'
| 'danger'
| 'error';
export const COLOR_MAP: Record<Color, string> = {
primary: '#2d8cf0',
success: '#19be6b',
info: '#909399',
warning: '#ff9900',
danger: '#35495E',
error: '#FF0000'
}
export default { logUtil, COLOR_MAP }
+26
View File
@@ -0,0 +1,26 @@
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
// Vuetify
import 'vuetify/styles'
import { createVuetify } from 'vuetify'
import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives'
const app = createApp(App)
const vuetify = createVuetify({
components,
directives,
})
app.use(createPinia())
app.use(router)
app.use(vuetify)
app.mount('#app')
+79
View File
@@ -0,0 +1,79 @@
import {
createRouter,
createWebHistory,
type RouteComponent,
type RouteRecordRaw,
} from 'vue-router'
import HomeView from '../views/HomeView.vue'
import DevIndex from '@/views/dev/DevIndex.vue'
import StringUtils from '@/utils/stringUtils'
import PathUtils from '@/utils/pathUtils'
import NotFound from '@/views/NotFoundView.vue'
import AboutView from '@/views/AboutView.vue'
// 导入所有 .vue 文件
const routes = import.meta.glob('@/views/dev/**/*.vue')
// 自动生成路由配置
const devPageViews: RouteRecordRaw[] = Object.keys(routes)
.map((path) => {
if ('.vue' === PathUtils.extname(path).trim()) {
const fileName: string = PathUtils.basename(path).split('.')[0]
const componentView: RouteComponent = () => routes[path]() // 动态导入组件
return {
path: '/dev-index' + `/${StringUtils.toCustomCase(fileName).toLowerCase()}`, // 生成路径
component: componentView,
name: fileName,
}
}
return null
})
.filter(Boolean) as RouteRecordRaw[] // 过滤掉 null 值
console.log('自动生成路由', devPageViews)
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
// name: '/',
// component: HomeView,
// children: children,
// 重定向到主页
redirect: '/home',
},
{
path: '/home',
name: 'home',
component: HomeView,
children: [
{
path: '/', // 捕获所有其他路径
component: NotFound, // 任何其他路径都跳转到默认页面
},
],
},
{
path: '/dev',
name: 'dev',
component: DevIndex,
children: [...devPageViews],
},
{
path: '/about',
name: 'about',
component: AboutView,
},
{
path: '/:pathMatch(.*)*', // 通配符路由
name: 'NotFound',
component: NotFound,
},
],
})
export default router
+12
View File
@@ -0,0 +1,12 @@
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})
+38
View File
@@ -0,0 +1,38 @@
type paramType = {
/**
* 参数名称【该名臣是传递到后端的参数名称,如:name ,传递后后端时将使用 name: xxx】
*/
name: string
/**
* 提示字段 如:【输入名称】
*/
label: string
type: 'text' | 'checkbox'
/**
* 默认值
*/
value: string | boolean
/**
* 提示字段,如:【输入名称】
*/
placeholder: string
}
export type CommandType = {
/**
* 指令名称 如:greet
*/
name: string
/**
* 指令描述
*/
description: string
/**
* 具体参数
*/
params: paramType[] | []
/**
* 返回结果
*/
result: null | string | number | boolean | object
}
+29
View File
@@ -0,0 +1,29 @@
/**
* Time:2024/7/27 下午1:00 23
* Name:BuiltInInstructionsView.type.ts
* Path:src/types/views/dev/vue
* ProjectName:utopia-front-vue
* Author:charlatans
*
* Il n'ya qu'un héroïsme au monde :
* c'est de voir le monde tel qu'il est et de l'aimer.
*/
/**
* 子路由
*/
export type subRoute = {
/**
* 展示名字
*/
displayName: string
/**
* 跳转路径
*/
path: string
}
/**
* 子路由列表
*/
export type subRouteList = subRoute[]
+22
View File
@@ -0,0 +1,22 @@
/**
* Time:2024/7/27 上午10:36 15
* Name:arrayUtils.ts
* Path:src/utils
* ProjectName:utopia-front-vue
* Author:charlatans
*
* Il n'ya qu'un héroïsme au monde :
* c'est de voir le monde tel qu'il est et de l'aimer.
*/
import type { arrayUtilsNotEmptyCallback } from '@/types/utils'
export default class ArrayUtils {
/**
* 指定数组或未知元素是否为 `null` 或 `empty`
* @param arr
*/
static isEmpty (arr: any[] | null): boolean {
return arr === null || arr.length === 0
}
}
+15
View File
@@ -0,0 +1,15 @@
import { invoke } from '@tauri-apps/api/core'
/**
* Time:2024/12/14 12:21 30
* Name:commandUtil.ts
* Path:src/utils
* ProjectName:argus-src
* Author:charlatans
*
* Il n'ya qu'un héroïsme au monde :
* c'est de voir le monde tel qu'il est et de l'aimer.
*/
export function baseInvoke<T>(command: string, args: boolean | string | number | object) {
return invoke<T>(command, args)
}
+25
View File
@@ -0,0 +1,25 @@
/**
* Time:2024/12/10 16:29 48
* Name:darkUtil.ts
* Path:src/utils
* ProjectName:argus-src
* Author:charlatans
*
* Il n'ya qu'un héroïsme au monde :
* c'est de voir le monde tel qu'il est et de l'aimer.
*/
import { useDark, useToggle } from "@vueuse/core";
export const isDark = useDark();
export const toggleDark = useToggle(isDark)
export function changedTheme() {
console.log(isDark.value);
document.documentElement.removeAttribute("data-theme")
if (isDark.value){
document.documentElement.setAttribute("data-theme","light");
}else{
document.documentElement.setAttribute("data-theme","dark");
}
toggleDark()
}
+46
View File
@@ -0,0 +1,46 @@
/**
* Time:2025/3/19 21:09 52
* Name:fileUtil.ts
* Path:src/utils
* ProjectName:argus-src
* Author:charlatans
*
* Il n'ya qu'un héroïsme au monde :
* c'est de voir le monde tel qu'il est et de l'aimer.
*/
import { FILE_SIZE_UNIT_SIZE } from '@/constants/constants'
import { FILE_SIZE_UNIT_ENUM } from '@/constants/enums'
import type { FILE_SIZE_UNIT_STR } from '@/types/utils'
/**
* 将字节数转换合适的单位
* @param size 字节数
* @param unit 单位
* @param decimalPlaces 小数位数
* @returns { size: number, unit: string } 转换后的大小和单位
*/
export const convertFileSize = (
size: number,
unit: FILE_SIZE_UNIT_STR | FILE_SIZE_UNIT_ENUM,
decimalPlaces = 2
): { size: number; unit: string } => {
if (size < 0) throw new Error('文件大小不能为负数')
// 重新计算字节数
const element = FILE_SIZE_UNIT_SIZE[FILE_SIZE_UNIT_ENUM[unit]]
const values = Object.values(FILE_SIZE_UNIT_SIZE)
// 选择合适单位
let bestUnitIndex = 0
let bestSize = element.bytes * size
while (bestSize >= 1024 && bestUnitIndex < values.length - 1) {
bestSize /= 1024
bestUnitIndex++
}
const bestUnit = values[bestUnitIndex]
const formattedNumber = parseFloat(bestSize.toFixed(decimalPlaces))
return { size: formattedNumber, unit: bestUnit.name }
}
+63
View File
@@ -0,0 +1,63 @@
/**
* Time:2025/4/24 18:13 49
* Name:imageCacheManager.ts
* Path:src/utils
* ProjectName:argus-src
* Author:charlatans
*
* Il n'ya qu'un héroïsme au monde :
* c'est de voir le monde tel qu'il est et de l'aimer.
*/
/**
* 照片缓存管理器
*/
class ImageCacheManager {
private static instance: ImageCacheManager
private cache: Map<string, HTMLImageElement>
private maxSize: number
private constructor(maxSize = 20) {
this.cache = new Map()
this.maxSize = maxSize
}
static getInstance(): ImageCacheManager {
if (!ImageCacheManager.instance) {
ImageCacheManager.instance = new ImageCacheManager()
}
return ImageCacheManager.instance
}
async preloadImage(src: string): Promise<void> {
if (this.cache.has(src)) return
return new Promise((resolve, reject) => {
const img = new Image()
img.src = src
img.onload = () => {
this.addToCache(src, img)
resolve()
}
img.onerror = reject
})
}
private addToCache(src: string, img: HTMLImageElement) {
if (this.cache.size >= this.maxSize) {
const firstKey = this.cache.keys().next().value
this.cache.delete(firstKey)
}
this.cache.set(src, img)
}
getFromCache(src: string): HTMLImageElement | undefined {
return this.cache.get(src)
}
clearCache() {
this.cache.clear()
}
}
export default ImageCacheManager
@@ -0,0 +1,79 @@
/**
* Time:2025/4/25 20:13 16
* Name:imageInfoCacheManager.ts
* Path:src/utils
* ProjectName:argus-src
* Author:charlatans
*
* Il n'ya qu'un héroïsme au monde :
* c'est de voir le monde tel qu'il est et de l'aimer.
*/
import { getImageInfo } from '@/services/imageService'
import type { ImageInfo } from '@/types/image.type'
/**
* 负责处理与图像信息相关的缓存机制
*/
class ImageInfoCacheManager {
private static instance: ImageInfoCacheManager
private maxSize: number
private cache: Map<string, ImageInfo>
private constructor(maxSize = 50) {
this.cache = new Map()
this.maxSize = maxSize
}
static getInstance(): ImageInfoCacheManager {
if (!ImageInfoCacheManager.instance) {
ImageInfoCacheManager.instance = new ImageInfoCacheManager()
}
return ImageInfoCacheManager.instance
}
async preloadImageInfo(src: string): Promise<void> {
if (this.cache.has(src)) return
return new Promise((resolve, reject) => {
getImageInfo(src).then((res) => {
let info = JSON.parse(res)
this.addToCache(src, info)
resolve()
})
})
}
getImageInfo(src: string): Promise<ImageInfo> {
return new Promise((resolve, reject) => {
let newVar = this.cache.get(src)
if (newVar !== undefined && newVar !== null) {
resolve(newVar)
} else {
getImageInfo(src).then((res) => {
let info = JSON.parse(res)
this.addToCache(src, info)
resolve(info)
}).catch(reject)
}
})
}
getImageInfoSync(src: string): ImageInfo | undefined {
return this.cache.get(src)
}
private addToCache(src: string, info: ImageInfo) {
if (this.cache.size >= this.maxSize) {
const firstKey = this.cache.keys().next().value
this.cache.delete(firstKey)
}
this.cache.set(src, info)
}
clearCache() {
this.cache.clear()
}
}
export default ImageInfoCacheManager
+86
View File
@@ -0,0 +1,86 @@
/**
* Time:2024/8/31 下午4:46 46
* Name:logEnum.ts
* Path:src/utils/logHelper
* ProjectName:utopia-front-vue
* Author:charlatans
*
* Il n'ya qu'un héroïsme au monde :
* c'est de voir le monde tel qu'il est et de l'aimer.
*/
/**
* 日志等级列表,重要等级越高数字越小
*/
export enum LogLevelEnum {
/**
* v.
*/
VERBOSE = 40,
/**
* d.
*/
DEBUG = 30,
/**
* i.
*/
INFO = 20,
/**
* w.
*/
WARN = 10,
/**
* e.
*/
ERROR = 0,
}
/**
* 日志等级名称
*/
export const LevelName = {
VERBOSE: 'VERBOSE',
DEBUG: 'DEBUG',
INFO: 'INFO',
WARN: 'WARN',
ERROR: 'ERROR'
}
/**
* 日志样式
*/
enum logStyle {
BackgroundColor = 10,
TextColor = 20,
BackgroundAndTextColor = 30
}
/**
* 日志输出颜色
*/
enum logColor {
}
/**
* 获取指定等级名称
* @param level
*/
export function getLogLevelName (level: LogLevelEnum): string {
switch (level) {
case LogLevelEnum.VERBOSE:
return LevelName.VERBOSE
case LogLevelEnum.DEBUG:
return LevelName.DEBUG
case LogLevelEnum.INFO:
return LevelName.INFO
case LogLevelEnum.WARN:
return LevelName.WARN
case LogLevelEnum.ERROR:
return LevelName.ERROR
}
}
+146
View File
@@ -0,0 +1,146 @@
import app from '@/constants/app'
import type { Color } from '@/constants/logUtil'
import { COLOR_MAP } from '@/constants/logUtil'
import { LevelName, LogLevelEnum } from '@/utils/logHelper/logEnum'
const env = import.meta.env
// 是否输出日志
const isPrint = env.VITE_MODE !== app.PRODUCTION
const COLORS: Color[] = ['primary', 'success', 'info', 'warning', 'danger', 'error']
// 日志输出等级
const logPrintLevel = LogLevelEnum.DEBUG
const getColor = (type: Color) => COLOR_MAP[type]
/**
* 定义日志参数类型
*/
type LogParam = string | number | boolean | object | null | undefined
/**
* 输出函数
*/
type Log<T extends LogParam[]> = (type: Color, ...args: T) => void
/**
* 返回值
*/
type Created<T extends LogParam[]> = Record<Color, (...args: T) => void>
const createLog = <T extends LogParam[]>(fn: Log<T>): Created<T> => {
return COLORS.reduce((logs, type) => {
logs[type] = (...args: T) => fn(type, ...args)
return logs
}, {} as Created<T>)
}
const nsLog = (type: Color, ns: string, msg: string, ...args: LogParam[]) => {
const color = getColor(type)
console.log(
`%c ${ns} %c ${msg} %c ${args.length ? '%o' : ''}`,
`background:${color};border:1px solid ${color}; padding: 1px; border-radius: 4px 0 0 4px; color: #fff;`,
`border:1px solid ${color}; padding: 1px; border-radius: 0 4px 4px 0; color: ${color};`,
'background:transparent',
...args,
)
}
/**
* 打印格式化日志
*/
export const logN = createLog(nsLog)
const sLog = (type: Color, msg: string, ...args: LogParam[]) => {
const color = getColor(type)
console.log(`%c ${msg} ${args.length ? '%o' : ''}`, `color: ${color};`, ...args)
}
/**
* 打印带有颜色的日志
*/
export const logS = createLog(sLog)
const bLog = (type: Color, msg: string, ...args: LogParam[]) => {
const color = getColor(type)
console.log(
`%c ${msg} ${args.length ? '%o' : ''}`,
`background:${color}; padding: 2px; border-radius: 4px; color: #fff;`,
...args,
)
}
/**
* 打印带有颜色背景的日志
*/
export const logB = createLog(bLog)
export const disLog = () => {
console.log = function () {
// 禁用日志输出的空函数
}
}
interface LogHelperInterface {
d: (tag: string, msg: string) => void;
}
const logHelper: LogHelperInterface = {
/**
* DEBUG
*/
d: (tag: string, msg: string) => {},
}
const logStyleFormat = (): string => {
return ''
}
/**
* 打印日志
* @param level 日志等级
* @param tag 标记
* @param msg 信息
* @param e 报错信息【存在报错信息,默认使用 **ERROR** 等级】
*/
const print = (
level: LogLevelEnum,
tag: string,
msg: string,
e: Error | null | undefined = null,
): string => {
if (logPrintLevel > level) {
return ''
}
// 格式化字符串
// let str = formatMessage(msg)
//
// // 获取日志等级名称
// let levelName = getLogLevelName(level)
switch (level) {
case LogLevelEnum.VERBOSE:
console.log()
return LevelName.VERBOSE
case LogLevelEnum.DEBUG:
return LevelName.DEBUG
case LogLevelEnum.INFO:
return LevelName.INFO
case LogLevelEnum.WARN:
return LevelName.WARN
case LogLevelEnum.ERROR:
return LevelName.ERROR
default:
return ''
}
}
/**
* 格式化输出信息
* @param str
*/
const formatMessage = (str: string): string => {
return `[${str}]`
}
export { logHelper, print, formatMessage }
+201
View File
@@ -0,0 +1,201 @@
/**
* Time:2024/7/30 下午5:35 52
* Name:chineseLoremGenerate.ts
* Path:src/utils
* ProjectName:utopia-front-vue
* Author:charlatans
*
* Il n'ya qu'un héroïsme au monde :
* c'est de voir le monde tel qu'il est et de l'aimer.
*/
/**
* 常用字
*/
const ChineseCommonWords = '一乙二十丁厂七卜人入八九几儿了力乃刀又三于干亏士工土才寸下大丈与万上小口巾山千乞川亿个勺久凡及夕丸么广亡门义之尸弓己已子卫也女飞刃习叉马乡丰王井开夫天无元专云扎艺木五支厅不太犬区历尤友匹车巨牙屯比互切瓦止少日中冈贝内水见午牛手毛气升长仁什片仆化仇币仍仅斤爪反介父从今凶分乏公仓月氏勿欠风丹匀乌凤勾文六方火为斗忆订计户认心尺引丑巴孔队办以允予劝双书幻玉刊示末未击打巧正扑扒功扔去甘世古节本术汇头汉宁穴它讨写让礼训必议讯记永司尼民出汁丙左厉右石布龙平灭轧东卡北占业旧帅归且旦目叶甲申叮电号田由史只央兄叼叫另叨叹四生失禾丘付仗代仙们仪白仔他斥瓜乎丛令用甩印乐句匆册犯外处冬鸟务包饥主市立闪兰半辽奶奴加召皮边发孕圣对台矛纠母幼丝可式刑动扛寺吉扣考托老执巩圾扩扫地扬场耳共芒亚芝朽朴机权过臣再协西压厌在有百存而页匠夸夺灰达列死成夹轨邪划迈毕至此贞师尘尖劣光当早吐吓虫曲团同吊吃因吸吗屿帆岁回岂刚则肉网年朱先丢舌竹迁乔伟传乒乓休伍伏优伐延件任伤价份华仰仿伙伪自血向似后行舟全会杀合兆企众爷伞创肌朵杂危旬旨负各名多争色壮冲冰庄庆亦刘齐交次衣产决充妄闭问闯羊并关米灯州汗污江池汤忙兴宇守宅字安讲军许论农讽设访寻那迅尽导异孙阵阳收阶阴防奸如妇好她妈戏羽观欢买红纤级约纪驰巡寿弄麦形进戒吞远违运扶抚坛技坏扰拒找批扯址走抄坝贡攻赤折抓扮抢孝均抛投坟抗坑坊抖护壳志扭块声把报却劫芽花芹芬苍芳严芦劳克苏杆杠杜材村杏极李杨求更束豆两丽医辰励否还歼来连步坚旱盯呈时吴助县里呆园旷围呀吨足邮男困吵串员听吩吹呜吧吼别岗帐财针钉告我乱利秃秀私每兵估体何但伸作伯伶佣低你住位伴身皂佛近彻役返余希坐谷妥含邻岔肝肚肠龟免狂犹角删条卵岛迎饭饮系言冻状亩况床库疗应冷这序辛弃冶忘闲间闷判灶灿弟汪沙汽沃泛沟没沈沉怀忧快完宋宏牢究穷灾良证启评补初社识诉诊词译君灵即层尿尾迟局改张忌际陆阿陈阻附妙妖妨努忍劲鸡驱纯纱纳纲驳纵纷纸纹纺驴纽奉玩环武青责现表规抹拢拔拣担坦押抽拐拖拍者顶拆拥抵拘势抱垃拉拦拌幸招坡披拨择抬其取苦若茂苹苗英范直茄茎茅林枝杯柜析板松枪构杰述枕丧或画卧事刺枣雨卖矿码厕奔奇奋态欧垄妻轰顷转斩轮软到非叔肯齿些虎虏肾贤尚旺具果味昆国昌畅明易昂典固忠咐呼鸣咏呢岸岩帖罗帜岭凯败贩购图钓制知垂牧物乖刮秆和季委佳侍供使例版侄侦侧凭侨佩货依的迫质欣征往爬彼径所舍金命斧爸采受乳贪念贫肤肺肢肿胀朋股肥服胁周昏鱼兔狐忽狗备饰饱饲变京享店夜庙府底剂郊废净盲放刻育闸闹郑券卷单炒炊炕炎炉沫浅法泄河沾泪油泊沿泡注泻泳泥沸波泼泽治怖性怕怜怪学宝宗定宜审宙官空帘实试郎诗肩房诚衬衫视话诞询该详建肃录隶居届刷屈弦承孟孤陕降限妹姑姐姓始驾参艰线练组细驶织终驻驼绍经贯奏春帮珍玻毒型挂封持项垮挎城挠政赴赵挡挺括拴拾挑指垫挣挤拼挖按挥挪某甚革荐巷带草茧茶荒茫荡荣故胡南药标枯柄栋相查柏柳柱柿栏树要咸���歪研砖厘厚砌砍面耐耍牵残殃轻鸦皆背战点临览竖省削尝是盼眨哄显哑冒映星昨畏趴胃贵界虹虾蚁思蚂虽品咽骂哗咱响哈咬咳哪炭峡罚贱贴骨钞钟钢钥钩卸缸拜看矩怎牲选适秒香种秋科重复竿段便俩贷顺修保促侮俭俗俘信皇泉鬼侵追俊盾待律很须叙剑逃食盆胆胜胞胖脉勉狭狮独狡狱狠贸怨急饶蚀饺饼弯将奖哀亭亮度迹庭疮疯疫疤姿亲音帝施闻阀阁差养美姜叛送类迷前首逆总炼炸炮烂剃洁洪洒浇浊洞测洗活派洽染济洋洲浑浓津恒恢恰恼恨举觉宣室宫宪突穿窃客冠语扁袄祖神祝误诱说诵垦退既屋昼费陡眉孩除险院娃姥姨姻娇怒架贺盈勇怠柔垒绑绒结绕骄绘给络骆绝绞统耕耗艳泰珠班素蚕顽盏匪捞栽捕振载赶起盐捎捏埋捉捆捐损都哲逝捡换挽热恐壶挨耻耽恭莲莫荷获晋恶真框桂档桐株桥桃格校核样根索哥速逗栗配翅辱唇夏础破原套逐烈殊顾轿较顿毙致柴桌虑监紧党晒眠晓鸭晃晌晕蚊哨哭恩唤啊唉罢峰圆贼贿钱钳钻铁铃铅缺氧特牺造乘敌秤租积秧秩称秘透笔笑笋债借值倚倾倒倘俱倡候俯倍倦健臭射躬息徒徐舰舱般航途拿爹爱颂翁脆脂胸胳脏胶脑狸狼逢留皱饿恋桨浆衰高席准座脊症病疾疼疲效离唐资凉站剖竞部旁旅畜阅羞瓶拳粉料益兼烤烘烦烧烛烟递涛浙涝酒涉消浩海涂浴浮流润浪浸涨烫涌悟悄悔悦害宽家宵Line truncated
const ChineseCommonWordsLength = ChineseCommonWords.length
/**
* 常用标点
*/
const simplifiedChinesePunctuationDB = ',,,,,,、、;。。。。!!……??**%@'
const simplifiedChinesePunctuationDBLength = simplifiedChinesePunctuationDB.length
/**
* 英文常用假文单词
*/
const loremIpsumWords = [
'lorem',
'ipsum',
'dolor',
'sit',
'amet',
'consectetur',
'adipiscing',
'elit',
'sed',
'do',
'eiusmod',
'tempor',
'incididunt',
'ut',
'labore',
'et',
'dolore',
'magna',
'aliqua',
'ut',
'enim',
'ad',
'minim',
'veniam',
'quis',
'nostrud',
'exercitation',
'ullamco',
'laboris',
'nisi',
'ut',
'aliquip',
'ex',
'ea',
'commodo',
'consequat',
'duis',
'aute',
'irure',
'dolor',
'in',
'reprehenderit',
'in',
'voluptate',
'velit',
'esse',
'cillum',
'dolore',
'eu',
'fugiat',
'nulla',
'pariatur',
'excepteur',
'sint',
'occaecat',
'cupidatat',
'non',
'proident',
'sunt',
'in',
'culpa',
'qui',
'officia',
'deserunt',
'mollit',
'anim',
'id',
'est',
'laborum'
]
export default class loremGenerate {
/**
* 中文乱文假数生成
* @param len 生成的文字长度
*/
static generateChinese (len: number): string {
// 如果数据违法
if (len > ChineseCommonWordsLength || len <= 0) {
return ''
}
let characters = ChineseCommonWords[loremGenerate.getRandomNumber(
ChineseCommonWordsLength)]
for (let i = 1; i < len; i++) {
//var radomLocation = Math.floor(ChineseCommonWordsLength*Math.random());
if (Math.random() < 0.04) {
// 前一个字符是标点则后一个字符不为标点
if (simplifiedChinesePunctuationDB.indexOf(characters[i - 1]) === -1) {
characters += simplifiedChinesePunctuationDB[loremGenerate.getRandomNumber(
simplifiedChinesePunctuationDBLength)]
continue
}
}
characters += ChineseCommonWords[loremGenerate.getRandomNumber(
ChineseCommonWordsLength)]
}
// 当文本最后一个字符非标点符号时为其添加末尾标点
if (simplifiedChinesePunctuationDB.indexOf(
characters[characters.length - 1]) === -1) {
const endPunctuatoins = ['。', '!', '?']
characters += endPunctuatoins[loremGenerate.getRandomNumber(
endPunctuatoins.length)]
}
// 添加换行
characters = characters.replace(/\*/g, '。').
replace(/%/g, '!').
replace(/@/g, '?')
return characters
}
static getRandomNumber (size: number) {
return Math.floor(Math.random() * size)
}
/**
* 从单词数组中随机选择一个单词
*/
static getRandomWord () {
const randomIndex = Math.floor(Math.random() * loremIpsumWords.length)
return loremIpsumWords[randomIndex]
}
/**
* 生成指定长度的 Lorem Ipsum 句子
* @param wordCount 句子长度
*/
static generateLoremSentence (wordCount: number = 10) {
const sentence = []
for (let i = 0; i < wordCount; i++) {
sentence.push(loremGenerate.getRandomWord())
}
// 将单词拼接成句子,并在句尾添加句号
return sentence.join(' ') + '.'
}
/**
* 生成指定句数的 Lorem Ipsum 段落
* @param sentenceCount 行数
* @param wordsPerSentence 句子长度
*/
static generateLoremParagraph (
sentenceCount: number = 10, wordsPerSentence: number = 10) {
const paragraph = []
for (let i = 0; i < sentenceCount; i++) {
paragraph.push(
loremGenerate.generateLoremSentence(wordsPerSentence))
}
// 将句子拼接成段落
return paragraph.join(' ')
}
/**
* 生成指定段落数的 Lorem Ipsum 文章
* @param paragraphCount 页数
* @param sentencesPerParagraph 行数
* @param wordsPerSentence 句子长度
*/
static generateLoremArticle (
paragraphCount: number = 20, sentencesPerParagraph: number = 10,
wordsPerSentence: number = 10) {
const article = []
for (let i = 0; i < paragraphCount; i++) {
article.push(
loremGenerate.generateLoremParagraph(sentencesPerParagraph,
wordsPerSentence))
}
// 将段落拼接成文章
return article.join('\n')
}
}
+17
View File
@@ -0,0 +1,17 @@
/**
* Time:2024/12/20 19:52 50
* Name:nameUtil.ts
* Path:src/utils
* ProjectName:argus-src
* Author:charlatans
*
* Il n'ya qu'un héroïsme au monde :
* c'est de voir le monde tel qu'il est et de l'aimer.
*/
export function toCamelCase(str: string): string {
return str
.replace(/_./g, (match: string) => match.charAt(1).toUpperCase())
.replace(/^./, (match: string) => match.toLowerCase())
}
+48
View File
@@ -0,0 +1,48 @@
/**
* Time:2024/7/27 上午11:25 48
* Name:pathUtils.ts
* Path:src/utils
* ProjectName:utopia-front-vue
* Author:charlatans
*
* Il n'ya qu'un héroïsme au monde :
* c'est de voir le monde tel qu'il est et de l'aimer.
*/
export default class PathUtils {
/**
* 使用平台特定的分隔符将所有给定的路径段连接在一起.
* @param {...string[]} paths - 要连接的路径段。
*/
static join (...paths: string[]): string {
const s = paths.join('/').replace(/\/+/g, '/').trim()
return s === '/' ? '' : s
}
/**
* 返回路径的目录名称.
* @param {string} path - 完整路径
*/
static dirname (path: string): string {
return path.replace(/\/[^/]*$/, '')
}
/**
* 返回路径的扩展名,从最后一个“.”到路径最后一部分的字符串结尾.
* @param {string} path - 需要转换的路径
*/
static extname (path: string): string {
const match = path.match(/(\.[^/]*)$/)
return match ? match[0] : ''
}
/**
* 返回文件名.
* @param {string} path - The path to evaluate.
* @returns {string} The basename.
*/
static basename (path: string): string {
return path.replace(/^.*[/\\]/, '')
}
}
+10
View File
@@ -0,0 +1,10 @@
/**
* Time:2024/7/30 下午6:37 52
* Name:radomUtils.ts
* Path:src/utils
* ProjectName:utopia-front-vue
* Author:charlatans
*
* Il n'ya qu'un héroïsme au monde :
* c'est de voir le monde tel qu'il est et de l'aimer.
*/
+100
View File
@@ -0,0 +1,100 @@
/**
* Time:2024/7/26 下午9:56 48
* Name:stringExtension.ts
* Path:src/utils
* ProjectName:utopia-front-vue
* Author:charlatans
* @description 字符串工具类
*
* Il n'ya qu'un héroïsme au monde :
* c'est de voir le monde tel qu'il est et de l'aimer.
*/
export default class StringUtils {
/**
* 是否为 `null` 或 `空字符串`
* @param str
*/
static isBlank(str: string | null | undefined): boolean {
if (str === null || str === undefined) return true
return StringUtils.isEmpty(str) || str!.trim().length === 0
}
/**
* 此字符序列为空(不包含任何字符),则返回 `true`
* @param str
*/
static isEmpty(str: string | null | undefined) {
return str === null || str === undefined || str.length === 0
}
/**
* 将字符串转换为小驼峰命名(camelCase)
* @param str
*/
static toCamelCase(str: string): string {
return str
.replace(/^\w|[A-Z]|\b\w|\s+/g, (match: string, index: number) => {
if (index === 0 || match.trim().length > 0) {
return match.toUpperCase()
}
return ''
})
.replace(/^[A-Z]/, (match) => match.toLowerCase())
}
/**
* 将字符串转换为大驼峰命名(PascalCase)
* @param str
*/
static toPascalCase(str: string): string {
return (
str
// 使用正则表达式处理非中文字符
.replace(/^\w|[A-Z]|\b\w|\s+/g, (match: string, index: number) => {
// 处理首字母大写,保留中文字符不变
if (index === 0 || match.trim().length > 0) {
return match.toUpperCase()
}
return ''
})
.replace(/^[a-z]/, (match: string) => match.toUpperCase())
) // 确保首字母大写
}
/**
* 将字符串转换为指定字符连接的格式
* @param str
* @param separator 连接字符,例如 "_" 或 "-"
*/
static toCustomCase(str: string, separator: string = '-'): string {
if (StringUtils.isEmpty(str)) return ''
if (StringUtils.isBlank(str)) return str
return str
.replace(/([a-z])([A-Z])/g, `$1${separator}$2`)
.replace(/([A-Z])([A-Z][a-z])/g, `$1${separator}$2`)
}
/**
* 对比两个文本字符是否相同
*
* 原生的 === 字符串对比
* 和 计算哈希值后对比,原生性能非常高,100w 汉字差距在10倍以上
*/
static equals(str1: string, str2: string): boolean {
console.log(str1.length)
return str1 === str2
const s1 = StringUtils.hashString(str1)
const s2 = StringUtils.hashString(str2)
return s1 === s2
}
static hashString(str: string): number {
let hash = 0
for (let i = 0; i < str.length; i++) {
hash = (hash << 5) - hash + str.charCodeAt(i)
hash |= 0 // Convert to 32bit integer
}
return hash
}
}
+15
View File
@@ -0,0 +1,15 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>
<style>
@media (min-width: 1024px) {
.about {
min-height: 100vh;
display: flex;
align-items: center;
}
}
</style>
+9
View File
@@ -0,0 +1,9 @@
<script setup lang="ts">
import TheWelcome from '../components/TheWelcome.vue'
</script>
<template>
<main>
<TheWelcome />
</main>
</template>
+7
View File
@@ -0,0 +1,7 @@
<script setup lang="ts"></script>
<template>
<h1>NotFound</h1>
</template>
<style scoped></style>
+240
View File
@@ -0,0 +1,240 @@
<script setup lang="ts">
import { RouterView, useRoute, useRouter } from 'vue-router'
import PathUtils from '@/utils/pathUtils'
import type { subRoute, subRouteList } from '@/types/devIndex'
import StringUtils from '@/utils/stringUtils'
import ArrayUtils from '@/utils/arrayUtils'
import { onMounted } from 'vue'
import gsap from 'gsap'
import { TextPlugin } from 'gsap/TextPlugin'
import { computed } from 'vue'
gsap.registerPlugin(TextPlugin)
const router = useRouter()
const route = useRoute()
const routes = import.meta.glob('@/views/dev/**/*.vue')
// region 子路由列表 点击、激活 处理
// 子路由列表
const subRouteLists: subRouteList = []
// 遍历路由page列表进行路由渲染
Object.keys(routes).map((path) => {
if ('.vue' === PathUtils.extname(path).trim()) {
const fileName = PathUtils.basename(path).split('.')[0]
const items = {
displayName: fileName,
path: '/dev-index' + `/${StringUtils.toCustomCase(fileName).toLowerCase()}`, // 生成路径
}
subRouteLists.push(items)
}
return ''
})
/**
* 列表点击调准
*/
function listClickJump(item: subRoute) {
console.log(item.path)
router.push({ path: item.path })
}
/**
* 列表路径是否激活
* @param path
*/
const isActive = (path: string) => {
return route.path.includes(path)
}
// endregion
// region 无子路由数据自动跳转至主页
// 多久后跳转
let countdownSeconds = 9
// 指定跳转路径
const targetUrl = '/'
// 跳转定时器
let targetCountdown: number
// 路由列表是否为空【计算属性存在缓存】
const routerListIsEmpty = computed(() => {
return ArrayUtils.isEmpty(subRouteLists)
})
onMounted(() => {
// 如果数据为空,设置跳转倒计时
if (routerListIsEmpty.value) {
targetCountdown = setInterval(() => {
gsap.fromTo(
'#animated-text',
{ text: countdownSeconds.toString(), opacity: 1, y: -10 },
{
duration: 1,
y: 150,
text: (countdownSeconds--).toString(),
ease: 'expo.in',
},
)
if (countdownSeconds < 0) {
jumpToHome()
}
}, 1500)
}
})
/**
* 跳转至主页
*/
function jumpToHome() {
clearInterval(targetCountdown)
router.replace({ path: targetUrl })
}
// endregion
</script>
<template>
<div class="root-container">
<h1 class="title">Dev</h1>
<hr />
<div v-if="routerListIsEmpty" class="empty-container">
<div class="empty-message">
无数据!
<br />
<div class="countdown-box">
<div class="countdown-top">
<p id="animated-text" class="countdown-number">9</p>
</div>
<p class="jump-message">
<span>秒后跳转至</span>
<span class="jump-link" @click="jumpToHome">主页</span>
</p>
</div>
</div>
</div>
<div v-else class="main-content">
<ul class="nav-list">
<li
class="nav-item"
v-for="(subRoute, index) in subRouteLists"
:class="{ active: isActive(subRoute.path) }"
:key="index"
@click="listClickJump(subRoute)"
>
{{ subRoute.displayName }}
</li>
</ul>
<main class="router-content">
<router-view />
</main>
</div>
</div>
</template>
<style scoped lang="scss">
.root-container {
height: auto;
user-select: none;
}
.title {
color: #1e3a8a; // tailwind's text-blue-900
font-weight: bold;
font-family: sans-serif;
font-size: 3rem;
text-align: center;
padding: 2.5rem 0;
}
.empty-container {
background-color: #f8fafc; // slate-50
overflow: hidden;
}
.empty-message {
text-align: center;
font-family: serif;
color: #ef4444; // red-500
font-size: 3.75rem;
padding-bottom: 20rem;
}
.countdown-box {
display: inline-flex;
transform: translate(-20rem, 2.5rem); // 原 translate-x-80 translate-y-10
font-size: 1.5rem;
min-height: 10rem;
height: 10rem;
max-height: 10rem;
color: #1e3a8a;
justify-content: center;
align-items: center;
}
.countdown-top {
height: 6rem;
overflow: hidden;
text-align: center;
}
.countdown-number {
font-size: 2.25rem;
}
.jump-message {
margin-left: 1rem;
}
.jump-link {
text-decoration: underline;
cursor: pointer;
margin-left: 0.375rem;
}
.main-content {
display: flex;
margin-top: 1.25rem;
flex-direction: column;
margin-inline: auto;
width: 75%; // 相当于 lg:w-9/12
}
.nav-list {
margin-right: 5rem;
display: none;
@media (min-width: 1024px) {
display: block;
}
}
.nav-item {
list-style: none;
padding: 0.25rem;
margin: 0.25rem;
transition: all 0.3s ease;
user-select: none;
cursor: pointer;
color: #4b5563; // gray-700
&:hover {
color: #facc15; // yellow-300
}
&.active {
font-weight: bold;
text-decoration: underline;
font-size: 1.5rem;
}
}
.router-content {
display: flex;
flex-direction: column;
margin: 1.25rem auto auto;
overflow-y: auto;
}
</style>
@@ -0,0 +1,27 @@
<script setup lang="ts">
import CommandManager from '@/components/CommandManager.vue'
import type { CommandType } from '@/types/command'
const commands: CommandType[] = [
{
name: 'greet',
description: '查询exif信息',
params: [
{
name: 'name',
label: 'name',
type: 'text',
value: 'Vue',
placeholder: 'string'
}
],
result: null
}
]
</script>
<template>
<CommandManager :pro="commands"></CommandManager>
</template>
<style scoped></style>
@@ -0,0 +1,25 @@
<script setup lang="ts">
import CommandManager from '@/components/CommandManager.vue'
import type { CommandType } from '@/types/command'
const commands: CommandType[] = [
{
name: 'is_elevated_command',
description: '是否有管理员权限',
params: [],
result: null,
},
{
name: 'get_exe_directory_command',
description: '获取程序所在目录',
params: [],
result: null,
},
]
</script>
<template>
<CommandManager :pro="commands"></CommandManager>
</template>
<style scoped></style>
@@ -0,0 +1,57 @@
<script setup lang="ts">
import CommandManager from '@/components/CommandManager.vue'
import type { CommandType } from '@/types/command'
const commands: CommandType[] = [
{
name: 'win_divert_command',
description: '显示所有网络连接信息',
params: [],
result: null,
},
{
name: 'monitor_network_with_windivert_command',
description: '使用WinDivert监控网络数据包',
params: [],
result: null,
},
{
name: 'recv_packet_command',
description: '安全接收数据包的辅助函数',
params: [],
result: null,
},
{
name: 'recv_packet_command1',
description: '外部包接口!',
params: [],
result: null,
},
{
name: 'get_all_tcp_info',
description: '获取所有 TCP 信息!',
params: [],
result: null,
},
{
name: 'get_more_info',
description: 'get_more_info',
params: [
{
name: 'id',
label: 'id',
type: 'text',
value: 'Vue',
placeholder: 'text'
}
],
result: null,
},
]
</script>
<template>
<CommandManager :pro="commands"></CommandManager>
</template>
<style scoped></style>