Compare commits

..

11 Commits

@ -0,0 +1,130 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="43036cd9-090a-4a6e-bf01-2c7620e3e84b" name="更改" comment="注释6">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/MTAS-guest/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/MTAS-guest/package-lock.json" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 6
}</component>
<component name="ProjectId" id="2qNfiEa4IWyMomjiwsjO5NuM8ut" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"RunOnceActivity.ShowReadmeOnStart": "true",
"git-widget-placeholder": "feature/zuyuan1",
"last_opened_file_path": "D:/download/yolov8-master",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
"node.js.selected.package.tslint": "(autodetect)",
"nodejs_package_manager_path": "npm",
"vue.rearranger.settings.migration": "true"
}
}]]></component>
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-js-predefined-d6986cc7102b-7c0b70fcd90d-JavaScript-PY-242.21829.153" />
<option value="bundled-python-sdk-464836ebc622-b74155a9e76b-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-242.21829.153" />
</set>
</attachedChunks>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="应用程序级" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="默认任务">
<changelist id="43036cd9-090a-4a6e-bf01-2c7620e3e84b" name="更改" comment="" />
<created>1734508079065</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1734508079065</updated>
<workItem from="1734508080118" duration="3063000" />
<workItem from="1734511202090" duration="5161000" />
</task>
<task id="LOCAL-00001" summary="hygygy">
<option name="closed" value="true" />
<created>1734511308690</created>
<option name="number" value="00001" />
<option name="presentableId" value="LOCAL-00001" />
<option name="project" value="LOCAL" />
<updated>1734511308690</updated>
</task>
<task id="LOCAL-00002" summary="注释1">
<option name="closed" value="true" />
<created>1734511636494</created>
<option name="number" value="00002" />
<option name="presentableId" value="LOCAL-00002" />
<option name="project" value="LOCAL" />
<updated>1734511636494</updated>
</task>
<task id="LOCAL-00003" summary="注释2">
<option name="closed" value="true" />
<created>1734511852004</created>
<option name="number" value="00003" />
<option name="presentableId" value="LOCAL-00003" />
<option name="project" value="LOCAL" />
<updated>1734511852004</updated>
</task>
<task id="LOCAL-00004" summary="注释3">
<option name="closed" value="true" />
<created>1734512321520</created>
<option name="number" value="00004" />
<option name="presentableId" value="LOCAL-00004" />
<option name="project" value="LOCAL" />
<updated>1734512321520</updated>
</task>
<task id="LOCAL-00005" summary="注释3">
<option name="closed" value="true" />
<created>1734512463524</created>
<option name="number" value="00005" />
<option name="presentableId" value="LOCAL-00005" />
<option name="project" value="LOCAL" />
<updated>1734512463524</updated>
</task>
<task id="LOCAL-00006" summary="注释5">
<option name="closed" value="true" />
<created>1734514189748</created>
<option name="number" value="00006" />
<option name="presentableId" value="LOCAL-00006" />
<option name="project" value="LOCAL" />
<updated>1734514189748</updated>
</task>
<task id="LOCAL-00007" summary="注释6">
<option name="closed" value="true" />
<created>1734515679705</created>
<option name="number" value="00007" />
<option name="presentableId" value="LOCAL-00007" />
<option name="project" value="LOCAL" />
<updated>1734515679705</updated>
</task>
<option name="localTasksCounter" value="8" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="VcsManagerConfiguration">
<MESSAGE value="hygygy" />
<MESSAGE value="注释1" />
<MESSAGE value="注释2" />
<MESSAGE value="注释3" />
<MESSAGE value="注释5" />
<MESSAGE value="注释6" />
<option name="LAST_COMMIT_MESSAGE" value="注释6" />
</component>
</project>

