4.16更新
This commit is contained in:
parent
ca27783bb2
commit
f84af8856e
10
src/apis/apiKey.ts
Normal file
10
src/apis/apiKey.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { get, post } from '@/utlis/request';
|
||||
|
||||
// 获取当前账号下可查看的 API Key 应用列表
|
||||
export const getApiKeyList = <T>(): Promise<T> => get('/dapi/downapps.dspy');
|
||||
|
||||
// 申请新的 API Key
|
||||
export const applyApiKey = <T, D>(data: D): Promise<T> => post('/dapi/apply_apikey.dspy', data);
|
||||
|
||||
// 根据应用 id 查看对应的 API Key 详情
|
||||
export const getApiKeyDetail = <T, D>(id:D ): Promise<T> => get('/dapi/get_apikey.dspy', id);
|
||||
@ -54,6 +54,12 @@
|
||||
<div class="content unicode" style="display: block;">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">apikey</div>
|
||||
<div class="code-name">&#xe617;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">消费记录</div>
|
||||
@ -180,9 +186,9 @@
|
||||
<pre><code class="language-css"
|
||||
>@font-face {
|
||||
font-family: 'iconfont';
|
||||
src: url('iconfont.woff2?t=1775638325739') format('woff2'),
|
||||
url('iconfont.woff?t=1775638325739') format('woff'),
|
||||
url('iconfont.ttf?t=1775638325739') format('truetype');
|
||||
src: url('iconfont.woff2?t=1776308634739') format('woff2'),
|
||||
url('iconfont.woff?t=1776308634739') format('woff'),
|
||||
url('iconfont.ttf?t=1776308634739') format('truetype');
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
|
||||
@ -208,6 +214,15 @@
|
||||
<div class="content font-class">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-apikey"></span>
|
||||
<div class="name">
|
||||
apikey
|
||||
</div>
|
||||
<div class="code-name">.icon-apikey
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-xiaofeijilu"></span>
|
||||
<div class="name">
|
||||
@ -397,6 +412,14 @@
|
||||
<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-apikey"></use>
|
||||
</svg>
|
||||
<div class="name">apikey</div>
|
||||
<div class="code-name">#icon-apikey</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-xiaofeijilu"></use>
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 5136943 */
|
||||
src: url('iconfont.woff2?t=1775638325739') format('woff2'),
|
||||
url('iconfont.woff?t=1775638325739') format('woff'),
|
||||
url('iconfont.ttf?t=1775638325739') format('truetype');
|
||||
src: url('iconfont.woff2?t=1776308634739') format('woff2'),
|
||||
url('iconfont.woff?t=1776308634739') format('woff'),
|
||||
url('iconfont.ttf?t=1776308634739') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@ -13,6 +13,10 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-apikey:before {
|
||||
content: "\e617";
|
||||
}
|
||||
|
||||
.icon-xiaofeijilu:before {
|
||||
content: "\e654";
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -5,6 +5,13 @@
|
||||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "44608574",
|
||||
"name": "apikey",
|
||||
"font_class": "apikey",
|
||||
"unicode": "e617",
|
||||
"unicode_decimal": 58903
|
||||
},
|
||||
{
|
||||
"icon_id": "1020316",
|
||||
"name": "消费记录",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/assets/images/idfile.mp4
Normal file
BIN
src/assets/images/idfile.mp4
Normal file
Binary file not shown.
BIN
src/assets/images/idfile1.mp4
Normal file
BIN
src/assets/images/idfile1.mp4
Normal file
Binary file not shown.
315
src/assets/less/apiKey/apikey.css
Normal file
315
src/assets/less/apiKey/apikey.css
Normal file
@ -0,0 +1,315 @@
|
||||
.apikey-page {
|
||||
height: 100%;
|
||||
padding-bottom: 40px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.apikey-card {
|
||||
height: 100%;
|
||||
background: #1e2024;
|
||||
border: 1px solid #2a2d32;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.apikey-card :deep(.el-card__body) {
|
||||
padding: 28px 32px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
margin-bottom: 24px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.page-header .header-main .title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
}
|
||||
.page-header .header-main .title-row .iconfont {
|
||||
font-size: 26px;
|
||||
color: #00c4b3;
|
||||
}
|
||||
.page-header .header-main .header-desc {
|
||||
margin: 10px 0 0;
|
||||
color: rgba(255, 255, 255, 0.65);
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.page-header .header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.page-header .header-actions .refresh-btn {
|
||||
background: #25282e;
|
||||
border-color: #3a3e45;
|
||||
color: rgba(255, 255, 255, 0.88);
|
||||
}
|
||||
.guide-card {
|
||||
margin-bottom: 24px;
|
||||
padding: 24px 28px;
|
||||
border-radius: 14px;
|
||||
background: #25282e;
|
||||
border: 1px solid #32363d;
|
||||
}
|
||||
.guide-section + .guide-section {
|
||||
margin-top: 24px;
|
||||
}
|
||||
.guide-title {
|
||||
margin-bottom: 14px;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
.guide-list {
|
||||
margin: 0;
|
||||
padding-left: 24px;
|
||||
color: rgba(255, 255, 255, 0.86);
|
||||
line-height: 1.9;
|
||||
font-size: 15px;
|
||||
}
|
||||
.guide-list li + li {
|
||||
margin-top: 4px;
|
||||
}
|
||||
.login-require,
|
||||
.empty-state {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.login-require-content,
|
||||
.empty-state {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
.login-require-content img,
|
||||
.empty-state img {
|
||||
width: 220px;
|
||||
height: 220px;
|
||||
object-fit: contain;
|
||||
opacity: 0.55;
|
||||
}
|
||||
.login-require-content {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
.login-title,
|
||||
.empty-title {
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
.login-desc,
|
||||
.empty-desc {
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
line-height: 1.7;
|
||||
}
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.stat-card {
|
||||
padding: 20px;
|
||||
border-radius: 14px;
|
||||
background: linear-gradient(135deg, rgba(44, 255, 248, 0.08), rgba(44, 104, 255, 0.08));
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
.stat-card .stat-label {
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.stat-card .stat-value {
|
||||
font-size: 28px;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
}
|
||||
.stat-card .stat-value.success {
|
||||
color: #3fe0b5;
|
||||
}
|
||||
.stat-card .stat-value.warning {
|
||||
color: #f5b04c;
|
||||
}
|
||||
.toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.toolbar .toolbar-left {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.toolbar .toolbar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.toolbar .toolbar-tip {
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
}
|
||||
.search-input {
|
||||
width: 320px;
|
||||
}
|
||||
.table-wrapper {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
border-radius: 12px;
|
||||
}
|
||||
.apikey-table {
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
}
|
||||
.apikey-table :deep(.el-table__header-wrapper) .el-table__header th {
|
||||
background-color: #1e2024;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
border-bottom: 1px solid #2a2d32;
|
||||
font-weight: 600;
|
||||
}
|
||||
.apikey-table :deep(.el-table__body-wrapper) .el-table__body tr {
|
||||
background-color: #1e2024;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.apikey-table :deep(.el-table__body-wrapper) .el-table__body tr:hover td {
|
||||
background-color: #25282e !important;
|
||||
}
|
||||
.apikey-table :deep(.el-table__body-wrapper) .el-table__body tr td {
|
||||
border-bottom: 1px solid #2a2d32;
|
||||
color: rgba(255, 255, 255, 0.88);
|
||||
}
|
||||
.apikey-table :deep(.el-table__row--striped) td {
|
||||
background-color: #1a1c20 !important;
|
||||
}
|
||||
.apikey-table .key-mask {
|
||||
font-family: Consolas, 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
color: #87f5eb;
|
||||
background: #14161a;
|
||||
padding: 4px 8px;
|
||||
border-radius: 6px;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
.view-key-content .view-key-name,
|
||||
.view-key-content .view-key-value {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.view-key-content .view-key-name .label,
|
||||
.view-key-content .view-key-value .label {
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
display: inline-block;
|
||||
width: 90px;
|
||||
}
|
||||
.view-key-content .view-key-name .value,
|
||||
.view-key-content .view-key-value .value {
|
||||
color: #fff;
|
||||
}
|
||||
.view-key-content .key-box {
|
||||
margin-top: 8px;
|
||||
padding: 14px;
|
||||
background: #14161a;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #2a2d32;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.view-key-content .key-box code {
|
||||
font-family: Consolas, 'Courier New', monospace;
|
||||
color: #87f5eb;
|
||||
font-size: 14px;
|
||||
word-break: break-all;
|
||||
flex: 1;
|
||||
}
|
||||
.view-key-content .view-key-tip {
|
||||
margin-top: 16px;
|
||||
padding: 10px 12px;
|
||||
background: rgba(245, 176, 76, 0.12);
|
||||
border-radius: 8px;
|
||||
color: #f5b04c;
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.view-key-content .view-key-tip i {
|
||||
font-size: 16px;
|
||||
}
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
:deep(.el-input__wrapper),
|
||||
:deep(.el-textarea__inner),
|
||||
:deep(.el-select__wrapper) {
|
||||
background-color: #25282e !important;
|
||||
border: 1px solid #3a3e45 !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
:deep(.el-input__inner),
|
||||
:deep(.el-textarea__inner) {
|
||||
color: rgba(255, 255, 255, 0.92) !important;
|
||||
}
|
||||
:deep(.el-radio) {
|
||||
margin-right: 20px;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
:deep(.el-dialog) {
|
||||
background: #1e2024;
|
||||
border: 1px solid #2a2d32;
|
||||
border-radius: 16px;
|
||||
}
|
||||
:deep(.el-dialog__title),
|
||||
:deep(.el-form-item__label) {
|
||||
color: #fff !important;
|
||||
}
|
||||
:deep(.el-button--primary.is-link) {
|
||||
color: #3fe0b5 !important;
|
||||
}
|
||||
:deep(.el-button--danger.is-link) {
|
||||
color: #f56c6c !important;
|
||||
}
|
||||
:deep(.el-table__inner-wrapper) {
|
||||
border: 0;
|
||||
}
|
||||
@media (max-width: 1200px) {
|
||||
.stats-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.apikey-card :deep(.el-card__body) {
|
||||
padding: 20px;
|
||||
}
|
||||
.stats-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.toolbar {
|
||||
flex-direction: column;
|
||||
}
|
||||
.search-input {
|
||||
width: 100%;
|
||||
}
|
||||
.table-wrapper {
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
384
src/assets/less/apiKey/apikey.less
Normal file
384
src/assets/less/apiKey/apikey.less
Normal file
@ -0,0 +1,384 @@
|
||||
.apikey-page {
|
||||
height: 100%;
|
||||
padding-bottom: 40px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.apikey-card {
|
||||
height: 100%;
|
||||
background: #1e2024;
|
||||
border: 1px solid #2a2d32;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 28px 32px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
margin-bottom: 24px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.header-main {
|
||||
.title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
|
||||
.iconfont {
|
||||
font-size: 26px;
|
||||
color: #00c4b3;
|
||||
}
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
margin: 10px 0 0;
|
||||
color: rgba(255, 255, 255, 0.65);
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
.refresh-btn {
|
||||
background: #25282e;
|
||||
border-color: #3a3e45;
|
||||
color: rgba(255, 255, 255, 0.88);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.guide-card {
|
||||
margin-bottom: 24px;
|
||||
padding: 24px 28px;
|
||||
border-radius: 14px;
|
||||
background: #25282e;
|
||||
border: 1px solid #32363d;
|
||||
}
|
||||
|
||||
.guide-section+.guide-section {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.guide-title {
|
||||
margin-bottom: 14px;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.guide-list {
|
||||
margin: 0;
|
||||
padding-left: 24px;
|
||||
color: rgba(255, 255, 255, 0.86);
|
||||
line-height: 1.9;
|
||||
font-size: 15px;
|
||||
|
||||
li+li {
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.login-require,
|
||||
.empty-state {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.login-require-content,
|
||||
.empty-state {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
|
||||
img {
|
||||
width: 220px;
|
||||
height: 220px;
|
||||
object-fit: contain;
|
||||
opacity: 0.55;
|
||||
}
|
||||
}
|
||||
|
||||
.login-require-content {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.login-title,
|
||||
.empty-title {
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.login-desc,
|
||||
.empty-desc {
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
padding: 20px;
|
||||
border-radius: 14px;
|
||||
background: linear-gradient(135deg, rgba(44, 255, 248, 0.08), rgba(44, 104, 255, 0.08));
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
|
||||
.stat-label {
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 28px;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
|
||||
&.success {
|
||||
color: #3fe0b5;
|
||||
}
|
||||
|
||||
&.warning {
|
||||
color: #f5b04c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.toolbar-left {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.toolbar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.toolbar-tip {
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
}
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.apikey-table {
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
|
||||
:deep(.el-table__header-wrapper) {
|
||||
.el-table__header th {
|
||||
background-color: #1e2024;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
border-bottom: 1px solid #2a2d32;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper) {
|
||||
.el-table__body tr {
|
||||
background-color: #1e2024;
|
||||
transition: background 0.2s;
|
||||
|
||||
&:hover td {
|
||||
background-color: #25282e !important;
|
||||
}
|
||||
|
||||
td {
|
||||
border-bottom: 1px solid #2a2d32;
|
||||
color: rgba(255, 255, 255, 0.88);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-table__row--striped) {
|
||||
td {
|
||||
background-color: #1a1c20 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.key-mask {
|
||||
font-family: Consolas, 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
color: #87f5eb;
|
||||
background: #14161a;
|
||||
padding: 4px 8px;
|
||||
border-radius: 6px;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
}
|
||||
|
||||
.view-key-content {
|
||||
|
||||
.view-key-name,
|
||||
.view-key-value {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.label {
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
display: inline-block;
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.key-box {
|
||||
margin-top: 8px;
|
||||
padding: 14px;
|
||||
background: #14161a;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #2a2d32;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
code {
|
||||
font-family: Consolas, 'Courier New', monospace;
|
||||
color: #87f5eb;
|
||||
font-size: 14px;
|
||||
word-break: break-all;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.view-key-tip {
|
||||
margin-top: 16px;
|
||||
padding: 10px 12px;
|
||||
background: rgba(245, 176, 76, 0.12);
|
||||
border-radius: 8px;
|
||||
color: #f5b04c;
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
i {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.el-input__wrapper),
|
||||
:deep(.el-textarea__inner),
|
||||
:deep(.el-select__wrapper) {
|
||||
background-color: #25282e !important;
|
||||
border: 1px solid #3a3e45 !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
:deep(.el-input__inner),
|
||||
:deep(.el-textarea__inner) {
|
||||
color: rgba(255, 255, 255, 0.92) !important;
|
||||
}
|
||||
|
||||
:deep(.el-radio) {
|
||||
margin-right: 20px;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
|
||||
:deep(.el-dialog) {
|
||||
background: #1e2024;
|
||||
border: 1px solid #2a2d32;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__title),
|
||||
:deep(.el-form-item__label) {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
:deep(.el-button--primary.is-link) {
|
||||
color: #3fe0b5 !important;
|
||||
}
|
||||
|
||||
:deep(.el-button--danger.is-link) {
|
||||
color: #f56c6c !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__inner-wrapper) {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.stats-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.apikey-card :deep(.el-card__body) {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
@ -75,6 +75,25 @@
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</div>
|
||||
<!-- 我的分组 -->
|
||||
<div class="tool-navigation mb-8">
|
||||
<div class="menu-title">我的</div>
|
||||
<el-menu
|
||||
:default-active="activeIndex"
|
||||
class="custom-menu"
|
||||
background-color="transparent"
|
||||
text-color="#fff"
|
||||
active-text-color="#00FFCC"
|
||||
@select="handleSelect"
|
||||
>
|
||||
<el-menu-item index="apikey">
|
||||
<div class="menu-item-content">
|
||||
<i class="iconfont icon-apikey"></i>
|
||||
<span class="menu-text">令牌管理</span>
|
||||
</div>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</div>
|
||||
|
||||
<!-- 底部占位空白 -->
|
||||
<div class="pb-20"></div>
|
||||
@ -126,6 +145,9 @@ const handleSelect = (index: string) => {
|
||||
case 'material-square':
|
||||
router.push('/material-square');
|
||||
break;
|
||||
case 'apikey':
|
||||
router.push('/apikey');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -152,7 +174,10 @@ const updateActiveIndex = () => {
|
||||
activeIndex.value = 'digital-human';
|
||||
} else if (path.includes('/material-square')) {
|
||||
activeIndex.value = 'material-square';
|
||||
} else {
|
||||
} else if (path.includes('/apikey')) {
|
||||
activeIndex.value = 'apikey';
|
||||
}
|
||||
else {
|
||||
activeIndex.value = 'index';
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,8 +83,13 @@ const router = createRouter({
|
||||
path: '/material-square',
|
||||
name: 'material-square',
|
||||
component: () => import('@/views/videoGenerate/videoGenerate.vue')
|
||||
},
|
||||
// apikey
|
||||
{
|
||||
path: '/apikey',
|
||||
name: 'apikey',
|
||||
component: () => import('@/views/APiKey/apikey.vue')
|
||||
}
|
||||
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
32
src/types/apiKey.ts
Normal file
32
src/types/apiKey.ts
Normal file
@ -0,0 +1,32 @@
|
||||
export interface ListApikeys {
|
||||
id: string;
|
||||
name: null;
|
||||
apikeyid: string;
|
||||
};
|
||||
|
||||
export interface ListResult {
|
||||
apikeys: Array< ListApikeys >;
|
||||
};
|
||||
|
||||
export interface AddParams {
|
||||
appname: string;
|
||||
description: string;
|
||||
}
|
||||
export interface AddResult{
|
||||
status: string;
|
||||
}
|
||||
|
||||
interface DetailData {
|
||||
id: string;
|
||||
name: null;
|
||||
description: null;
|
||||
secretkey: string;
|
||||
allowedips: null;
|
||||
orgid: string;
|
||||
apikey: string;
|
||||
};
|
||||
|
||||
export interface DetailResult {
|
||||
status: string;
|
||||
data: DetailData;
|
||||
};
|
||||
@ -22,11 +22,11 @@ const shouldIncludeCredentials = (url?: string, baseURL?: string) => {
|
||||
axios.interceptors.request.use(
|
||||
function (config) {
|
||||
// console.log('=== 请求拦截器 ===')
|
||||
console.log('请求 URL:', config)
|
||||
// console.log('请求 URL:', config)
|
||||
// console.log('请求方法:', config.method)
|
||||
// console.log('请求参数 (params):', config.params)
|
||||
// console.log('请求数据 (data):', config.data)
|
||||
console.log('请求头:', config.headers)
|
||||
// console.log('请求头:', config.headers)
|
||||
config.baseURL = apiBaseURL
|
||||
config.withCredentials = shouldIncludeCredentials(config.url, config.baseURL)
|
||||
return config
|
||||
|
||||
332
src/views/APiKey/apikey.vue
Normal file
332
src/views/APiKey/apikey.vue
Normal file
@ -0,0 +1,332 @@
|
||||
<template>
|
||||
<div class="apikey-page">
|
||||
<el-card class="apikey-card">
|
||||
<div class="page-header">
|
||||
<div class="header-main">
|
||||
<div class="title-row">
|
||||
<i class="iconfont icon-apikey"></i>
|
||||
<span>API Key 管理</span>
|
||||
</div>
|
||||
<p class="header-desc">创建和管理访问令牌,用于在外部系统中安全调用你的能力接口。</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<el-button class="refresh-btn" :loading="loading" @click="loadKeys">刷新</el-button>
|
||||
<el-button type="primary" @click="openCreateDialog">创建新令牌</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="guide-card">
|
||||
<div class="guide-section">
|
||||
<div class="guide-title">API 密钥安全指南:</div>
|
||||
<ul class="guide-list">
|
||||
<li>请不要与他人共享您的 API Keys,暴露 API Key 可能会破坏您的帐户安全和访问的服务。</li>
|
||||
<li>避免将 API Key 暴露在浏览器或其他客户端代码中。</li>
|
||||
<li>为了保护您的帐户安全,一旦 API Keys 被发现泄露,平台发现后可能会将其禁用。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!isLoggedIn" class="login-require">
|
||||
<div class="login-require-content">
|
||||
<img src="../../assets/images/preparation.png" alt="">
|
||||
<div class="login-title">请先登录后管理 API Key</div>
|
||||
<div class="login-desc">登录后可创建、复制、查看和删除自己的访问令牌。</div>
|
||||
<el-button type="primary" @click="handleLogin">去登录</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">令牌总数</div>
|
||||
<div class="stat-value">{{ stats.total }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">启用中</div>
|
||||
<div class="stat-value success">{{ stats.active }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-wrapper">
|
||||
<el-table :data="keys" class="apikey-table" stripe v-loading="loading">
|
||||
<el-table-column prop="name" label="令牌名称" min-width="150" />
|
||||
<!-- <el-table-column prop="note" label="备注" min-width="150" show-overflow-tooltip /> -->
|
||||
<el-table-column label="id" min-width="220">
|
||||
<template #default="{ row }">
|
||||
<code class="key-mask">{{ maskKey(row.key || '') }}</code>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" size="small" @click="openViewDialog(row)">查看</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
</el-card>
|
||||
|
||||
<!-- 创建 API Key 对话框 -->
|
||||
<el-dialog v-model="createDialogVisible" title="创建 API Key" width="560px" destroy-on-close
|
||||
class="create-key-dialog">
|
||||
<el-form ref="formRef" :model="createForm" :rules="formRules" label-position="top">
|
||||
<el-form-item label="令牌名称" prop="appname">
|
||||
<el-input v-model="createForm.appname" placeholder="例如:前端开发环境 / 自动化脚本" maxlength="30" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="createForm.description" type="textarea" :rows="3" maxlength="100" show-word-limit
|
||||
placeholder="填写使用场景,方便后续区分" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="createDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="createLoading" @click="createKey">确认创建</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 查看完整 Key 对话框 -->
|
||||
<el-dialog v-model="viewDialogVisible" title="API Key 详情" width="560px" class="view-key-dialog" @close="handleViewDialogClose">
|
||||
<div v-loading="detailLoading" class="dialog-content-wrapper">
|
||||
<div class="view-key-content">
|
||||
<div class="view-key-name">
|
||||
<span class="label">令牌名称:</span>
|
||||
<span class="value">{{ viewingKey?.name || '未命名' }}</span>
|
||||
</div>
|
||||
<div class="view-key-value">
|
||||
<span class="label">完整 Key:</span>
|
||||
<div class="key-box">
|
||||
<code>{{ viewingKey?.key }}</code>
|
||||
<el-button link type="primary" size="small" @click="copyViewingKey">
|
||||
复制
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="view-key-tip">
|
||||
<i class="el-icon-info"></i>
|
||||
请妥善保管该密钥 ,切勿泄露。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang='ts'>
|
||||
import { computed, reactive, ref, watch, onMounted } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useLoginStore } from '@/store/LoginStore';
|
||||
import { applyApiKey, getApiKeyList, getApiKeyDetail } from '@/apis/apiKey';
|
||||
import type { AddParams, AddResult, DetailResult, ListApikeys } from '@/types/apiKey';
|
||||
|
||||
const router = useRouter();
|
||||
const loginStore = useLoginStore();
|
||||
const isLoggedIn = computed(() => loginStore.isLoggedIn);
|
||||
|
||||
// 数据状态
|
||||
const keys = ref<ListApikeys[]>([]);
|
||||
const loading = ref(false);
|
||||
|
||||
// 统计数据
|
||||
const stats = computed(() => {
|
||||
const total = keys.value.length;
|
||||
const active = keys.value.length; // 接口未返回状态字段,默认所有令牌为启用状态
|
||||
return { total, active };
|
||||
});
|
||||
|
||||
// 创建对话框相关
|
||||
const createDialogVisible = ref(false);
|
||||
const createLoading = ref(false);
|
||||
const formRef = ref<FormInstance>();
|
||||
const createForm = reactive<AddParams>({
|
||||
appname: '',
|
||||
description: '',
|
||||
});
|
||||
|
||||
const formRules: FormRules = {
|
||||
appname: [
|
||||
{ required: true, message: '请输入令牌名称', trigger: 'blur' },
|
||||
{ max: 30, message: '名称不能超过30个字符', trigger: 'blur' }
|
||||
]
|
||||
};
|
||||
|
||||
// 查看对话框相关
|
||||
const viewDialogVisible = ref(false);
|
||||
const viewingKey = ref<any>(null);
|
||||
const detailLoading = ref(false);
|
||||
|
||||
// 工具函数:掩码处理 API Key
|
||||
const maskKey = (key: string) => {
|
||||
if (!key || key.length <= 8) return '****';
|
||||
const start = key.slice(0, 6);
|
||||
const end = key.slice(-4);
|
||||
return `${start}****${end}`;
|
||||
};
|
||||
|
||||
const copyToClipboard = async (text: string, successMsg = '复制成功') => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
ElMessage.success(successMsg);
|
||||
} catch (err) {
|
||||
ElMessage.error('复制失败,请手动复制');
|
||||
}
|
||||
};
|
||||
|
||||
// 加载列表(适配接口返回结构)
|
||||
const loadKeys = async () => {
|
||||
if (!isLoggedIn.value) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getApiKeyList<any>();
|
||||
// 适配实际返回结构:{ status: 'ok', data: { apikeys: [...] } }
|
||||
const apikeys = res.data?.apikeys || res.apikeys || [];
|
||||
keys.value = apikeys.map((item: any) => ({
|
||||
id: item.id,
|
||||
name: item.name || '未命名',
|
||||
note: item.description || '暂无备注', // 后端可能用 description 表示备注
|
||||
key: item.apikeyid || '', // 列表中的 key 可能是掩码或空
|
||||
status: 'active',
|
||||
createdAt: new Date().toISOString()
|
||||
}));
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error?.message || '获取 API Key 列表失败');
|
||||
keys.value = [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 打开创建对话框
|
||||
const openCreateDialog = () => {
|
||||
createForm.appname = '';
|
||||
createForm.description = '';
|
||||
formRef.value?.clearValidate();
|
||||
createDialogVisible.value = true;
|
||||
};
|
||||
|
||||
// 创建 API Key
|
||||
const createKey = async () => {
|
||||
if (!formRef.value) return;
|
||||
await formRef.value.validate();
|
||||
createLoading.value = true;
|
||||
try {
|
||||
const res = await applyApiKey<AddResult, AddParams>({
|
||||
appname: createForm.appname,
|
||||
description: createForm.description,
|
||||
});
|
||||
if (res.status === 'ok') {
|
||||
createDialogVisible.value = false;
|
||||
await loadKeys();
|
||||
ElMessage.success('创建成功,点击查看可复制完整 Key');
|
||||
}
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error?.message || '创建失败');
|
||||
} finally {
|
||||
createLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 查看完整 Key(调用详情接口)
|
||||
const openViewDialog = async (row: ListApikeys) => {
|
||||
if (!row.id) {
|
||||
ElMessage.error('令牌 ID 无效');
|
||||
return;
|
||||
}
|
||||
|
||||
detailLoading.value = true;
|
||||
viewDialogVisible.value = false; // 先关闭旧对话框,避免残留数据
|
||||
|
||||
try {
|
||||
const res = await getApiKeyDetail<DetailResult, { id: string }>({ id: row.id });
|
||||
|
||||
if (res.status === 'ok' && res.data) {
|
||||
// 根据实际接口返回字段构建 viewingKey(字段名 apikey 为完整密钥)
|
||||
viewingKey.value = {
|
||||
...row,
|
||||
key: res.data.apikey || '',
|
||||
name: res.data.name || row.name || '未命名',
|
||||
note: res.data.description || '暂无备注',
|
||||
};
|
||||
viewDialogVisible.value = true;
|
||||
} else {
|
||||
ElMessage.error('获取 API Key 详情失败');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('获取详情异常:', error);
|
||||
ElMessage.error(error?.message || '网络错误,请稍后重试');
|
||||
} finally {
|
||||
detailLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 查看对话框内复制完整 Key
|
||||
const copyViewingKey = () => {
|
||||
if (viewingKey.value?.key) {
|
||||
copyToClipboard(viewingKey.value.key, '完整 API Key 已复制');
|
||||
} else {
|
||||
ElMessage.warning('无可复制的 Key');
|
||||
}
|
||||
};
|
||||
|
||||
// 关闭查看对话框时重置数据
|
||||
const handleViewDialogClose = () => {
|
||||
viewDialogVisible.value = false;
|
||||
viewingKey.value = null;
|
||||
};
|
||||
|
||||
// 去登录
|
||||
const handleLogin = () => {
|
||||
router.push('/login');
|
||||
};
|
||||
|
||||
// 监听登录状态变化
|
||||
watch(
|
||||
() => loginStore.isLoggedIn,
|
||||
(loggedIn) => {
|
||||
if (loggedIn) {
|
||||
loadKeys();
|
||||
} else {
|
||||
keys.value = [];
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
if (loginStore.isLoggedIn) {
|
||||
loadKeys();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style scoped lang='less'>
|
||||
@import url('@/assets/less/apiKey/apikey.less');
|
||||
</style>
|
||||
|
||||
<style lang='less'>
|
||||
.custom-select-popup {
|
||||
background-color: #2A2D34 !important;
|
||||
border-radius: 12px !important;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4) !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.custom-select-popup .el-select-dropdown__item {
|
||||
color: #ffffff !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.custom-select-popup .el-select-dropdown__item.selected,
|
||||
.custom-select-popup .el-select-dropdown__item:hover {
|
||||
background-color: #3a3d46 !important;
|
||||
}
|
||||
.el-input__count-inner{
|
||||
background-color: #25282e !important;
|
||||
}
|
||||
.el-input__count{
|
||||
background-color: #25282e !important;
|
||||
|
||||
}
|
||||
</style>
|
||||
@ -9,7 +9,7 @@
|
||||
loop
|
||||
playsinline
|
||||
crossorigin="anonymous"
|
||||
src="../../assets/images/login.mp4"
|
||||
src="../../assets/images/idfile.mp4"
|
||||
></video>
|
||||
</div>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user