A:前端增加资料库页面

This commit is contained in:
jimmy committed 2025-06-16 07:13:08 +08:00
1 parent 76d747bbd9
commit bc0053e051
31 files changed
+2364 -204

No files matched your search

-86
View File
@@ -1,86 +0,0 @@
/* color palette from <https://github.com/vuejs/theme> */
:root {
--vt-c-white: #ffffff;
--vt-c-white-soft: #f8f8f8;
--vt-c-white-mute: #f2f2f2;
--vt-c-black: #181818;
--vt-c-black-soft: #222222;
--vt-c-black-mute: #282828;
--vt-c-indigo: #2c3e50;
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
--vt-c-text-light-1: var(--vt-c-indigo);
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
--vt-c-text-dark-1: var(--vt-c-white);
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
}
/* semantic color variables for this project */
:root {
--color-background: var(--vt-c-white);
--color-background-soft: var(--vt-c-white-soft);
--color-background-mute: var(--vt-c-white-mute);
--color-border: var(--vt-c-divider-light-2);
--color-border-hover: var(--vt-c-divider-light-1);
--color-heading: var(--vt-c-text-light-1);
--color-text: var(--vt-c-text-light-1);
--section-gap: 160px;
}
@media (prefers-color-scheme: dark) {
:root {
--color-background: var(--vt-c-black);
--color-background-soft: var(--vt-c-black-soft);
--color-background-mute: var(--vt-c-black-mute);
--color-border: var(--vt-c-divider-dark-2);
--color-border-hover: var(--vt-c-divider-dark-1);
--color-heading: var(--vt-c-text-dark-1);
--color-text: var(--vt-c-text-dark-2);
}
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
font-weight: normal;
}
body {
min-height: 100vh;
color: var(--color-text);
background: var(--color-background);
transition:
color 0.5s,
background-color 0.5s;
line-height: 1.6;
font-family:
Inter,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Fira Sans',
'Droid Sans',
'Helvetica Neue',
sans-serif;
font-size: 15px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@@ -0,0 +1,539 @@
/* Logo 字体 */
@font-face {
font-family: "iconfont logo";
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
}
.logo {
font-family: "iconfont logo";
font-size: 160px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* tabs */
.nav-tabs {
position: relative;
}
.nav-tabs .nav-more {
position: absolute;
right: 0;
bottom: 0;
height: 42px;
line-height: 42px;
color: #666;
}
#tabs {
border-bottom: 1px solid #eee;
}
#tabs li {
cursor: pointer;
width: 100px;
height: 40px;
line-height: 40px;
text-align: center;
font-size: 16px;
border-bottom: 2px solid transparent;
position: relative;
z-index: 1;
margin-bottom: -1px;
color: #666;
}
#tabs .active {
border-bottom-color: #f00;
color: #222;
}
.tab-container .content {
display: none;
}
/* 页面布局 */
.main {
padding: 30px 100px;
width: 960px;
margin: 0 auto;
}
.main .logo {
color: #333;
text-align: left;
margin-bottom: 30px;
line-height: 1;
height: 110px;
margin-top: -50px;
overflow: hidden;
*zoom: 1;
}
.main .logo a {
font-size: 160px;
color: #333;
}
.helps {
margin-top: 40px;
}
.helps pre {
padding: 20px;
margin: 10px 0;
border: solid 1px #e7e1cd;
background-color: #fffdef;
overflow: auto;
}
.icon_lists {
width: 100% !important;
overflow: hidden;
*zoom: 1;
}
.icon_lists li {
width: 100px;
margin-bottom: 10px;
margin-right: 20px;
text-align: center;
list-style: none !important;
cursor: default;
}
.icon_lists li .code-name {
line-height: 1.2;
}
.icon_lists .icon {
display: block;
height: 100px;
line-height: 100px;
font-size: 42px;
margin: 10px auto;
color: #333;
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
-moz-transition: font-size 0.25s linear, width 0.25s linear;
transition: font-size 0.25s linear, width 0.25s linear;
}
.icon_lists .icon:hover {
font-size: 100px;
}
.icon_lists .svg-icon {
/* 通过设置 font-size 来改变图标大小 */
width: 1em;
/* 图标和文字相邻时,垂直对齐 */
vertical-align: -0.15em;
/* 通过设置 color 来改变 SVG 的颜色/fill */
fill: currentColor;
/* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
normalize.css 中也包含这行 */
overflow: hidden;
}
.icon_lists li .name,
.icon_lists li .code-name {
color: #666;
}
/* markdown 样式 */
.markdown {
color: #666;
font-size: 14px;
line-height: 1.8;
}
.highlight {
line-height: 1.5;
}
.markdown img {
vertical-align: middle;
max-width: 100%;
}
.markdown h1 {
color: #404040;
font-weight: 500;
line-height: 40px;
margin-bottom: 24px;
}
.markdown h2,
.markdown h3,
.markdown h4,
.markdown h5,
.markdown h6 {
color: #404040;
margin: 1.6em 0 0.6em 0;
font-weight: 500;
clear: both;
}
.markdown h1 {
font-size: 28px;
}
.markdown h2 {
font-size: 22px;
}
.markdown h3 {
font-size: 16px;
}
.markdown h4 {
font-size: 14px;
}
.markdown h5 {
font-size: 12px;
}
.markdown h6 {
font-size: 12px;
}
.markdown hr {
height: 1px;
border: 0;
background: #e9e9e9;
margin: 16px 0;
clear: both;
}
.markdown p {
margin: 1em 0;
}
.markdown>p,
.markdown>blockquote,
.markdown>.highlight,
.markdown>ol,
.markdown>ul {
width: 80%;
}
.markdown ul>li {
list-style: circle;
}
.markdown>ul li,
.markdown blockquote ul>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown>ul li p,
.markdown>ol li p {
margin: 0.6em 0;
}
.markdown ol>li {
list-style: decimal;
}
.markdown>ol li,
.markdown blockquote ol>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown code {
margin: 0 3px;
padding: 0 5px;
background: #eee;
border-radius: 3px;
}
.markdown strong,
.markdown b {
font-weight: 600;
}
.markdown>table {
border-collapse: collapse;
border-spacing: 0px;
empty-cells: show;
border: 1px solid #e9e9e9;
width: 95%;
margin-bottom: 24px;
}
.markdown>table th {
white-space: nowrap;
color: #333;
font-weight: 600;
}
.markdown>table th,
.markdown>table td {
border: 1px solid #e9e9e9;
padding: 8px 16px;
text-align: left;
}
.markdown>table th {
background: #F7F7F7;
}
.markdown blockquote {
font-size: 90%;
color: #999;
border-left: 4px solid #e9e9e9;
padding-left: 0.8em;
margin: 1em 0;
}
.markdown blockquote p {
margin: 0;
}
.markdown .anchor {
opacity: 0;
transition: opacity 0.3s ease;
margin-left: 8px;
}
.markdown .waiting {
color: #ccc;
}
.markdown h1:hover .anchor,
.markdown h2:hover .anchor,
.markdown h3:hover .anchor,
.markdown h4:hover .anchor,
.markdown h5:hover .anchor,
.markdown h6:hover .anchor {
opacity: 1;
display: inline-block;
}
.markdown>br,
.markdown>p>br {
clear: both;
}
.hljs {
display: block;
background: white;
padding: 0.5em;
color: #333333;
overflow-x: auto;
}
.hljs-comment,
.hljs-meta {
color: #969896;
}
.hljs-string,
.hljs-variable,
.hljs-template-variable,
.hljs-strong,
.hljs-emphasis,
.hljs-quote {
color: #df5000;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-type {
color: #a71d5d;
}
.hljs-literal,
.hljs-symbol,
.hljs-bullet,
.hljs-attribute {
color: #0086b3;
}
.hljs-section,
.hljs-name {
color: #63a35c;
}
.hljs-tag {
color: #333333;
}
.hljs-title,
.hljs-attr,
.hljs-selector-id,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #795da3;
}
.hljs-addition {
color: #55a532;
background-color: #eaffea;
}
.hljs-deletion {
color: #bd2c00;
background-color: #ffecec;
}
.hljs-link {
text-decoration: underline;
}
/* 代码高亮 */
/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre)>code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre)>code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
@@ -0,0 +1,326 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>iconfont Demo</title>
<link rel="shortcut icon" href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg" type="image/x-icon"/>
<link rel="icon" type="image/svg+xml" href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg"/>
<link rel="stylesheet" href="https://g.alicdn.com/thx/cube/1.3.2/cube.min.css">
<link rel="stylesheet" href="demo.css">
<link rel="stylesheet" href="iconfont.css">
<script src="iconfont.js"></script>
<!-- jQuery -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/7bfddb60-08e8-11e9-9b04-53e73bb6408b.js"></script>
<!-- 代码高亮 -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/a3f714d0-08e6-11e9-8a15-ebf944d7534c.js"></script>
<style>
.main .logo {
margin-top: 0;
height: auto;
}
.main .logo a {
display: flex;
align-items: center;
}
.main .logo .sub-title {
margin-left: 0.5em;
font-size: 22px;
color: #fff;
background: linear-gradient(-45deg, #3967FF, #B500FE);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
</style>
</head>
<body>
<div class="main">
<h1 class="logo"><a href="https://www.iconfont.cn/" title="iconfont 首页" target="_blank">
<img width="200" src="https://img.alicdn.com/imgextra/i3/O1CN01Mn65HV1FfSEzR6DKv_!!6000000000514-55-tps-228-59.svg">
</a></h1>
<div class="nav-tabs">
<ul id="tabs" class="dib-box">
<li class="dib active"><span>Unicode</span></li>
<li class="dib"><span>Font class</span></li>
<li class="dib"><span>Symbol</span></li>
</ul>
<a href="https://www.iconfont.cn/manage/index?manage_type=myprojects&projectId=4810610" target="_blank" class="nav-more">查看项目</a>
</div>
<div class="tab-container">
<div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xe648;</span>
<div class="name">重新开始</div>
<div class="code-name">&amp;#xe648;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe7a8;</span>
<div class="name">重新</div>
<div class="code-name">&amp;#xe7a8;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe629;</span>
<div class="name">体质辨别-重新测试</div>
<div class="code-name">&amp;#xe629;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe659;</span>
<div class="name">箭头-左</div>
<div class="code-name">&amp;#xe659;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe61c;</span>
<div class="name">箭头 右</div>
<div class="code-name">&amp;#xe61c;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe7b8;</span>
<div class="name">重新加载</div>
<div class="code-name">&amp;#xe7b8;</div>
</li>
</ul>
<div class="article markdown">
<h2 id="unicode-">Unicode 引用</h2>
<hr>
<p>Unicode 是字体在网页端最原始的应用方式,特点是:</p>
<ul>
<li>支持按字体的方式去动态调整图标大小,颜色等等。</li>
<li>默认情况下不支持多色,直接添加多色图标会自动去色。</li>
</ul>
<blockquote>
<p>注意:新版 iconfont 支持两种方式引用多色图标:SVG symbol 引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。)</p>
</blockquote>
<p>Unicode 使用步骤如下:</p>
<h3 id="-font-face">第一步:拷贝项目下面生成的 <code>@font-face</code></h3>
<pre><code class="language-css"
>@font-face {
font-family: 'iconfont';
src: url('iconfont.woff2?t=1737291281379') format('woff2'),
url('iconfont.woff?t=1737291281379') format('woff'),
url('iconfont.ttf?t=1737291281379') format('truetype');
}
</code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
<pre><code class="language-css"
>.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取字体编码,应用于页面</h3>
<pre>
<code class="language-html"
>&lt;span class="iconfont"&gt;&amp;#x33;&lt;/span&gt;
</code></pre>
<blockquote>
<p>"iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。</p>
</blockquote>
</div>
</div>
<div class="content font-class">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont icon-zhongxinkaishi"></span>
<div class="name">
重新开始
</div>
<div class="code-name">.icon-zhongxinkaishi
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-zhongxin"></span>
<div class="name">
重新
</div>
<div class="code-name">.icon-zhongxin
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-tizhibianbiezhongxinceshi"></span>
<div class="name">
体质辨别-重新测试
</div>
<div class="code-name">.icon-tizhibianbiezhongxinceshi
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-jiantouzuo"></span>
<div class="name">
箭头-左
</div>
<div class="code-name">.icon-jiantouzuo
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-jiantouyou"></span>
<div class="name">
箭头 右
</div>
<div class="code-name">.icon-jiantouyou
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-zhongxinjiazai"></span>
<div class="name">
重新加载
</div>
<div class="code-name">.icon-zhongxinjiazai
</div>
</li>
</ul>
<div class="article markdown">
<h2 id="font-class-">font-class 引用</h2>
<hr>
<p>font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。</p>
<p>与 Unicode 使用方式相比,具有如下特点:</p>
<ul>
<li>相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。</li>
<li>因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-fontclass-">第一步:引入项目下面生成的 fontclass 代码:</h3>
<pre><code class="language-html">&lt;link rel="stylesheet" href="./iconfont.css"&gt;
</code></pre>
<h3 id="-">第二步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;span class="iconfont icon-xxx"&gt;&lt;/span&gt;
</code></pre>
<blockquote>
<p>"
iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。</p>
</blockquote>
</div>
</div>
<div class="content symbol">
<ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-zhongxinkaishi"></use>
</svg>
<div class="name">重新开始</div>
<div class="code-name">#icon-zhongxinkaishi</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-zhongxin"></use>
</svg>
<div class="name">重新</div>
<div class="code-name">#icon-zhongxin</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-tizhibianbiezhongxinceshi"></use>
</svg>
<div class="name">体质辨别-重新测试</div>
<div class="code-name">#icon-tizhibianbiezhongxinceshi</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-jiantouzuo"></use>
</svg>
<div class="name">箭头-左</div>
<div class="code-name">#icon-jiantouzuo</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-jiantouyou"></use>
</svg>
<div class="name">箭头 右</div>
<div class="code-name">#icon-jiantouyou</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-zhongxinjiazai"></use>
</svg>
<div class="name">重新加载</div>
<div class="code-name">#icon-zhongxinjiazai</div>
</li>
</ul>
<div class="article markdown">
<h2 id="symbol-">Symbol 引用</h2>
<hr>
<p>这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇<a href="">文章</a>
这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:</p>
<ul>
<li>支持多色图标了,不再受单色限制。</li>
<li>通过一些技巧,支持像字体那样,通过 <code>font-size</code>, <code>color</code> 来调整样式。</li>
<li>兼容性较差,支持 IE9+,及现代浏览器。</li>
<li>浏览器渲染 SVG 的性能一般,还不如 png。</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-symbol-">第一步:引入项目下面生成的 symbol 代码:</h3>
<pre><code class="language-html">&lt;script src="./iconfont.js"&gt;&lt;/script&gt;
</code></pre>
<h3 id="-css-">第二步:加入通用 CSS 代码(引入一次就行):</h3>
<pre><code class="language-html">&lt;style&gt;
.icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
&lt;/style&gt;
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;svg class="icon" aria-hidden="true"&gt;
&lt;use xlink:href="#icon-xxx"&gt;&lt;/use&gt;
&lt;/svg&gt;
</code></pre>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$('.tab-container .content:first').show()
$('#tabs li').click(function (e) {
var tabContent = $('.tab-container .content')
var index = $(this).index()
if ($(this).hasClass('active')) {
return
} else {
$('#tabs li').removeClass('active')
$(this).addClass('active')
tabContent.hide().eq(index).fadeIn()
}
})
})
</script>
</body>
</html>
@@ -0,0 +1,39 @@
@font-face {
font-family: "iconfont"; /* Project id 4810610 */
src: url('iconfont.woff2?t=1737291281379') format('woff2'),
url('iconfont.woff?t=1737291281379') format('woff'),
url('iconfont.ttf?t=1737291281379') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-zhongxinkaishi:before {
content: "\e648";
}
.icon-zhongxin:before {
content: "\e7a8";
}
.icon-tizhibianbiezhongxinceshi:before {
content: "\e629";
}
.icon-jiantouzuo:before {
content: "\e659";
}
.icon-jiantouyou:before {
content: "\e61c";
}
.icon-zhongxinjiazai:before {
content: "\e7b8";
}
@@ -0,0 +1 @@
window._iconfont_svg_string_4810610='<svg><symbol id="icon-zhongxinkaishi" viewBox="0 0 1024 1024"><path d="M895.1296 485.888a384 384 0 1 0-149.9136 331.2128 25.6 25.6 0 0 1 31.1296 40.6528 435.2 435.2 0 1 1 168.9088-387.328l21.504-21.504a25.6 25.6 0 0 1 36.1984 36.1984l-72.3968 72.3968a25.4976 25.4976 0 0 1-36.1984 0l-72.3968-72.3968a25.6 25.6 0 0 1 36.1984-36.1984l36.9664 36.9664z m-225.0752-2.56a35.84 35.84 0 0 1 0 57.344l-177.5104 133.12a35.84 35.84 0 0 1-57.344-28.672V378.88a35.84 35.84 0 0 1 57.344-28.672l177.5104 133.12zM486.4 409.6v204.8l136.5504-102.4L486.4 409.6z" fill="#9DA7B2" ></path></symbol><symbol id="icon-zhongxin" viewBox="0 0 1024 1024"><path d="M362.666667 938.666667c0-17.066667-8.533333-32-23.466667-38.4C189.866667 834.133333 85.333333 684.8 85.333333 512S189.866667 189.866667 339.2 123.733333C354.133333 115.2 362.666667 100.266667 362.666667 85.333333v-2.133333c0-32-32-51.2-59.733334-38.4C123.733333 125.866667 0 305.066667 0 512s123.733333 386.133333 302.933333 467.2c27.733333 12.8 59.733333-8.533333 59.733334-40.533333z" ></path><path d="M467.2 985.6l-102.4-179.2c-10.666667-17.066667-34.133333-17.066667-44.8 0l-102.4 179.2c-10.666667 17.066667 2.133333 38.4 21.333333 38.4h206.933334c19.2 0 32-21.333333 21.333333-38.4zM661.333333 85.333333c0 17.066667 8.533333 32 23.466667 38.4C834.133333 189.866667 938.666667 339.2 938.666667 512s-104.533333 322.133333-253.866667 388.266667c-14.933333 6.4-23.466667 21.333333-23.466667 38.4v2.133333c0 32 32 51.2 59.733334 38.4C900.266667 898.133333 1024 718.933333 1024 512S900.266667 125.866667 721.066667 44.8C693.333333 32 661.333333 53.333333 661.333333 85.333333z" ></path><path d="M556.8 38.4l102.4 179.2c10.666667 17.066667 34.133333 17.066667 44.8 0l102.4-179.2c10.666667-17.066667-2.133333-38.4-21.333333-38.4h-206.933334c-19.2 0-32 21.333333-21.333333 38.4z" ></path></symbol><symbol id="icon-tizhibianbiezhongxinceshi" viewBox="0 0 1024 1024"><path d="M149.996806 543.064927 297.814628 386.075665l-86.602808 0c0.798407-2.29414 1.595534-4.584441 2.459195-6.845313 51.376955-138.245898 178.257074-236.080139 326.671142-236.080139 193.875904 0 351.062208 166.989816 351.062208 372.930105 0 205.969717-157.186304 372.964651-351.062208 372.964651-142.633296 0-265.392154-90.328706-320.290286-220.063385-0.532271-1.230877-0.931474-2.560275-1.395932-3.824419l-94.178715 99.399066c0.597525 1.161784 1.061983 2.495021 1.727322 3.654246 0.133068 0.267415 0.266136 0.532271 0.397924 0.798407 83.080351 152.435529 238.274477 254.991116 416.064535 254.991116 265.326899 0 480.404081-228.502953 480.404081-510.381435 0-281.840098-215.077182-510.311063-480.404081-510.311063-221.789428 0-408.487349 159.648058-463.785964 376.652164-0.532271 1.996017-0.665339 4.087995-1.131076 6.11472L2.181544 386.074386 149.996806 543.064927z" fill="#262535" ></path></symbol><symbol id="icon-jiantouzuo" viewBox="0 0 1024 1024"><path d="M257.243317 303.056224c-13.846356 0-25.071002 11.224646-25.071002 25.071002s11.224646 25.071002 25.071002 25.071002c13.845333 0 25.071002-11.224646 25.071002-25.071002S271.089673 303.056224 257.243317 303.056224zM257.243317 350.691127c-12.44238 0-22.562878-10.121522-22.562878-22.563902 0-12.443403 10.120498-22.563902 22.562878-22.563902s22.562878 10.120498 22.562878 22.563902C279.806195 340.569606 269.685697 350.691127 257.243317 350.691127z" ></path><path d="M255.843434 309.422212l2.801812 0 0 20.205181-2.801812 0 0-20.205181Z" ></path><path d="M255.941672 326.72632l20.203134 0 0 2.801812-20.203134 0 0-2.801812Z" ></path><path d="M239.460302 462.750319C378.354676 335.865467 517.179464 208.980615 656.052349 82.228793c57.18849-52.247968 142.42785 32.586163 85.012186 85.016279C617.487008 280.122909 493.910505 392.96186 370.332978 505.844813 494.544955 622.62554 618.711905 739.541343 742.945371 856.364025c56.576553 53.15564-28.662807 138.079822-85.059258 84.96716C518.404362 810.141284 378.943077 678.951383 239.460302 547.761482 216.39498 525.96506 215.805556 484.274542 239.460302 462.750319z" ></path></symbol><symbol id="icon-jiantouyou" viewBox="0 0 1024 1024"><path d="M312.889 995.556c-17.067 0-28.445-5.69-39.822-17.067-22.756-22.756-17.067-56.889 5.689-79.645L642.844 568.89c11.378-11.378 17.067-22.756 17.067-34.133 0-11.378-5.689-22.756-17.067-34.134L273.067 187.733c-22.756-22.755-28.445-56.889-5.69-79.644 22.756-22.756 56.89-28.445 79.645-5.689l364.09 312.889C745.243 443.733 768 489.244 768 534.756s-17.067 85.333-51.2 119.466L352.711 984.178c-11.378 5.689-28.444 11.378-39.822 11.378z" ></path></symbol><symbol id="icon-zhongxinjiazai" viewBox="0 0 1024 1024"><path d="M952.529455 459.752727a34.909091 34.909091 0 0 0-49.361455 0l-21.085091 21.085091C860.625455 276.317091 687.220364 116.363636 477.090909 116.363636a400.570182 400.570182 0 0 0-48.686545 3.211637c-7.842909 0.930909-15.616 2.164364-23.342546 3.560727-4.701091 0.837818-9.402182 1.582545-14.010182 2.56-9.844364 2.164364-19.572364 4.654545-29.160727 7.540364-6.865455 2.024727-13.521455 4.398545-20.Line truncated
@@ -0,0 +1,51 @@
{
"id": "4810610",
"name": "argus",
"font_family": "iconfont",
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "2743897",
"name": "重新开始",
"font_class": "zhongxinkaishi",
"unicode": "e648",
"unicode_decimal": 58952
},
{
"icon_id": "29849119",
"name": "重新",
"font_class": "zhongxin",
"unicode": "e7a8",
"unicode_decimal": 59304
},
{
"icon_id": "375868",
"name": "体质辨别-重新测试",
"font_class": "tizhibianbiezhongxinceshi",
"unicode": "e629",
"unicode_decimal": 58921
},
{
"icon_id": "830289",
"name": "箭头-左",
"font_class": "jiantouzuo",
"unicode": "e659",
"unicode_decimal": 58969
},
{
"icon_id": "15726826",
"name": "箭头 右",
"font_class": "jiantouyou",
"unicode": "e61c",
"unicode_decimal": 58908
},
{
"icon_id": "4430546",
"name": "重新加载",
"font_class": "zhongxinjiazai",
"unicode": "e7b8",
"unicode_decimal": 59320
}
]
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
+11 -1
View File
@@ -1,5 +1,15 @@
*{
@import "base.css";
@import "var.css";
* {
margin: 0;
padding: 0;
list-style: none;
text-decoration: none;
}
/* 禁止选中 */
.no-select {
user-select: none;
-webkit-user-select: none; /* Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE10+/Edge */
}
+3
View File
@@ -0,0 +1,3 @@
:root {
--header-height: 64px;
}
@@ -4,12 +4,12 @@
<n-layout-header bordered class="layout-header">
<div class="header-content">
<!-- 左侧 Logo 和标题 -->
<div class="header-left">
<router-link to="/" class="header-left no-select">
<n-icon size="32" class="logo-icon">
<img src="@/assets/logo.svg" alt="logo" />
</n-icon>
<span class="site-title">标题</span>
</div>
<span class="site-title">Argus</span>
</router-link>
<!-- 中间搜索框 -->
<div class="header-center">
@@ -42,7 +42,7 @@
show-trigger
collapse-mode="width"
:collapsed-width="64"
:width="240"
:width="290"
:native-scrollbar="false"
class="layout-sider"
>
@@ -71,7 +71,7 @@
</template>
<script lang="ts" setup>
import { computed, h } from 'vue'
import { ref, watch, computed, h } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { NMenu, NIcon } from 'naive-ui'
import {
@@ -137,25 +137,31 @@ const menuOptionsWithIcons = computed(() => {
})
})
// 当前激活的菜单项
const activeKey = computed(() => {
const currentPath = route.path
// 从菜单选项中找到匹配当前路径的key
for (const option of menuOptions) {
if ('children' in option) {
for (const child of option.children) {
if (child.path === currentPath) {
return child.key
const activeKey = ref('')
// 监听路由变化,更新activeKey
watch(
() => route.path,
(currentPath) => {
// 从菜单选项中找到匹配当前路径的key
for (const option of menuOptions) {
if ('children' in option) {
for (const child of option.children) {
if (child.path === currentPath) {
activeKey.value = child.key
return
}
}
} else {
if (option.path === currentPath) {
activeKey.value = option.key
return
}
}
} else {
if (option.path === currentPath) {
return option.key
}
}
}
return ''
})
activeKey.value = ''
},
{ immediate: true },
)
// 处理菜单选择
const handleMenuSelect = (key: string) => {
@@ -184,11 +190,6 @@ const handleMenuSelect = (key: string) => {
}
}
</script>
<style>
:root {
--header-height: 64px;
}
</style>
<style scoped>
.layout-container {
height: 100vh;
@@ -216,6 +217,12 @@ const handleMenuSelect = (key: string) => {
align-items: center;
flex-shrink: 0;
min-width: 200px;
cursor: pointer;
}
.header-left:hover {
opacity: 0.8;
transition: opacity 0.2s;
}
.logo-icon {
@@ -264,6 +271,7 @@ const handleMenuSelect = (key: string) => {
.layout-sider {
height: 100%;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.04);
margin-left: 7px;
}
.sidebar-menu {
@@ -0,0 +1,67 @@
/**
* Time:2025/6/15 20:13 49
* Name:httpConfig.ts
* Path:src/config
* ProjectName:argus-front
* Author:charlatans
*
* Il n'ya qu'un héroïsme au monde :
* c'est de voir le monde tel qu'il est et de l'aimer.
*/
import type { HttpConfig } from '@/types/http.ts'
// 2. 配置管理器 (config/httpConfig.ts)
class HttpConfigManager {
private config: HttpConfig = {
baseURL: import.meta.env.VITE_APP_API_URL || 'http://localhost:8726',
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
};
/**
* 设置配置
*/
setConfig(config: Partial<HttpConfig>): void {
this.config = { ...this.config, ...config };
}
/**
* 获取配置
*/
getConfig(): HttpConfig {
return { ...this.config };
}
/**
* 设置基础URL
*/
setBaseURL(baseURL: string): void {
this.config.baseURL = baseURL;
}
/**
* 设置端口
*/
setPort(host: string, port: number): void {
const protocol = this.config.baseURL.startsWith('https') ? 'https' : 'http';
this.config.baseURL = `${protocol}://${host}:${port}`;
}
/**
* 设置超时时间
*/
setTimeout(timeout: number): void {
this.config.timeout = timeout;
}
/**
* 设置默认请求头
*/
setHeaders(headers: Record<string, string>): void {
this.config.headers = { ...this.config.headers, ...headers };
}
}
export const httpConfig = new HttpConfigManager();
+33
View File
@@ -0,0 +1,33 @@
/**
* Time:2024/8/30 下午2:21 50
* Name:app.ts
* Path:src/constants
* ProjectName:utopia-front-vue
* Author:charlatans
*
* Il n'ya qu'un héroïsme au monde :
* c'est de voir le monde tel qu'il est et de l'aimer.
*/
const app = {
/**
* 生产
*/
PRODUCTION: 'production',
/**
* 开发
*/
DEVELOPMENT: 'development',
/**
* 是否是生产环境【默认是】
*/
IS_PRODUCTION: true,
/**
* 是否展示生成路由
*/
IS_SHOW_GENERATE_ROUTER: false
}
export default app
@@ -0,0 +1,59 @@
/**
* Time:2024/8/30 下午2:28 44
* Name:logUtil.ts
* Path:src/constants
* ProjectName:utopia-front-vue
* Author:charlatans
*
* Il n'ya qu'un héroïsme au monde :
* c'est de voir le monde tel qu'il est et de l'aimer.
*/
/**
* 日志级别
*/
const level = {
error: 0,
warn: 1,
info: 2,
http: 3,
verbose: 4,
debug: 5,
silly: 6
}
/**
* 日志工具类常量
*/
const logUtil = {
/**
* 日志级别
*/
level: level,
ERROR: 'error',
WARN: 'warn',
INFO: 'info',
HTTP: 'http',
VERBOSE: 'verbose',
DEBUG: 'debug',
SILLY: 'silly'
}
export type Color =
'primary'
| 'success'
| 'info'
| 'warning'
| 'danger'
| 'error';
export const COLOR_MAP: Record<Color, string> = {
primary: '#2d8cf0',
success: '#19be6b',
info: '#909399',
warning: '#ff9900',
danger: '#35495E',
error: '#FF0000'
}
export default { logUtil, COLOR_MAP }
+5
View File
@@ -1,4 +1,5 @@
import './assets/main.css'
import "./assets/icon/ali/iconfont.css"
import { createApp } from 'vue'
import { createPinia } from 'pinia'
@@ -8,6 +9,10 @@ import router from './router'
const app = createApp(App)
import httpPlugin from './plugins/http';
app.use(httpPlugin);
app.use(createPinia())
app.use(router)
+29
View File
@@ -0,0 +1,29 @@
/**
* Time:2025/6/15 20:23 36
* Name:http.ts
* Path:src/plugins
* ProjectName:argus-front
* Author:charlatans
*
* Il n'ya qu'un héroïsme au monde :
* c'est de voir le monde tel qu'il est et de l'aimer.
*/
import { type App } from 'vue';
import { httpConfig } from '@/config/httpConfig.ts'
import { httpClient } from '@/utils/http.ts'
export default {
install(app: App) {
// 更新HTTP配置
const config = httpConfig.getConfig();
httpClient.updateConfig(config);
// 注册全局属性
app.config.globalProperties.$http = httpClient;
app.config.globalProperties.$httpConfig = httpConfig;
// 提供注入
app.provide('$http', httpClient);
app.provide('$httpConfig', httpConfig);
}
};
+1 -21
View File
@@ -1,31 +1,11 @@
import { createRouter, createWebHistory } from 'vue-router'
// const router = createRouter({
// history: createWebHistory(import.meta.env.BASE_URL),
// routes: [
// {
// path: '/',
// name: 'home',
// component: HomeView,
// },
// {
// path: '/about',
// name: 'about',
// // route level code-splitting
// // this generates a separate chunk (About.[hash].js) for this route
// // which is lazy-loaded when the route is visited.
// component: () => import('../views/AboutView.vue'),
// },
// ],
// })
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
redirect: '/photo'
redirect: '/photos'
},
{
path: '/photos',
+33
View File
@@ -0,0 +1,33 @@
/**
* Time:2025/6/15 20:12 37
* Name:http.ts
* Path:src/types
* ProjectName:argus-front
* Author:charlatans
*
* Il n'ya qu'un héroïsme au monde :
* c'est de voir le monde tel qu'il est et de l'aimer.
*/
// 1. 配置接口和类型定义
export interface HttpConfig {
baseURL: string;
timeout?: number;
headers?: Record<string, string>;
}
export interface ApiResponse<T = unknown> {
code: number;
data: T;
message: string;
success: boolean;
}
export interface RequestConfig {
url: string;
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
params?: unknown;
data?: unknown;
headers?: Record<string, string>;
timeout?: number;
}
+283
View File
@@ -0,0 +1,283 @@
import axios, {
type AxiosInstance,
type AxiosRequestConfig,
type AxiosResponse,
AxiosError,
type InternalAxiosRequestConfig,
} from 'axios'
// 或者混合导入(如果同时需要导入值和类型)
import type { ApiResponse, HttpConfig, RequestConfig } from '@/types/http.ts'
import { disLog, logB, logN, logS } from '@/utils/logHelper/logUtils'
class HttpClient {
private axiosInstance: AxiosInstance
private pending: Map<string, AbortController> = new Map()
constructor() {
this.axiosInstance = axios.create()
this.setupInterceptors()
}
/**
* 更新配置
*/
updateConfig(config: HttpConfig): void {
this.axiosInstance.defaults.baseURL = config.baseURL
this.axiosInstance.defaults.timeout = config.timeout || 10000
this.axiosInstance.defaults.headers.common = {
...this.axiosInstance.defaults.headers.common,
...config.headers,
}
}
/**
* 生成请求标识符
*/
private getRequestKey(config: AxiosRequestConfig): string {
return `${config.method?.toUpperCase()}_${config.url}_${JSON.stringify(
config.params,
)}_${JSON.stringify(config.data)}`
}
/**
* 添加请求到待处理列表
*/
private addPending(config: InternalAxiosRequestConfig): void {
const requestKey = this.getRequestKey(config)
config.signal = new AbortController().signal
if (this.pending.has(requestKey)) {
this.pending.get(requestKey)?.abort()
}
const controller = new AbortController()
config.signal = controller.signal
this.pending.set(requestKey, controller)
}
/**
* 移除请求从待处理列表
*/
private removePending(config: AxiosRequestConfig): void {
const requestKey = this.getRequestKey(config)
if (this.pending.has(requestKey)) {
this.pending.delete(requestKey)
}
}
/**
* 设置拦截器
*/
private setupInterceptors(): void {
// 请求拦截器
this.axiosInstance.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
console.group("请求链接: "+config.url);
// 防止重复请求
this.addPending(config)
// 添加时间戳防止缓存
if (config.method?.toLowerCase() === 'get') {
config.params = {
...config.params,
_t: Date.now(),
}
}
// 添加token
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
logB.success('请求发送:', config)
return config
},
(error: AxiosError) => {
logB.error('请求错误:', error)
return Promise.reject(error)
},
)
// 响应拦截器
this.axiosInstance.interceptors.response.use(
(response: AxiosResponse<ApiResponse>) => {
this.removePending(response.config)
logB.success('响应接收:', response)
// 统一处理响应数据
const { data } = response
console.groupEnd();
if (data.success || data.code === 200) {
return response
} else {
// 业务错误处理
const errorMessage = data.message || '请求失败'
return Promise.reject(new Error(errorMessage))
}
},
(error: AxiosError<ApiResponse>) => {
this.removePending(error.config || {})
logB.error('响应错误:', error)
// 统一错误处理
this.handleError(error)
console.groupEnd();
return Promise.reject(error)
},
)
}
/**
* 统一错误处理
*/
private handleError(error: AxiosError<ApiResponse>): void {
let message = '网络错误'
if (error.response) {
const { status, data } = error.response
switch (status) {
case 400:
message = data?.message || '请求参数错误'
break
case 401:
message = '未授权,请重新登录'
// 清除token并跳转到登录页
localStorage.removeItem('token')
// router.push('/login');
break
case 403:
message = '拒绝访问'
break
case 404:
message = '请求的资源不存在'
break
case 500:
message = '服务器内部错误'
break
default:
message = data?.message || `错误码: ${status}`
}
} else if (error.code === 'ECONNABORTED') {
message = '请求超时'
} else if (error.message.includes('Network Error')) {
message = '网络连接异常'
}
logB.error(message)
}
/**
* 通用请求方法
*/
async request<T = unknown>(config: RequestConfig): Promise<ApiResponse<T>> {
try {
const response = await this.axiosInstance.request<ApiResponse<T>>(config)
return response.data
} catch (error) {
throw error
}
}
/**
* GET请求
*/
async get<T = unknown>(
url: string,
params?: unknown,
config?: AxiosRequestConfig,
): Promise<ApiResponse<T>> {
return this.request<T>({
url,
method: 'GET',
params,
...config,
})
}
/**
* POST请求
*/
async post<T = unknown>(
url: string,
data?: unknown,
config?: AxiosRequestConfig,
): Promise<ApiResponse<T>> {
return this.request<T>({
url,
method: 'POST',
data,
...config,
})
}
/**
* PUT请求
*/
async put<T = unknown>(
url: string,
data?: unknown,
config?: AxiosRequestConfig,
): Promise<ApiResponse<T>> {
return this.request<T>({
url,
method: 'PUT',
data,
...config,
})
}
/**
* DELETE请求
*/
async delete<T = unknown>(
url: string,
params?: unknown,
config?: AxiosRequestConfig,
): Promise<ApiResponse<T>> {
return this.request<T>({
url,
method: 'DELETE',
params,
...config,
})
}
/**
* PATCH请求
*/
async patch<T = unknown>(
url: string,
data?: unknown,
config?: AxiosRequestConfig,
): Promise<ApiResponse<T>> {
return this.request<T>({
url,
method: 'PATCH',
data,
...config,
})
}
/**
* 取消所有请求
*/
cancelAllRequests(): void {
this.pending.forEach((controller) => {
controller.abort()
})
this.pending.clear()
}
/**
* 获取Axios实例
*/
getAxiosInstance(): AxiosInstance {
return this.axiosInstance
}
}
export const httpClient = new HttpClient()
@@ -0,0 +1,86 @@
/**
* Time:2024/8/31 下午4:46 46
* Name:logEnum.ts
* Path:src/utils/logHelper
* ProjectName:utopia-front-vue
* Author:charlatans
*
* Il n'ya qu'un héroïsme au monde :
* c'est de voir le monde tel qu'il est et de l'aimer.
*/
/**
* 日志等级列表,重要等级越高数字越小
*/
export enum LogLevelEnum {
/**
* v.
*/
VERBOSE = 40,
/**
* d.
*/
DEBUG = 30,
/**
* i.
*/
INFO = 20,
/**
* w.
*/
WARN = 10,
/**
* e.
*/
ERROR = 0,
}
/**
* 日志等级名称
*/
export const LevelName = {
VERBOSE: 'VERBOSE',
DEBUG: 'DEBUG',
INFO: 'INFO',
WARN: 'WARN',
ERROR: 'ERROR'
}
/**
* 日志样式
*/
enum logStyle {
BackgroundColor = 10,
TextColor = 20,
BackgroundAndTextColor = 30
}
/**
* 日志输出颜色
*/
enum logColor {
}
/**
* 获取指定等级名称
* @param level
*/
export function getLogLevelName (level: LogLevelEnum): string {
switch (level) {
case LogLevelEnum.VERBOSE:
return LevelName.VERBOSE
case LogLevelEnum.DEBUG:
return LevelName.DEBUG
case LogLevelEnum.INFO:
return LevelName.INFO
case LogLevelEnum.WARN:
return LevelName.WARN
case LogLevelEnum.ERROR:
return LevelName.ERROR
}
}
@@ -0,0 +1,156 @@
/**
* Time:2024/8/26 下午5:02 17
* Name:logUtils.ts
* Path:src/utils
* ProjectName:utopia-front-vue
* Author:charlatans
*
* Il n'ya qu'un héroïsme au monde :
* c'est de voir le monde tel qu'il est et de l'aimer.
*/
import app from '@/constants/app'
import type { Color } from '@/constants/logUtil'
import { COLOR_MAP } from '@/constants/logUtil'
import {
LevelName,
LogLevelEnum
} from '@/utils/logHelper/logEnum'
const env = import.meta.env
// 是否输出日志
const isPrint = env.VITE_MODE !== app.PRODUCTION
const COLORS: Color[] = [
'primary',
'success',
'info',
'warning',
'danger',
'error'
]
// 日志输出等级
const logPrintLevel = LogLevelEnum.DEBUG
const getColor = (type: Color) => COLOR_MAP[type]
/**
* 输出函数
*/
type Log<T extends any[]> = (type: Color, ...args: T) => void
/**
* 返回值
*/
type Created<T extends any[]> = Record<Color, (...args: T) => void>
const createLog = <T extends any[]> (
fn: Log<T>
): Created<T> => {
return COLORS.reduce((logs, type) => {
logs[type] = (...args: T) => fn(type, ...args)
return logs
}, {} as Created<T>)
}
const nsLog = (type: Color, ns: string, msg: string, ...args: any[]) => {
const color = getColor(type)
console.log(
`%c ${ns} %c ${msg} %c ${args.length ? '%o' : ''}`,
`background:${color};border:1px solid ${color}; padding: 1px; border-radius: 4px 0 0 4px; color: #fff;`,
`border:1px solid ${color}; padding: 1px; border-radius: 0 4px 4px 0; color: ${color};`,
'background:transparent',
...args
)
}
/**
* 打印格式化日志
*/
export const logN = createLog(nsLog)
const sLog = (type: Color, msg: string, ...args: any[]) => {
const color = getColor(type)
console.log(
`%c ${msg} ${args.length ? '%o' : ''}`,
`color: ${color};`,
...args
)
}
/**
* 打印带有颜色的日志
*/
export const logS = createLog(sLog)
const bLog = (type: Color, msg: string, ...args: any[]) => {
const color = getColor(type)
console.log(
`%c ${msg} ${args.length ? '%o' : ''}`,
`background:${color}; padding: 2px; border-radius: 4px; color: #fff;`,
...args
)
}
/**
* 打印带有颜色背景的日志
*/
export const logB = createLog(bLog)
export const disLog = () => {
console.log = function() {}
}
const logHelper = {
/**
* DEBUG
*/
d: (tag: string, msg: string) => {
}
}
const logStyleFormat = (): string => {
return ''
}
/**
* 打印日志
* @param level 日志等级
* @param tag 标记
* @param msg 信息
* @param e 报错信息【存在报错信息,默认使用 **ERROR** 等级】
*/
const print = (
level: LogLevelEnum, tag: string, msg: string,
e: Error | null | undefined = null) => {
if (logPrintLevel > level) {
return
}
// 格式化字符串
// let str = formatMessage(msg)
//
// // 获取日志等级名称
// let levelName = getLogLevelName(level)
switch (level) {
case LogLevelEnum.VERBOSE:
console.log()
return LevelName.VERBOSE
case LogLevelEnum.DEBUG:
return LevelName.DEBUG
case LogLevelEnum.INFO:
return LevelName.INFO
case LogLevelEnum.WARN:
return LevelName.WARN
case LogLevelEnum.ERROR:
return LevelName.ERROR
}
}
/**
* 格式化输出信息
* @param str
*/
const formatMessage = (str: string): string => {
return `[${str}]`
}
+304
View File
@@ -1,11 +1,315 @@
<script setup lang="ts">
import { httpClient } from '@/utils/http.ts'
import { CloseOutline, FolderOutline, CheckmarkCircle, EllipseOutline } from '@vicons/ionicons5'
import { onMounted, ref } from 'vue'
import { NSpace, NInput, NButton, NIcon, NTag, NCard, NEmpty, useMessage } from 'naive-ui'
// 消息提示
const message = useMessage()
// 当前输入的路径
const inputPath = ref('')
// 已添加的路径列表
interface LibraryPath {
id: string
path: string
enabled: boolean
}
const libraryPaths = ref<LibraryPath[]>([
{ id: '1', path: 'C:\\Users\\Documents\\教师资料', enabled: true },
{ id: '2', path: 'D:\\程序员学习资料', enabled: true }
])
// 选择文件夹
// const selectFolder = async () => {
// // 这里应该调用 Electron 或其他桌面框架的文件选择 API
// // 示例代码,实际需要根据你的环境调整
// try {
// // const result = await window.electronAPI.selectFolder()
// // if (result) {
// // inputPath.value = result
// // }
//
// // 临时模拟
// inputPath.value = 'C:\\Users\\Documents\\示例路径'
// message.info('请在实际环境中实现文件夹选择功能')
// } catch (error) {
// message.error('选择文件夹失败')
// }
// }
// 选择文件夹(使用浏览器 File System Access API)
const selectFolder = async () => {
try {
// 检查浏览器是否支持
if (!('showDirectoryPicker' in window)) {
message.warning('您的浏览器不支持文件夹选择功能,请手动输入路径')
return
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-expect-error
const dirHandle = await window.showDirectoryPicker()
// 获取文件夹名称作为路径(实际项目中可能需要完整路径)
inputPath.value = dirHandle.name
} catch (err) {
// 用户取消选择
console.log('用户取消选择文件夹')
}
}
// 添加路径
const addPath = async () => {
if (!inputPath.value.trim()) {
message.warning('请输入或选择文件路径')
return
}
// 检查是否已存在
if (libraryPaths.value.some(item => item.path === inputPath.value)) {
message.warning('该路径已存在')
return
}
try {
// 发送到后端
const response = await httpClient.post('/v1/library', {
path: inputPath.value
})
// 添加到列表
libraryPaths.value.push({
id: Date.now().toString(),
path: inputPath.value,
enabled: true
})
// 清空输入
inputPath.value = ''
message.success('添加成功')
} catch (error) {
message.error('添加失败,请重试')
}
}
// 切换启用状态
const toggleEnabled = async (item: LibraryPath) => {
try {
item.enabled = !item.enabled
// 发送到后端
await httpClient.put(`/api/library/${item.id}`, {
enabled: item.enabled
})
message.success(item.enabled ? '已启用' : '已禁用')
} catch (error) {
// 恢复原状态
item.enabled = !item.enabled
message.error('操作失败')
}
}
// 删除路径
const removePath = async (item: LibraryPath) => {
try {
// 发送到后端
await httpClient.delete(`/api/library/${item.id}`)
// 从列表中移除
const index = libraryPaths.value.findIndex(p => p.id === item.id)
if (index > -1) {
libraryPaths.value.splice(index, 1)
}
message.success('删除成功')
} catch (error) {
message.error('删除失败')
}
}
// 开始任务
const startTask = async () => {
const enabledPaths = libraryPaths.value.filter(item => item.enabled)
if (enabledPaths.length === 0) {
message.warning('请至少选择一个启用的路径')
return
}
try {
const response = await httpClient.post('/api/task/create', {
paths: enabledPaths.map(item => item.path)
})
message.success('任务已创建,正在处理中...')
} catch (error) {
message.error('创建任务失败')
}
}
onMounted(() => {
httpClient.get("v1/library").then((res) => {
console.log(res);
})
})
</script>
<template>
<div class="library-manager">
<!-- 顶部:添加资料库区域 -->
<n-card class="add-section" title="添加资料库" size="small">
<n-space>
<n-input
v-model:value="inputPath"
size="large"
placeholder="输入文件夹路径..."
style="width: 500px"
@keyup.enter="addPath"
>
<template #prefix>
<n-icon :component="FolderOutline" />
</template>
<template #suffix>
<n-button
type="tertiary"
size="small"
@click="selectFolder"
>
选择文件夹
</n-button>
</template>
</n-input>
<n-button
type="primary"
size="large"
@click="addPath"
>
添加
</n-button>
</n-space>
</n-card>
<!-- 中间:已选择的路径列表 -->
<n-card class="paths-section" title="资料库列表" size="small">
<div v-if="libraryPaths.length > 0" class="paths-container">
<n-tag
v-for="item in libraryPaths"
:key="item.id"
class="library-tag"
:type="item.enabled ? 'success' : 'default'"
size="large"
>
<n-space align="center" :size="12">
<n-icon
size="20"
:component="item.enabled ? CheckmarkCircle : EllipseOutline"
class="check-icon"
@click="toggleEnabled(item)"
/>
<span class="path-text">{{ item.path }}</span>
<n-icon
size="18"
:component="CloseOutline"
class="close-icon"
@click="removePath(item)"
/>
</n-space>
</n-tag>
</div>
<n-empty v-else description="暂无资料库,请添加文件夹路径" />
</n-card>
<!-- 底部:操作按钮 -->
<n-card class="action-section" size="small">
<n-space justify="start">
<n-button
type="primary"
size="large"
@click="startTask"
:disabled="libraryPaths.length === 0"
>
<template #icon>
<span class="iconfont icon-zhongxinkaishi"></span>
</template>
开始检索
</n-button>
</n-space>
</n-card>
</div>
</template>
<style scoped>
.library-manager {
padding: 20px;
margin: 0 auto;
}
.add-section,
.paths-section,
.action-section {
margin-bottom: 20px;
}
.paths-container {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.library-tag {
padding: 8px 12px;
border-radius: 6px;
transition: all 0.3s ease;
}
.library-tag:hover {
transform: translateY(-2px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.path-text {
font-size: 14px;
max-width: 400px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.check-icon {
cursor: pointer;
transition: all 0.2s ease;
}
.check-icon:hover {
transform: scale(1.1);
}
.close-icon {
cursor: pointer;
color: #666;
transition: all 0.2s ease;
}
.close-icon:hover {
color: #d03050;
transform: scale(1.1);
}
/* 深色主题适配 */
:deep(.n-card) {
border-radius: 8px;
}
:deep(.n-card-header) {
padding: 16px 20px;
}
:deep(.n-card__content) {
padding: 20px;
}
</style>