@ -1,53 +1,56 @@
{
"name": "yolov8-vue3",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"name": "yolov8-vue3", //
"version": "1.0.0", //
"lockfileVersion": 3, // lock
"requires": true, //
"packages": {
"": {
"": { //
"name": "yolov8-vue3",
"version": "1.0.0",
"dependencies": {
"@kangc/v-md-editor": "^2.3.15",
"@vueuse/core": "^9.5.0",
"axios": "^1.3.0",
"dayjs": "^1.11.7",
"element-plus": "^2.3.7",
"nprogress": "^0.2.0",
"pinia": "^2.0.25",
"pinia-plugin-persistedstate": "^2.4.0",
"prismjs": "^1.29.0",
"swiper": "^8.4.5",
"tocbot": "^4.20.0",
"v-viewer": "^3.0.11",
"vue": "^3.2.41",
"vue-cropper": "^1.0.5",
"vue-router": "^4.0.12",
"vue3-danmaku": "^1.2.0",
"vue3-lazy": "^1.0.0-alpha.1",
"vue3-social-share": "^0.1.7"
},
"devDependencies": {
"@types/node": "^18.11.9",
"@types/nprogress": "^0.2.0",
"@types/prismjs": "^1.26.0",
"@vicons/ionicons5": "^0.12.0",
"@vitejs/plugin-vue": "^3.2.0",
"naive-ui": "^2.34.3",
"sass": "^1.56.1",
"sass-loader": "^13.2.0",
"typescript": "^4.6.4",
"unplugin-auto-import": "^0.12.1",
"unplugin-vue-components": "^0.22.12",
"vite": "^3.2.3",
"vite-plugin-prismjs": "0.0.8",
"vite-plugin-svg-icons": "^2.0.1",
"vue-tsc": "^1.0.9"
"dependencies": { //
"@kangc/v-md-editor": "^2.3.15", // markdown
"@vueuse/core": "^9.5.0", // Vue
"axios": "^1.3.0", // HTTP
"dayjs": "^1.11.7", //
"element-plus": "^2.3.7", // Vue UI
"nprogress": "^0.2.0", //
"pinia": "^2.0.25", // Vue
"pinia-plugin-persistedstate": "^2.4.0", // pinia
"prismjs": "^1.29.0", //
"swiper": "^8.4.5", //
"tocbot": "^4.20.0", //
"v-viewer": "^3.0.11", //
"vue": "^3.2.41", // Vue.js
"vue-cropper": "^1.0.5", // Vue
"vue-router": "^4.0.12", // Vue
"vue3-danmaku": "^1.2.0", // Vue
"vue3-lazy": "^1.0.0-alpha.1", // Vue
"vue3-social-share": "^0.1.7" // Vue
},
"devDependencies": { //
"@types/node": "^18.11.9", // Node.js
"@types/nprogress": "^0.2.0", // nprogress
"@types/prismjs": "^1.26.0", // prismjs
"@vicons/ionicons5": "^0.12.0", // Ionicons 5
"@vitejs/plugin-vue": "^3.2.0", // ViteVue
"naive-ui": "^2.34.3", // Vue UI
"sass": "^1.56.1", // CSS
"sass-loader": "^13.2.0", // sassloader
"typescript": "^4.6.4", // TypeScript
"unplugin-auto-import": "^0.12.1", //
"unplugin-vue-components": "^0.22.12", // Vue
"vite": "^3.2.3", //
"vite-plugin-prismjs": "0.0.8", // Viteprismjs
"vite-plugin-svg-icons": "^2.0.1", // ViteSVG
"vue-tsc": "^1.0.9" // Vue TypeScript
}
},
// resolvedintegritydevdependencies
"node_modules/@ampproject/remapping": {
"version": "2.2.0",
"resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.2.0.tgz",
"version": "2.2.0", //
"resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/",
"integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==",
"dev": true,
"dependencies": {

@ -1,53 +1,98 @@
{
//
"name": "yolov8-vue3",
// true
"private": true,
//
"version": "1.0.0",
// ES
"type": "module",
"scripts": {
// 使 vite
"dev": "vite",
// TypeScript vue-tsc使 vite
"build": "vue-tsc && vite build",
// 使 vite
"preview": "vite preview"
},
// yolov8
"description": "基于yolov8的车流识别项目",
"dependencies": {
// markdown "^2.3.15"
"@kangc/v-md-editor": "^2.3.15",
// Vue API "^9.5.0"
"@vueuse/core": "^9.5.0",
// HTTP API "^1.3.0"
"axios": "^1.3.0",
// 便 "^1.11.7"
"dayjs": "^1.11.7",
// JavaScript "^2.1.0"
"easy-typer-js": "^2.1.0",
// "^5.4.1"
"echarts": "^5.4.1",
// Vue 3 UI "^2.3.7"
"element-plus": "^2.3.7",
// cookies 便 "^3.0.1"
"js-cookie": "^3.0.1",
// "^0.2.0"
"nprogress": "^0.2.0",
// Vue "^2.0.25"
"pinia": "^2.0.25",
// Pinia "^2.4.0"
"pinia-plugin-persistedstate": "^2.4.0",
// "^1.29.0"
"prismjs": "^1.29.0",
// "^8.4.5"
"swiper": "^8.4.5",
// JavaScript "^4.20.0"
"tocbot": "^4.20.0",
// 便 "^3.0.11"
"v-viewer": "^3.0.11",
// Vue 3 "^3.2.41"
"vue": "^3.2.41",
// "^1.0.5"
"vue-cropper": "^1.0.5",
// Vue 3 "^4.0.12"
"vue-router": "^4.0.12",
// Vue 3 "^1.2.0"
"vue3-danmaku": "^1.2.0",
// Vue 3 "^1.0.0-alpha.1"
"vue3-lazy": "^1.0.0-alpha.1",
// Vue 3 "^0.1.7"
"vue3-social-share": "^0.1.7"
},
"devDependencies": {
// js-cookie TypeScript 便 TypeScript 使 "^3.0.2"
"@types/js-cookie": "^3.0.2",
// Node.js TypeScript 使 Node.js API "^18.11.9"
"@types/node": "^18.11.9",
// nprogress TypeScript "^0.2.0"
"@types/nprogress": "^0.2.0",
// prismjs TypeScript "^1.26.0"
"@types/prismjs": "^1.26.0",
// ionicons5 "^0.12.0"
"@vicons/ionicons5": "^0.12.0",
// vite Vue 使 vite Vue "^3.2.0"
"@vitejs/plugin-vue": "^3.2.0",
// Vue 3 UI "^2.34.3"
"naive-ui": "^2.34.3",
// Sass CSS "^1.56.1"
"sass": "^1.56.1",
// webpack Sass loader "^13.2.0"
"sass-loader": "^13.2.0",
// TypeScript TypeScript "^4.6.4"
"typescript": "^4.6.4",
// Vue API "^0.12.1"
"unplugin-auto-import": "^0.12.1",
// Vue "^0.22.12"
"unplugin-vue-components": "^0.22.12",
// "^3.2.3"
"vite": "^3.2.3",
// vite prismjs "0.0.8"
"vite-plugin-prismjs": "0.0.8",
// vite SVG "^2.0.1"
"vite-plugin-svg-icons": "^2.0.1",
// Vue TypeScript "^1.0.9"
"vue-tsc": "^1.0.9"
}
}
}

@ -1,50 +1,52 @@
<template style="height=100px;">
<!-- 使用router-view组件来根据当前的路由展示对应的组件内容同时通过v-slot指令接收传递过来的一些属性这里接收了"Component"要展示的组件本身"route"当前的路由信息 -->
<router-view v-slot="{ Component, route }">
<!-- keep-alive组件用于缓存内部包裹的组件避免组件在切换路由等情况下被频繁销毁和重新创建提高性能尤其是对于一些状态需要保留或者初始化成本较高的组件很有用 -->
<keep-alive>
<!-- component组件是一个动态组件通过":is"属性绑定"Component"变量来动态地渲染由router-view传递过来的对应组件":key"属性绑定"route.path"用于给每个不同路由对应的组件提供一个唯一标识方便Vue进行差异对比和更新 -->
<component :is="Component" :key="route.path" />
</keep-alive>
</router-view>
</template>
<script setup lang="ts">
// "@/api/blogInfo""getBlogInfo""report"访
// import { getBlogInfo, report } from "@/api/blogInfo";
// VuexuseStore"user"store
import useStore from '@/store';
const { user } = useStore();
// DOM
onMounted(() => {
// %cCSS"AI""By pan"
console.log(
"%c AI系统 %c By pan %c",
"background:#e9546b ; padding: 1px; border-radius: 3px 0 0 3px; color: #fff; padding:5px 0;",
"background:#ec8c69 ; padding: 1px; border-radius: 0 3px 3px 0; color: #000; padding:5px 0;",
"background:transparent"
);
//
// "getBlogInfo"Vuex"blog"Vuex"setBlogInfo"
// getBlogInfo().then(({ data }) => {
// blog.setBlogInfo(data.data);
// });
// 访
// 访"report"
// report();
})
</script>
<style scoped>
/* 最短距离——320px */
/* 为类名为"app-wrapper"的元素设置样式将其定位方式设置为相对定位设置最小高度为100vh也就是占满整个视口的高度方便在不同屏幕尺寸下保持页面高度的基本布局最小宽度设置为320px确保页面在较窄的设备上也有一个最小的展示宽度范围 */
.app-wrapper {
position: relative;
min-height: 100vh;
min-width: 320px;
}
/* 为类名为"main-wrapper"的元素设置样式将其设置为flex布局并且布局方向为垂直方向列布局使其内部子元素按列排列设置宽度为100%占满父容器宽度底部内边距为8rem用于预留一定空间可能用于放置Footer等元素或者保证内容底部有一定空白区域 */
.main-wrapper {
display: flex;
flex-direction: column;
width: 100%;
padding: 0 0 8rem;
}
</style>
</style>

@ -1,45 +1,72 @@
@import "./mixin.scss";
/* 背景色 */
/* 以下是.bg类的样式定义用于设置元素的背景色 */
.bg {
background-color: var(--grey-0);
}
/* 页面布局 */
/* 页面头部相关样式 */
.page-header {
/* 设置元素为相对定位 */
position: relative;
/* 宽度占满父元素 */
width: 100%;
/* 高度为视口高度的70% */
height: 70vh;
/* 设置背景颜色为红色,并使其在水平和垂直方向都居中,且铺满整个元素 */
background: var(--color-red) no-repeat center center / cover;
/* 设置z-index值使其处于较低层级可能在一些元素下方显示 */
z-index: -9;
&::after {
/* 生成一个伪元素 */
content: '';
/* 使其以块级元素显示 */
display: block;
/* 设置为绝对定位 */
position: absolute;
/* 距离顶部0距离 */
top: 0;
/* 距离左边0距离 */
left: 0;
/* 宽度占满父元素 */
width: 100%;
/* 高度占满父元素 */
height: 100%;
background-color: rgba(0, 0, 0, .2);
transition: all .2s ease-in-out 0s;
/* 设置背景颜色为半透明黑色 */
background-color: rgba(0, 0, 0,.2);
/* 设置过渡效果所有属性变化在0.2秒内以缓入缓出的方式进行,无延迟 */
transition: all.2s ease-in-out 0s;
}
}
/* 页面标题相关样式 */
.page-title {
/* 引入名为flex的混入mixin具体样式由mixin.scss中定义 */
@include flex;
/* 设置弹性布局的方向为列方向 */
flex-direction: column;
/* 设置为固定定位 */
position: fixed;
/* 宽度占满父元素 */
width: 100%;
/* 高度为视口高度的50% */
height: 50vh;
/* 设置最小高度为10rem */
min-height: 10rem;
/* 设置内边距 */
padding: 4rem 5rem 0;
/* 设置字体大小 */
font-size: 2.25em;
/* 设置文字颜色 */
color: var(--header-text-color);
/* 应用名为titleScale的动画动画时长1秒 */
animation: titleScale 1s;
/* 设置较高的z-index值使其在页面中处于较上层显示 */
z-index: 1;
}
/* 页面封面相关样式,可能用于覆盖页面某个区域等 */
.page-cover {
position: fixed;
top: 0;
@ -49,48 +76,70 @@
object-fit: cover;
}
/* 页面容器相关样式 */
.page-container {
/* 设置为相对定位 */
position: relative;
/* 根据计算设置宽度,默认减去一定边距值 */
width: calc(100% - 0.625rem);
/* 水平居中 */
margin: 1.5rem auto;
/* 设置内边距 */
padding: 1.75rem 2.25rem;
/* 设置边框圆角 */
border-radius: 0.75rem;
/* 设置盒子阴影效果 */
box-shadow: 0 0 1rem var(--box-bg-shadow);
/* 应用名为slideUpIn的动画动画时长1秒 */
animation: slideUpIn 1s;
}
/* 当屏幕宽度大于等于1200px时的页面容器样式调整 */
@media (min-width: 1200px) {
.page-container {
.page-container {
width: 60.5rem;
}
}
/* 当屏幕宽度小于等于767px时的页面容器样式调整 */
@media (max-width: 767px) {
.page-container {
.page-container {
padding: 1rem 0.875rem;
}
.page-title {
.page-title {
font-size: 2rem;
padding: 3rem 0.5rem 0;
}
}
/* 主容器相关样式,用于整体布局 */
.main-container {
/* 使用弹性布局 */
display: flex;
/* 垂直方向上顶部对齐 */
align-items: flex-start;
/* 水平方向上居中对齐 */
justify-content: center;
/* 根据计算设置宽度,默认减去一定边距值 */
width: calc(100% - 0.625rem);
/* 水平居中 */
margin: 0 auto;
/* 设置底部内边距 */
padding-bottom: 1.75rem;
/* 应用名为slideUpIn的动画动画时长1秒 */
animation: slideUpIn 1s;
}
/* 左侧容器相关样式 */
.left-container {
/* 根据计算设置宽度 */
width: calc(100% - 18.75rem);
/* 设置过渡效果所有属性变化在0.3秒内进行 */
transition: all 0.3s;
}
/* 右侧容器相关样式,有粘性定位效果 */
.right-container {
position: sticky;
top: 1rem;
@ -98,129 +147,159 @@
margin-left: 0.8rem;
}
/* 以下类名为test的元素样式宽度占满父元素 */
.test {
width: 100%;
}
/* 以下类名为temp的元素默认不显示 */
.temp {
display: none;
}
/* 当屏幕宽度大于等于1200px时的主容器样式调整 */
@media (min-width: 1200px) {
.main-container {
.main-container {
width: 72.5rem;
}
}
/* 当屏幕宽度小于等于991px时的左右容器样式调整 */
@media (max-width: 991px) {
.left-container {
.left-container {
width: 100%;
}
.right-container {
.right-container {
display: none;
width: 100%;
}
}
/* 卡片 */
/* 卡片样式定义 */
.side-card {
/* 设置内边距 */
padding: 1rem;
/* 设置边框圆角 */
border-radius: 0.5rem;
/* 设置盒子阴影效果 */
box-shadow: 0 0 1rem var(--box-bg-shadow);
/* 设置过渡效果所有属性变化在0.2秒内以缓入缓出的方式进行,无延迟 */
transition: all 0.2s ease-in-out 0s;
/* 选择不是第一个子元素的.side-card元素设置其上外边距 */
&:not(:first-child) {
margin-top: 1.25rem;
}
}
/* 当屏幕宽度小于等于991px时的.side-card样式调整 */
@media (max-width: 991px) {
.side-card {
.side-card {
margin: 0 1rem;
}
}
/* 对话框 */
/* 对话框外层包装样式定义 */
.dialog-wrapper {
width: 600px !important;
/* 设置宽度,并且加上!important提高优先级确保样式生效 */
width: 600px!important;
}
/* 当屏幕宽度大于等于760px时的.dialog-wrapper样式调整 */
@media (min-width: 760px) {
.dialog-wrapper {
.dialog-wrapper {
/* 设置内边距 */
padding: 2rem 2.5rem 0;
/* 设置高度 */
height: 500px;
}
}
/* 对话框内文本样式,设置文本颜色 */
.dialog-text {
color: var(--grey-5);
}
/* 具有颜色标记的元素样式,设置鼠标指针样式及颜色 */
.colorFlag {
cursor: pointer;
color: var(--grey-6);
}
/* 边距 */
/* 以下是一些用于设置元素上外边距的类,通过类名直观体现外边距大小 */
/* 上外边距为1rem的类 */
.mt-4 {
margin-top: 1rem;
}
/* 上外边距为65px的类 */
.mt-12 {
margin-top: 65px;
}
/* 上外边距为40px的类 */
.mt-10 {
margin-top: 40px;
}
/* 上外边距为55px的类 */
.mt-11 {
margin-top: 55px;
}
/* 隐藏抽屉滚动条 */
/* 隐藏抽屉滚动条样式针对webkit内核浏览器 */
.n-drawer-body-content-wrapper::-webkit-scrollbar {
display: none !important;
display: none!important;
}
/* 代码 */
/* 代码块样式定义,设置字体族及字体大小 */
.hljs {
font-family: "consolas";
font-size: 1rem;
}
/* 分页 */
/* 分页相关样式 */
/* 分页项鼠标悬停及激活时的样式 */
.n-pagination-item:hover,
.n-pagination-item--active {
color: var(--grey-0) !important;
background-image: linear-gradient(to right, var(--color-pink) 0, var(--color-orange) 100%) !important;
box-shadow: 0 0 .75rem var(--color-pink-a3) !important;
border: none !important;
/* 设置文本颜色 */
color: var(--grey-0)!important;
/* 设置背景图片为线性渐变,颜色从 var(--color-pink)到 var(--color-orange) */
background-image: linear-gradient(to right, var(--color-pink) 0, var(--color-orange) 100%)!important;
/* 设置盒子阴影效果 */
box-shadow: 0 0.75rem var(--color-pink-a3)!important;
/* 去除边框 */
border: none!important;
}
/* 分页整体布局样式,使其内容居中 */
.pagination {
justify-content: center;
padding: 1.25rem 3.125rem;
}
.n-pagination .n-pagination-item {
/* 分页项默认样式,设置文本颜色 */
.n-pagination.n-pagination-item {
color: var(--grey-5);
}
/* 加载更多按钮 */
/* 加载更多按钮相关样式 */
.loading-warp {
/* 引入名为flex的混入mixin具体样式由相应文件定义 */
@include flex;
margin-top: 20px;
}
.loading-warp .btn {
/* 加载更多按钮内.btn元素的样式 */
.loading-warp.btn {
letter-spacing: 1.25px;
color: var(--grey-0) !important;
color: var(--grey-0)!important;
background-image: linear-gradient(to right, var(--color-pink) 0, var(--color-orange) 100%);
}
/* 文章标签 */
/* 文章标签样式 */
.article-tag {
display: inline-block;
position: relative;
@ -230,6 +309,7 @@
background: var(--note-bg);
color: var(--note-text);
/* 除了最后一个元素外,设置右边距 */
&:not(:last-child) {
margin-right: 0.625rem;
}
@ -256,29 +336,32 @@
}
}
/* 标签相关信息样式,设置文本对齐、上外边距及字体大小 */
.tag-info {
text-align: left;
margin-top: 0.625rem;
font-size: 0.75em;
}
/* 评论框 */
/* 评论框相关样式 */
.reply-box {
display: flex;
flex-direction: column;
}
/* 评论框常规状态样式 */
.box-normal {
display: flex;
height: 50px;
transition: 0.2s;
.reply-box-warp {
.reply-box-warp {
flex: auto;
margin-left: 0.6rem;
}
.reply-box-send {
.reply-box-send {
/* 引入名为flex的混入mixin具体样式由相应文件定义 */
@include flex;
flex-basis: 70px;
margin-left: 10px;
@ -288,17 +371,19 @@
cursor: pointer;
}
.send-active {
.send-active {
background-color: var(--color-pink);
}
}
/* 评论框头像相关样式 */
.reply-box-avatar {
@include flex;
width: 3rem;
height: 3.125rem;
}
/* 评论框文本区域样式,设置宽度、高度、内边距、边框、背景色、字体等相关属性 */
.reply-box-textarea {
width: 100%;
height: 100%;
@ -313,6 +398,7 @@
outline: none;
}
/* 评论框展开状态下相关样式,设置对齐、左边距及上外边距 */
.box-expand {
display: flex;
align-items: center;
@ -320,17 +406,19 @@
margin-top: 0.3125rem;
}
/* 特定头像样式,设置宽度、高度及边框圆角 */
.shoka-avatar {
width: 3rem;
height: 3rem;
border-radius: 50%;
}
/* 抽屉 */
/* 抽屉相关背景样式,设置背景颜色 */
.side-bg {
background: var(--grey-1);
}
/* 作者头像样式 */
.author-avatar {
display: block;
max-width: 10rem;
@ -347,6 +435,7 @@
}
}
/* 作者名字样式,设置上外边距、字体粗细、文本对齐及颜色 */
.author-name {
margin-top: 0.5rem;
font-weight: 400;
@ -354,6 +443,7 @@
color: var(--grey-7);
}
/* 站点描述样式,设置上外边距、字体大小、文本对齐及颜色 */
.site-desc {
margin-top: 0.5rem;
font-size: 1em;
@ -361,6 +451,7 @@
color: var(--grey-5);
}
/* 博客容器样式,设置布局为弹性布局且内容居中,设置上外边距、行高及文本对齐 */
.blog-container {
display: flex;
justify-content: center;
@ -369,6 +460,7 @@
text-align: center;
}
/* 博客项目样式,设置颜色及内边距,对非第一个子元素设置左边框 */
.blog-item {
color: var(--grey-6);
padding: 0 0.7rem;
@ -378,21 +470,24 @@
}
}
/* 计数相关样式,设置字体大小及字体粗细,文本对齐 */
.count {
font-size: 1.25rem;
font-weight: 600;
text-align: center;
}
/* 名字相关样式,设置字体大小 */
.name {
font-size: 0.875rem;
}
/* 社交容器样式,设置上外边距及文本对齐 */
.social-container {
margin-top: 1rem;
text-align: center;
.social-item {
.social-item {
display: inline-block;
width: 1.875rem;
height: 1.875rem;
@ -401,7 +496,7 @@
}
}
/* 说说 */
/* 说说相关样式 */
.talk-user-name {
display: flex;
align-items: center;
@ -428,7 +523,7 @@
flex-wrap: wrap;
margin-top: 0.3125rem;
.image {
.image {
max-width: 15rem;
max-height: 12.5rem;
padding: 1px;
@ -446,16 +541,16 @@
color: #9499a0;
}
/* 点赞 */
/* 点赞相关样式,设置颜色 */
.like-flag {
color: var(--color-pink);
}
/* 音乐播放器显示隐藏 */
.aplayer.aplayer-fixed.aplayer-narrow .aplayer-body {
left: -66px !important;
/* 音乐播放器显示隐藏相关样式,设置特定状态下播放器主体的左边距 */
.aplayer.aplayer-fixed.aplayer-narrow.aplayer-body {
left: -66px!important;
}
.aplayer.aplayer-fixed.aplayer-narrow .aplayer-body:hover {
left: 0 !important;
.aplayer.aplayer-fixed.aplayer-narrow.aplayer-body:hover {
left: 0!important;
}

@ -1,45 +1,67 @@
// SCSS
@import "./theme-shoka.scss";
// SCSS
@import "./transition.scss";
// markdown SCSS markdown
@import "./markdown.scss";
// SCSS
@import "./common.scss";
// :after :before
*,
:after,
:before {
// border-box使
box-sizing: border-box;
//
margin: 0;
//
padding: 0;
}
//
// body
body {
// 使 normal.cur 使
cursor: url("../icons/normal.cur"), default;
// --grey-0
background: var(--grey-0);
// --text-color
color: var(--text-color);
// 使 Mulish
font-family: Mulish, -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
// 1em
font-size: 1em;
//
overflow-x: hidden;
// 2
line-height: 2;
}
// abuttonimg使 link.cur 使!important
a,
button,
img {
cursor: url(https://ik.imagekit.io/nicexl/cursor/link.cur), default !important;
cursor: url(https://ik.imagekit.io/nicexl/cursor/link.cur), default!important;
}
// h1 - h6
h1,
h2,
h3,
h4,
h5,
h6 {
// 使 NotoSerifSC
font-family: "NotoSerifSC", -apple-system, "Microsoft YaHei", sans-serif;
// 700
font-weight: 700;
// 1.5
line-height: 1.5;
// 1.25rem 0.9375rem 0
margin: 1.25rem 0 0.9375rem;
}
// buttoninputselecttextarea
button,
input,
select,
@ -48,29 +70,45 @@ textarea {
border-style: none;
}
// li
li {
list-style: none;
}
// a
a {
//
border: none;
// currentColor
color: currentColor;
// 线
outline: 0;
// 线
text-decoration: none;
//
overflow-wrap: break-word;
word-wrap: break-word;
// 0.2
transition: all 0.2s ease-in-out 0s;
//
cursor: pointer;
}
// .clearfix :after
.clearfix:after {
//
visibility: hidden;
//
clear: both;
// 使便
display: block;
//
content: ".";
// 0使
height: 0;
}
// IE6/7使 zoom hasLayout
.clearfix {
zoom: 1;
}

@ -1,23 +1,30 @@
// 567px.mdh1 - h6
// 0.25rem
@media (max-width: 567px) {
.md h1,
.md h2,
.md h3,
.md h4,
.md h5,
.md h6 {
.md h1,
.md h2,
.md h3,
.md h4,
.md h5,
.md h6 {
padding-left: 0.25rem;
}
}
// .md
.md {
// 使 Mulish
font-family: Mulish, -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
//
overflow-wrap: break-word;
word-wrap: break-word;
}
.md .links:last-child,
.md .tabs:last-child,
// .md.links.tabsblockquote
// imgolpretableul
// 使
.md.links:last-child,
.md.tabs:last-child,
.md blockquote:last-child,
.md img:last-child,
.md ol:last-child,
@ -27,44 +34,62 @@
margin-bottom: 0;
}
// .mdp00.8rem0
.md p {
margin: 0 0 0.8rem;
}
// .mda!important --primary-color
.md a {
color: var(--primary-color) !important;
color: var(--primary-color)!important;
}
// .mda!important --color-blue
.md a:hover {
color: var(--color-blue) !important;
color: var(--color-blue)!important;
}
// .mdblockquote
.md blockquote {
padding: 10px 1rem !important;
font-size: 90% !important;
border-left: 0.2rem solid var(--grey-4) !important;
border-left-color: var(--primary-color) !important;
// 10px1rem!important
padding: 10px 1rem!important;
// 90%!important
font-size: 90%!important;
// 0.2rem --grey-4 --primary-color !important
border-left: 0.2rem solid var(--grey-4)!important;
border-left-color: var(--primary-color)!important;
//
border-radius: 0.1875rem;
background-color: var(--grey-2) !important;
color: var(--grey-5) !important;
// --grey-2 !important
background-color: var(--grey-2)!important;
// --grey-5 !important
color: var(--grey-5)!important;
//
word-wrap: break-word;
}
// .mdblockquoteul0.625rem0!important
.md blockquote ul {
margin: 0.625rem 0 !important;
margin: 0.625rem 0!important;
}
// .mdblockquoteulli ::before
// !important
.md blockquote ul li::before {
width: 0.375rem !important;
height: 0.375rem !important;
top: 0.6875rem !important;
width: 0.375rem!important;
height: 0.375rem!important;
top: 0.6875rem!important;
}
// .mdblockquotep!important
.md blockquote p {
margin: 0 !important;
margin: 0!important;
}
//
//
// .mddlolul00.8em0
// 0.1em1.4em0.2em
.md dl,
.md ol,
.md ul {
@ -72,10 +97,14 @@
padding: 0.1em 0.2em 0.1em 1.4em;
}
// .mdstartolcounter
.md ol:not([start]) {
counter-reset: counter;
}
// .mdolli ::before
// counter
//
.md ol>li::before {
counter-increment: counter;
content: counter(counter);
@ -93,16 +122,19 @@
cursor: pointer;
}
// .mdolli ::before
.md ol>li:hover::before {
color: var(--grey-1);
background: var(--color-pink);
}
// .mdliulli ::before
.md li ul>li::before {
background: var(--grey-1);
border: 1px solid var(--primary-color);
}
// .mdliolli ::before
.md li ol>li::before {
content: counter(counter) ".";
background: 0 0;
@ -111,41 +143,50 @@
line-height: 1;
}
// .mdliolli ::before
.md li ol>li:hover::before {
background: 0 0;
color: var(--color-pink);
}
// .mdli0.2rem0
.md li {
position: relative;
margin: 0.2rem 0;
}
// .mdli ::before 0.2
.md li:before {
transition: all 0.2s ease-in-out 0s;
}
// .mdlip00.5em0
.md li p {
margin: 0 0 0.5em;
}
// .mddldt
.md dl dt {
position: relative;
}
// dt700
dt {
font-weight: 700;
}
// .mddldd0.9375em
.md dl dd {
padding-left: 0.9375em;
}
// .mddldtulli ::before
.md dl dt:hover::before,
.md ul>li:hover::before {
background: var(--color-pink);
}
// .mddldtulli ::before
.md dl dt::before,
.md ul>li::before {
content: "";
@ -158,25 +199,31 @@ dt {
left: -1em;
}
//
//
// .mdtable使
.md table:last-child {
margin-bottom: 0;
}
// .mdtable0
//
.md table {
border-collapse: collapse !important;
border-collapse: collapse!important;
border-spacing: 0;
font-size: 0.875em;
width: 100%;
overflow: auto;
}
// .mdtableth700
.md table th {
font-weight: 700;
padding-bottom: 0.625rem;
text-align: center;
}
// .mdtabletd
.md table td {
border: 0.0625rem solid var(--grey-3);
font-weight: 400;
@ -185,32 +232,35 @@ dt {
vertical-align: middle;
}
// .mdtabletbodytr
.md table tbody tr:hover {
background: var(--grey-2);
}
// code
// <pre><code>!important
:not(pre)>code {
color: var(--primary-color) !important;
border-radius: 0.3rem !important;
border: 0.0625rem solid rgba(0, 0, 0, .1);
background-color: var(--grey-0) !important;
padding: 0.2rem 0.3rem !important;
color: var(--primary-color)!important;
border-radius: 0.3rem!important;
border: 0.0625rem solid rgba(0, 0, 0,.1);
background-color: var(--grey-0)!important;
padding: 0.2rem 0.3rem!important;
word-wrap: break-word;
}
// .mdpre
.md pre {
position: relative;
font-size: 85%;
line-height: 1.45;
color: #abb2bf;
background: #282c34;
margin-bottom: 1rem !important;
padding: 3rem 1.5rem 1rem !important;
border-radius: 5px !important;
margin-bottom: 1rem!important;
padding: 3rem 1.5rem 1rem!important;
border-radius: 5px!important;
overflow: auto;
}
// .v-md-pre-wrapper ::after
.v-md-pre-wrapper::after {
position: absolute;
top: 16px;
@ -221,34 +271,36 @@ dt {
background: #fc625d;
-webkit-box-shadow: 20px 0 #fdbc40, 40px 0 #35cd4b;
box-shadow: 20px 0 #fdbc40, 40px 0 #35cd4b;
content: ' ';
content:'';
}
// .mdcode!important
.md code {
font-family: "consolas" !important;
font-size: 0.9rem !important;
font-family: "consolas"!important;
font-size: 0.9rem!important;
}
// 419px.v-md-pre-wrapper06px!important
@media (max-width: 419px) {
.v-md-pre-wrapper {
margin: 0 !important;
border-radius: 6px !important;
.v-md-pre-wrapper {
margin: 0!important;
border-radius: 6px!important;
}
}
// 767px.vuepress-markdown-body.custom
@media (max-width: 767px) {
.vuepress-markdown-body:not(.custom) {
padding: 1.5rem 0.5rem !important;
.vuepress-markdown-body:not(.custom) {
padding: 1.5rem 0.5rem!important;
}
}
//
// .mdmark
.md mark {
background-color: #dbfdad;
}
// 线
// .md线u线线线
.md u {
--line-color: var(--note-hover, var(--primary-color));
text-decoration: none;
@ -258,23 +310,26 @@ dt {
border-bottom: none;
}
// 线
// .md线s线
.md s {
color: var(--grey-5);
-webkit-text-decoration-color: var(--note-hover, var(--grey-5));
text-decoration-color: var(--note-hover, var(--grey-5));
}
// .md线hr线 --primary-color !important
.md hr {
margin: 20px auto;
border: 2px dashed var(--primary-color) !important;
border: 2px dashed var(--primary-color)!important;
}
// .vuepress-markdown-body!important
.vuepress-markdown-body {
color: var(--text-color) !important;
background: var(--grey-0) !important;
color: var(--text-color)!important;
background: var(--grey-0)!important;
}
// .vuepress-markdown-bodytable2n!important
.vuepress-markdown-body tr:nth-child(2n) {
background-color: var(--grey-2) !important;
background-color: var(--grey-2)!important;
}

@ -1,109 +1,197 @@
/* 白天颜色 */
/* 白天颜色相关的变量定义,这些变量可在整个样式表中用于设置各种元素的颜色等样式属性 */
:root {
/* 评论框的背景颜色 */
--comment-color: #f1f2f3;
/* 白色,常用于背景等元素 */
--grey-0: #fff;
/* 接近白色的一种浅灰色,用于不同场景下较浅的背景等 */
--grey-1: #fdfdfd;
/* 另一种稍深一点的浅灰色,用于页面中不同的灰色调背景或元素颜色区分 */
--grey-2: #f7f7f7;
/* 再深一点的灰色,可用于边框、阴影等元素的颜色设置 */
--grey-3: #eff2f3;
/* 中等灰度,用于一些次要元素的文本颜色等 */
--grey-4: #ccc;
/* 较深的灰色,用于不太重要的提示性文本等颜色设置 */
--grey-5: #999;
/* 更深的灰色,用于一些弱化显示的元素文本颜色等 */
--grey-6: #666;
/* 很深的灰色,常用于需要突出对比的场景下 */
--grey-7: #333;
/* 非常深的灰色,接近黑色,可用于特定的深色元素 */
--grey-8: #222;
/* 黑色,用于一些特定的背景、文本等颜色需求 */
--grey-9: #000;
/* 灰色1的透明版本透明度为0完全透明可用于叠加效果等场景 */
--grey-1-a0: rgba(253, 253, 253, 0);
/* 灰色1的透明版本透明度为0.7,可用于半透明的覆盖、阴影等效果 */
--grey-1-a7: rgba(253, 253, 253, 0.7);
/* 灰色1的透明版本透明度为0.5,常用于元素半透明背景等 */
--grey-1-a5: rgba(253, 253, 253, 0.5);
/* 灰色1的透明版本透明度为0.3,例如用于元素的淡阴影效果等 */
--grey-1-a3: rgba(253, 253, 253, 0.3);
/* 黑色的透明版本透明度为0.1,常用于阴影、边框等元素营造淡淡的黑色效果 */
--grey-9-a1: rgba(0, 0, 0, 0.1);
/* 黑色的透明版本透明度为0.5,可用于更明显一点的半透明黑色效果 */
--grey-9-a5: rgba(0, 0, 0, 0.5);
/* 灰色2的透明版本透明度为0完全透明用于特定的透明效果场景 */
--grey-2-a0: rgba(247, 247, 247, 0);
/* 浅粉色,用于特定元素的背景、装饰等颜色 */
--color-pink-light: #ffe6fa;
/* 浅青色,用于页面中一些需要清新色调的元素背景等 */
--color-cyan-light: #e3fdf5;
/* 红色,常作为重要元素、操作按钮等的突出颜色 */
--color-red: #e9546b;
/* 粉色,可用于不同功能按钮、提示元素等的颜色 */
--color-pink: #ed6ea0;
/* 橙色,用于页面中需要醒目显示的元素颜色 */
--color-orange: #ec8c69;
/* 黄色,例如可用于警告、提醒类元素的颜色 */
--color-yellow: #eab700;
/* 绿色,常用于表示成功、通过等积极意义的元素颜色 */
--color-green: #0a7426;
/* 水蓝色,用于一些特定的装饰性元素颜色 */
--color-aqua: #3e999f;
/* 蓝色,常用于链接、选中状态等元素的颜色 */
--color-blue: #49b1f5;
/* 紫色,用于区分不同类型的元素等颜色设置 */
--color-purple: #9d5b8b;
/* 灰色,用于一些通用的、不太突出的元素颜色表示 */
--color-grey: #869194;
/* 红色的透明版本透明度为0.1,用于元素的淡红色背景、边框等效果 */
--color-red-a1: rgba(233, 84, 107, 0.1);
/* 红色的透明版本透明度为0.3,例如用于元素的红色阴影、提示背景等效果 */
--color-red-a3: rgba(233, 84, 107, 0.3);
/* 粉色的透明版本透明度为0.3,用于粉色系的半透明效果元素 */
--color-pink-a3: rgba(237, 110, 160, 0.3);
/* 浅粉色的透明版本透明度为0.3,用于特定的浅粉色半透明效果 */
--color-pink-light-a3: rgba(255, 230, 250, 0.3);
/* 浅粉色的透明版本透明度为0.5,用于不同程度的半透明浅粉色效果 */
--color-pink-light-a5: rgba(255, 230, 250, 0.5);
/* 浅粉色的透明版本透明度为0.7,用于更明显的浅粉色半透明覆盖等效果 */
--color-pink-light-a7: rgba(255, 230, 250, 0.7);
/* 页面主体背景的阴影颜色通常用于元素的投影效果等这里使用灰色2 */
--body-bg-shadow: var(--grey-2);
/* 盒子元素如卡片、容器等的阴影颜色使用黑色的透明版本透明度0.1 */
--box-bg-shadow: var(--grey-9-a1);
/* 页面文本的主要颜色使用灰色7 */
--text-color: var(--grey-7);
/* 头部区域文本的颜色,使用白色 */
--header-text-color: var(--grey-0);
/* 主要颜色,这里定义为红色,用于重要元素、突出显示的按钮等 */
--primary-color: var(--color-red);
/* 导航栏的背景,使用线性渐变,颜色从浅青色到浅粉色 */
--nav-bg: linear-gradient(-225deg, var(--color-cyan-light) 0, var(--color-pink-light) 100%);
/* 页脚的背景,使用多色线性渐变,营造多彩效果 */
--footer-bg: linear-gradient(-45deg, #ec8c69, #e9546b, #38a1db, #23d5ab);
/* 笔记相关的边框颜色 */
--note-border: #cda0c7;
/* 笔记的背景颜色 */
--note-bg: #fdf8ff;
/* 笔记的文本颜色 */
--note-text: #8a51c0;
/* 笔记元素鼠标悬停时的颜色,用于交互效果 */
--note-hover: #f14668;
/* 评论按钮的颜色 */
--comment-btn: #ed9abb;
}
/* 黑夜颜色 */
/* 黑夜颜色相关的变量定义,当页面主题切换为黑暗模式(通过 [theme="dark"] 选择器匹配)时生效 */
[theme="dark"]:root {
/* 评论框的背景颜色使用灰色1 */
--comment-color: var(--grey-1);
/* 黑色,用于背景等元素,与白天的白色对应 */
--grey-0: #222;
/* 较深的灰色,用于不同场景下较浅的背景等,比白天的对应颜色深 */
--grey-1: #21252b;
/* 另一种更深一点的灰色,用于页面中不同的灰色调背景或元素颜色区分 */
--grey-2: #363636;
/* 再深一点的灰色,可用于边框、阴影等元素的颜色设置 */
--grey-3: #444;
/* 中等灰度,用于一些次要元素的文本颜色等,比白天的对应颜色浅 */
--grey-4: #666;
/* 较深的灰色,用于不太重要的提示性文本等颜色设置,比白天的对应颜色浅 */
--grey-5: #aaa;
/* 更深的灰色,用于一些弱化显示的元素文本颜色等,比白天的对应颜色浅 */
--grey-6: #ccc;
/* 很深的灰色,常用于需要突出对比的场景下,比白天的对应颜色浅 */
--grey-7: #ddd;
/* 非常深的灰色,接近白色,可用于特定的浅色元素 */
--grey-8: #eee;
/* 白色,用于一些特定的背景、文本等颜色需求,与白天的黑色对应 */
--grey-9: #f7f7f7;
/* 灰色1的透明版本透明度为0.7,可用于半透明的覆盖、阴影等效果,颜色比白天的对应版本深 */
--grey-1-a7: rgba(34, 34, 34, 0.7);
/* 灰色1的透明版本透明度为0.5,常用于元素半透明背景等,颜色比白天的对应版本深 */
--grey-1-a5: rgba(34, 34, 34, 0.5);
/* 灰色1的透明版本透明度为0.3,例如用于元素的淡阴影效果等,颜色比白天的对应版本深 */
--grey-1-a3: rgba(34, 34, 34, 0.3);
/* 灰色1的透明版本透明度为0完全透明可用于叠加效果等场景颜色比白天的对应版本深 */
--grey-1-a0: rgba(34, 34, 34, 0);
/* 白色的透明版本透明度为0.1,常用于阴影、边框等元素营造淡淡的白色效果,与白天的黑色透明版本对应 */
--grey-9-a1: rgba(51, 51, 51, 0.1);
/* 灰色2的透明版本透明度为0完全透明用于特定的透明效果场景颜色比白天的对应版本深 */
--grey-2-a0: rgba(54, 54, 54, 0);
/* 浅粉色,用于特定元素的背景、装饰等颜色,颜色比白天的对应版本深 */
--color-pink-light: #322d31;
/* 浅青色,用于页面中一些需要清新色调的元素背景等,颜色比白天的对应版本深 */
--color-cyan-light: #2d3230;
/* 红色,常作为重要元素、操作按钮等的突出颜色,颜色变为半透明的红色调 */
--color-red: rgba(237, 118, 137, 0.9);
/* 粉色,可用于不同功能按钮、提示元素等的颜色,颜色变为半透明的粉色调 */
--color-pink: rgba(241, 139, 179, 0.8);
/* 橙色,用于页面中需要醒目显示的元素颜色,颜色变为半透明的橙色调 */
--color-orange: rgba(240, 163, 135, 0.8);
/* 黄色,例如可用于警告、提醒类元素的颜色,颜色比白天的对应版本稍深 */
--color-yellow: #ffe175;
/* 绿色,常用于表示成功、通过等积极意义的元素颜色,颜色比白天的对应版本深 */
--color-green: #86c59d;
/* 水蓝色,用于一些特定的装饰性元素颜色,颜色比白天的对应版本深 */
--color-aqua: #97d3d6;
/* 蓝色,常用于链接、选中状态等元素的颜色,颜色比白天的对应版本深 */
--color-blue: #9cd0ed;
/* 紫色,用于区分不同类型的元素等颜色设置,颜色比白天的对应版本深 */
--color-purple: #cfacc5;
/* 灰色,用于一些通用的、不太突出的元素颜色表示,颜色比白天的对应版本深 */
--color-grey: #c3c8ca;
/* 页面主体背景的阴影颜色,使用黑色,与白天不同 */
--body-bg-shadow: #000;
/* 盒子元素(如卡片、容器等)的阴影颜色,使用黑色,与白天不同 */
--box-bg-shadow: #000;
/* 页面文本的主要颜色使用灰色5 */
--text-color: var(--grey-5);
/* 头部区域文本的颜色,使用白色,与白天不同 */
--header-text-color: var(--grey-9);
/* 笔记的背景颜色,使用半透明的深灰色调 */
--note-bg: rgba(48, 49, 50, 0.8);
/* 笔记的文本颜色,使用半透明的浅蓝色调 */
--note-text: rgba(109, 164, 219, 0.8);
/* 笔记元素鼠标悬停时的颜色,用于交互效果,使用半透明的红色调 */
--note-hover: rgba(168, 49, 72, 0.8);
/* 对于所有图片元素,在黑暗模式下应用亮度滤镜,使其整体亮度降低,呈现更暗的效果 */
img {
filter: brightness(0.8);
}
}
/* 滚动条*/
/* 滚动条相关样式针对webkit内核浏览器如Chrome、Safari等 */
::-webkit-scrollbar {
/* 设置滚动条的宽度 */
width: 0.5rem;
/* 设置滚动条的高度 */
height: 0.5rem;
}
::-webkit-scrollbar-track {
/* 设置滚动条轨道(即滚动条的背景区域)的边框圆角 */
border-radius: 2em;
}
::-webkit-scrollbar-thumb {
/* 设置滚动条滑块(可拖动部分)的背景颜色为浅粉色 */
background-color: var(--color-pink-light);
/* 设置滚动条滑块的背景图片为线性渐变,用于营造一种有质感的效果 */
background-image: -webkit-linear-gradient(45deg,
hsla(0, 0%, 100%, 0.4) 25%,
transparent 0,
@ -112,16 +200,17 @@
hsla(0, 0%, 100%, 0.4) 75%,
transparent 0,
transparent);
/* 设置滚动条滑块的边框圆角 */
border-radius: 2em;
}
::-webkit-scrollbar-corner {
/* 设置滚动条角落(当水平和垂直滚动条相交处)的背景颜色为透明 */
background-color: transparent;
}
/* 加载进度条 */
#nprogress .bar {
background: var(--primary-color) !important;
/* 加载进度条相关样式,通过 #nprogress.bar 选择器匹配加载进度条元素 */
#nprogress.bar {
/* 设置加载进度条的背景颜色,使用主要颜色(白天为红色,黑暗模式下对应相应颜色),通过!important提高优先级确保样式生效 */
background: var(--primary-color)!important;
}

@ -1,106 +1,150 @@
// slideUpBigIn
@keyframes slideUpBigIn {
// 0%
0% {
// 0
opacity: 0;
// 80px使80px
transform: translateY(80px);
}
// 100%
100% {
// 1
opacity: 1;
// 0使
transform: translateY(0);
}
}
// slideDownIn
@keyframes slideDownIn {
// 0%
0% {
// 0
opacity: 0;
// 50px使50px
transform: translateY(-50px);
}
// 100%
100% {
// 1
opacity: 1;
// 0使
transform: translateY(0);
}
}
// slideUpIn
@keyframes slideUpIn {
// 0%
0% {
// 0
opacity: 0;
// 40px使40px
transform: translateY(40px);
}
// 100%
100% {
// 1
opacity: 1;
// 0使
transform: translateY(0);
}
}
// titleScale
@keyframes titleScale {
// 0%
0% {
// 0
opacity: 0;
// 0.7
transform: scale(0.7);
}
// 100%
100% {
// 1
opacity: 1;
// 1
transform: scale(1);
}
}
// author-shake
@keyframes author-shake {
// 0%
0% {
//
transform: scale(1);
}
// 10%20%
10%,
20% {
// 0.93
transform: scale(0.9) rotate(3deg);
}
// 30%50%70%90%
30%,
50%,
70%,
90% {
// 1.13
transform: scale(1.1) rotate(-3deg);
}
// 40%60%80%
40%,
60%,
80% {
// 1.13
transform: scale(1.1) rotate(3deg);
}
// 100%
100% {
//
transform: scale(1);
}
}
// blur
@keyframes blur {
// 0%
0% {
// 10px使
filter: blur(10px);
}
// 100%
100% {
// 使
filter: blur(0);
}
}
// sidebarItem
@keyframes sidebarItem {
// 0%
0% {
// 200px使200px
transform: translateX(200px);
}
// 100%
100% {
// 0使
transform: translateX(0);
}
}
// zoomIn
@keyframes zoomIn {
// 0%
0% {
// 0
opacity: 0;
// xyz0.3使
transform: scale3d(0.3, 0.3, 0.3);
}
// 50%
50% {
// 1
opacity: 1;
}
}

@ -1,35 +1,44 @@
<template>
<!-- 定义一个外层的 div 容器应用了名为'menu'的类用于包裹整个菜单相关内容 -->
<div class="menu">
<!-- 显示用户名的菜单项使用双花括号插值表达式绑定了名为'username'的变量用于展示用户昵称 -->
<div class="menu-item title-index">
{{ username }}
</div>
<!-- 使用 v-for 指令循环遍历'menuList'数组来生成菜单项每个菜单项根据'menu'对象的属性进行渲染同时设置了 :key 绑定用于 Vue 的虚拟 DOM 高效更新 -->
<template v-for="menu of menuList" :key="menu.name">
<!-- 根据条件判断如果'menu'对象存在则渲染这个菜单项并且通过动态绑定类名当菜单项的'name'属性为'首页'添加'active' -->
<div v-if="menu" class="menu-item" :class="{ active: '首页' === menu.name }">
<!-- 创建一个可点击的菜单项按钮通过 :to 属性绑定菜单对应的路由路径点击时会触发'go'方法并传入路径参数 -->
<div :to="menu.path" class="menu-btn" @click='go(menu.path)'>
<svg-icon :icon-class="menu.icon"></svg-icon> {{ menu.name }}
<!-- 使用'svg-icon'组件展示对应菜单的图标通过绑定':icon-class'属性传入图标名称这里图标名称由'menu'对象的'icon'属性指定 -->
<svg-icon :icon-class="menu.icon"></svg-icon>
<!-- 显示菜单的名称同样通过双花括号插值表达式绑定'menu'对象的'name'属性 -->
{{ menu.name }}
</div>
</div>
</template>
</div>
</template>
<script setup lang="ts">
// Vue Router
import router from "@/router";
// store
import useStore from "@/store";
//
// store 'user'
const { user } = useStore();
// 'username' String 'user' 'getUser'
const username: String = user.getUser();
//
// 'menuList'
const menuList = [
{
name: "首页",
icon: "home",
path: "/#"
},
//
// {
// name: "",
// icon: "archives",
@ -42,19 +51,21 @@ const menuList = [
},
];
//
// 'go' 'path'
const go = (path: string) => {
// '/' B URL URL
if (path === "/") {
// BURL
// B URL
window.location.href = "https://space.bilibili.com/279540198";
}
// '/archives'使 Vue Router 'push' '/manage'
if (path === "/archives") {
router.push("/manage")
}
}
</script>
<style lang="scss" scoped>
//
.user-avatar {
display: inline-block;
position: relative;
@ -65,12 +76,14 @@ const go = (path: string) => {
cursor: pointer;
}
// 使使
.menu {
display: flex;
align-items: center;
height: 100%;
}
//
.menu-item {
position: relative;
display: inline-block;
@ -79,7 +92,8 @@ const go = (path: string) => {
font-size: 17px;
text-align: center;
&:not(.title-index) .menu-btn::before {
// 'title-index''menu-btn' ::before 0
&:not(.title-index).menu-btn::before {
content: "";
position: absolute;
width: 0;
@ -92,16 +106,19 @@ const go = (path: string) => {
transition: all 0.4s ease-in-out 0s;
}
&:hover .submenu {
// .submenu
&:hover.submenu {
display: block;
}
}
.menu-item.active:not(.dropdown) .menu-btn::before,
.menu-item:not(.dropdown):hover .menu-btn::before {
// .active.dropdown'menu-btn' ::before 70%线
.menu-item.active:not(.dropdown).menu-btn::before,
.menu-item:not(.dropdown):hover.menu-btn::before {
width: 70%;
}
// .submenudisplay: none使'slideUpIn' 0.3
.submenu {
display: none;
position: absolute;
@ -114,6 +131,7 @@ const go = (path: string) => {
border-radius: 0.625rem 0;
animation: slideUpIn 0.3s;
// ::before
&::before {
position: absolute;
top: -1.25rem;
@ -124,41 +142,49 @@ const go = (path: string) => {
}
}
// .subitem
.subitem {
display: block;
font-size: 1rem;
//
&:first-child {
border-radius: 0.625rem 0 0 0;
}
//
&:last-child {
border-radius: 0 0 0.625rem 0;
}
.link {
// .link
.link {
display: inline-block;
padding: 0.3rem 0.7rem;
width: 100%;
text-shadow: none;
}
&:hover .link {
// .link 0.3rem
&:hover.link {
transform: translateX(0.3rem);
}
}
.submenu .subitem.active,
.submenu .subitem:hover {
// 线
.submenu.subitem.active,
.submenu.subitem:hover {
color: var(--grey-0);
background-image: linear-gradient(to right, var(--color-pink) 0, var(--color-orange) 100%);
box-shadow: 0 0 0.75rem var(--color-pink-a3);
}
.sub.menu .submenu {
// 'sub.menu'.submenu '.submenu' --grey-1
.sub.menu.submenu {
background-color: var(--grey-1);
}
// 'drop' ::after 使
.drop::after {
content: "";
display: inline-block;
@ -168,17 +194,18 @@ const go = (path: string) => {
border-bottom: 0;
}
// 865px 使'title-index'
@media (max-width: 865px) {
.menu {
.menu {
justify-content: center;
}
.menu .menu-item {
.menu.menu-item {
display: none;
}
.menu .title-index {
.menu.title-index {
display: block;
}
}
</style>
</style>

@ -1,134 +1,180 @@
<template>
<!-- 定义页面头部的外层包装元素应用了名为.header-wrapper的类并通过绑定:class指令动态添加fixedClass变量对应的类名 -->
<header class="header-wrapper" :class="fixedClass">
<!-- 切换按钮 -->
<!-- 切换按钮部分此处被注释掉了可能原本是用于实现某种切换功能的自定义组件 -->
<!-- <Toggle></Toggle> -->
<!-- 菜单 -->
<!-- 菜单部分使用NavBar组件并且通过绑定:class指令根据变量y的值来动态添加名为sub的类当y > 0时添加 -->
<NavBar :class="{ sub: y > 0 }"></NavBar>
<!-- 右侧按钮 -->
<!-- 右侧按钮部分使用无序列表ul来组织按钮元素 -->
<ul class="right">
<!-- <li class="subitem" v-show="showLogin === 1">
<!-- 以下是登录相关的按钮部分根据showLogin变量的值来决定是否显示当前被注释掉了
<li class="subitem" v-show="showLogin === 1">
<a class="link" @click="login"><svg-icon icon-class="author"></svg-icon> </a>
</li>
<li class="subitem" v-show="showLogin === 0">
<a class="link" @click="logout"><svg-icon icon-class="logout"></svg-icon> 退 </a>
</li> -->
<!-- 管理中心按钮点击时会触发goManager方法 -->
<li class="subitem">
<a class="link" @click="goManager"><svg-icon icon-class="archives"></svg-icon> </a>
</li>
</ul>
</header>
</template>
<script setup lang="ts">
// Element PlusElMessage
import { ElMessage } from "element-plus";
// Vueref
import { ref } from 'vue';
// Vue Router
import router from "@/router";
// VueUseuseScroll
import { useScroll } from "@vueuse/core";
// store
import useStore from "@/store";
//
// storeuser
const { user } = useStore();
const showLogin = ref(0); // 0
// showLogin0/退
const showLogin = ref(0);
// showLogin""showLogin10退
if (user.getUser() == "游客") {
showLogin.value = 1; // 1
showLogin.value = 1;
} else {
showLogin.value = 0; // 0
showLogin.value = 0;
}
// goManagerwindow.openURL
const goManager = () => {
window.open("http://127.0.0.1:8080");
};
//
// 使router.push/login
const login = () => {
router.push("/login")
};
// 退
// 退退
const logout = () => {
// userLogOut退Promisethen
user.LogOut().then((data: any) => {
// "退
// 退
console.log(data)
// code200退
if (data.code === 200) {
// 使ElMessage退
ElMessage({
message: "账号已退出",
type: "success",
duration: 1 * 1000,
//
onClose: () => {
window.location.reload();
},
});
// "/"
router.replace({ path: "/" });
}
}).catch(() => {
// 退
});
};
// 使useScrollwindowy
const { y } = useScroll(window);
// fixedClass
const fixedClass = ref("");
// 使watchyy
watch(y, (newValue, oldValue) => {
// y0
if (newValue > 0) {
// newValueoldValueshowup
if (newValue < oldValue) {
fixedClass.value = "show up";
} else {
// showdown
fixedClass.value = "show down";
}
} else {
// y0fixedClass
fixedClass.value = "";
}
});
</script>
<style lang="scss" scoped>
//
.header-wrapper {
// 使
position: fixed;
// 使使
display: flex;
flex-wrap: nowrap;
// 使
align-items: center;
// 使
justify-content: space-between;
//
width: 100%;
// 3.125rem
height: 3.125rem;
// 1rem0
padding: 0 1rem;
// 50%
text-shadow: 0 0.2rem 0.3rem rgb(0 0 0 / 50%);
// --header-text-color
color: var(--header-text-color);
// 0.2
transition: all 0.2s ease-in-out 0s;
// z-index使
z-index: 9;
}
// show
.show {
// --nav-bg 线
background: var(--nav-bg);
// --grey-9-a1
box-shadow: 0.1rem 0.1rem 0.2rem var(--grey-9-a1);
// --grey-9-a1
text-shadow: 0 0 0.625rem var(--grey-9-a1);
// --text-color
color: var(--text-color);
}
// up
.up {
// 0使
transform: translateY(0);
}
// down100%
.down {
transform: translateY(-100%);
}
// .right
.right {
// 便
display: inline-flex;
// 使
align-items: center;
// 使
justify-content: center;
//
height: 100%;
.item {
// .item
.item {
padding: 0.625rem 0.5rem;
}
}
// 991px
@media (max-width: 991px) {
.header-wrapper {
.header-wrapper {
padding: 0;
}
}
</style>

@ -1,58 +1,150 @@
<template>
<!-- 使用 SVG可缩放矢量图形元素来创建波浪图形定义了 XML 命名空间以及视图框等属性preserveAspectRatio="none" 表示不保持纵横比shape-rendering="auto" 用于设置图形渲染的质量模式为自动 -->
<svg class="waves" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 24 150 28"
preserveAspectRatio="none" shape-rendering="auto">
<!--形状容器-->
<!-- 定义图形元素的复用资源区域defs在这里面定义的图形可以通过 <use> 元素在其他地方复用 -->
<defs>
<!-- 使用 <path> 元素定义一个名为 "gentle-wave" 的路径其具体的路径数据通过 "d" 属性指定这个路径描述了波浪的形状后续会通过复用该路径来生成多个波浪实例 -->
<path id="gentle-wave" d="M-160 44c30 0 58-18 88-18s 58 18 88 18 58-18 88-18 58 18 88 18 v44h-352z">
</path>
</defs>
<!--组合波浪-->
<!-- 创建一个分组g元素应用了 "parallax" 用于将多个复用的波浪元素组合在一起方便进行统一的样式设置和动画控制 -->
<g class="parallax">
<!-- 通过 <use> 元素复用之前定义的 "gentle-wave" 路径创建一个波浪实例设置其在 x 轴方向上的位置为 48 像素y 轴方向位置为 0 像素并且应用了 "use" -->
<use class="use" xlink:href="#gentle-wave" x="48" y="0"></use>
<!-- 同样复用 "gentle-wave" 路径创建另一个波浪实例y 轴位置稍有不同用于营造层次感其他属性类似 -->
<use class="use" xlink:href="#gentle-wave" x="48" y="3"></use>
<use class="use" xlink:href="#gentle-wave" x="48" y="5"></use>
<use class="use" xlink:href="#gentle-wave" x="48" y="7"></use>
</g>
</svg>
</template>
<style lang="scss" scoped>
// "waves" SVG
.waves {
// 便
position: absolute;
// 使
left: 0;
//
bottom: 0;
//
width: 100%;
// 12%使 vhviewport height使
height: 12vh;
// 3.125rem
min-height: 3.125rem;
// 9.375rem
max-height: 9.375rem;
// z-index 使 z-index
z-index: 1;
}
// "parallax" "use"
.parallax {
.use {
// "move-forever" 25 使线cubic-bezier
animation: move-forever 25s cubic-bezier(0.55, 0.5, 0.45, 0.5) infinite;
// :nth-child(1)
&:nth-child(1) {
animation-delay: -2s;
animation-duration: 7s;
fill: var(--grey-1-a7);
}
// :nth-child(2)使
&:nth-child(2) {
animation-delay: -3s;
animation-duration: 10s;
fill: var(--grey-1-a5);
}
// :nth-child(3)
&:nth-child(3) {
animation-delay: -4s;
animation-duration: 13s;
fill: var(--grey-1-a3);
}
// :nth-child(4)
&:nth-child(4) {
animation-delay: -5s;
animation-duration: 20s;
fill: var(--grey-1);
}
}
}
<script setup lang="ts">
</script>
// "move-forever"
@keyframes move-forever {
// 0%沿 x 90
0% {
transform: translate3d(-90px, 0, 0);
}
// 100%沿 x 85 使
100% {
transform: translate3d(85px, 0, 0);
}
}
// 768px "waves" 使
@media (max-width: 768px) {
.waves {
height: 10vh;
min-height: 10vh;
}
}
</style>
<style lang="scss" scoped>
// "waves" SVG
.waves {
// 便
position: absolute;
// 使
left: 0;
//
bottom: 0;
//
width: 100%;
// 12%使 vhviewport height使
height: 12vh;
// 3.125rem
min-height: 3.125rem;
// 9.375rem
max-height: 9.375rem;
// z-index 使 z-index
z-index: 1;
}
// "parallax" "use"
.parallax {
.use {
.use {
// "move-forever" 25 使线cubic-bezier
animation: move-forever 25s cubic-bezier(0.55, 0.5, 0.45, 0.5) infinite;
// :nth-child(1)
&:nth-child(1) {
animation-delay: -2s;
animation-duration: 7s;
fill: var(--grey-1-a7);
}
// :nth-child(2)使
&:nth-child(2) {
animation-delay: -3s;
animation-duration: 10s;
fill: var(--grey-1-a5);
}
// :nth-child(3)
&:nth-child(3) {
animation-delay: -4s;
animation-duration: 13s;
fill: var(--grey-1-a3);
}
// :nth-child(4)
&:nth-child(4) {
animation-delay: -5s;
animation-duration: 20s;
@ -61,21 +153,23 @@
}
}
/* 波浪动画 */
// "move-forever"
@keyframes move-forever {
// 0%沿 x 90
0% {
transform: translate3d(-90px, 0, 0);
}
// 100%沿 x 85 使
100% {
transform: translate3d(85px, 0, 0);
}
}
// 768px "waves" 使
@media (max-width: 768px) {
.waves {
.waves {
height: 10vh;
min-height: 10vh;
}
}
</style>
</style>

@ -1,40 +1,59 @@
<script lang="ts">
// Vue defineComponent Vue ref
import { defineComponent, ref } from 'vue';
// @element-plus/icons-vue Menu IconMenuMessageSetting
import { Menu as IconMenu, Message, Setting } from '@element-plus/icons-vue'
// 使 defineComponent Vue
export default defineComponent({
// data isMenuOpen false
data() {
return {
isMenuOpen: false,
};
},
// methods
methods: {
// toggleMenu isMenuOpen
toggleMenu() {
this.isMenuOpen = !this.isMenuOpen;
this.isMenuOpen =!this.isMenuOpen;
},
},
});
</script>
<template>
<!-- 使用 nav 元素作为菜单的外层容器应用了名为 "menu" 的类并通过绑定 :class 指令动态添加类名 isMenuOpen true 时添加 "open" -->
<nav class="menu" :class="{ open: isMenuOpen }">
<!-- 使用 el-tooltip 组件创建一个提示框设置提示效果为 "dark"深色背景样式提示内容为 "切换菜单样式"提示框显示位置为 "right"右侧 -->
<el-tooltip effect="dark" content="切换菜单样式" placement="right">
<!-- 创建一个包含按钮和标题的 div 元素点击该 div 会触发 toggleMenu 方法用于切换菜单的展开与收起状态 -->
<div class="actionsBar" @click="toggleMenu">
<div>
<!-- 创建一个按钮元素设置按钮的 id "menuBtn"按钮类型为 "button"内部使用 el-icon 组件包裹一个图标组件这里是从导入的图标中使用 <icon-menu /> 展示菜单图标 -->
<button id="menuBtn" type="button">
<el-icon><icon-menu /></el-icon>
</button>
<!-- 创建一个 h3 标题元素应用了名为 "menuText" 的类并通过绑定 :class 指令动态添加 "open2" isMenuOpen true 时改变其样式显示菜单相关的文本这里是 "管理菜单" -->
<h3 class="menuText" :class="{ open2: isMenuOpen }">管理菜单</h3>
</div>
</div>
</el-tooltip>
<!-- 创建一个无序列表ul元素应用了名为 "optionsBar" 的类用于展示菜单的各个选项 -->
<ul class="optionsBar">
<!-- 创建一个列表项li元素应用了名为 "menuItem" 的类用于表示单个菜单选项 -->
<li class="menuItem">
<!-- 使用 el-tooltip 组件为该菜单选项创建一个提示框提示效果为 "dark"提示内容为 "主页"提示框显示位置为 "right" -->
<el-tooltip effect="dark" content="主页" placement="right">
<!-- 使用 RouterLink 组件创建一个路由链接链接到 "/"首页路径应用了名为 "menuOption" 的类用于设置菜单选项的样式 -->
<RouterLink to="/" class="menuOption">
<!-- 使用 el-icon 组件展示一个图标这里使用自定义的 class "iconfont icon-shouye" 的图标 -->
<el-icon class="iconfont icon-shouye"></el-icon>
<!-- 创建一个 h5 标题元素同样应用了 "menuText" 类并通过动态绑定 "open2" 类来根据菜单展开状态改变样式显示菜单选项的文本这里是 "主页" -->
<h5 class="menuText" :class="{ open2: isMenuOpen }">主页</h5>
</RouterLink>
</el-tooltip>
</li>
<!-- 创建一个用于分隔菜单选项的列表项li元素应用了名为 "menuBreak" 的类内部包含一个水平分割线hr元素 -->
<li class="menuBreak">
<hr>
</li>
@ -42,7 +61,7 @@ export default defineComponent({
<el-tooltip effect="dark" content="发布" placement="right">
<RouterLink to="/user/uploads" class="menuOption">
<el-icon size="x-large">
<!-- 替换为 Element Plus 的图标组件 -->
<!-- 这里原本注释提示要替换为 Element Plus 的图标组件当前使用的是 <el-icon-plus> 图标组件可能需要确保正确引入和使用 -->
<el-icon-plus></el-icon-plus>
</el-icon>
<h5 class="menuText" :class="{ open2: isMenuOpen }">控制面板</h5>
@ -50,50 +69,12 @@ export default defineComponent({
</el-tooltip>
</li>
</ul>
<!-- 创建一个空的 div 元素应用了名为 "about" 的类并设置了 id "about"可能用于后续添加关于页面相关的内容或样式 -->
<div class="about" id="about"></div>
</nav>
</template>
<!--
<el-dialog v-model="dialogFormVisible" title="更新个人信息" center draggable>
<div class="fileUpload">
<el-upload v-model:file-list="fileList" ref="upload" action="http://localhost:8000/user/avatar/" :limit="1"
:on-exceed="handleExceed" :auto-upload="false" :on-change="handleChange" :headers="userStore.headersObj"
:on-success="onSuccess" :on-error="onError">
<template #trigger>
<el-button class="btn" type="primary" round>选择一个文件</el-button>
</template>
<template #tip>
<div class="el-upload__tip" style="color:red;text-align: left">
仅限一个文件新文件将会被覆盖
</div>
</template>
</el-upload>
</div>
<div class="fileUpload">
<el-form :model="form" ref="formRef" :rules="rules" label-position="top">
<el-form-item prop="username" label="昵称" label-width="100px" style="margin: 30px;">
<el-input v-model="form.username" maxlength="6" show-word-limit class="my" />
</el-form-item>
<el-form-item prop="signature" label="个性签名" label-width="100px" style="margin: 30px;">
<el-input v-model="form.signature" class="my" />
</el-form-item>
</el-form>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogFormVisible = false" round>取消</el-button>
<el-button type="primary" @click="doUpdate" round>
确认
</el-button>
</span>
</template>
</el-dialog> -->
<style scoped>
/* 名为 "fileUpload" 的类的样式定义,用于设置文件上传相关区域的布局样式,使其内部元素在水平和垂直方向上居中对齐,并且宽度占满父元素 */
.fileUpload {
display: flex;
justify-content: center;
@ -101,11 +82,13 @@ export default defineComponent({
width: 100%;
}
/* 名为 "fileUpload" 类下的按钮元素样式,设置其左边距和下边距,用于调整按钮在页面中的位置布局 */
.fileUpload button {
margin-left: 20px;
margin-bottom: 20px;
}
/* 名为 "overlay" 的类的样式定义,用于创建一个覆盖整个页面的半透明遮罩层,常用于模态框等弹出层的背景效果,设置其位置、尺寸、背景颜色以及较高的 z-index 值以确保覆盖在其他内容之上 */
.overlay {
position: fixed;
top: 0;
@ -115,9 +98,10 @@ export default defineComponent({
background-color: rgba(0, 0, 0, 0.5);
/* 设置透明度的背景色 */
z-index: 9999;
/* 设置一个较大的z-index值确保图层位于其他内容之上 */
/* 设置一个较大的 z-index 值,确保图层位于其他内容之上 */
}
/* 名为 "close" 的类的样式定义,用于设置关闭按钮相关的样式,去除边框,设置其位置、背景颜色以及较高的 z-index 值,使其在页面中处于合适的显示位置并能覆盖其他元素 */
.close {
border: 0;
position: absolute;
@ -125,19 +109,22 @@ export default defineComponent({
top: 18%;
background-color: #fff;
z-index: 1000;
/* 设置一个较大的z-index值确保图层位于其他内容之上 */
/* 设置一个较大的 z-index 值,确保图层位于其他内容之上 */
}
/* 全局的样式重置,将所有元素的外边距和内边距设置为 0并设置盒模型为 border-box使得元素的尺寸计算包含边框和内边距便于布局的统一控制 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* 名为 "iconfont" 的类的样式定义,用于设置图标字体的大小,这里设置为 20px可根据实际需求调整图标大小 */
.iconfont {
font-size: 20px;
}
/* 菜单整体(.menu的样式类定义设置其定位方式为绝对定位指定宽度、背景颜色、z-index 值、在页面中的位置(距离顶部、左侧的距离以及垂直居中),设置边框圆角以及过渡效果等属性,用于营造一个固定位置且有动画效果的菜单样式 */
.menu {
position: absolute;
width: 60px;
@ -154,18 +141,21 @@ export default defineComponent({
height: 100%;
}
/* 菜单内链接a元素的通用样式去除默认的文本下划线装饰用于保持菜单内链接的简洁样式 */
.menu a {
text-decoration: none;
}
.menu .actionsBar {
/* 菜单中操作栏(.actionsBar的样式类定义设置其宽度占满父元素、高度、内边距以及溢出隐藏等属性用于控制操作栏在菜单中的布局和显示效果 */
.menu.actionsBar {
width: 100%;
height: 10%;
padding: 0.5rem;
overflow: hidden;
}
.menu .actionsBar div {
/* 操作栏内的 div 元素样式,设置其宽度、高度、使用弹性布局使其内部元素在水平方向上两端对齐并居中显示,设置边框圆角以及过渡效果等属性,方便对操作栏内按钮和标题等元素的布局管理 */
.menu.actionsBar div {
width: 100%;
height: 100%;
display: flex;
@ -175,7 +165,8 @@ export default defineComponent({
transition: 0.3s ease;
}
.menu .actionsBar div button {
/* 操作栏内按钮的样式定义,设置其背景颜色为透明,去除默认的轮廓线和边框,设置边框圆角、文本颜色、尺寸以及过渡效果等属性,用于创建一个简洁的按钮样式,并且在鼠标悬停时可以改变背景颜色和文本颜色 */
.menu.actionsBar div button {
background-color: transparent;
outline: none;
border: none;
@ -187,18 +178,20 @@ export default defineComponent({
font-size: 1rem;
}
.menu .actionsBar div button:hover {
/* 操作栏内按钮鼠标悬停时的样式变化,改变背景颜色和文本颜色,用于提供交互反馈效果 */
.menu.actionsBar div button:hover {
background-color: #d5d0d0;
color: #F19FA3;
}
.menu .actionsBar div h3 {
/* 操作栏内标题h3元素的样式定义设置其宽度根据按钮宽度自适应剩余空间以及文本居中对齐用于合理布局操作栏内的文本内容 */
.menu.actionsBar div h3 {
width: calc(100% - 45px);
text-align: center;
}
.menu .optionsBar {
/* 菜单中选项栏(.optionsBar的样式类定义设置其溢出隐藏、使用弹性布局按列方向排列内部元素、设置宽度占满父元素、高度以及内边距等属性用于组织管理菜单的各个选项内容 */
.menu.optionsBar {
overflow: hidden;
display: flex;
width: 100%;
@ -208,13 +201,15 @@ export default defineComponent({
flex-direction: column;
}
.menu .optionsBar .menuItem {
/* 选项栏内单个菜单选项(.menuItem的样式定义设置其宽度占满父元素、高度以及外边距等属性用于确定每个菜单选项在选项栏中的基本布局尺寸 */
.menu.optionsBar.menuItem {
width: 100%;
height: 45px;
margin: 0.3rem;
}
.menu .optionsBar .menuItem .menuOption {
/* 菜单选项内链接样式(.menuOption的定义设置字体大小、去除默认的轮廓线和边框、背景颜色为透明使用弹性布局使其内部图标和标题元素在水平方向上两端对齐并居中显示设置边框圆角以及过渡效果等属性用于创建菜单选项的可点击样式效果 */
.menu.optionsBar.menuItem.menuOption {
font-size: 1rem;
outline: none;
border: none;
@ -228,28 +223,30 @@ export default defineComponent({
transition: 0.3s ease;
}
.menu .optionsBar .menuItem .menuOption:hover {
/* 菜单选项内链接鼠标悬停时的样式变化,改变背景颜色和文本颜色,并且使内部图标和标题元素的文本颜色也相应改变,用于突出显示当前悬停的菜单选项 */
.menu.optionsBar.menuItem.menuOption:hover {
background-color: #d5d0d0;
color: #f5131e;
}
.menu .optionsBar .menuItem .menuOption:hover i,
.menu .optionsBar .menuItem .menuOption:hover h5 {
.menu.optionsBar.menuItem.menuOption:hover i,
.menu.optionsBar.menuItem.menuOption:hover h5 {
color: #f5131e;
}
.menu .optionsBar .menuItem .menuOption i {
/* 菜单选项内图标i元素的样式定义设置其宽度、文本居中对齐以及文本颜色用于确定图标在菜单选项中的显示样式 */
.menu.optionsBar.menuItem.menuOption i {
width: 45px;
text-align: center;
color: #000;
}
.menu .optionsBar .menuItem .menuOption h5 {
/* 菜单选项内标题h5元素的样式定义设置其宽度根据图标宽度自适应剩余空间用于合理布局菜单选项内的文本内容 */
.menu.optionsBar.menuItem.menuOption h5 {
width: calc(100% - 45px);
}
/* 菜单文本(.menuText的通用样式定义设置文本颜色、初始时在水平方向上向左平移使其隐藏通过 transform 属性)、设置透明度为 0 以及过渡效果等属性,用于实现菜单展开和收起时文本的动画显示效果 */
.menuText {
color: #000;
transform: translateX(-250px);
@ -257,7 +254,8 @@ export default defineComponent({
transition: transform 0.3s ease 0.1s;
}
.menu .about {
/* 菜单中关于区域(.about的样式类定义设置其宽度占满父元素、高度、使用弹性布局使其内部元素在垂直方向上底部对齐并居中显示设置内边距、字体样式以及初始时透明度为 0 和过渡效果等属性,可能用于后续添加关于页面相关的内容展示及动画效果 */
.menu.about {
width: 100%;
height: 10%;
display: flex;
@ -271,18 +269,21 @@ export default defineComponent({
transition: 0.3s ease 0.15s;
}
/* 当菜单(.menu添加 "open" 类时的样式变化,改变其宽度使其展开,设置透明度,用于实现菜单展开时的动画效果和样式调整 */
.menu.open {
width: 240px;
opacity: 0.9;
}
/* 当菜单文本(.menuText添加 "open2" 类时的样式变化,改变其透明度为 1将水平方向的平移变换还原为 0 使其显示出来,并设置文本居中对齐,用于实现菜单展开时文本的显示动画效果 */
.menuText.open2 {
opacity: 1;
transform: translateX(0);
text-align: center;
}
.menu .menuBreak {
/* 菜单中用于分隔的列表项(.menuBreak的样式定义设置其宽度占满父元素、高度以及使用弹性布局使其内部元素在水平方向上居中显示用于控制水平分割线在菜单中的布局位置 */
.menu.menuBreak {
width: 100%;
height: 10px;
display: flex;
@ -290,7 +291,8 @@ export default defineComponent({
justify-content: center;
}
.menu .menuBreak hr {
/* 菜单分隔列表项内水平分割线hr元素的样式定义设置其宽度、高度、背景颜色以及去除默认边框等属性用于创建一个自定义样式的水平分割线 */
.menu.menuBreak hr {
width: 70%;
height: 3px;
background-color: #000;
@ -298,132 +300,15 @@ export default defineComponent({
border-radius: 5px;
}
.menu .themeBar {
/* 菜单中主题栏(.themeBar的样式类定义当前代码中未看到具体使用场景可能是预留用于后续添加主题切换相关功能的布局样式设置其溢出隐藏、宽度占满父元素、高度以及内边距等属性与操作栏等类似的布局控制方式 */
.menu.themeBar {
overflow: hidden;
width: 100%;
height: 10%;
padding: 0.5rem;
}
.menu .themeBar div {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: space-around;
border-radius: 0.5rem;
transition: 0.3s ease;
}
.menu .themeBar div button {
background-color: transparent;
outline: none;
border: none;
border-radius: 0.5rem;
color: #000;
width: 100%;
height: 45px;
transition: 0.3s ease;
font-size: 1rem;
}
.menu .themeBar div button:hover {
background-color: #d5d0d0;
color: #f5131e;
}
.menu .menuUser {
width: 100%;
height: 10%;
}
.menu .menuUser a {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
text-decoration: none;
padding: 0.5rem;
position: relative;
}
.menu .menuUser a div {
width: 45px;
height: 45px;
position: relative;
border-radius: 0.5rem;
}
.menu .menuUser a div img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 0.5rem;
}
.menu .menuUser a .Username {
width: calc(70% - 45px);
}
.menu .menuUser a p {
width: calc(30% - 45px);
}
.menu .menuUser a:hover p {
animation: animArrow 0.3s ease 2;
color: #F19FA3;
}
@keyframes animArrow {
0% {
transform: translateX(0);
}
50% {
transform: translateX(5px);
}
100% {
transform: translateX(0);
}
}
.menu .menuUser .userInfo {
position: absolute;
width: 20rem;
height: 18rem;
opacity: 0;
pointer-events: none;
top: 34%;
left: 1.5rem;
transition: 0.3s ease;
transform: scale(0);
transform-origin: bottom left;
}
.menu .menuUser:hover .userInfo {
pointer-events: all;
opacity: 1;
transform: scale(1);
color: #F19FA3;
}
.el-button--text {
margin-right: 15px;
}
.el-select {
width: 300px;
}
.el-input {
width: 300px;
}
.dialog-footer button:first-child {
margin-right: 10px;
}
/* 主题栏内的 div 元素样式定义,设置其宽度、高度、使用弹性布局使其内部元素在水平方向上两端对齐并居中显示,设置边框圆角以及过渡效果等属性,与操作栏内 div 元素类似的布局和交互效果设置 */
.menu.themeBar div {
width: 100%
</style>

@ -1,8 +1,13 @@
<template>
<!-- 定义一个名为app-main的section元素作为主要的页面容器 -->
<section class="app-main">
<!-- router-view用于展示路由对应的组件内容通过v-slot接收组件相关信息 -->
<router-view v-slot="{ Component, route }">
<!-- 使用过渡效果名称为fade-transform模式为out-in实现组件切换时的过渡动画 -->
<transition name="fade-transform" mode="out-in">
<!-- keep-alive用于缓存组件避免组件重复创建和销毁提高性能 -->
<keep-alive>
<!-- 根据传入的Component动态渲染组件并以route.fullPath作为组件的唯一标识key -->
<component :is="Component" :key="route.fullPath" />
</keep-alive>
</transition>
@ -11,37 +16,42 @@
</template>
<script setup lang="ts">
// JavaScript
</script>
<style lang="scss" scoped>
/* 定义app-main类的样式规则 */
.app-main {
/* 50= navbar 50 */
/* 通过计算100vh视口高度减去50px来设置最小高度可能是预留了顶部导航栏高度为50px的空间 */
min-height: calc(100vh - 50px);
width: 100%;
position: relative;
overflow: hidden;
}
.fixed-header+.app-main {
/* 当存在.fixed-header类并且与.app-main相邻时给.app-main添加顶部内边距50px */
.fixed-header +.app-main {
padding-top: 50px;
}
.hasTagsView {
.app-main {
/* 84 = navbar + tags-view = 50 + 34 */
/* 针对.hasTagsView类下的.app-main元素设置样式 */
.app-main {
/* 通过计算100vh减去84px来设置最小高度可能此时页面布局包含了高度为50px的导航栏和高度为34px的其他元素比如标签栏之类 */
min-height: calc(100vh - 84px);
}
.fixed-header+.app-main {
/* 当存在.fixed-header类并且与.hasTagsView下的.app-main相邻时给该.app-main添加顶部内边距84px */
.fixed-header +.app-main {
padding-top: 84px;
}
}
</style>
<style lang="scss">
// fix css style bug in open el-dialog
// el-dialogCSSBug
// .el-popup-parent--hidden.fixed-header17px
.el-popup-parent--hidden {
.fixed-header {
.fixed-header {
padding-right: 17px;
}
}

@ -1,42 +1,53 @@
<template>
<!-- 定义一个具有navbar类名的div元素作为导航栏的外层容器 -->
<div class="navbar">
<!-- 折叠按钮 -->
<!-- 折叠按钮组件使用了hamburger组件并添加了hamburger-container类名用于样式控制 -->
<hamburger class="hamburger-container"></hamburger>
<!-- 面包屑 -->
<!-- 面包屑组件使用了breadcrumb组件并添加了breadcrumb-container类名用于样式控制 -->
<breadcrumb class="breadcrumb-container"></breadcrumb>
<div class="right-menu">
<!-- 根据条件此处固定为1实际可能是更复杂的条件判断决定是否渲染内部元素 -->
<template v-if="1">
<!-- 博客首页 -->
<!-- el-tooltip组件用于展示提示信息当鼠标悬停在对应元素上时显示博客首页提示效果为暗色位置在底部 -->
<el-tooltip content="博客首页" effect="dark" placement="bottom">
<!-- 定义一个具有right-menu-item和hover-effect类名的div元素作为博客首页操作的容器添加了鼠标悬停效果 -->
<div class="right-menu-item hover-effect">
<!-- svg-icon组件绑定点击事件openHome用于显示指定的图标这里是home图标设置图标大小为1.2rem -->
<svg-icon @click="openHome" icon-class="home" size="1.2rem" />
</div>
</el-tooltip>
<!-- 修改密码 -->
<!-- 类似上述el-tooltip组件用于展示修改密码提示信息鼠标悬停时显示 -->
<el-tooltip content="修改密码" effect="dark" placement="bottom">
<!-- password组件作为修改密码操作的元素同样添加了right-menu-item和hover-effect类名以实现鼠标悬停效果 -->
<password class="right-menu-item hover-effect"></password>
</el-tooltip>
<!-- 全屏 -->
<!-- screenfull组件用于实现全屏相关操作添加了right-menu-item和hover-effect类名 -->
<screenfull class="right-menu-item hover-effect"></screenfull>
<!-- 布局大小 -->
<!-- el-tooltip组件用于展示布局大小提示信息鼠标悬停时显示 -->
<el-tooltip content="布局大小" effect="dark" placement="bottom">
<!-- size-select组件用于布局大小相关操作添加了right-menu-item和hover-effect类名 -->
<size-select class="right-menu-item hover-effect" />
</el-tooltip>
</template>
<!-- el-dropdown组件用于创建下拉菜单绑定了command事件处理函数handleCommand添加了相关类名 -->
<el-dropdown @command="handleCommand" class="avatar-container right-menu-item hover-effect" trigger="click">
<!-- 头像 -->
<!-- 定义一个包含头像及下拉箭头元素的div作为头像相关操作的外层容器 -->
<div class="avatar-wrapper">
<!-- 用于展示用户头像的img元素实际可能通过绑定属性等方式设置具体头像图片 -->
<img class="user-avatar" />
<!-- el-icon组件使用了caret-bottom图标用于显示下拉箭头 -->
<el-icon class="el-icon-caret-bottom">
<caret-bottom />
</el-icon>
</div>
<!-- 选项 -->
<!-- 使用template插槽来定义下拉菜单的具体内容 -->
<template #dropdown>
<el-dropdown-menu>
<!-- el-dropdown-item组件定义一个下拉菜单项点击时触发command为setLayout的事件 -->
<el-dropdown-item command="setLayout">
<span>布局设置</span>
</el-dropdown-item>
<!-- 类似上述el-dropdown-item组件点击时触发command为logout的事件且添加了分割线样式 -->
<el-dropdown-item divided command="logout">
<span>退出登录</span>
</el-dropdown-item>
@ -48,21 +59,34 @@
</template>
<script setup lang="ts">
//
import breadcrumb from "@/components/Breadcrumb/index.vue";
//
import hamburger from "@/components/Hamburger/index.vue";
//
import password from "@/components/Password/index.vue";
//
import Screenfull from '@/components/Screenfull/index.vue';
//
import SizeSelect from '@/components/SizeSelect/index.vue';
// Vuex store
import useStore from "@/store";
// Vuecomputed使
import { computed } from "vue";
// openHomehttps://www.ttkwsd.top
const openHome = () => {
window.open("https://www.ttkwsd.top");
};
// handleCommandcommand
const handleCommand = (command: string) => {
switch (command) {
// commandsetLayoutsetLayout
case "setLayout":
setLayout();
break;
// commandlogoutlogout
case "logout":
logout();
break;
@ -70,47 +94,61 @@ const handleCommand = (command: string) => {
break;
}
};
// logout退
const logout = () => {
};
// emitssetLayout
const emits = defineEmits(['setLayout']);
// setLayoutemitssetLayout
const setLayout = () => {
emits('setLayout');
};
</script>
<style lang="scss" scoped>
/* 定义navbar类的样式规则 */
.navbar {
/* 设置导航栏高度为50px */
height: 50px;
overflow: hidden;
position: relative;
display: flex;
background-color: var(--el-bg-color);
.hamburger-container {
/* 定义hamburger-container类的样式规则用于折叠按钮所在容器的样式控制 */
.hamburger-container {
line-height: 46px;
height: 100%;
float: left;
cursor: pointer;
/* 鼠标悬停时的背景样式变化,添加一个透明度较低的背景色 */
&:hover {
background: rgba(0, 0, 0, 0.025);
}
}
.breadcrumb-container {
/* 定义breadcrumb-container类的样式规则用于面包屑所在容器的样式控制 */
.breadcrumb-container {
float: left;
}
.right-menu {
/* 定义right-menu类的样式规则用于导航栏右侧菜单区域的样式控制 */
.right-menu {
margin-left: auto;
height: 100%;
line-height: 50px;
/* 去除聚焦时的默认轮廓样式 */
&:focus {
outline: none;
}
.right-menu-item {
/* 定义right-menu-item类的样式规则用于右侧菜单各子项的通用样式控制 */
.right-menu-item {
display: inline-block;
padding: 0 8px;
height: 100%;
@ -118,31 +156,37 @@ const setLayout = () => {
color: #5a5e66;
vertical-align: text-bottom;
/* 定义hover-effect类的样式规则用于添加鼠标悬停效果的样式控制 */
&.hover-effect {
cursor: pointer;
transition: background 0.3s;
/* 鼠标悬停时的背景样式变化,添加一个透明度较低的背景色 */
&:hover {
background: rgba(0, 0, 0, 0.025);
}
}
}
.avatar-container {
/* 定义avatar-container类的样式规则用于头像及下拉菜单所在容器的样式控制 */
.avatar-container {
margin-right: 30px;
.avatar-wrapper {
/* 定义avatar-wrapper类的样式规则用于头像元素外层包裹容器的样式控制 */
.avatar-wrapper {
margin-top: 5px;
position: relative;
.user-avatar {
/* 定义user-avatar类的样式规则用于用户头像图片的样式控制 */
.user-avatar {
cursor: pointer;
width: 40px;
height: 40px;
border-radius: 10px;
}
.el-icon-caret-bottom {
/* 定义el-icon-caret-bottom类的样式规则用于下拉箭头图标的样式控制 */
.el-icon-caret-bottom {
cursor: pointer;
position: absolute;
right: -20px;
@ -153,4 +197,4 @@ const setLayout = () => {
}
}
}
</style>
</style>

@ -1,10 +1,16 @@
<template>
<!-- 创建一个名为sidebar-logo-container的div容器通过动态绑定class根据isCollapse的值来决定是否添加collapse类名 -->
<div class="sidebar-logo-container" :class="{ collapse: isCollapse }">
<!-- 根据collapse属性的值来决定是否显示这个router-link组件当collapse为真时显示同时设置了唯一的key为"collapse"用于Vue的虚拟DOM diff算法 -->
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
<!-- 再次根据logo属性的值决定是否显示图片如果logo有值则显示对应的图片图片设置了sidebar-logo类名 -->
<img v-if="logo" :src="logo" class="sidebar-logo" />
<!-- 如果logo没有值则显示一个h1标题标题设置了sidebar-title类名显示内容为博客后台管理系统 -->
<h1 v-else class="sidebar-title">博客后台管理系统</h1>
</router-link>
<!-- 当collapse属性为假时显示这个router-link组件设置key为"expand"同样链接到根路径/ -->
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
<!-- 与上面类似根据logo的值决定显示图片还是标题 -->
<img v-if="logo" :src="logo" class="sidebar-logo" />
<h1 class="sidebar-title">博客后台管理系统</h1>
</router-link>
@ -12,8 +18,10 @@
</template>
<script setup lang="ts">
// vuereactivetoRefsreactivetoRefs
import { reactive, toRefs } from "vue";
// 使definePropscollapse
const props = defineProps({
collapse: {
type: Boolean,
@ -21,16 +29,21 @@ const props = defineProps({
},
});
// stateisCollapseprops.collapse
// logoURL../../../assets/logo.pngimport.meta.url
const state = reactive({
isCollapse: props.collapse,
logo: new URL(`../../../assets/logo.png`, import.meta.url).href,
});
// 使toRefsstate便使logoisCollapse
const { logo, isCollapse } = toRefs(state);
</script>
<style lang="scss" scoped>
/* 定义sidebar-logo-container类的样式规则 */
.sidebar-logo-container {
/* 设置元素相对定位宽度占满父容器高度为50px文本垂直居中内容水平居中超出部分隐藏 */
position: relative;
width: 100%;
height: 50px;
@ -38,17 +51,23 @@ const { logo, isCollapse } = toRefs(state);
text-align: center;
overflow: hidden;
& .sidebar-logo-link {
/* 定义sidebar-logo-container下的sidebar-logo-link类的样式规则 */
&.sidebar-logo-link {
/* 高度和宽度都占满父容器 */
height: 100%;
width: 100%;
& .sidebar-logo {
/* 定义sidebar-logo-link下的sidebar-logo类的样式规则 */
&.sidebar-logo {
/* 设置图片宽度和高度为20px使其垂直居中显示 */
width: 20px;
height: 20px;
vertical-align: middle;
}
& .sidebar-title {
/* 定义sidebar-logo-link下的sidebar-title类的样式规则 */
&.sidebar-title {
/* 使其作为行内块元素显示去除默认外边距设置文本颜色为白色字体加粗字号为14px字体族等样式并使其垂直居中同时设置左边距为12px */
display: inline-block;
margin: 0;
color: #fff;
@ -61,10 +80,12 @@ const { logo, isCollapse } = toRefs(state);
}
}
/* 当sidebar-logo-container具有collapse类名时的样式规则 */
&.collapse {
.sidebar-logo {
/* 针对sidebar-logo类元素设置右边距为0px的样式 */
.sidebar-logo {
margin-right: 0px;
}
}
}
</style>
</style>

@ -1,29 +1,42 @@
<template>
<div v-if="!item.meta || !item.meta.hidden">
<!-- 根据条件判断是否显示该 div 内容如果 item.meta 不存在或者 item.meta.hidden false即不隐藏则显示内部元素 -->
<div v-if="!item.meta ||!item.meta.hidden">
<!-- 进一步根据多个条件判断来决定是否渲染内部元素只有当满足这些复杂条件时才会渲染后续的 app-link el-menu-item 相关结构 -->
<template v-if="
hasOneShowingChild(item.children, item) &&
(!onlyOneChild.children || onlyOneChild.noShowingChildren) &&
(!item.alwaysShow)
">
<!-- 如果 onlyOneChild.meta 存在则创建一个 app-link 组件用于路由跳转 to 属性设置为通过 resolvePath 函数处理后的 onlyOneChild.path -->
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
<!-- 创建一个 el-menu-item 组件设置其 index 属性为通过 resolvePath 函数处理后的 onlyOneChild.path同时根据 isNest 的值动态添加类名 -->
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown':!isNest }">
<!-- 创建一个 el-icon 组件用于显示图标 -->
<el-icon>
<!-- el-icon 内部如果 onlyOneChild.meta 存在且有对应的 icon 属性则使用 svg-icon 组件来显示具体的图标图标类名由 onlyOneChild.meta.icon 指定 -->
<svg-icon v-if="onlyOneChild.meta && onlyOneChild.meta.icon"
:icon-class="onlyOneChild.meta.icon" />
</el-icon>
<!-- 使用模板插槽来显示菜单项的标题标题内容取自 onlyOneChild.meta.title -->
<template #title>
{{ onlyOneChild.meta.title }}
</template>
</el-menu-item>
</app-link>
</template>
<!-- 如果不满足上面模板中的条件则创建一个 el-sub-menu 组件设置其 index 属性为通过 resolvePath 函数处理后的 item.path -->
<el-sub-menu v-else :index="resolvePath(item.path)">
<!-- 使用模板插槽来定义 el-sub-menu 的标题部分 -->
<template #title>
<!-- 创建一个 el-icon 组件用于显示图标 -->
<el-icon>
<!-- 如果 item.meta 存在且有对应的 icon 属性则使用 svg-icon 组件来显示具体的图标图标类名由 item.meta.icon 指定 -->
<svg-icon v-if="item.meta && item.meta.icon" :icon-class="item.meta.icon" />
</el-icon>
<!-- 创建一个 span 元素用于显示菜单项的标题添加 menu-title 类名标题内容取自 item.meta.title -->
<span class="menu-title">{{ item.meta.title }}</span>
</template>
<!-- 通过 v-for 循环遍历 item.children 数组为每个子项创建一个 sidebar-item 组件传递相关属性用于构建嵌套的菜单结构 -->
<sidebar-item v-for="child in item.children" :key="child.path" :item="child" :is-nest="true"
:base-path="resolvePath(child.path)" class="nest-menu" />
</el-sub-menu>
@ -31,61 +44,88 @@
</template>
<script setup lang="ts">
// SvgIcon
import SvgIcon from '@/components/SvgIcon/index.vue';
// vue ref
import { ref } from "vue";
// RouteRecordRaw
import { RouteRecordRaw } from 'vue-router';
// AppLink
import AppLink from './Link.vue';
// onlyOneChild
const onlyOneChild = ref();
// 使 defineProps
const props = defineProps({
// item
item: {
type: Object,
required: true
},
// isNest
isNest: {
type: Boolean,
required: false
},
// basePath
basePath: {
type: String,
required: true
}
});
// hasOneShowingChild
const hasOneShowingChild = (children: RouteRecordRaw[], parent: any) => {
// children
if (!children) {
children = [];
}
// item.meta item.meta.hidden false
const showingChildren = children.filter((item) => {
if (item.meta && item.meta.hidden) {
return false;
} else {
// onlyOneChild
onlyOneChild.value = item;
return true;
}
});
// 1 true
if (showingChildren.length === 1) {
return true;
}
// 0 parent onlyOneChild true
if (showingChildren.length === 0) {
onlyOneChild.value = { ...parent, path: '', noShowingChildren: true };
onlyOneChild.value = {...parent, path: '', noShowingChildren: true };
return true;
}
// 1 0 false
return false;
}
// resolvePath getNormalPath routePath
const resolvePath = (routePath: string) => {
return getNormalPath(props.basePath + '/' + routePath)
}
// getNormalPath
const getNormalPath = (p: string) => {
if (p.length === 0 || !p || p == 'undefined') {
// 'undefined'
if (p.length === 0 ||!p || p == 'undefined') {
return p
};
//
let res = p.replace('//', '/')
//
if (res[res.length - 1] === '/') {
return res.slice(0, res.length - 1)
}
//
return res;
}
</script>
<style scoped>
//
</style>

@ -1,12 +1,15 @@
<template>
<!-- 根据showLogo这个响应式数据的值来动态添加类名如果showLogo为真则添加'has-logo'类名 -->
<div :class="{ 'has-logo': showLogo }">
<!-- 网站Logo -->
<!-- 网站Logo组件通过v-if指令根据showLogo的值决定是否显示同时接收collapse属性目前此处被注释掉了 -->
<!-- <logo v-if="showLogo" :collapse="isCollapse" /> -->
<!-- 侧边栏 -->
<!-- 使用el-scrollbar组件创建一个可滚动的区域并添加了wrap-class类名用于样式定制 -->
<el-scrollbar wrap-class="scrollbar-wrapper">
<!-- el-menu组件用于创建侧边栏菜单以下是对其配置的一系列属性 -->
<el-menu :default-active="activeMenu" :unique-opened="true" :collapse="isCollapse" :collapse-transition="false"
:background-color="variables.menuBg" :text-color="variables.menuText"
:active-text-color="variables.menuActiveText">
<!-- 原本应该是通过v-for循环遍历routes数组来动态生成SidebarItem组件用于展示侧边栏的各个菜单项目前此处被注释掉了 -->
<!-- <sidebar-item v-for="route in routes" :item="route" :key="route.path" :base-path="route.path" /> -->
</el-menu>
</el-scrollbar>
@ -14,19 +17,35 @@
</template>
<script setup lang="ts">
// 使
import variables from '@/assets/styles/variables.module.scss';
// VuexVuex
import useStore from "@/store";
// vuecomputed
import { computed } from "vue";
// vue-routeruseRoute
import { useRoute } from 'vue-router';
// Logologotemplate
import Logo from './Logo.vue';
// SidebarItemtemplate
import SidebarItem from './SidebarItem.vue';
//
const route = useRoute();
// isCollapse1
const isCollapse = computed(() => 1);
// showLogo1logo
const showLogo = computed(() => 1);
// routes1
const routes = computed(() => 1);
// activeMenu
const activeMenu = computed(() => route.path);
</script>
<style scoped>
</style>
//
</style>

@ -1,67 +1,148 @@
import "@/assets/fonts/font.css"; // 引入字体样式
import "@/assets/styles/index.scss"; // 引入全局样式
import SvgIcon from "@/components/SvgIcon/index.vue"; // 引入SvgIcon组件
import * as directive from "@/directive"; // 引入自定义指令
import router from "@/router"; // 引入路由配置
import { titleChange } from "@/utils/title"; // 引入标题变更函数
import createKatexPlugin from "@kangc/v-md-editor/lib/plugins/katex/cdn"; // 引入katex插件
import createTodoListPlugin from "@kangc/v-md-editor/lib/plugins/todo-list/index"; // 引入todo-list插件
import "@kangc/v-md-editor/lib/plugins/todo-list/todo-list.css"; // 引入todo-list样式
import VMdPreview from "@kangc/v-md-editor/lib/preview"; // 引入v-md-editor预览模块
import "@kangc/v-md-editor/lib/theme/style/vuepress.css"; // 引入v-md-editor主题样式
import vuepressTheme from "@kangc/v-md-editor/lib/theme/vuepress.js"; // 引入v-md-editor vuepress主题
import naive from "naive-ui"; // 引入Naive UI组件库
import "nprogress/nprogress.css"; // 引入NProgress加载条样式
import { createPinia } from "pinia"; // 引入Pinia状态管理库
import piniaPluginPersistedstate from "pinia-plugin-persistedstate"; // 引入Pinia的持久化插件
import Prism from "prismjs"; // 引入Prism代码高亮工具
import "swiper/css"; // 引入Swiper样式
import "swiper/css/autoplay"; // 引入Swiper自动播放样式
import "swiper/css/mousewheel"; // 引入Swiper鼠标滚轮样式
import "swiper/css/navigation"; // 引入Swiper导航按钮样式
import "swiper/css/pagination"; // 引入Swiper分页器样式
import VueViewer from "v-viewer"; // 引入VueViewer图片查看器
import "viewerjs/dist/viewer.css"; // 引入VueViewer样式
import "virtual:svg-icons-register"; // 引入SVG图标
import { createApp, Directive } from "vue"; // 引入Vue相关依赖
import lazyPlugin from "vue3-lazy"; // 引入Vue3懒加载插件
import App from "./App.vue"; // 引入根组件App.vue
import error from "./assets/images/404.gif"; // 引入404错误页面图片
import loading from "./assets/images/loading.gif"; // 引入加载中图片
const app = createApp(App); // 创建Vue应用实例
//引入Element plus以及原生组件样式文件
// 引入项目中"assets/fonts"目录下的"font.css"文件,用于定义和应用自定义字体样式,使页面能够使用特定的字体显示文本
import "@/assets/fonts/font.css";
// 引入项目中"assets/styles"目录下的"index.scss"文件,该文件可能包含了全局通用的样式规则,如页面布局、颜色变量、通用类等,用于统一整个项目的外观风格
import "@/assets/styles/index.scss";
// 引入自定义的SvgIcon组件其定义位于项目中"components/SvgIcon/index.vue"文件内该组件可能用于在页面中方便地展示SVG图标提升图标使用的灵活性和可维护性
import SvgIcon from "@/components/SvgIcon/index.vue";
// 引入自定义指令相关的所有内容,这些自定义指令定义在"@/directive"模块中后续可通过注册在Vue应用中使用用于实现一些特定的DOM操作或逻辑处理功能
import * as directive from "@/directive";
// 引入路由配置对象,该对象定义了项目中的路由规则,包括页面路径与对应组件的映射关系、路由跳转逻辑等,用于实现页面之间的导航功能,其配置位于"@/router"文件内
import router from "@/router";
// 引入用于更改页面标题的函数,该函数定义在"@/utils/title"文件内,可根据不同页面或业务逻辑需求动态改变浏览器标签页显示的标题内容
import { titleChange } from "@/utils/title";
// 引入katex插件该插件来自于"@kangc/v-md-editor/lib/plugins/katex/cdn"用于在v-md-editor中支持渲染数学公式等相关功能例如LaTeX语法的公式展示
import createKatexPlugin from "@kangc/v-md-editor/lib/plugins/katex/cdn";
// 引入todo-list插件其定义在"@kangc/v-md-editor/lib/plugins/todo-list/index"文件内用于在v-md-editor中支持展示待办事项列表等相关功能方便在富文本编辑或展示场景下使用
import createTodoListPlugin from "@kangc/v-md-editor/lib/plugins/todo-list/index";
// 引入todo-list插件对应的样式文件用于定义待办事项列表在页面上的显示样式确保其外观符合设计要求
import "@kangc/v-md-editor/lib/plugins/todo-list/todo-list.css";
// 引入v-md-editor的预览模块用于展示经过编辑或解析后的富文本内容提供相应的渲染和展示功能来自于"@kangc/v-md-editor/lib/preview"文件
import VMdPreview from "@kangc/v-md-editor/lib/preview";
// 引入v-md-editor的vuepress主题样式文件用于设置v-md-editor在使用vuepress主题时的外观风格使其与项目整体风格相匹配或者符合特定的设计需求
import "@kangc/v-md-editor/lib/theme/style/vuepress.css";
// 引入v-md-editor的vuepress主题配置对象用于定义v-md-editor在使用vuepress主题时的具体样式、交互逻辑等相关主题特性位于"@kangc/v-md-editor/lib/theme/vuepress.js"文件内
import vuepressTheme from "@kangc/v-md-editor/lib/theme/vuepress.js";
// 引入Naive UI组件库该组件库提供了丰富的UI组件方便用于构建页面的用户界面可提高开发效率并保证界面的一致性和美观性
import naive from "naive-ui";
// 引入NProgress加载条的样式文件用于定义加载条在页面上显示时的外观样式当页面进行一些异步操作如路由切换、数据加载等时可展示加载进度提示
import "nprogress/nprogress.css";
// 引入Pinia状态管理库用于在Vue项目中管理应用的状态方便在不同组件之间共享和修改数据替代Vuex等传统状态管理方案提供更简洁和灵活的使用方式
import { createPinia } from "pinia";
// 引入Pinia的持久化插件该插件可使Pinia管理的状态数据在页面刷新或重新打开时能够持久保存避免数据丢失提升用户体验
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
// 引入Prism代码高亮工具用于对代码片段进行语法高亮显示使代码在页面上展示时更易读、更清晰常用于文档展示、代码示例等场景
import Prism from "prismjs";
// 引入Swiper组件库的基础样式文件用于定义Swiper滑动组件的基本外观、布局等样式相关特性确保其在页面上正常展示和交互
import "swiper/css";
// 引入Swiper组件库的自动播放样式文件用于设置Swiper组件在开启自动播放功能时的相关样式如播放按钮、播放指示器等外观样式
import "swiper/css/autoplay";
// 引入Swiper组件库的鼠标滚轮样式文件用于定义当使用鼠标滚轮操作Swiper组件时相关交互元素的样式以及滚动效果的视觉呈现样式
import "swiper/css/mousewheel";
// 引入Swiper组件库的导航按钮样式文件用于设置Swiper组件左右导航按钮如果有的外观样式如形状、颜色、大小等提升用户操作的可视性和便捷性
import "swiper/css/navigation";
// 引入Swiper组件库的分页器样式文件用于定义Swiper组件在展示分页指示器用于显示当前页码、总页码等时的外观样式便于用户了解页面位置和数量情况
import "swiper/css/pagination";
// 引入VueViewer图片查看器组件用于在页面上方便地展示图片并提供放大、缩小、旋转等查看功能提升图片查看的用户体验
import VueViewer from "v-viewer";
// 引入VueViewer图片查看器对应的样式文件用于定义图片查看器在页面上弹出展示时的外观样式如背景、边框、操作按钮样式等
import "viewerjs/dist/viewer.css";
// 引入用于注册SVG图标相关的逻辑可能是虚拟模块具体实现取决于项目配置使得在项目中能够方便地使用SVG图标资源
import "virtual:svg-icons-register";
// 引入Vue相关的核心依赖包括创建Vue应用实例的函数以及定义指令类型的相关类型定义用于后续构建Vue应用和注册自定义指令
import { createApp, Directive } from "vue";
// 引入Vue3懒加载插件用于实现图片等资源的懒加载功能即在页面滚动到相应位置时再加载资源提高页面加载速度和性能
import lazyPlugin from "vue3-lazy";
// 引入根组件App.vue该组件作为整个Vue应用的顶层组件包含了其他子组件的组合和布局是整个页面结构的起点
import App from "./App.vue";
// 引入项目中"assets/images"目录下的"404.gif"文件该图片可能用于在页面出现404错误资源未找到时展示相应的提示图片提升用户友好性
import error from "./assets/images/404.gif";
// 引入项目中"assets/images"目录下的"loading.gif"文件,该图片可能用于在页面进行数据加载、资源获取等异步操作时展示加载中的提示图片,告知用户当前操作正在进行中
import loading from "./assets/images/loading.gif";
// 使用createApp函数创建一个Vue应用实例将根组件App.vue作为参数传入构建出整个Vue应用的基础框架后续可在此基础上添加插件、注册组件、挂载到DOM等操作
const app = createApp(App);
// 引入Element Plus组件库以及对应的中文语言包配置Element Plus提供了丰富的基于Vue 3的UI组件通过设置locale属性为中文语言环境确保组件显示的文本等内容以中文展示符合国内使用习惯
import ElementPlus from 'element-plus';
import 'element-plus/theme-chalk/index.css';
import locale from 'element-plus/lib/locale/lang/zh-cn'
app.use(ElementPlus, { locale })
// 遍历导入的所有自定义指令对象的属性每个属性对应一个自定义指令将每个自定义指令注册到Vue应用实例中使得指令可以在组件的模板中使用实现相应的DOM操作或逻辑处理功能
Object.keys(directive).forEach((key) => {
app.directive(key, (directive as { [key: string]: Directive })[key]); // 注册自定义指令
app.directive(key, (directive as { [key: string]: Directive })[key]);
});
const pinia = createPinia(); // 创建Pinia实例
pinia.use(piniaPluginPersistedstate); // 使用Pinia的持久化插件
// 创建一个Pinia实例用于后续管理应用的状态数据是整个应用状态管理的核心对象通过它可以定义、获取、修改各种状态数据并实现状态的响应式更新
const pinia = createPinia();
// 在创建的Pinia实例上使用持久化插件使得Pinia管理的状态数据具备持久化存储能力在页面刷新、重新打开等情况下能够保留之前的数据状态
pinia.use(piniaPluginPersistedstate);
// 在v-md-editor的预览模块VMdPreview上使用vuepress主题配置并传入Prism代码高亮工具实例使得预览模块在使用vuepress主题时能够正确渲染内容并对代码进行语法高亮显示
VMdPreview.use(vuepressTheme, {
Prism,
})
.use(createTodoListPlugin()) // 使用v-md-editor的todo-list插件
.use(createKatexPlugin()); // 使用v-md-editor的katex插件
app.use(VMdPreview); // 使用v-md-editor的预览模块
app.use(naive); // 使用Naive UI组件库
app.use(pinia); // 使用Pinia状态管理库
app.use(router); // 使用路由配置
app.use(VueViewer); // 使用VueViewer图片查看器
// 使用v-md-editor的todo-list插件为v-md-editor的预览模块添加待办事项列表展示功能丰富富文本展示的内容和功能
.use(createTodoListPlugin())
// 使用v-md-editor的katex插件为v-md-editor的预览模块添加对数学公式渲染展示的功能方便展示包含数学公式的富文本内容
.use(createKatexPlugin());
// 将配置好的v-md-editor预览模块注册到Vue应用实例中使其在组件中可以方便地使用该模块来展示富文本内容
app.use(VMdPreview);
// 将Naive UI组件库注册到Vue应用实例中使得项目中可以使用该组件库提供的各种UI组件来构建页面的用户界面
app.use(naive);
// 将创建并配置好的Pinia实例注册到Vue应用实例中启用Pinia状态管理功能让组件能够访问和操作应用的状态数据
app.use(pinia);
// 将路由配置对象注册到Vue应用实例中使应用能够根据定义好的路由规则进行页面导航实现多页面之间的切换和跳转功能
app.use(router);
// 将VueViewer图片查看器组件注册到Vue应用实例中使得组件中可以方便地使用该组件来展示图片并提供丰富的查看操作功能
app.use(VueViewer);
// 在Vue应用实例上使用Vue3懒加载插件并传入加载中图片和错误提示图片的路径配置懒加载插件的相关参数实现资源的懒加载功能
app.use(lazyPlugin, {
loading,
error,
});
// 将SvgIcon组件注册到Vue应用实例中使其可以在整个应用的组件模板中作为标签使用方便展示SVG图标
app.component("svg-icon", SvgIcon);
// 将创建好的Vue应用实例挂载到HTML页面中id为"app"的DOM节点上使得Vue应用能够在浏览器中渲染和展示出来启动整个应用
app.mount("#app");
app.component("svg-icon", SvgIcon); // 注册SvgIcon组件
app.mount("#app"); // 挂载应用实例到DOM节点
titleChange(); // 调用标题变更函数
// 调用标题变更函数,根据业务逻辑或页面需求动态改变浏览器标签页显示的标题内容,提升页面的辨识度和用户体验
titleChange();

@ -1,66 +1,85 @@
// 从相对路径../types/user 中导入 User 类型定义,用于规范用户相关的数据结构
import { User } from "../types/user";
// 从 @/api/login 模块中导入 login 和 logout 函数,分别用于处理用户登录和登出的相关网络请求操作
import { login, logout } from "@/api/login";
// 从 @/api/login/types 中导入 LoginForm 类型定义,可能用于规范登录表单的数据结构
import { LoginForm } from "@/api/login/types";
// 从 pinia 库中导入 defineStore 函数,用于创建 Vuex 风格的状态管理 store
import { defineStore } from "pinia";
// 使用 defineStore 函数创建一个名为 useUserStore 的 store
const useUserStore = defineStore("useUserStore", {
state: (): User => ({
id: "",
username: "游客",
email: "",
}),
actions: {
getUser(): any {
if (this.username) {
return this.username;
}
// 定义 store 的 state状态它是一个函数返回一个符合 User 类型的对象,初始时设置了一些默认值
state: (): User => ({
id: "",
username: "游客",
email: "",
}),
return '游客';
},
// 登录
LogIn(LoginForm: LoginForm) {
return new Promise((resolve, reject) => {
login(LoginForm)
.then(({ data }) => {
// console.log(data);
// 持久化存储
if (data.code === 200) {
this.username = data.data
// 定义 store 的 actions操作包含可以改变 state 或者执行异步操作等的方法
actions: {
// getUser 方法,用于获取当前用户的用户名,如果已经存在用户名则返回该用户名,否则返回默认的 '游客'
getUser(): any {
if (this.username) {
return this.username;
}
resolve(data); // 返回结果
})
.catch((error) => {
reject(error);
});
});
},
// 登出
LogOut() {
return new Promise((resolve, reject) => {
logout()
.then(({ data }) => {
if (data.code === 200) {
// 清除用户数据
this.$reset(); // 调用 $reset 方法
}
// 其他操作...
resolve(data); // 返回结果
})
.catch((error) => {
reject(error);
});
});
return '游客';
},
// LogIn 方法,用于处理用户登录操作,接收一个符合 LoginForm 类型的参数
LogIn(LoginForm: LoginForm) {
// 返回一个 Promise用于处理异步操作的结果和状态
return new Promise((resolve, reject) => {
// 调用 login 函数发起登录请求,传入登录表单数据
login(LoginForm)
.then(({ data }) => {
// 打印返回的数据(此处目前被注释掉了,可能用于调试)
// console.log(data);
// 进行持久化存储相关判断,如果返回数据的 code 为 200表示登录成功
if (data.code === 200) {
// 将登录成功后返回的用户名数据赋值给 store 中的 username 属性
this.username = data.data
}
// 通过 resolve 将请求结果传递出去,以便外部可以获取登录操作的结果
resolve(data);
})
.catch((error) => {
// 如果登录请求出现错误,通过 reject 传递错误信息
reject(error);
});
});
},
// LogOut 方法,用于处理用户登出操作
LogOut() {
return new Promise((resolve, reject) => {
// 调用 logout 函数发起登出请求
logout()
.then(({ data }) => {
// 如果返回数据的 code 为 200表示登出成功
if (data.code === 200) {
// 调用 $reset 方法清除当前 store 中的所有用户数据,将状态恢复到初始值
this.$reset();
}
// 此处可以添加其他登出相关的操作(目前代码中只是预留了注释位置)
// 其他操作...
// 通过 resolve 将请求结果传递出去,以便外部可以获取登出操作的结果
resolve(data);
})
.catch((error) => {
// 如果登出请求出现错误,通过 reject 传递错误信息
reject(error);
});
});
},
},
// 这里的 getters计算属性暂时为空通常可以用于根据 state 计算并返回新的值,类似 Vue 中的计算属性功能
getters: {},
// 配置持久化相关设置,指定持久化存储的 key 为 "user",使用 localStorage 进行数据存储(意味着即使浏览器会话关闭,数据依然保留)
persist: {
key: "user",
storage: localStorage, // session关闭了就没有了
},
},
getters: {},
persist: {
key: "user",
storage: localStorage, // session关闭了就没有了
},
});
export default useUserStore;
// 将定义好的 useUserStore 导出,以便在其他模块中可以使用这个状态管理 store
export default useUserStore;

@ -1,46 +1,113 @@
<template>
<div class="page-header" style="min-width: 400px;">
<h1 class="page-title">页面不存在</h1>
<img class="page-cover" src="../../assets/images/404.gif"
alt="">
<Waves></Waves>
</div>
<div class="notfound"><text>Σ( ° °|||)404-网页为找到</text></div>
<div class="notfound">来自pan的抱歉</div>
<div class="notfound"><router-link to="/">返回主页</router-link></div>
<!-- <div class="bg">
<div class="page-container">
<div class="notfound">Σ( ° °|||)404-网页为找到</div>
<div class="notfound">来自pan的抱歉</div>
<!-- 根据classObj这个计算属性返回的对象动态绑定类名同时添加固定的类名app-wrapper作为整个页面布局的外层容器 -->
<div :class="classObj" class="app-wrapper">
<!-- 一个条件判断始终为真这里v-if="1"可根据实际情况调整为合理的条件判断的div元素添加了drawer-bg类名用于点击事件目前@click绑定为空可能后续添加功能可能用于实现抽屉式背景效果之类的功能 -->
<div v-if="1" class="drawer-bg" @click="" />
<!-- 侧边栏组件添加了sidebar-container类名用于展示页面的侧边栏内容 -->
<SideBar class="sidebar-container"></SideBar>
<!-- 根据条件这里始终为真可按需修改动态添加hasTagsView类名同时添加main-container类名作为主要内容区域的容器 -->
<div :class="{ hasTagsView: 1 }" class="main-container">
<!-- 根据条件这里始终为真可按需修改动态添加'fixed-header'类名用于固定头部相关的布局 -->
<div :class="{ 'fixed-header': 1 }">
<!-- 导航栏组件绑定了@setLayout事件当触发该事件时会调用setLayout方法 -->
<NavBar @setLayout="setLayout"></NavBar>
<!-- 历史标签栏组件通过v-if条件判断是否显示这里始终为真可按需修改 -->
<TagView v-if="1"></TagView>
</div>
<!-- 主要内容展示组件用于呈现页面主体部分的内容 -->
<AppMain></AppMain>
<!-- 设置组件添加了ref属性用于在JavaScript中获取该组件实例可能用于操作设置相关的功能 -->
<Settings ref="settingRef"></Settings>
</div>
</div> -->
</div>
</template>
<script setup lang="ts">
</script>
// SettingsTagView使
// import Settings from "@/components/Settings/index.vue";
// import TagView from "@/components/TagView/index.vue";
// @vueuse/coreuseWindowSize
import { useWindowSize } from "@vueuse/core";
// vuecomputedrefwatchEffect
import { computed, ref, watchEffect } from "vue";
// AppMain
import AppMain from "./components/AppMain/index.vue";
// NavBar
import NavBar from "./components/NavBar/index.vue";
// SideBar
import SideBar from "./components/SideBar/index.vue";
// 使useWindowSizewidth便使
const { width } = useWindowSize();
// WIDTH992使
const WIDTH = 992;
// settingRefSettings便
const settingRef = ref();
// classObj1
const classObj = computed(() => ({
hideSidebar: 1,
openSidebar: 1,
mobile: 1,
}));
<style scoped>
.notfound {
width: 20.75rem;
height: 0.1rem;
/* background: url(../../assets/images/404.gif) no-repeat bottom; */
text-align: center;
margin: 3rem auto;
// setLayoutSettingssettingRefopenSetting
const setLayout = () => {
settingRef.value.openSetting();
}
</script>
<style lang="scss" scoped>
/* 定义app-wrapper类的样式规则 */
.app-wrapper {
/* 以下这行代码被注释掉了原本可能是用于引入一个清除浮动相关的样式混入mixin如果有相关需求可取消注释使用 */
// @include clearfix;
position: relative;
height: 100%;
width: 100%;
/* 当app-wrapper类同时具有mobile和openSidebar类名时的样式规则将元素设置为固定定位定位在页面顶部 */
&.mobile.openSidebar {
position: fixed;
top: 0;
}
}
.apology{
width: 18.75rem;
height: 0.1rem;
/* background: url(../../assets/images/404.gif) no-repeat bottom; */
text-align: center;
margin: 6.25rem auto;
/* 定义drawer-bg类的样式规则 */
.drawer-bg {
background: #000;
opacity: 0.3;
width: 100%;
top: 0;
height: 100%;
position: absolute;
z-index: 999;
}
/* 定义fixed-header类的样式规则 */
.fixed-header {
position: fixed;
top: 0;
right: 0;
z-index: 40;
/* 以下这行代码被注释掉了,原本可能是用于计算宽度并减去侧边栏宽度相关的值,可根据实际情况取消注释并完善相关变量等内容 */
// width: calc(100% - #{$sideBarWidth});
transition: width 0.28s;
}
/* 当元素具有hideSidebar类名时其内部fixed-header类元素的样式规则设置宽度为计算后的特定值减去64px */
.hideSidebar.fixed-header {
width: calc(100% - 64px);
}
/* 当元素具有sidebarHide类名时其内部fixed-header类元素的样式规则设置宽度为100% */
.sidebarHide.fixed-header {
width: 100%;
}
/* 当元素具有mobile类名时其内部fixed-header类元素的样式规则设置宽度为100% */
.mobile.fixed-header {
width: 100%;
}
</style>

@ -1,18 +1,25 @@
<template>
<!-- 创建一个名为dashboard-container的div容器作为整个页面布局的外层容器 -->
<div class="dashboard-container">
<!-- 使用github-corner组件添加了github-corner类名可能用于展示指向项目 GitHub 仓库的角标之类的功能具体取决于该组件的实现 -->
<github-corner class="github-corner" />
<!-- 使用el-row组件创建一个行布局设置 gutter列间距为40添加panel-group类名用于对内部列元素进行分组布局 -->
<el-row :gutter="40" class="panel-group">
<!-- 使用el-col组件创建一个列布局根据不同屏幕尺寸xssmlg设置占比添加card-panel-col类名用于展示具体的卡片面板内容 -->
<el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
<div class="card-panel">
<!-- 创建一个用于包裹卡片图标元素的div添加card-panel-icon-wrapper和icon-view类名用于定位和样式控制 -->
<div class="card-panel-icon-wrapper icon-view">
<!-- 使用svg-icon组件展示图标设置图标类名为"button"大小为"4em"添加card-panel-icon类名用于样式设置 -->
<svg-icon icon-class="button" size="4em" class-tagName="card-panel-icon" />
</div>
<!-- 创建一个用于描述卡片内容的div包含文本和对应的数据展示 -->
<div class="card-panel-description">
<div class="card-panel-text">
<!-- 显示访问量文本 -->
访问量
</div>
<!-- 使用双花括号插值语法展示viewCount变量的值用于显示具体的访问量数据 -->
<span class="card-panel-num">{{ viewCount }}</span>
</div>
</div>
@ -57,15 +64,21 @@
</div>
</el-col>
</el-row>
<!-- 使用el-row组件创建一个行布局添加data-card类名设置底部外边距为32px用于展示特定的数据卡片内容 -->
<el-row class="data-card" style="margin-bottom:32px;">
<!-- 创建一个div用于显示标题文章贡献统计🎉 -->
<div class="title">文章贡献统计🎉</div>
<!-- 使用calendar-heatmap组件展示日历热力图设置相关属性如数据列表结束日期颜色范围等 -->
<calendar-heatmap style=" width: 100%; margin-top: 0.5rem" :values="articleStatisticsList" :end-date="new Date()"
:range-color="['#ebedf0', '#9be9a8', '#40c463', '#30a14e', '#216e39']" />
</el-row>
<!-- 使用el-row组件创建一个行布局设置 gutter列间距为32用于对内部列元素进行分组布局 -->
<el-row :gutter="32">
<!-- 使用el-col组件创建一个列布局根据不同屏幕尺寸xssmlg设置占比用于展示具体的图表内容 -->
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<div class="title">文章浏览量排行🚀</div>
<!-- 使用Echarts组件展示图表传入对应的配置选项options和设置高度为350px -->
<Echarts :options="ariticleRank" height="350px"></Echarts>
</div>
</el-col>
@ -78,10 +91,12 @@
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<div class="title">文章标签统计🌈</div>
<!-- 使用TagCloud组件展示标签云通过v-if指令根据tagLoad变量的值决定是否显示传入标签列表tagList -->
<TagCloud v-if="tagLoad" :tag-list="tagList"></TagCloud>
</div>
</el-col>
</el-row>
<!-- 使用el-row组件创建一个行布局添加data-card类名用于展示特定的数据卡片内容 -->
<el-row class="data-card">
<div class="title">一周访问量</div>
<Echarts :options="userView" height="350px"></Echarts>
@ -89,46 +104,53 @@
</div>
</template>
<style lang="scss" scoped>
/* 定义title类的样式规则用于设置标题相关的样式如字体大小、颜色、粗细等 */
.title {
font-size: 14px;
color: var(--el-text-color-secondary);
font-weight: 700;
}
/* 定义data-card类的样式规则用于设置具有该类名元素的背景、内边距等样式 */
.data-card {
background: var(--el-bg-color-overlay);
padding: 1rem;
}
/* 定义dashboard-container类的样式规则用于设置整个页面布局容器的样式如内边距、背景色、相对定位等 */
.dashboard-container {
padding: 32px;
background: var(--el-bg-color-page);
position: relative;
.github-corner {
/* 定义dashboard-container下的github-corner类的样式规则设置其绝对定位在右上角顶部和右侧都为0去除边框 */
.github-corner {
position: absolute;
top: 0px;
border: 0;
right: 0;
}
.chart-wrapper {
/* 定义dashboard-container下的chart-wrapper类的样式规则用于设置图表容器的背景、内边距、底部外边距等样式 */
.chart-wrapper {
background: var(--el-bg-color-overlay);
padding: 1rem;
margin-bottom: 2rem;
}
}
/* 定义panel-group类的样式规则用于设置该组元素的顶部外边距 */
.panel-group {
margin-top: 18px;
.card-panel-col {
/* 定义panel-group下的card-panel-col类的样式规则用于设置该列元素的底部外边距 */
.card-panel-col {
margin-bottom: 32px;
}
.card-panel {
/* 定义panel-group下的card-panel类的样式规则用于设置卡片面板的高度、鼠标样式、字体大小、背景色、阴影等通用样式 */
.card-panel {
height: 108px;
cursor: pointer;
font-size: 12px;
@ -136,48 +158,51 @@
overflow: hidden;
color: #666;
background: var(--el-bg-color-overlay);
box-shadow: 4px 4px 40px rgba(0, 0, 0, .05);
border-color: rgba(0, 0, 0, .05);
box-shadow: 4px 4px 40px rgba(0, 0, 0,.05);
border-color: rgba(0, 0, 0,.05);
/* 鼠标悬停时的样式变化,针对图标包裹元素的颜色、背景色等进行设置 */
&:hover {
.card-panel-icon-wrapper {
.card-panel-icon-wrapper {
color: #fff;
}
.icon-people {
.icon-people {
background: #40c9c6;
}
.icon-message {
.icon-message {
background: #36a3f7;
}
.icon-money {
.icon-money {
background: #f4516c;
}
.icon-view {
.icon-view {
background: #34bfa3
}
}
.icon-people {
/* 针对不同图标元素设置默认的颜色样式 */
.icon-people {
color: #40c9c6;
}
.icon-message {
.icon-message {
color: #36a3f7;
}
.icon-money {
.icon-money {
color: #f4516c;
}
.icon-view {
.icon-view {
color: #34bfa3
}
.card-panel-icon-wrapper {
/* 定义card-panel下的card-panel-icon-wrapper类的样式规则用于设置图标包裹元素的浮动、内边距、过渡效果、圆角等样式 */
.card-panel-icon-wrapper {
float: left;
margin: 14px 0 0 14px;
padding: 16px;
@ -185,47 +210,52 @@
border-radius: 6px;
}
.card-panel-description {
/* 定义card-panel下的card-panel-description类的样式规则用于设置卡片描述部分的布局、字体粗细、外边距等样式 */
.card-panel-description {
float: right;
font-weight: bold;
margin: 26px;
margin-left: 0px;
.card-panel-text {
/* 定义card-panel-description下的card-panel-text类的样式规则用于设置文本的行高、颜色、字体大小、底部外边距等样式 */
.card-panel-text {
line-height: 18px;
color: var(--el-text-color-secondary);
font-size: 16px;
margin-bottom: 12px;
}
.card-panel-num {
/* 定义card-panel-description下的card-panel-num类的样式规则用于设置数字展示部分的字体大小 */
.card-panel-num {
font-size: 20px;
}
}
}
}
/* 媒体查询当屏幕宽度小于等于1024px时设置chart-wrapper类元素的内边距样式 */
@media (max-width:1024px) {
.chart-wrapper {
.chart-wrapper {
padding: 8px;
}
}
/* 媒体查询当屏幕宽度小于等于550px时设置card-panel-description类元素隐藏以及card-panel-icon-wrapper类元素的布局、尺寸、边距等样式调整 */
@media (max-width:550px) {
.card-panel-description {
.card-panel-description {
display: none;
}
.card-panel-icon-wrapper {
float: none !important;
.card-panel-icon-wrapper {
float: none!important;
width: 100%;
height: 100%;
margin: 0 !important;
margin: 0!important;
.svg-icon {
.svg-icon {
display: block;
margin: 14px auto !important;
float: none !important;
margin: 14px auto!important;
float: none!important;
}
}
}

@ -1,29 +1,28 @@
<template>
<!-- 创建一个名为brand-container的div容器作为整个品牌展示相关内容的外层容器 -->
<div class="brand-container">
<div class="brand">
<!-- 标题 -->
<!-- 创建一个p元素添加artboard类名用于显示标题YOLO8-MTAS -->
<p class="artboard">YOLO8-MTAS</p>
<!-- 打字机 -->
<!-- 创建一个用于展示打字机效果的div容器设置为flex布局并使其内部元素垂直居中对齐 -->
<div style="display: flex; align-items: center;">
<!-- 创建一个span元素通过ref绑定为myText添加text和title-print类名用于动态展示打字机打出的文字内容 -->
<span ref="myText" class="text title-print">
</span><span class="easy-typed-cursor">|</span>
</div>
</div>
<!-- 波浪 -->
<!-- 使用Waves自定义组件可能用于展示波浪相关的装饰效果具体功能取决于该组件的实现 -->
<Waves></Waves>
<!-- 向下按钮 -->
<!-- 使用svg-icon组件创建一个向下的箭头图标添加arrow-down类名设置图标类名为"down"大小为"32px"绑定点击事件scrollDown用于页面滚动操作 -->
<svg-icon class="arrow-down" icon-class="down" size="32px" @click="scrollDown"></svg-icon>
</div>
</template>
<script setup lang="ts">
// vuerefonMountedrefonMounted
import { ref, onMounted } from 'vue';
// txt
const txt = [
" 古之成大事者,不惟有超世之才,亦必有坚忍不拔之志!",
" 目之所及,心之所向,身之所往,终至所归!",
@ -32,28 +31,37 @@ const txt = [
" 相信奇迹的人,本身和奇迹一样了不起!",
];
// myTextnullDOMspan
const myText = ref<HTMLSpanElement | null>(null);
// index0
let index = 0;
// xiaBiaotxt0
let xiaBiao = 0;
// huantrue
let huan = true;
//
onMounted(() => {
// 200
setInterval(() => {
// myTextDOMnull
const myTextElement = myText.value;
if (!myTextElement) {
console.error("Target element is null.");
return;
}
// huantrueslicetxtxiaBiao1indexmyTextElementtextContent
if (huan) {
myTextElement.textContent = txt[xiaBiao].slice(1, ++index);
} else {
// huanfalseslicetxtxiaBiao1indexmyTextElementtextContent
myTextElement.textContent = txt[xiaBiao].slice(1, index--);
}
// indexxiaBiao3huanfalse
if (index === txt[xiaBiao].length + 3) {
huan = false;
} else if (index < 0) {
// index0index0huantruexiaBiaotxt0
index = 0;
huan = true;
xiaBiao++;
@ -64,22 +72,21 @@ onMounted(() => {
}, 200);
});
// scrollDown
const scrollDown = () => {
// 使window.scrollTosmooth
window.scrollTo({
behavior: "smooth",
top: document.documentElement.clientHeight,
});
};
</script>
<style lang="scss" scoped>
// @/assets/styles/mixin.scssmixin便使
@import "@/assets/styles/mixin.scss";
// body
body {
cursor: url("../icons/normal.cur"), default;
background: var(--grey-0);
@ -90,6 +97,7 @@ body {
line-height: 2;
}
// brand-containerflex100vh10rem使var(--header-text-color)
.brand-container {
@include flex;
flex-direction: column;
@ -100,13 +108,15 @@ body {
color: var(--header-text-color);
}
// brandflexz-index
.brand {
@include flex;
flex-direction: column;
position: fixed;
z-index: -1;
.artboard {
// brandartboard使
.artboard {
font-family: "Fredericka the Great", Mulish, -apple-system, "PingFang SC", "Microsoft YaHei",
sans-serif;
//font-family: Mulish, -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
@ -114,10 +124,10 @@ body {
line-height: 1.2;
animation: titleScale 1s;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.9);
}
.title-print {
// brandtitle-print
.title-print {
letter-spacing: 0.1em;
font-family: Mulish, -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
//font-size: 1em;
@ -126,6 +136,7 @@ body {
}
}
// easy-typed-cursor
.easy-typed-cursor {
margin-left: 0.625rem;
opacity: 1;
@ -134,6 +145,7 @@ body {
animation: blink 0.7s infinite;
}
// arrow-down70px使z-index使
.arrow-down {
position: absolute;
bottom: 70px;
@ -143,18 +155,21 @@ body {
z-index: 8;
}
// 767pxbrand-container
@media (max-width: 767px) {
.brand-container {
.brand-container {
padding: 3rem 0.5rem 0;
}
}
// 760pxtitle-print
@media (min-width: 760px) {
.title-print {
.title-print {
font-size: 1.5rem;
}
}
// keyframesarrow-down
@keyframes arrow-shake {
0% {
opacity: 1;
@ -172,6 +187,7 @@ body {
}
}
// keyframeseasy-typed-cursor
@keyframes blink {
0% {
opacity: 0;
@ -181,4 +197,4 @@ body {
opacity: 1;
}
}
</style>
</style>

@ -1,148 +1,165 @@
<template>
<div class="">
<!-- 拍照 -->
<!-- 拍照相关内容的外层容器这里类名未填写可根据实际需求补充合适的类名 -->
<!-- 拍照功能区域 -->
<div class="camera-card">
<div>
<!-- 摄像头调用 -->
<!-- 用于显示摄像头视频流的video元素通过ref绑定为videoCamera设置宽度和高度属性响应式绑定值来源于组件数据中的videoWidth和videoHeight并设置自动播放 -->
<video ref="videoCamera" :width="videoWidth" :height="videoHeight" autoplay></video>
<!-- 用绘制canvas-转换给img -->
<!-- 用于绘制图像的canvas元素初始设置为不显示display: none通过ref绑定为canvasCamera同样设置宽度和高度属性 -->
<canvas style="display: none" ref="canvasCamera" :width="videoWidth" :height="videoHeight"></canvas>
</div>
</div>
<!-- 3个按钮 -->
<!-- 包含三个操作按钮的区域 -->
<div class="card-child">
<!-- 点击按钮调用getCompetence方法按钮类型为主要按钮primary添加btn_photo类名用于开启相关功能此处是开启摄像头 -->
<el-button @click="getCompetence()" type="primary" class="btn_photo">开启</el-button>
<!-- 点击按钮调用stopNavigator方法按钮类型为警告按钮warning添加btn_photo类名用于关闭相关功能此处是关闭摄像头 -->
<el-button @click="stopNavigator()" type="warning" class="btn_photo">关闭</el-button>
<!-- 点击按钮调用setImage方法按钮类型为成功按钮success添加btn_photo类名用于执行拍照功能 -->
<el-button @click="setImage()" type="success" class="btn_photo">拍照</el-button>
</div>
<!-- 拍照结果 -->
<!-- 根据imgSrc的值决定是否显示拍照结果区域如果imgSrc有值即拍照成功获取到图片资源后则显示 -->
<div v-if="imgSrc" class="img_res_camera">
<div class="card-title">
拍摄预览
</div>
<!-- 显示拍摄的图片通过绑定src属性为imgSrc来展示对应的图片资源添加res_img类名 -->
<img :src="imgSrc" alt class="res_img" />
</div>
<!-- 上传按钮 -->
<!-- 包含上传相关操作按钮的区域同样根据imgSrc的值决定是否显示只有拍照后有图片了才显示这些按钮 -->
<div class="card-child">
<!-- 点击按钮执行取消相关操作具体功能需结合业务逻辑确定按钮类型为危险按钮danger添加btn_photo类名 -->
<el-button v-if="imgSrc" type="danger" class="btn_photo"></el-button>
<!-- 点击按钮执行分享相关操作具体功能需结合业务逻辑确定按钮类型为警告按钮warning添加btn_photo类名 -->
<el-button v-if="imgSrc" type="warning" class="btn_photo"></el-button>
<!-- 点击按钮调用sendPhotoToServer方法并传入当前的图片资源地址imgSrc按钮类型为成功按钮success添加btn_photo类名用于将照片上传到服务器 -->
<el-button v-if="imgSrc" type="success" @click="sendPhotoToServer(this.imgSrc)" class="btn_photo"></el-button>
</div>
</div>
</template>
<script lang="ts">
// vuedefineComponentrefonMounteddefineComponentVuerefonMounted
import { defineComponent, ref, onMounted } from 'vue';
import axios from 'axios'; // axios
// axiosHTTP
import axios from 'axios';
// 使defineComponentVue使使
export default defineComponent({
//
data() {
return {
// 300
videoWidth: 300,
// 200
videoHeight: 200,
//
imgSrc: "",
// canvasnull
thisCanvas: null as HTMLCanvasElement | null,
// canvas2Dnull
thisContext: null as CanvasRenderingContext2D | null,
// videonull
thisVideo: null as HTMLVideoElement | null,
// false
openVideo: false,
// 使
info: "",
};
},
//
mounted() {
this.getCompetence(); //
this.updateVideoSize(); //
// videoWidth videoHeight
// getCompetence
this.getCompetence();
// updateVideoSize
this.updateVideoSize();
// updateVideoSize
window.addEventListener('resize', this.updateVideoSize);
},
//
beforeUnmount() {
window.removeEventListener('resize', this.updateVideoSize);
},
//
methods: {
// video
//
updateVideoSize() {
//
const screenWidth = window.innerWidth;
// 600px
if (screenWidth < 600) {
this.videoWidth = 200;
this.videoHeight = 130;
return
}
//
this.videoWidth = 300;
this.videoHeight = 200;
console.log(this.videoWidth)
},
//
// Base64
sendPhotoToServer(photoData: string) {
const url = 'http://127.0.0.1:5500/photo'; //
// 使
const url = 'http://127.0.0.1:5500/photo';
console.log(photoData);
//
// FormData便
const params = new FormData();
// FormData'photo'
params.append('photo', photoData);
// 使 axios POST
// 使axiosPOSTFormData
axios.post(url, params)
.then(response => {
.then(response => {
console.log('照片上传成功:', response);
this.info = response
//
this.$emit('message', response.data); //
// 'message'
this.$emit('message', response.data);
})
.catch(error => {
//
this.$emit('message', response.data); //
.catch(error => {
// 'message'catchresponse
this.$emit('message', response.data);
console.error('照片上传失败', error);
});
},
//
//
getCompetence() {
// refcanvasHTMLCanvasElementthisCanvas
const thisCanvas = this.$refs.canvasCamera as HTMLCanvasElement;
// canvas2DthisContext
const thisContext = thisCanvas.getContext("2d");
// refvideoHTMLVideoElementthisVideo
const thisVideo = this.$refs.videoCamera as HTMLVideoElement;
this.thisCanvas = thisCanvas;
this.thisContext = thisContext;
this.thisVideo = thisVideo;
this.thisVideo.style.display = "block";
// mediaDevices
// navigatormediaDevices
if (navigator.mediaDevices === undefined) {
navigator.mediaDevices = {} as any;
}
// mediaDevices
// 使 getUserMedia
// getUserMedia
// mediaDevicesgetUserMedia
if (navigator.mediaDevices.getUserMedia === undefined) {
navigator.mediaDevices.getUserMedia = function (
constraints: MediaStreamConstraints
) {
// getUserMedia
// getUserMediawebkitGetUserMediamozGetUserMedia
const getUserMedia =
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.getUserMedia;
//
//
// getUserMediaPromise
if (!getUserMedia) {
//
return Promise.reject(
new Error("getUserMedia is not implemented in this browser")
);
}
// 使 Promise navigator.getUserMedia
// 使PromisegetUserMedia使Promise便.then.catch
return new Promise(function (resolve, reject) {
getUserMedia.call(navigator, constraints, resolve, reject);
});
};
}
//
const constraints: MediaStreamConstraints = {
audio: false,
video: {
@ -151,27 +168,29 @@ export default defineComponent({
// transform: "scaleX(-1)",
},
};
// 使navigator.mediaDevices.getUserMedia
navigator.mediaDevices
.getUserMedia(constraints)
.then((stream) => {
// srcObject
.getUserMedia(constraints)
.then((stream) => {
// srcObjectvideosrcObject
if ("srcObject" in this.thisVideo!) {
(this.thisVideo! as any).srcObject = stream;
} else {
// 使
// srcObject使URL.createObjectURLURLvideosrc
(this.thisVideo! as any).src = window.URL.createObjectURL(stream);
}
//
this.thisVideo!.onloadedmetadata = function (e) {
(this! as any).play();
};
})
.catch((err) => {
.catch((err) => {
console.log(err);
});
},
//
// canvascanvascanvasBase64imgSrc
setImage() {
// canvas
// 使canvasdrawImagevideocanvas0,0
this.thisContext!.drawImage(
this.thisVideo!,
0,
@ -179,11 +198,11 @@ export default defineComponent({
this.videoWidth,
this.videoHeight
);
// base64
// canvasBase64'image/png'
const image = this.thisCanvas!.toDataURL("image/png");
this.imgSrc = image; //
this.imgSrc = image; // imgSrc便
},
//
//
stopNavigator() {
(this.thisVideo!.srcObject as MediaStream).getTracks()[0].stop();
},
@ -191,9 +210,8 @@ export default defineComponent({
});
</script>
<style scoped>
/* 定义content类的样式规则设置为flex布局方向为垂直方向宽度占父容器的80%上下外边距为10vh视口高度的10%),禁止用户选中文本内容 */
.content {
display: flex;
flex-direction: column;
@ -205,31 +223,30 @@ export default defineComponent({
user-select: none;
}
/* 定义card-child类的样式规则设置内边距为0.5rem */
.card-child {
padding: 0.5rem;
}
/* 媒体查询当屏幕宽度小于等于600px时设置btn_photo类元素的宽度和字体大小样式用于适配小屏幕设备 */
@media (max-width: 600px) {
.btn_photo {
.btn_photo {
width: 3rem;
/* font: 1rem sans-serif; */
font-size: 1vw;
}
}
/* 定义camera-card类的样式规则设置为flex布局使其内部元素在主轴方向上左对齐在交叉轴方向上居中对齐 */
.camera-card {
display: flex;
justify-content: flex-start;
align-items: center;
}
/* 定义card-title类的样式规则设置字体大小为1.3vw,字体加粗 */
.card-title {
font-size: 1.3vw;
font-weight: bold;
}
</style>
</style>

@ -1,17 +1,18 @@
<!--
Bootstrap 的五个预设响应尺寸分别是
这里是对Bootstrap预设响应尺寸的说明注释用于解释在页面布局中不同屏幕尺寸范围对应的类前缀含义方便后续理解组件在不同设备上的布局表现
xs - Extra Small特小屏幕适用于小型移动设备如手机一般小于576px宽度
sm - Small小屏幕适用于较小的设备如平板电脑或较小的桌面显示器一般在576px到768px之间
md - Medium中等屏幕适用于中等尺寸的设备如普通的桌面显示器或笔记本电脑一般在768px到992px之间
lg - Large大屏幕适用于较大的设备如较大的桌面显示器一般在992px到1200px之间
xl - Extra Large特大屏幕适用于非常大尺寸的设备如大型显示器或电视一般大于1200px宽度
-->
<template>
<!-- 创建一个名为my-chart的div容器作为整个图表区域的外层容器 -->
<div class="my-chart">
<!-- 使用el-row组件创建一个行布局设置列间距gutter为24像素用于对内部列元素进行分组布局 -->
<el-row :gutter="24">
<!-- 第一行 -->
<!-- 以下是第一行的布局代码目前被注释掉了原本计划放置两个等宽的列元素在不同屏幕尺寸下有相应的占比设置每个列元素内包含一个空的chartCard类的div可能用于后续添加具体图表内容 -->
<!-- <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12">
<div class="chartCard " />
</el-col>
@ -19,9 +20,11 @@ xl - Extra Large特大屏幕适用于非常大尺寸的设备如大
<div class="chartCard " />
</el-col> -->
<!-- 第二行 -->
<!-- 第二行的布局包含三个列元素分别用于放置不同的图表 -->
<el-col :xs="24" :sm="24" :md="24" :lg="8" :xl="8">
<!-- 创建一个名为chartCard的div元素设置宽度为100%高度为20rem内边距为0.3rem作为折线图的容器 -->
<div class="chartCard " style='width:100%; height:20rem; padding: 0.3rem;'>
<!-- 创建一个id为lineChart的div元素设置宽度为100%高度为98%顶部内边距为1rem作为Echarts折线图的绘制区域 -->
<div id='lineChart' style='width:100%; height:98%; padding-top: 1rem;'></div>
</div>
</el-col>
@ -40,51 +43,73 @@ xl - Extra Large特大屏幕适用于非常大尺寸的设备如大
</template>
<script setup lang="ts">
// vuerefonMountedtoRefrefonMountedtoRef使
import { ref, onMounted, toRef } from "vue";
// echarts
import * as echarts from "echarts";
//
onMounted(() => {
// EChartsOptionecharts.EChartsOption便
type EChartsOption = echarts.EChartsOption;
// 线
// 线
// idlineChartDOM使!
var chartDomLineChart = document.getElementById('lineChart')!;
// 使echarts.init线DOM
var lineChart = echarts.init(chartDomLineChart);
// option线EChartsOption
var option: EChartsOption;
option = {
// 线线线
color: ['#80FFA5'],
title: {
//
text: ' 🚀 车流量折线图 ',
// left: 'center'
// subtext: ''
// left: 'center'使
// subtext: ''使
},
tooltip: {
//
trigger: 'axis',
axisPointer: {
// 便
type: 'cross',
label: {
//
backgroundColor: '#80FFA5'
}
},
// {b}{c}
formatter: '{b}:<br/> 车流量:{c} %'
},
legend: {
//
data: ['车流量'],
// 0
bottom: 0
},
//
//
toolbox: {
feature: {
saveAsImage: {
pixelRatio: 2, // 1
title: '下载', //
// icon: 'image://path/to/save-icon.png', //
name: '车流量折线图', // 使
// backgroundColor: 'transparent', //
excludeComponents: ['toolbox'], //
show: true, // true
// 12
pixelRatio: 2,
//
title: '下载',
//
// icon: 'image://path/to/save-icon.png',
// 使线
name: '车流量折线图',
//
// backgroundColor: 'transparent',
//
excludeComponents: ['toolbox'],
// true
show: true,
//
// emphasis: {
// show: true, // true
// iconStyle: {
@ -100,32 +125,50 @@ onMounted(() => {
grid: {
// 5%
left: '5%',
// 5%
right: '5%',
// 20%
top: '20%',
// 10%
bottom: '10%',
// true使
containLabel: true
},
xAxis: [{
// 使
boundaryGap: false,
//
type: 'category',
// x
data: ['12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26']
}],
yAxis: [{
// y
type: 'value'
}],
series: [
{
//
name: '车流量',
// 线
type: 'line',
// smooth: true, // 线
// 使线线线
// smooth: true,
// 线
// symbol: 'circle',
// 线
// symbolSize: 5,
//
//showSymbol: false,
//
data: [90, 80, 70, 75, 70, 80, 75, 70, 60, 75, 60, 60, 70, 85, 90,],
areaStyle: {
// 0.8使
opacity: 0.8,
// 线
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
@ -138,34 +181,50 @@ onMounted(() => {
])
},
emphasis: {
//
focus: 'series'
},
},
{
polyline: true,
//
// showSymbol: false,
// 线线
name: "流动光线",
// lines线
type: "lines",
// cartesian2d线
coordinateSystem: "cartesian2d",
effect: {
delay: 100, // 100ms
// 100ms使线
delay: 100,
// 线0.5线
trailLength: 0.5,
// true线
show: true,
// 55
period: 5,
// 线4
symbolSize: 4,
// true使线
loop: true,
},
lineStyle: {
// 线alphaf0
color: "#20db9df0",
// 线00线
width: 0,
// 线00线
opacity: 0,
curveness: 0.3, //
// type: "curve", // 线
// 线0.3使线
curveness: 0.3,
// 线线curveness
// type: "curve",
},
data: [
{
// 线xy线
coords: [[0, 90], [1, 80], [2, 70], [3, 75], [4, 70], [5, 80], [6, 75], [7, 70], [8, 60], [9, 75], [10, 60], [11, 60], [12, 70], [13, 85], [14, 90]],
},
]
@ -173,283 +232,25 @@ onMounted(() => {
]
};
// optionnullundefined线使
option && lineChart.setOption(option);
//
// handleResizehandleResize使
// window.addEventListener("resize", handleResize);
window.addEventListener("resize", function () {
lineChart.resize();
});
//
//
var chartDomBarChart = document.getElementById('barChart')!;
var barChart = echarts.init(chartDomBarChart);
//
// x使prettier-ignore
// prettier-ignore
let dataAxis = ['点', '击', '柱', '子', '或', '者', '两', '指', '在', '触', '屏', '上', '滑', '动', '能', '够', '自', '动', '缩', '放'];
// prettier-ignore
//
let data = [220, 182, 191, 234, 290, 330, 310, 123, 442, 321, 90, 149, 210, 122, 133, 334, 198, 123, 125, 220];
// yy500
let yMax = 500;
let dataShadow = [];
for (let i = 0; i < data.length; i++) {
dataShadow.push(yMax);
}
option = {
title: {
text: ' 🚀 车辆速度统计柱状图 ',
// left: 'center'
// subtext: 'Feature Sample: Gradient Color, Shadow, Click Zoom'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: 'rgb(0, 221, 255)'
}
},
formatter: '{b}:<br/> 车流量:{c} %'
},
legend: {
data: ['车流量'],
bottom: 0
},
//
toolbox: {
feature: {
saveAsImage: {
pixelRatio: 2, // 1
title: '下载', //
// icon: 'image://path/to/save-icon.png', //
name: '车流量折线图', // 使
// backgroundColor: 'transparent', //
excludeComponents: ['toolbox'], //
show: true, // true
// emphasis: {
// show: true, // true
// iconStyle: {
// textPosition: 'bottom',
// color: '#000',
// borderColor: '#000',
// borderWidth: 1
// }
// }
}
}
},
xAxis: {
data: dataAxis,
axisLabel: {
color: '#999'
}
},
yAxis: {
axisLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
color: '#999'
}
},
grid: {
left: '5%',
right: '5%',
top: '20%',
bottom: '10%',
containLabel: true
},
dataZoom: [
{
type: 'inside'
}
],
series: [
{
name: '车流量',
type: 'bar',
showBackground: true,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgb(0, 221, 255)' },
// { offset: 0.5, color: 'rgb(0, 221, 255)' },
{ offset: 1, color: 'rgb(77, 119, 255)' }
// { offset: 0, color: 'rgb(6,190,179)' },
// // { offset: 0.5, color: 'rgb(0, 221, 255)' },
// { offset: 1, color: 'rgb(1,141,160)' }
])
},
emphasis: {
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#2378f7' },
{ offset: 0.7, color: '#2378f7' },
{ offset: 1, color: '#83bff6' }
])
}
},
data: data
}
]
};
// Enable data zoom when user click bar.
const zoomSize = 6;
barChart.on('click', function (params) {
console.log(dataAxis[Math.max(params.dataIndex - zoomSize / 2, 0)]);
barChart.dispatchAction({
type: 'dataZoom',
startValue: dataAxis[Math.max(params.dataIndex - zoomSize / 2, 0)],
endValue:
dataAxis[Math.min(params.dataIndex + zoomSize / 2, data.length - 1)]
});
});
option && barChart.setOption(option);
//
window.addEventListener("resize", function () {
barChart.resize();
});
//
var chartDomPieChart = document.getElementById('pieChart')!;
var pieChart = echarts.init(chartDomPieChart);
option = {
title: {
text: ' 🚀 车辆类型统计图',
// subtext: 'Fake Data',
// left: 'center'
},
tooltip: {
trigger: 'item',
formatter: '{b}:<br/> 车流量:{c} %'
},
legend: {
bottom: 10,
left: 'center',
data: ['私家车', '面包车', '大客车', '大卡车']
},
series: [
{
type: 'pie',
radius: '65%',
center: ['50%', '50%'],
selectedMode: 'single',
data: [
{
value: 434, name: '大卡车', itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgb(89,253,193)' },
{ offset: 1, color: 'rgb(48,214,231)' }
])
},
},
{
value: 1548, name: '私家车',
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgb(37,210,234)' },
{ offset: 1, color: 'rgb(8,209,255)' }
])
},
},
{
value: 735, name: '面包车',
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgb(148,107,244)' },
{ offset: 1, color: 'rgb(151,162,247)' }
])
},
},
{
value: 510, name: '大客车',
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgb(61,185,249)' },
{ offset: 1, color: 'rgb(98,106,241)' }
])
},
},
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.02)'
}
}
}
]
};
option && pieChart.setOption(option);
//
window.addEventListener("resize", function () {
pieChart.resize();
});
//
});
// function handleResize() {
// var chartDom = document.getElementById('showorders')!;
// var myChart = echarts.init(chartDom);
// myChart.resize();
// }
</script>
<style>
.my-chart {
width: 80%;
margin: auto;
margin-top: 10vh;
margin-bottom: 10vh;
}
.el-row {
margin-bottom: 20px;
}
.el-row:last-child {
margin-bottom: 0;
}
.el-col {
margin-top: 20px;
border-radius: 4px;
}
.chartCard {
border-radius: 4px;
min-height: 36px;
border-radius: 15px;
/* box-shadow: 0 3px 3px 3px rgba(0, 0, 0, 0.04); */
transition: all 0.2s ease-in-out;
border: 1px solid rgba(181, 233, 248, 0.878);
}
.chartCard:hover {
transform: translateY(-0.5px);
box-shadow: 0 0 20px rgba(23, 196, 185, 0.2);
border: 1px solid rgba(40, 189, 230, 0.878);
}
</style>
// dataShadow

@ -1,554 +1,225 @@
<template>
<!-- 预测部分 -->
<!-- 预测部分的整体容器设置类名为"content"用于包裹与预测相关的各个功能模块通过样式控制其布局间距等显示效果 -->
<div class="content">
<div class="card card-float">
<!-- 模式设置 -->
<!-- 模式设置区域的标题用于提示下方是进行操作模式相关设置的部分 -->
<div class="card-title">
模式设置
</div>
<div class="card-child">
<div style="margin-top: 2px">
<!-- 使用el-radio-group组件创建一个单选框组通过v-model指令双向绑定到名为"radio1"的响应式变量用于让用户选择不同的操作模式 -->
<el-radio-group v-model="radio1">
<!-- 单个单选框选项设置label为"1"添加"border"样式类用于显示边框效果显示文本为上传图片表示选择此选项可进行图片上传操作 -->
<el-radio label="1" border class="radio">上传图片</el-radio>
<!-- 单个单选框选项设置label为"2"添加"border"样式类用于显示边框效果显示文本为摄像模式表示选择此选项可进入摄像相关功能操作模式 -->
<el-radio label="2" border class="radio">摄像模式</el-radio>
<!-- 以下这行单选框代码被注释掉了原本可能也是一种摄像相关的模式选项可根据实际业务需求决定是否启用 -->
<!-- <el-radio label="2" border>打开摄像头</el-radio> -->
</el-radio-group>
</div>
</div>
<hr class="divider">
<!-- 水平分割线用于在视觉上区分不同的功能模块区域设置类名为"divider"通过样式设置其外观如高度颜色等 -->
<!-- 图片上传 -->
<!-- 根据"radio1"的值决定是否显示图片上传相关内容如果"radio1"的值为'1'则显示此部分 -->
<div v-if="radio1 === '1'">
<!-- 以下这部分代码被注释掉了原本可能是用于显示图片上传标题的若需要可取消注释启用 -->
<!-- <div class="card-title" style="margin-top: 10px;">
图片上传
</div> -->
<div class="card-child">
<!-- 使用名为"Myupload"的自定义组件通过ref绑定为"upload"用于实现图片上传功能并监听该组件触发的"clickChild"事件事件处理函数为"clickEven" -->
<Myupload ref="upload" @clickChild="clickEven"></Myupload>
</div>
</div>
<!-- 摄像头功能 -->
<!-- 根据"radio1"的值决定是否显示摄像头相关功能内容如果"radio1"的值为'2'则显示此部分 -->
<div v-if="radio1 === '2'">
<!-- 以下这部分代码被注释掉了原本可能是用于显示开启摄像标题的若需要可取消注释启用 -->
<!-- <div class="card-title" style="margin-top: 10px;">
开启摄像
</div> -->
<div class="card-child">
<!-- 使用名为"Mycamera"的自定义组件用于实现摄像相关功能并监听该组件触发的"message"事件事件处理函数为"handleMessage" -->
<Mycamera @message="handleMessage"></Mycamera>
</div>
</div>
</div>
<!-- 预测结果 -->
<!-- 预测结果展示区域的容器设置类名为"card card-float"用于包裹展示预测结果相关的内容 -->
<div class="card card-float">
<!-- 预测结果区域的标题提示下方展示的是预测情况相关内容 -->
<div class="card-title">
预测情况
</div>
<div style="padding: 10px; width: 100%; height: 90%;">
<!-- 使用img标签展示预测结果图片通过绑定":src"属性为"after_img_path"变量来动态设置图片的源地址添加"pre-img"样式类用于控制图片的样式同时通过ref绑定为"imageRef"方便在JavaScript中操作该图片元素 -->
<img :src="after_img_path" class="pre-img" ref="imageRef">
</div>
</div>
</div>
<!-- 标签部分 -->
<!-- 标签部分的整体容器设置类名为"content-table"并添加了一个名为"bounce"的自定义属性可能用于后续的JavaScript交互或者样式控制等具体需看业务逻辑用于包裹展示标签相关信息的表格 -->
<div class="content-table" name="bounce">
<div>
<!-- 使用el-table组件创建一个表格通过":data"属性绑定名为"mytableData"的响应式变量来设置表格的数据来源设置表格宽度为100%并通过":row-class-name"属性绑定"tableRowClassName"函数来动态设置表格行的样式类名 -->
<el-table :data="mytableData" style="width: 100%" :row-class-name="tableRowClassName">
<!-- 定义表格列展示"标签ID"信息通过"prop"属性指定对应数据对象中的属性名通过"label"属性设置表头显示的文本设置列宽度为180像素 -->
<el-table-column prop="id" label="标签ID" width="180" />
<!-- 定义表格列展示"类别"信息通过"prop"属性指定对应数据对象中的属性名通过"label"属性设置表头显示的文本设置列宽度为180像素 -->
<el-table-column prop="class" label="类别" width="180" />
<!-- 定义表格列展示"置信度"信息通过"prop"属性指定对应数据对象中的属性名通过"label"属性设置表头显示的文本设置列宽度为180像素 -->
<el-table-column prop="cf" label="置信度" width="180" />
<!-- 定义表格列展示"坐标(x1)"信息通过"prop"属性指定对应数据对象中的属性名通过"label"属性设置表头显示的文本设置列宽度为180像素 -->
<el-table-column prop="x1" label="坐标(x1)" width="180" />
<!-- 定义表格列展示"坐标(y1)"信息通过"prop"属性指定对应数据对象中的属性名通过"label"属性设置表头显示的文本设置列宽度为180像素 -->
<el-table-column prop="y1" label="坐标(y1)" width="180" />
<!-- 定义表格列展示"坐标(x2)"信息通过"prop"属性指定对应数据对象中的属性名通过"label"属性设置表头显示的文本设置列宽度为180像素 -->
<el-table-column prop="x2" label="坐标(x2)" width="180" />
<!-- 定义表格列展示"坐标(y2)"信息通过"prop"属性指定对应数据对象中的属性名通过"label"属性设置表头显示的文本设置列宽度为180像素 -->
<el-table-column prop="y2" label="坐标(y2)" width="180" />
</el-table>
</div>
</div>
<!-- 数据部分 -->
<!-- 数据部分使用名为"Mychart"的自定义组件用于展示相关数据图表具体图表内容和功能由该组件内部实现决定 -->
<Mychart></Mychart>
<!-- 侧边栏 -->
<!-- 侧边栏的整体容器设置类名为"config-box"通过v-show指令绑定"sidebarVisible"变量来控制侧边栏的显示与隐藏 -->
<div class="config-box" v-show="sidebarVisible">
<!-- 配置部分 -->
<!-- 配置部分的容器设置类名为"config-tool"用于包裹各种配置相关的功能组件 -->
<div class="config-tool">
<!-- 车流阈值 -->
<!-- 车流阈值配置区域的标题用于提示下方是进行车流阈值相关设置的部分 -->
<div class="config_title" style="margin-top: 8px;">
车流阈值
</div>
<div class="card-child">
<!-- 使用el-slider组件创建一个滑块通过v-model指令双向绑定到名为"Slidervalue1"的响应式变量用于用户调整车流阈值的值设置显示输入框尺寸为"small" -->
<el-slider v-model="Slidervalue1" show-input size="small" />
</div>
<!-- 交互比 -->
<!-- 交互比配置区域的标题用于提示下方是进行交互比相关设置的部分 -->
<div class="config_title" style="margin-top: 8px;">
交互比
</div>
<div class="card-child">
<!-- 使用el-slider组件创建一个滑块通过v-model指令双向绑定到名为"Slidervalue2"的响应式变量用于用户调整交互比的值设置显示输入框尺寸为"small" -->
<el-slider v-model="Slidervalue2" show-input size="small" />
</div>
<!-- 置信度 -->
<!-- 置信度配置区域的标题用于提示下方是进行置信度相关设置的部分 -->
<div class="config_title" style="margin-top: 8px;">
置信度
</div>
<div class="card-child">
<!-- 使用el-slider组件创建一个滑块通过v-model指令双向绑定到名为"Slidervalue3"的响应式变量用于用户调整置信度的值设置显示输入框尺寸为"small" -->
<el-slider v-model="Slidervalue3" show-input size="small" />
</div>
</div>
<!-- 侧边栏右边箭头 -->
<!-- 侧边栏右边箭头的容器用于展示一个向右的箭头图标通过里面的"fuhao"元素展示">"符号来模拟箭头用于提示用户可点击展开或收起侧边栏 -->
<div class="right-arrow" >
<div class="fuhao">
&gt;
</div>
</div>
</div>
</template>
<script setup lang="ts">
</template><script setup lang="ts">
// vuerefonMountedrefonMounted
import { ref, onMounted } from 'vue';
// "Label""@/models/index"
import {Label} from "@/models/index"
// "Myupload""../el-components/upload.vue"
import Myupload from "../el-components/upload.vue";
// "Mychart""../components/chart.vue"
import Mychart from "../components/chart.vue";
// "Mycamera""../components/camera.vue"
import Mycamera from "../components/camera.vue";
import afterImgPath from "@/assets/images/predit111.jpg"; //
// "assets/images""predit111.jpg""afterImgPath"
import afterImgPath from "@/assets/images/predit111.jpg";
// "element-plus""ElMessage"
import { ElMessage } from "element-plus";
// "Mylive"
// import Mylive from "../el-components/live2d.vue"
const radio1 = ref('1')
// "radio1"'1''1''2'
const radio1 = ref('1');
// --------------------------------------------------------------- config
const Slidervalue1 = ref(0)
const Slidervalue2 = ref(0)
const Slidervalue3 = ref(0)
// "Slidervalue1"0
const Slidervalue1 = ref(0);
// "Slidervalue2"0
const Slidervalue2 = ref(0);
// "Slidervalue3"0
const Slidervalue3 = ref(0);
// ---------------------------------------------------------------TS
// "Alert""title""type""success""warning""error"
interface Alert {
title: string;
type: string;
}
// "alerts""Alert"
const alerts = ref<Alert[]>([]);
// "showAlerts"
function showAlerts(msg: string, type: string) {
// "alerts"
if (alerts.value.length > 0) {
//
alerts.value.shift();
}
// "Alert"
const newAlert: Alert = {
title: msg,
type: type,
};
// Alert
// "Alert""alerts"
alerts.value.push(newAlert);
// 20002"alerts"
setTimeout(() => {
alerts.value = [];
}, 2000); // 3
}, 2000);
}
// ---------------------------------------------------------------TS
// ref
// "message""Mycamera"
const message = ref('');
//
// "handleMessage""Mycamera""message""message""clickEven""clickEven"
const handleMessage = (info: any) => {
message.value = info;
console.log('父组件:', info)
clickEven(info); // clickEven
console.log('父组件:', info);
clickEven(info);
};
// ---------------------------------------------------
// "sidebarVisible"false
const sidebarVisible = ref(false);
//
onMounted(() => {
//
// document.querySelector"config-box"HTML"HTMLElement""music"
const music = document.querySelector<HTMLElement>(".config-box");
// document.querySelector"right-arrow"HTML"HTMLElement""arrow"
const arrow = document.querySelector<HTMLElement>(".right-arrow");
// document.querySelector"fuhao"HTML"HTMLElement""fuhao"
const fuhao = document.querySelector<HTMLElement>(".fuhao");
let isFlipped = false;
// "arrowClickHandler"
const arrowClickHandler = () => {
if (music && arrow && fuhao) {
// "isFlipped"true
if (isFlipped) {
// "config-box""show"使CSS
music.classList.remove("show");
// "fuhao""flip"使CSS
fuhao.classList.remove("flip");
isFlipped = false;
arrow.setAttribute("transform", "rotate(0)");
} else {
isFlipped = true;
music.classList.add("show");
fuhao.classList.add("flip");
arrow.setAttribute("transform", "rotate(180)");
}
}
};
arrow?.addEventListener("click", arrowClickHandler);
//
const updateSidebarVisibility = () => {
sidebarVisible.value = window.pageYOffset > 500;
// console.log(sidebarVisible.value)
};
window.addEventListener('scroll', updateSidebarVisibility);
});
const rownumber = ref(0)
const tableRowClassName = ({
row,
rowIndex,
}: {
row: Label
rowIndex: number
}) => {
if (rownumber.value === 0) {
rownumber.value = 1
return 'primary-row'
} else if (rownumber.value === 1) {
rownumber.value = 2
return ''
} else if (rownumber.value === 2) {
rownumber.value = 3
return 'success-row'
}
rownumber.value = 0
return ''
}
let mytableData = ref<Label[]>([
// {
// id: '1',
// class: 'car',
// cf: '0.5',
// x1: '200',
// y1: '100',
// x2: '200',
// y2: '100',
// },
]);
let after_img_path: string = afterImgPath
let labels: any[];
// ---------------------------------------------------------------
const clickEven = (val: { code: number, data: any, msg: string }) => {
if (val.data == '照片中没有目标物体哟!') {
ElMessage({
message: '照片中没有目标物体哟!',
type: "warning",
duration: 5 * 1000,
});
return;
}
if (val.msg == '服务器繁忙,请稍后再试!') {
ElMessage({
message: '服务器繁忙,请稍后再试!',
type: "error",
duration: 5 * 1000,
});
return;
}
if (val.msg == '执行成功!') {
ElMessage({
message: '识别成功!',
type: "success",
duration: 5 * 1000,
});
// showAlerts('', 'success');
}
if (imageRef.value) {
imageRef.value.src = val.data.after_img_path;
}
labels = val.data.labels; // val.data.labels labels
// rowData使 map labels
const rowData: any[] = [];
labels.map((label) => {
const parts = label.split(' ').filter(Boolean);
console.log(parts);
const obj = {
id: parts[1],
class: parts[3],
cf: parts[5],
x1: parts[7],
y1: parts[8],
x2: parts[9],
y2: parts[10].slice(0, -1),
};
rowData.push(obj);
});
// console.log(rowData); // rowData
mytableData.value = rowData
console.log(mytableData.value)
};
// ---------------------------------------------------------------
const imageRef = ref<HTMLImageElement | null>(null);
const adjustImageSize = () => {
if (imageRef.value && imageRef.value.naturalHeight > imageRef.value.naturalWidth) {
imageRef.value.style.height = '100%';
}
};
adjustImageSize();
</script>
<style scoped>
.content {
display: flex;
flex-direction: column;
width: 80%;
/* height: 80vh; */
margin: auto;
margin-top: 10vh;
margin-bottom: 10vh;
user-select: none;
}
@media (min-width: 600px) {
.content {
flex-direction: row;
}
}
.content-table {
display: flex;
flex-direction: column;
width: 80%;
/* height: 80vh; */
margin: auto;
margin-top: 10vh;
margin-bottom: 10vh;
}
.card {
flex: 1;
padding: 1rem;
margin: 1rem;
border-radius: 15px !important;
/* box-shadow: 4px 4px 2px rgba(131, 220, 238, 0.284); */
transition: all 0.3s ease-in-out;
border: 1px solid rgba(181, 233, 248, 0.878);
}
.card-title {
font-size: 1.3vw;
font-weight: bold;
}
.card-child {
padding: 0.5rem;
}
/* 卡片悬停 */
.card-float:hover {
transform: translateY(-1px);
box-shadow: 0 0 20px rgba(23, 196, 185, 0.2);
border: 1px solid rgba(40, 189, 230, 0.878);
}
.config_title {
font-size: 1vw;
font-weight: bold;
}
.pre-img {
height: auto;
width: 100%;
}
</style>
<!-- 表格CSS -->
<style>
.el-table .primary-row {
--el-table-tr-bg-color: var(--el-color-primary-light-9);
}
.el-table .success-row {
--el-table-tr-bg-color: var(--el-color-success-light-9);
}
</style>
<!-- 侧边栏CSS -->
<style>
/* 定义样式 */
.right-arrow {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
font-size: 30px;
transition: all .2s ease-in-out;
border-radius: 0px 1.25rem 1.25rem 0px;
/* 无法选中 */
user-select: none;
}
.right-arrow:hover {
background-color: rgb(248, 253, 253);
color: rgb(82, 196, 248);
}
/* 符号 > */
.fuhao {
transition: all .3s ease-in-out;
justify-content: center;
align-items: center;
font-size: 2rem;
transition: all .2s ease-in-out;
}
.fuhao.flip {
transform: rotate(-180deg);
}
.config-tool {
flex: 8;
position: relative;
overflow: hidden;
transition: all 0.5s ease-in-out;
padding-left: 1rem;
padding-right: 1rem;
}
.config-box {
z-index: 9999;
position: fixed;
flex: 9;
width: 400px;
height: 250px;
padding-left: 0.8rem;
transform: scaleX(-1);
transform: translateY(-50%);
/* 左下角 */
left: -360px;
bottom: 18%;
/* 添加阴影 */
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.2);
background-color: rgb(248, 253, 253);
/* 横向排列 */
display: flex;
flex-direction: row;
border-radius: 0px 1.25rem 1.25rem 0px;
/* 平滑 */
transition: all .2s ease-in-out;
}
.config-box:hover {
box-shadow: 0 0 20px rgba(23, 196, 185, 0.5);
}
.config-box.show {
left: 0px;
opacity: 1;
}
@media (max-width: 767px) {
/* 手机设备 */
.config-box {
position: fixed;
flex: 9;
width: 300px;
/* 左下角 */
left: -270px;
transform: scaleX(-1);
transform: translateY(-50%);
/* 添加阴影 */
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.2);
/* 横向排列 */
display: flex;
flex-direction: row;
border-radius: 10px;
/* 平滑 */
transition: all .2s ease-in-out;
}
}
/* 分割线 */
.divider {
margin: 0.5rem;
border: none;
height: 1px;
background-color: rgb(161, 225, 241);
}
</style>
<!-- radio -->
<style>
@media (max-width: 600px) {
.radio {
margin: 0.5rem;
}
}
</style>@/models/label
// "right-arrow""

@ -1,28 +1,37 @@
<template>
<!-- 创建一个div容器作为图片元素的外层包裹 -->
<div>
<!-- 使用img标签展示一张图片图片的源地址指向项目中assets/images目录下的404.gif文件添加image类名用于样式设置通过ref绑定为imageRef方便在JavaScript中操作该图片元素 -->
<img src="../../../assets/images/404.gif" class="image" ref="imageRef">
</div>
</template>
<script lang="ts">
// vuedefineComponentonMountedrefdefineComponentVueonMountedref
import { defineComponent, onMounted, ref } from 'vue';
// 使defineComponentVue
export default defineComponent({
//
mounted() {
// onMountedadjustImageSize
onMounted(() => {
this.adjustImageSize();
});
},
//
methods: {
//
adjustImageSize() {
// this.$refsrefimageRefDOMHTMLImageElementimageElement便
const imageElement = this.$refs.imageRef as HTMLImageElement;
// CSS
if (imageElement.naturalHeight > imageElement.naturalWidth) {
// 8px
imageElement.style.height = '8px';
} else {
// 80px
imageElement.style.width = '80px';
}
},
@ -30,10 +39,10 @@ export default defineComponent({
});
</script>
<style scoped>
/* 定义image类的样式规则设置该类元素的底部外边距为20px用于调整图片与下方元素的间距 */
.image {
margin-bottom: 20px;
}
</style>
</style>

@ -1,21 +1,25 @@
<template>
<!-- 创建一个外层的div容器类名为"slider-demo-block"它将作为整个滑块示例或相关功能的包裹元素后续通过对应的样式来控制其内部元素布局等显示效果 -->
<div class="slider-demo-block">
</div>
</template>
<script lang="ts" setup>
// vuerefref使VueDOM
import { ref } from 'vue'
// "value"0使
const value = ref(0)
</script>
<style scoped>
</script><style scoped>
/* 为类名为"slider-demo-block"的元素设置样式这里将其设置为flex布局使得内部子元素可以按照flex布局规则进行排列同时让子元素在交叉轴垂直方向默认情况下上居中对齐 */
.slider-demo-block {
display: flex;
align-items: center;
}
.slider-demo-block .el-slider {
/* 为类名为"slider-demo-block"下的类名为"el-slider"的子元素设置样式这里将其顶部外边距设置为0使其顶部与相邻元素紧密排列并且设置左边距为15px用于控制它与左侧元素的间隔距离 */
.slider-demo-block.el-slider {
margin-top: 0;
margin-left: 15px;
}
</style>
</style>

@ -1,27 +1,40 @@
<template>
<el-upload v-model:file-list="fileList" action="http://127.0.0.1:5500/recognize" list-type="picture-card"
:on-success="handleSuccess" :on-error="handleError" :on-exceed="handleExceed" :auto-upload="false" ref="uploadRef"
:limit="1">
<!-- 使用el-upload组件创建文件上传功能区域以下是对该组件各属性及插槽的配置说明 -->
<el-upload
v-model:file-list="fileList"
action="http://127.0.0.1:5500/recognize"
list-type="picture-card"
:on-success="handleSuccess"
:on-error="handleError"
:on-exceed="handleExceed"
:auto-upload="false"
ref="uploadRef"
:limit="1"
>
<!-- 在el-upload组件内使用el-icon组件展示一个+图标Plus图标组件用于提示用户点击此处可添加文件进行上传 -->
<el-icon>
<Plus />
</el-icon>
<!-- 使用具名插槽#file来自定义文件列表中每个文件项的展示内容接收当前文件对象作为参数 -->
<template #file="{ file }">
<div>
<!-- 展示上传文件的缩略图通过绑定:src属性为文件对象的url属性来显示对应的图片添加相应的样式类用于控制缩略图样式 -->
<img class="el-upload-list__item-thumbnail" :src="file.url" alt="" />
<span class="el-upload-list__item-actions">
<!-- 以下这部分代码被注释掉了原本可能用于实现点击预览图片的功能可根据需求决定是否启用 -->
<!-- <span class="el-upload-list__item-preview" @click="handlePictureCardPreview(file)">
<el-icon><zoom-in /></el-icon>
</span> -->
<!-- 以下这部分代码被注释掉了原本可能用于实现点击下载图片的功能可根据需求决定是否启用 -->
<!-- <span v-if="!disabled" class="el-upload-list__item-delete" @click="handleDownload(file)">
<el-icon>
<Download />
</el-icon>
</span> -->
<!-- 展示一个删除图标Delete图标组件当用户点击时触发handleRemove函数来删除对应的文件添加条件判断v-if="!disabled"可能用于控制是否可删除文件的权限 -->
<span v-if="!disabled" class="el-upload-list__item-delete" @click="handleRemove(file)">
<el-icon>
<Delete />
@ -31,194 +44,206 @@
</div>
</template>
<!-- 使用具名插槽#tip来自定义上传提示信息的展示内容 -->
<template #tip>
<div class="el-upload__tip">
jpg/png 文件必须小于 5MB
</div>
</template>
</el-upload>
<!-- 使用el-dialog组件创建一个对话框通过v-model双向绑定dialogVisible变量来控制对话框的显示与隐藏对话框内用于展示预览图片 -->
<el-dialog v-model="dialogVisible">
<img w-full :src="dialogImageUrl" alt="Preview Image" />
</el-dialog>
<!-- 预测按钮 -->
<!-- 预测按钮相关功能区域的外层容器 -->
<div class="card-child" style="margin-top: 25px; margin: auto;">
<!-- 以下这部分代码被注释掉了原本是一个简单的开始预测按钮点击可触发submitUpload函数可根据需求决定是否启用 -->
<!-- <el-button class="" type="success" @click="submitUpload" style="width: 150px;">
开始预测
</el-button> -->
<!-- 使用el-popover组件创建一个弹出框用于在按钮点击时展示提示信息以下是对该组件各属性及插槽的配置说明 -->
<el-popover placement="bottom" title="提示" :width="200" trigger="click" :visible="uploadVisible"
:content="uploadContent">
<template #reference>
<el-button class="" :type="loading ? 'info' : 'success'" :loading="loading" @click="submitUpload"
<!-- 在弹出框的参考元素插槽内放置一个el-button按钮按钮文本根据loading变量的值动态显示正在上传... 开始预测按钮类型根据loading变量的值动态改变info或success点击按钮触发submitUpload函数同时设置按钮的宽度外边距等样式 -->
<el-button class="" :type="loading? 'info' : 'success'" :loading="loading" @click="submitUpload"
style="width: 130px; margin: 10px; ">
{{ loading ? '正在上传...' : '开始预测' }}
{{ loading? '正在上传...' : '开始预测' }}
</el-button>
</template>
</el-popover>
<!-- 使用el-popover组件创建另一个弹出框用于在按钮点击时展示提示信息以下是对该组件各属性及插槽的配置说明 -->
<el-popover placement="bottom" title="提示" :width="200" trigger="click" :visible="removeVisible"
:content="removeContent">
<template #reference>
<!-- 在弹出框的参考元素插槽内放置一个el-button按钮按钮类型为danger危险样式通常表示删除等重要操作点击按钮触发handleRemove函数来移除图片同时设置按钮的宽度外边距等样式 -->
<el-button class="" type="danger" @click="handleRemove" style="width: 130px; margin: 10px;">
移除图片
</el-button>
</template>
</el-popover>
</div>
</template>
<script lang="ts" setup>
// vuerefRefref使Vue
import { Ref, ref } from 'vue'
// @element-plus/icons-vueDeleteDownloadPlusZoomIn
import { Delete, Download, Plus, ZoomIn } from '@element-plus/icons-vue'
// element-plusUploadInstanceel-uploadUploadFileUploadUserFile
import type { UploadInstance, UploadFile, UploadUserFile } from 'element-plus'
const fileList = ref<UploadUserFile[]>([
])
// fileListUploadUserFileel-uploadfile-list
const fileList = ref<UploadUserFile[]>([])
// uploadVisiblefalseel-popover
const uploadVisible = ref(false)
// removeVisiblefalseel-popover
const removeVisible = ref(false)
// removeContent
const removeContent = ref("")
// uploadContent
const uploadContent = ref("")
// loadingfalse
const loading = ref(false)
// timersetTimeoutnullnull
let timer: ReturnType<typeof setTimeout> | null = null
// 1000
// showUploadVisible
// visibleRef<boolean>timertime10001
const showUploadVisible = (visible: Ref<boolean>, timer: ReturnType<typeof setTimeout> | null, time: number | 1000) => {
// timernull
if (timer) {
clearTimeout(timer)
timer = null
}
// visibletrue使
visible.value = true
// timevisiblefalse使timernull
timer = setTimeout(() => {
visible.value = false
timer = null
}, time)
}
//
// uploadRefUploadInstanceel-upload便
const uploadRef = ref<UploadInstance>()
const submitUpload = () => {
//
// submitUpload
const submitUpload = () => {
// fileList0
if (fileList.value.length == 0) {
//
uploadContent.value = "您还没有上传图片哟!"
// showUploadVisibleuploadVisibletimer1000
showUploadVisible(uploadVisible, timer, 1000)
//
return
}
//
console.log('上传!!!')
// uploadRefel-uploadsubmit:auto-upload="false"
uploadRef.value!.submit()
// loadingtrue
loading.value = true
}
// dialogImageUrlel-dialogURL
const dialogImageUrl = ref('')
// dialogVisiblefalseel-dialog
const dialogVisible = ref(false)
// disabledfalse使v-if="!disabled"
const disabled = ref(false)
//
// handleRemoveUploadFile
const handleRemove = (file: UploadFile) => {
//
console.log(file)
// fileList
const index = fileList.value.indexOf(file)
// fileList
if (fileList.value.length == 1) {
// 使splicefileListindex
fileList.value.splice(index, 1)
//
//
removeContent.value = "移除成功(*^▽^*)"
// showUploadVisibleremoveVisibletimer1000
showUploadVisible(removeVisible, timer, 1000)
return
}
//
// fileList
removeContent.value = "您还没有上传图片哟!"
// showUploadVisibleremoveVisibletimer1000
showUploadVisible(removeVisible, timer, 1000)
}
//
// handleExceedfilesFileuploadFilesUploadUserFile
const handleExceed = (files: File[], uploadFiles: UploadUserFile[]) => {
//
uploadContent.value = "上传文件数超出限制"
// showUploadVisibleuploadVisibletimer1000
showUploadVisible(uploadVisible, timer, 1000)
}
// 使defineEmitsemit'clickChild''clickChild'
import { defineEmits } from 'vue'
// 使defineEmits
const emit = defineEmits(['clickChild'])
// handleSuccessel-uploadresponseuploadFileuploadFileselement-plus
const handleSuccess = (response: any, uploadFile: any, uploadFiles: any) => {
//
//
// console.log('', response);
loading.value = false //
// loadingfalse
loading.value = false
//
uploadContent.value = "上传成功"
// showUploadVisibleuploadVisibletimer1000
showUploadVisible(uploadVisible, timer, 1000)
//
// 使emit'clickChild'response
emit('clickChild', response)
//
// fileList
fileList.value = []
};
// handleErrorel-uploadresponseuploadFileuploadFileselement-plus
const handleError = (response: any, uploadFile: any, uploadFiles: any) => {
//
//
console.log('上传失败', response);
loading.value = false //
// loadingfalse
loading.value = false
//
uploadContent.value = "上传失败"
// showUploadVisibleuploadVisibletimer1000
showUploadVisible(uploadVisible, timer, 1000)
//
// fileList
fileList.value = []
};
//
// handlePictureCardPreviewUploadFile
const handlePictureCardPreview = (file: UploadFile) => {
// dialogImageUrlURL便el-dialog
dialogImageUrl.value = file.url!
// dialogVisibletrue
dialogVisible.value = true
}
//
const handleDownload = (file: UploadFile) => {
console.log(file)
}
</script>
//

@ -1,45 +1,58 @@
<template>
<!-- Provider组件可能是一个提供数据上下文等功能的外层包裹组件具体功能取决于其自身定义整个页面内容都放置在它内部 -->
<Provider>
<!-- 创建一个类名为"app-wrapper"的div容器作为整个应用页面的外层包裹元素后续通过样式来控制页面整体的布局尺寸等显示相关特性 -->
<div class="app-wrapper">
<!-- 创建一个类名为"main-wrapper"的main元素通常用于表示页面的主要内容区域这里作为页面主体内容的包裹容器 -->
<main class="main-wrapper">
<!-- 使用Header组件用于展示页面的头部内容比如页面标题导航栏等具体内容由Header组件内部实现决定 -->
<Header></Header>
<!--背景轮播-->
<!-- 背景轮播部分使用Images组件推测其功能是实现图片的轮播切换效果作为页面的背景展示元素营造视觉效果 -->
<Images></Images>
<!-- 品牌标题 -->
<!-- 品牌标题部分使用Brand组件可能用于展示品牌相关的标题标识等信息突出品牌特色 -->
<Brand></Brand>
<!-- 创建一个类名为"bg"的div容器可能用于设置背景相关的样式或者作为特定内容的背景容器内部放置Mycontent组件 -->
<div class="bg">
<!-- 使用Mycontent组件用于展示页面主体的具体内容具体展示什么内容由该组件内部实现决定 -->
<Mycontent></Mycontent>
</div>
<!-- 使用Footer组件用于展示页面的底部内容比如版权信息友情链接联系方式等具体内容由Footer组件内部实现决定 -->
<Footer></Footer>
</main>
</div>
</Provider>
</template>
<script setup lang="ts">
// Brand"./Brand/index.vue"
import Brand from "./Brand/index.vue";
// Images"./Swiper/Images.vue"
import Images from "./Swiper/Images.vue";
// Mycontent"./content/content.vue"
import Mycontent from "./content/content.vue"
</script>
<style lang="scss" scoped>
/* 定义一个名为"mt"的样式类用于设置元素的上外边距为1remrem是相对单位相对根元素字体大小并且设置下内边距为1.75rem,可应用到需要这种间距样式的元素上 */
.mt {
margin-top: 1rem;
padding-bottom: 1.75rem;
}
/* 为类名为"app-wrapper"的元素设置样式将其定位方式设置为相对定位设置最小高度为100vh即占满整个视口高度最小宽度为320px确保页面在不同设备上有一个基本的尺寸显示范围 */
.app-wrapper {
position: relative;
min-height: 100vh;
min-width: 320px;
}
/* 为类名为"main-wrapper"的元素设置样式将其设置为flex布局并且布局方向为垂直方向列布局使其内部子元素按列排列设置宽度为100%占满父容器宽度底部内边距为8rem用于预留一定空间可能用于放置Footer等元素 */
.main-wrapper {
display: flex;
flex-direction: column;
width: 100%;
padding: 0 0 8rem;
}
</style>
</style>

@ -1,59 +1,89 @@
<template>
<!-- 创建一个类名为"login"的div容器用于包裹整个登录页面的内容并通过内联样式设置其背景图片图片地址通过绑定的Img_1变量来确定初始值或动态变化时会相应改变背景图 -->
<div class="login" :style="{ 'background-image': 'url(' + Img_1 + ')' }">
<!-- 使用el-form组件创建一个表单用于收集用户登录的相关信息通过ref绑定为"ruleFormRef"方便在JavaScript代码中获取表单实例通过:model绑定"loginForm"对象来实现表单数据的双向绑定:rules绑定"rules"对象来设置表单各字段的验证规则 -->
<el-form ref="ruleFormRef" :model="loginForm" :rules="rules" class="login-form">
<!-- 展示一个标题为"MTSP系统"的h3元素用于标识登录页面所属的系统名称 -->
<h3 class="title">MTSP系统</h3>
<!-- 定义一个表单字段项对应"username"字段用于用户输入账号信息通过prop属性与表单验证规则中的"username"规则相关联 -->
<el-form-item prop="username">
<!-- 使用el-input组件创建一个输入框通过v-model双向绑定到"loginForm.username"设置输入框类型为"text"普通文本输入尺寸为"large"较大尺寸样式并设置占位提示文本为"账号"同时通过具名插槽#prefix插入一个名为"user"的svg-icon图标作为输入框前缀装饰 -->
<el-input v-model="loginForm.username" type="text" size="large" placeholder="账号">
<template #prefix><svg-icon icon-class="user"></svg-icon></template>
</el-input>
</el-form-item>
<!-- 定义一个表单字段项对应"password"字段用于用户输入密码信息通过prop属性与表单验证规则中的"password"规则相关联 -->
<el-form-item prop="password">
<!-- 使用el-input组件创建一个输入框通过v-model双向绑定到"loginForm.password"设置输入框类型为"password"密码输入类型输入内容会显示为密文并设置显示密码切换功能通常是一个眼睛图标用于切换显示密码明文尺寸为"large"占位提示文本为"密码"同时监听键盘按下回车键事件触发handleLogin函数并传入表单实例进行登录操作通过具名插槽#prefix插入一个名为"password"的svg-icon图标作为输入框前缀装饰 -->
<el-input v-model="loginForm.password" type="password" show-password size="large" placeholder="密码"
@keyup.enter="handleLogin(ruleFormRef)">
<template #prefix><svg-icon icon-class="password"></svg-icon></template>
</el-input>
</el-form-item>
<!-- 定义一个表单按钮项用于触发登录操作 -->
<el-form-item>
<!-- 使用el-button组件创建一个按钮通过:loading绑定"loading"变量来控制按钮是否显示加载状态比如登录过程中显示加载动画设置按钮类型为"primary"通常是主要的突出的样式绑定点击事件使用.prevent修饰符阻止默认的表单提交行为点击时触发handleLogin函数并传入表单实例进行登录操作设置按钮宽度为100%占满父容器宽度 -->
<el-button :loading="loading" type="primary" @click.prevent="handleLogin(ruleFormRef)" style="width:100%;">
<!-- 根据"loading"变量的值来动态显示按钮文本内容如果"loading"为false显示"登 录"如果"loading"为true显示"登 录中..." -->
<span v-if="!loading"> </span>
<span v-else> ...</span>
</el-button>
</el-form-item>
</el-form>
<!-- 底部 -->
<!-- 创建一个类名为"el-login-footer"的div容器用于展示页面底部的版权信息等内容 -->
<div class="el-login-footer">
<!-- 展示版权声明文本显示版权所有者及年份信息 -->
<span>Copyright © 2023 By Pan</span>
</div>
</div>
</template>
<script setup lang="ts">
// "element-plus"ElMessage
import { ElMessage } from "element-plus";
// "assets/images""index.jpg"Img_1使
import Img_1 from "@/assets/images/index.jpg";
// "@/router"
import router from "@/router";
// VuexuseStore
import useStore from '@/store';
// "element-plus"FormInstanceFormRules便
import { FormInstance, FormRules } from 'element-plus';
// vuereactiverefreactiveref
import { reactive, ref } from 'vue';
// useStoreVuexuserstore
const { user } = useStore();
// "ruleFormRef"el-formundefined
const ruleFormRef = ref<FormInstance>();
// "loading"falsetruefalse
const loading = ref(false);
// 使reactive"loginForm""username""pan""password""123456"
const loginForm = reactive({
username: "pan",
password: "123456",
});
// 使reactive"rules"FormRules
const rules = reactive<FormRules>({
username: [{ required: true, message: "请输入用户名", trigger: "blur" }],
// "password""blur"6
password: [{ required: true, message: "请输入密码", trigger: "blur" }, { min: 6, message: "密码不能少于6位", trigger: "blur" }],
});
// "handleLogin"undefined
const handleLogin = async (formEl: FormInstance | undefined) => {
//
if (!formEl) return;
// validatevalid
await formEl.validate((valid) => {
// validtrue
if (valid) {
// "loading"true使
loading.value = true;
// userLogInPromise"loginForm".then
user.LogIn(loginForm).then((data: any) => {
//
// console.log(data)
// "code"200
if (data.code === 200) {
// 使ElMessage"""success"绿0.75 * 10000.75onClosewindow.location.reload()使
ElMessage({
message: "登录成功",
type: "success",
@ -62,13 +92,17 @@ const handleLogin = async (formEl: FormInstance | undefined) => {
window.location.reload();
},
});
// 使replace"/"
router.replace({ path: "/" });
}
// "loading"false使
loading.value = false;
}).catch(() => {
// "loading"false使
loading.value = false;
});
} else {
// "loading"false使false
loading.value = false;
return false;
}
@ -76,6 +110,7 @@ const handleLogin = async (formEl: FormInstance | undefined) => {
}
</script>
<style>
/* 为类名为"login"的元素设置样式将其设置为flex布局使其内部子元素可以在主轴和交叉轴上进行灵活排列通过justify-content和align-items属性让子元素在水平和垂直方向上都居中对齐设置背景图片的样式使其铺满整个元素通过background-size: cover并设置初始高度为660px同时又设置高度为100%(可能根据父容器的高度情况自适应铺满整个可用高度,具体效果取决于布局结构) */
.login {
display: flex;
justify-content: center;
@ -87,12 +122,14 @@ const handleLogin = async (formEl: FormInstance | undefined) => {
height: 100%;
}
/* "title"使#707070 -->
.title {
margin: 0px auto 30px auto;
text-align: center;
color: #707070;
}
/* 为类名为"login-form"的元素设置样式设置其边框圆角为6px背景颜色为白色#ffffff设置宽度为400px并设置内边距用于控制内部元素与边框的间距 */
.login-form {
border-radius: 6px;
background: #ffffff;
@ -100,12 +137,14 @@ const handleLogin = async (formEl: FormInstance | undefined) => {
padding: 25px 25px 5px 25px;
}
/* "login-tip"13px#bfbfbfHTML使使 -->
.login-tip {
font-size: 13px;
text-align: center;
color: #bfbfbf;
}
/* "el-login-footer"40pxline-height使bottom: 0100%#fffArial12px1px -->
.el-login-footer {
height: 40px;
line-height: 40px;
@ -121,6 +160,7 @@ const handleLogin = async (formEl: FormInstance | undefined) => {
</style>
<style>
// id"app"div100%HTML使id使
div#app {
height: 100%;
}

@ -1,7 +1,9 @@
<template>
<!-- 使用el-container组件它通常作为整体布局的外层容器提供了一种方便的布局结构内部可以放置头部el-header侧边栏el-aside主体内容el-main等部分 -->
<el-container>
<!-- <el-header>
<!-- 以下这部分代码被注释掉了原本可能是用于展示页面头部的内容若需要可取消注释启用
<el-header>
<div>
<h1 class="div_center">
<el-icon style="margin-right: 30px; margin-top: 1px">
@ -12,12 +14,13 @@
</div>
</el-header> -->
<!-- 再嵌套一个el-container组件用于进一步细分内部的侧边栏和主体内容区域的布局 -->
<el-container>
<!-- 侧边栏 -->
<!-- 侧边栏部分使用el-aside组件定义通过width属性设置其宽度为200px用于展示一些导航菜单等功能选项 -->
<el-aside width="200px">
<!-- 标题栏 -->
<!-- 标题栏部分展示系统名称相关信息可能用于在侧边栏顶部突出显示系统名称 -->
<div>
<h1 class="div_center">
<el-icon style="margin-right: 8px; margin-top: 1px">
@ -27,10 +30,12 @@
</h1>
</div>
<!-- 选择栏 -->
<!-- 选择栏部分使用el-scrollbar组件包裹el-menu组件el-scrollbar用于在内容超出可视区域时提供滚动条功能方便查看所有菜单选项 -->
<el-scrollbar>
<!-- el-menu组件用于创建菜单里面包含多个菜单项 -->
<el-menu>
<!-- 单个菜单项设置index属性为"1"用于菜单的唯一标识在一些交互操作中可能会用到比如根据索引判断选中的菜单项等内部通过el-icon组件展示一个图标并显示文本"系统主页"表示点击可进入系统主页相关功能页面 -->
<el-menu-item index="1"><el-icon><icon-menu /></el-icon></el-menu-item>
</el-menu>
@ -39,11 +44,15 @@
</el-menu>
<el-menu>
<!-- el-sub-menu组件用于创建带有子菜单的菜单项设置index属性为"3"用于标识该父级菜单 -->
<el-sub-menu index="3">
<!-- 通过具名插槽#title自定义父级菜单的标题显示内容这里展示一个图标和文本"视频流模块" -->
<template #title>
<el-icon><icon-menu /></el-icon>
</template>
<!-- el-menu-item-group组件用于对相关的子菜单项进行分组通常用于在视觉上或逻辑上对相似功能的子菜单进行归类 -->
<el-menu-item-group>
<!-- 具体的子菜单项设置index属性为"3-1"显示文本"视频流管理"点击可进入对应的视频流管理功能页面 -->
<el-menu-item index="3-1">视频流管理</el-menu-item>
</el-menu-item-group>
<el-menu-item-group>
@ -85,8 +94,9 @@
</el-scrollbar>
</el-aside>
<!-- 主体内容区域使用el-main组件定义设置类名为"maxSize"并赋予id为"content"用于展示主要的页面内容通常会根据不同的路由路径动态展示相应的组件通过RouterView组件实现 -->
<el-main class="maxSize" id="content">
<!-- RouterView组件是Vue Router提供的用于根据当前路由展示对应组件的功能组件通过设置:key属性为"$route.fullPath"可以确保当路由路径变化时组件能够正确地重新渲染 -->
<RouterView :key="$route.fullPath" />
</el-main>
@ -94,10 +104,10 @@
</el-container>
</template>
<style scoped>
/* 为类名为"maxSize"的元素对应el-main组件设置样式将其高度设置为100%使其占满父容器的高度同时设置当内容超出其自身高度时显示垂直方向的滚动条overflow-y: scroll方便查看超出部分的内容 */
.maxSize {
height: 100%;
overflow-y: scroll;
}
</style>
</style>

@ -8,6 +8,7 @@
"build": "vue-tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^2.0.10",
"@kangc/v-md-editor": "^2.3.15",

@ -35,6 +35,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# 主窗口向yolo实例发送执行信号
main2yolo_begin_sgl = Signal()
# 主函数
def __init__(self, parent=None):
super(MainWindow, self).__init__()

@ -11,7 +11,7 @@ class CustomGrip(QWidget):
self.parent = parent
self.setParent(parent)
self.wi = Widgets()
#woshidongfquan
# SHOW TOP GRIP
if position == Qt.TopEdge:
self.wi.top(self)

@ -0,0 +1 @@
Subproject commit 4e49bc52ef47d23bdd93d6f853f6d806038e0a5c
Loading…
Cancel
Save