Refactor:11; Fix:6; Chore:1; Feat:7; Build:1 #49
@@ -34,6 +34,7 @@ module.exports = {
|
|||||||
'warn',
|
'warn',
|
||||||
{ allowConstantExport: true }
|
{ allowConstantExport: true }
|
||||||
],
|
],
|
||||||
'@typescript-eslint/no-non-null-assertion': 'off'
|
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||||
|
'react-hooks/exhaustive-deps': 'off',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3406
package-lock.json
generated
3406
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
63
package.json
63
package.json
@@ -15,30 +15,33 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^5.2.6",
|
"@ant-design/icons": "^5.3.6",
|
||||||
"@marsidev/react-turnstile": "^0.5.3",
|
"@dnd-kit/core": "^6.1.0",
|
||||||
|
"@dnd-kit/sortable": "^8.0.0",
|
||||||
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
|
"@marsidev/react-turnstile": "^0.6.0",
|
||||||
"@monaco-editor/react": "^4.6.0",
|
"@monaco-editor/react": "^4.6.0",
|
||||||
"@typescript/ata": "^0.9.4",
|
"@typescript/ata": "^0.9.4",
|
||||||
"antd": "^5.13.2",
|
"antd": "^5.16.5",
|
||||||
"axios": "^1.6.5",
|
"axios": "^1.6.8",
|
||||||
"custom-protocol-check": "^1.4.0",
|
"custom-protocol-check": "^1.4.0",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.11",
|
||||||
"echarts": "^5.4.3",
|
"echarts": "^5.5.0",
|
||||||
"esbuild-wasm": "^0.19.12",
|
"esbuild-wasm": "^0.20.2",
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"fflate": "^0.8.1",
|
"fflate": "^0.8.2",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"localforage": "^1.10.0",
|
"localforage": "^1.10.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"match-sorter": "^6.3.3",
|
"match-sorter": "^6.3.4",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"monaco-editor": "^0.45.0",
|
"monaco-editor": "^0.48.0",
|
||||||
"monaco-jsx-syntax-highlight": "^1.2.0",
|
"monaco-jsx-syntax-highlight": "^1.2.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.3.1",
|
||||||
"react-draggable": "^4.4.6",
|
"react-draggable": "^4.4.6",
|
||||||
"react-router": "^6.21.3",
|
"react-router": "^6.23.0",
|
||||||
"react-router-dom": "^6.21.3",
|
"react-router-dom": "^6.23.0",
|
||||||
"size-sensor": "^1.0.2",
|
"size-sensor": "^1.0.2",
|
||||||
"vanilla-tilt": "^1.8.1"
|
"vanilla-tilt": "^1.8.1"
|
||||||
},
|
},
|
||||||
@@ -46,28 +49,28 @@
|
|||||||
"@svgr/core": "^8.1.0",
|
"@svgr/core": "^8.1.0",
|
||||||
"@svgr/plugin-jsx": "^8.1.0",
|
"@svgr/plugin-jsx": "^8.1.0",
|
||||||
"@types/jsdom": "^21.1.6",
|
"@types/jsdom": "^21.1.6",
|
||||||
"@types/lodash": "^4.14.202",
|
"@types/lodash": "^4.17.0",
|
||||||
"@types/node": "^20.11.5",
|
"@types/node": "^20.12.7",
|
||||||
"@types/react": "^18.2.48",
|
"@types/react": "^18.3.1",
|
||||||
"@types/react-dom": "^18.2.18",
|
"@types/react-dom": "^18.3.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.19.1",
|
"@typescript-eslint/eslint-plugin": "^7.8.0",
|
||||||
"@typescript-eslint/parser": "^6.19.1",
|
"@typescript-eslint/parser": "^7.8.0",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^8.57.0",
|
||||||
|
"eslint-config-love": "^47.0.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-config-standard-with-typescript": "^43.0.1",
|
|
||||||
"eslint-plugin-import": "^2.29.1",
|
"eslint-plugin-import": "^2.29.1",
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"eslint-plugin-promise": "^6.1.1",
|
"eslint-plugin-promise": "^6.1.1",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.2",
|
||||||
"eslint-plugin-react-refresh": "^0.4.5",
|
"eslint-plugin-react-refresh": "^0.4.6",
|
||||||
"jsdom": "^24.0.0",
|
"jsdom": "^24.0.0",
|
||||||
"prettier": "^3.2.4",
|
"prettier": "^3.2.5",
|
||||||
"sass": "^1.70.0",
|
"sass": "^1.76.0",
|
||||||
"stylelint-config-prettier": "^9.0.5",
|
"stylelint-config-prettier": "^9.0.5",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.4.5",
|
||||||
"unplugin-auto-import": "^0.17.3",
|
"unplugin-auto-import": "^0.17.5",
|
||||||
"unplugin-icons": "^0.18.2",
|
"unplugin-icons": "^0.19.0",
|
||||||
"vite": "^5.0.12"
|
"vite": "^5.2.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.vertical-scrollbar {
|
.vertical-scrollbar {
|
||||||
padding: 12px 2px;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
left: 100%;
|
left: 100%;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -80,7 +79,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.horizontal-scrollbar {
|
.horizontal-scrollbar {
|
||||||
padding: 4px 12px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 100%;
|
top: 100%;
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
font-size: constants.$SIZE_ICON_SM;
|
font-size: constants.$SIZE_ICON_SM;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
transform: rotateZ(180deg);
|
transform: rotateZ(180deg);
|
||||||
transition: all .3s;
|
transition: all .3s;
|
||||||
@@ -55,14 +56,16 @@
|
|||||||
.scroll {
|
.scroll {
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
> li {
|
> li, > div > li {
|
||||||
|
padding: 2px 14px;
|
||||||
|
|
||||||
&.item {
|
&.item {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 4px 14px;
|
font-size: 1rem;
|
||||||
font-size: 1.4em;
|
|
||||||
|
|
||||||
> .menu-bt {
|
> .menu-bt {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@@ -78,6 +81,10 @@
|
|||||||
height: 40px;
|
height: 40px;
|
||||||
font-size: constants.$SIZE_ICON_SM;
|
font-size: constants.$SIZE_ICON_SM;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@@ -86,15 +93,24 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
background-color: constants.$origin-color;
|
||||||
|
|
||||||
.text {
|
.text {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
|
width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
color: constants.$origin-color;
|
color: constants.$origin-color;
|
||||||
background-color: constants.$main-color !important;
|
background-color: constants.$main-color;
|
||||||
|
|
||||||
|
img {
|
||||||
|
filter: drop-shadow(1000px 0 0 constants.$origin-color);
|
||||||
|
transform: translate(-1000px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,7 +118,7 @@
|
|||||||
.submenu {
|
.submenu {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
padding-left: 20px;
|
padding-left: 10px;
|
||||||
z-index: 10000;
|
z-index: 10000;
|
||||||
animation: 0.1s ease forwards;
|
animation: 0.1s ease forwards;
|
||||||
@include mixins.unique-keyframes {
|
@include mixins.unique-keyframes {
|
||||||
@@ -123,11 +139,13 @@
|
|||||||
padding: 10px 10px;
|
padding: 10px 10px;
|
||||||
background-color: constants.$origin-color;
|
background-color: constants.$origin-color;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
box-shadow: 2px 2px 10px 0 rgba(0,0,0,0.1);
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
display: block;
|
display: block;
|
||||||
@@ -136,11 +154,11 @@
|
|||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
color: constants.$origin-color;
|
color: constants.$origin-color;
|
||||||
background-color: constants.$main-color !important;
|
background-color: constants.$main-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover a {
|
&:hover a:not(.active) {
|
||||||
background-color: constants.$background-color;
|
background-color: constants.$background-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,7 +167,7 @@
|
|||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
> .menu-bt {
|
> .menu-bt {
|
||||||
a {
|
a:not(.active) {
|
||||||
background-color: constants.$background-color;
|
background-color: constants.$background-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,6 +189,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.delete {
|
||||||
|
.menu-bt {
|
||||||
|
border: {
|
||||||
|
width: 1px;
|
||||||
|
color: constants.$error-secondary-color;
|
||||||
|
style: dashed;
|
||||||
|
};
|
||||||
|
filter: drop-shadow(1000px 0 0 constants.$error-secondary-color);
|
||||||
|
transform: translate(-1000px);
|
||||||
|
|
||||||
|
> a {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,13 +285,14 @@
|
|||||||
transition: all .3s;
|
transition: all .3s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.text {
|
.text {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-bt {
|
.menu-bt {
|
||||||
.text {
|
.text, .extend {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -272,6 +307,7 @@
|
|||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.text {
|
.text {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -291,6 +327,7 @@
|
|||||||
|
|
||||||
.icon-exit {
|
.icon-exit {
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background-color: constants.$background-color;
|
background-color: constants.$background-color;
|
||||||
|
|||||||
28
src/assets/css/components/common/url-card.scss
Normal file
28
src/assets/css/components/common/url-card.scss
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
@use '@/assets/css/constants' as constants;
|
||||||
|
|
||||||
|
[data-component=component-url-card] {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.url-card {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin-top: 80px;
|
||||||
|
text-align: center;
|
||||||
|
gap: 42px;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
color: constants.$production-color;
|
||||||
|
font-size: constants.$SIZE_ICON_XL;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
font-weight: bolder;
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/assets/css/components/dnd/drag-handle.scss
Normal file
5
src/assets/css/components/dnd/drag-handle.scss
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[data-component=component-drag-handle] {
|
||||||
|
background-color: transparent;
|
||||||
|
color: unset;
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
30
src/assets/css/components/dnd/drop-mask.scss
Normal file
30
src/assets/css/components/dnd/drop-mask.scss
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
@use "@/assets/css/constants" as constants;
|
||||||
|
|
||||||
|
[data-component=component-drop-mask] {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: {
|
||||||
|
left: 10px;
|
||||||
|
right: 10px;
|
||||||
|
bottom: 10px;
|
||||||
|
};
|
||||||
|
background-color: constants.$origin-color;
|
||||||
|
|
||||||
|
.drop-mask-border {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border: {
|
||||||
|
width: 2px;
|
||||||
|
color: constants.$font-secondary-color;
|
||||||
|
style: dashed;
|
||||||
|
radius: 8px;
|
||||||
|
};
|
||||||
|
font-size: 1.8em;
|
||||||
|
}
|
||||||
|
}
|
||||||
33
src/assets/css/components/system/setting-card.scss
Normal file
33
src/assets/css/components/system/setting-card.scss
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
@use '@/assets/css/constants' as constants;
|
||||||
|
|
||||||
|
[data-component=component-setting-card] {
|
||||||
|
.settings-card {
|
||||||
|
padding: 20px;
|
||||||
|
gap: 20px;
|
||||||
|
color: constants.$main-color;
|
||||||
|
|
||||||
|
> .head {
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
font-size: constants.$SIZE_ICON_MD;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
:nth-child(n+3) {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
color: constants.$font-main-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bt-save {
|
||||||
|
color: constants.$main-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
78
src/assets/css/components/system/statistics-card.scss
Normal file
78
src/assets/css/components/system/statistics-card.scss
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
@use '@/assets/css/constants' as constants;
|
||||||
|
|
||||||
|
[data-component=component-statistics-card] {
|
||||||
|
.statistics-card {
|
||||||
|
padding: 20px;
|
||||||
|
gap: 20px;
|
||||||
|
|
||||||
|
> .head {
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
color: constants.$main-color;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
font-size: constants.$SIZE_ICON_MD;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
:nth-child(n+3) {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
color: constants.$font-main-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
font-size: 1.1em;
|
||||||
|
padding: 0 10px;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
.key, .value-percent {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
color: constants.$font-main-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
color: constants.$font-secondary-color;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-chart {
|
||||||
|
justify-content: space-around;
|
||||||
|
width: 0;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
max-height: 12px;
|
||||||
|
height: 12px;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
transform: translateY(1px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-percent {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.big-chart {
|
||||||
|
width: 0;
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> * {
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/assets/css/components/tools/load-more-card.scss
Normal file
27
src/assets/css/components/tools/load-more-card.scss
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
@use '@/assets/css/constants' as constants;
|
||||||
|
|
||||||
|
[data-component=component-load-more-card] {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.load-more-card {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
text-align: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
display: flex;
|
||||||
|
font-size: constants.$SIZE_ICON_XXL;
|
||||||
|
color: constants.$production-color;
|
||||||
|
align-items: center;
|
||||||
|
transform: translateY(-20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
position: absolute;
|
||||||
|
top: 60%;
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
src/assets/css/components/tools/repository-card.scss
Normal file
51
src/assets/css/components/tools/repository-card.scss
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
@use '@/assets/css/constants' as constants;
|
||||||
|
|
||||||
|
[data-component=component-repository-card] {
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.repository-card {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
text-align: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
display: block;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
.version-select {
|
||||||
|
width: 9em;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
>:not(.version-select) {
|
||||||
|
font-size: 1.6em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
display: flex;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
color: constants.$production-color;
|
||||||
|
font-size: constants.$SIZE_ICON_XL;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: constants.$SIZE_ICON_XL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-name {
|
||||||
|
font-weight: bolder;
|
||||||
|
font-size: 1.6em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
110
src/assets/css/components/tools/store-card.scss
Normal file
110
src/assets/css/components/tools/store-card.scss
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
@use '@/assets/css/constants' as constants;
|
||||||
|
|
||||||
|
[data-component=component-store-card] {
|
||||||
|
height: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.store-card {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
text-align: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
display: block;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.version {
|
||||||
|
width: 0;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation {
|
||||||
|
display: flex;
|
||||||
|
font-size: 1.6em;
|
||||||
|
gap: 4px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
> *:hover {
|
||||||
|
color: constants.$font-secondary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
display: flex;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
color: constants.$production-color;
|
||||||
|
font-size: constants.$SIZE_ICON_XL;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: constants.$SIZE_ICON_XL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
padding-top: 20px;
|
||||||
|
|
||||||
|
.tool-name {
|
||||||
|
font-weight: bolder;
|
||||||
|
font-size: 1.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-desc {
|
||||||
|
margin: {
|
||||||
|
top: 10px;
|
||||||
|
left: auto;
|
||||||
|
right: auto;
|
||||||
|
};
|
||||||
|
color: constants.$font-secondary-color;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-height: 40px;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.author {
|
||||||
|
display: flex;
|
||||||
|
margin-top: auto;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: end;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
> * {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-name {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:hover {
|
||||||
|
.header {
|
||||||
|
.version {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,33 +9,9 @@
|
|||||||
|
|
||||||
> .card-box {
|
> .card-box {
|
||||||
width: 200px;
|
width: 200px;
|
||||||
height: 360px;
|
height: 320px;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
overflow: hidden !important;
|
overflow: hidden !important;
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
.common-card {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
margin-top: 100px;
|
|
||||||
text-align: center;
|
|
||||||
gap: 42px;
|
|
||||||
|
|
||||||
> * {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
color: constants.$production-color;
|
|
||||||
font-size: constants.$SIZE_ICON_XL;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
font-weight: bolder;
|
|
||||||
font-size: 2em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,36 +11,6 @@
|
|||||||
> * {
|
> * {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-card {
|
|
||||||
padding: 20px;
|
|
||||||
gap: 20px;
|
|
||||||
color: constants.$main-color;
|
|
||||||
|
|
||||||
> .head {
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
font-size: constants.$SIZE_ICON_MD;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
display: flex;
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
:nth-child(n+3) {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
color: constants.$font-main-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bt-save {
|
|
||||||
color: constants.$main-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,81 +10,6 @@
|
|||||||
> .card-box {
|
> .card-box {
|
||||||
width: 48%;
|
width: 48%;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
|
||||||
.common-card {
|
|
||||||
padding: 20px;
|
|
||||||
gap: 20px;
|
|
||||||
|
|
||||||
> .head {
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
color: constants.$main-color;
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
font-size: constants.$SIZE_ICON_MD;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
display: flex;
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
:nth-child(n+3) {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
color: constants.$font-main-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-content {
|
|
||||||
font-size: 1.1em;
|
|
||||||
padding: 0 10px;
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
.key, .value-percent {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
color: constants.$font-main-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.value {
|
|
||||||
color: constants.$font-secondary-color;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
> * {
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.value-chart {
|
|
||||||
justify-content: space-around;
|
|
||||||
width: 0;
|
|
||||||
|
|
||||||
> div {
|
|
||||||
max-height: 12px;
|
|
||||||
height: 12px;
|
|
||||||
|
|
||||||
> * {
|
|
||||||
transform: translateY(1px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.value-percent {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.big-chart {
|
|
||||||
width: 0;
|
|
||||||
height: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> * {
|
|
||||||
gap: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
>*:first-child {
|
>*:first-child {
|
||||||
|
width: 0;
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
43
src/assets/css/pages/system/tools/template.scss
Normal file
43
src/assets/css/pages/system/tools/template.scss
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
@use '@/assets/css/constants' as constants;
|
||||||
|
|
||||||
|
[data-component=system-tools-template] {
|
||||||
|
.root-content {
|
||||||
|
padding: 20px;
|
||||||
|
gap: 10px;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.has-edited::after {
|
||||||
|
content: '*';
|
||||||
|
color: constants.$font-secondary-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
>*:first-child {
|
||||||
|
width: 0;
|
||||||
|
height: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
> *:nth-child(2) {
|
||||||
|
position: sticky;
|
||||||
|
top: 20px;
|
||||||
|
height: calc(100vh - 40px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-editor-btn {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
background-color: constants.$font-secondary-color;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
color: white;
|
||||||
|
opacity: 0.6;
|
||||||
|
box-shadow: 2px 2px 10px 0 rgba(0,0,0,0.2);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,14 @@
|
|||||||
[data-component=tools-framework] {
|
[data-component=tools-framework] {
|
||||||
.left-panel {
|
.left-panel {
|
||||||
background-color: constants.$origin-color;
|
background-color: constants.$origin-color;
|
||||||
|
|
||||||
|
.menu-droppable {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
min-height: 0;
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-panel {
|
.right-panel {
|
||||||
|
|||||||
@@ -5,57 +5,16 @@
|
|||||||
.root-content {
|
.root-content {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
|
|
||||||
|
.own-content {
|
||||||
|
gap: 20px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
|
||||||
> .card-box {
|
> .card-box, > div {
|
||||||
width: 180px;
|
width: 180px;
|
||||||
height: 290px;
|
height: 290px;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
|
||||||
.common-card {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
text-align: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
> * {
|
|
||||||
display: block;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.version-select {
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
left: 10px;
|
|
||||||
width: 9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upgrade-bt {
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
right: 10px;
|
|
||||||
font-size: 1.8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
display: flex;
|
|
||||||
padding-top: 50px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
color: constants.$production-color;
|
|
||||||
font-size: constants.$SIZE_ICON_XL;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: constants.$SIZE_ICON_XL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tool-name {
|
|
||||||
font-weight: bolder;
|
|
||||||
font-size: 1.6em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -112,4 +71,40 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.favorite-divider {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
:first-child, :last-child {
|
||||||
|
height: 0;
|
||||||
|
border: {
|
||||||
|
width: 1px;
|
||||||
|
color: constants.$divide-color;
|
||||||
|
style: dashed;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider-text {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
font-size: 1.2em;
|
||||||
|
color: constants.$font-secondary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.star-content {
|
||||||
|
gap: 20px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
> .card-box, > div {
|
||||||
|
width: 180px;
|
||||||
|
height: 290px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,113 +26,10 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
> .card-box {
|
> div {
|
||||||
width: 180px;
|
width: 180px;
|
||||||
height: 290px;
|
height: 290px;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
.common-card {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
text-align: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
> * {
|
|
||||||
display: block;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
display: flex;
|
|
||||||
padding-top: 40px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
color: constants.$production-color;
|
|
||||||
font-size: constants.$SIZE_ICON_XL;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: constants.$SIZE_ICON_XL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.version {
|
|
||||||
position: absolute;
|
|
||||||
left: 10px;
|
|
||||||
top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info {
|
|
||||||
padding-top: 20px;
|
|
||||||
|
|
||||||
.tool-name {
|
|
||||||
font-weight: bolder;
|
|
||||||
font-size: 1.6em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tool-desc {
|
|
||||||
margin-top: 10px;
|
|
||||||
color: constants.$font-secondary-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.author {
|
|
||||||
display: flex;
|
|
||||||
margin-top: auto;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: end;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
> * {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.author-name {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.operation {
|
|
||||||
display: flex;
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
right: 12px;
|
|
||||||
font-size: 1.6em;
|
|
||||||
gap: 4px;
|
|
||||||
|
|
||||||
> *:hover {
|
|
||||||
color: constants.$font-secondary-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.load-more-card {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
text-align: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
display: flex;
|
|
||||||
font-size: constants.$SIZE_ICON_XXL;
|
|
||||||
color: constants.$production-color;
|
|
||||||
align-items: center;
|
|
||||||
transform: translateY(-20px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
position: absolute;
|
|
||||||
top: 60%;
|
|
||||||
font-size: 1.2em;
|
|
||||||
font-weight: bolder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-tool {
|
.no-tool {
|
||||||
|
|||||||
@@ -68,113 +68,10 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
> .card-box {
|
> div {
|
||||||
width: 180px;
|
width: 180px;
|
||||||
height: 290px;
|
height: 290px;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
.common-card {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
text-align: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
> * {
|
|
||||||
display: block;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
display: flex;
|
|
||||||
padding-top: 40px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
color: constants.$production-color;
|
|
||||||
font-size: constants.$SIZE_ICON_XL;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: constants.$SIZE_ICON_XL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.version {
|
|
||||||
position: absolute;
|
|
||||||
left: 10px;
|
|
||||||
top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info {
|
|
||||||
padding-top: 20px;
|
|
||||||
|
|
||||||
.tool-name {
|
|
||||||
font-weight: bolder;
|
|
||||||
font-size: 1.6em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tool-desc {
|
|
||||||
margin-top: 10px;
|
|
||||||
color: constants.$font-secondary-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.author {
|
|
||||||
display: flex;
|
|
||||||
margin-top: auto;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: end;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
> * {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.author-name {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.operation {
|
|
||||||
display: flex;
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
right: 12px;
|
|
||||||
font-size: 1.6em;
|
|
||||||
gap: 4px;
|
|
||||||
|
|
||||||
> *:hover {
|
|
||||||
color: constants.$font-secondary-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.load-more-card {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
text-align: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
display: flex;
|
|
||||||
font-size: constants.$SIZE_ICON_XXL;
|
|
||||||
color: constants.$production-color;
|
|
||||||
align-items: center;
|
|
||||||
transform: translateY(-20px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
position: absolute;
|
|
||||||
top: 60%;
|
|
||||||
font-size: 1.2em;
|
|
||||||
font-weight: bolder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-tool {
|
.no-tool {
|
||||||
|
|||||||
1
src/assets/svg/handle.svg
Normal file
1
src/assets/svg/handle.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M7 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 2zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 14zm6-8a2 2 0 1 0-.001-4.001A2 2 0 0 0 13 6zm0 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 14z" /></svg>
|
||||||
|
After Width: | Height: | Size: 325 B |
1
src/assets/svg/receive.svg
Normal file
1
src/assets/svg/receive.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path d="M927.929032 640.065509a31.994541 31.994541 0 0 0-31.994541 31.994541v127.978163c0 52.918971-43.064652 95.983623-95.983623 95.983623h-575.901736c-52.918971 0-95.983623-43.064652-95.983623-95.983623v-127.978163a31.994541 31.994541 0 0 0-63.989082 0v127.978163C64.076427 888.279157 135.872177 960.010918 224.049132 960.010918h575.901736c88.240944 0 159.972705-71.731761 159.972705-159.972705v-127.978163a31.994541 31.994541 0 0 0-31.994541-31.994541z" /><path d="M412.113043 777.386079c18.684812 18.684812 43.256619 28.027218 67.892416 28.027217s49.143615-9.342406 67.892416-28.027217l274.705128-274.705129A31.930552 31.930552 0 0 0 799.950868 448.098263h-159.140846a629.524587 629.524587 0 0 1 44.408423-202.909378L765.652721 44.0712a32.122519 32.122519 0 0 0-7.998636-35.449951 32.122519 32.122519 0 0 0-35.961864-5.055137L566.582687 81.120879A443.124392 443.124392 0 0 0 321.184558 448.098263H160.06005a31.994541 31.994541 0 0 0-22.652135 54.646676l274.705128 274.64114zM352.027295 512.023356a31.994541 31.994541 0 0 0 31.994541-31.994541A379.711212 379.711212 0 0 1 595.185806 138.391107l79.858375-39.929187-49.207604 122.923026A692.7458 692.7458 0 0 0 575.989082 480.092804a31.994541 31.994541 0 0 0 31.994541 31.994541h114.732424l-220.058453 220.058453a31.994541 31.994541 0 0 1-45.240281 0L237.294872 512.087345 352.027295 512.023356z" /></svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
1
src/assets/svg/star.svg
Normal file
1
src/assets/svg/star.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1151 1024"><path d="M1056.069394 342.982943L763.905526 300.38821 633.321676 35.620955c-23.397106-47.194163-91.188723-47.794089-114.785804 0L387.952021 300.38821 95.788154 342.982943c-52.39352 7.59906-73.390924 72.191072-35.395623 109.186496l211.373859 205.974527-49.993817 290.964016c-8.998887 52.593496 46.394262 91.988624 92.788525 67.391666L575.928774 879.116638l261.367676 137.38301c46.394262 24.396983 101.787412-14.79817 92.788525-67.391666l-49.993818-290.964016 211.373859-205.974527c37.995301-36.995425 16.997898-101.587436-35.395622-109.186496zM777.103894 624.548121l47.394139 276.765772L575.928774 770.730042l-248.569259 130.583851 47.394139-276.765772-201.175121-195.975763 277.965624-40.395005 124.384617-251.968838 124.384617 251.968838 277.965623 40.395005-201.17512 195.975763z" /></svg>
|
||||||
|
After Width: | Height: | Size: 856 B |
1
src/assets/svg/starFilled.svg
Normal file
1
src/assets/svg/starFilled.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1151 1024"><path d="M518.535872 35.620955L387.952021 300.38821 95.788154 342.982943c-52.39352 7.59906-73.390924 72.191072-35.395623 109.186496l211.373859 205.974527-49.993817 290.964016c-8.998887 52.593496 46.394262 91.988624 92.788525 67.391666L575.928774 879.116638l261.367676 137.38301c46.394262 24.396983 101.787412-14.79817 92.788525-67.391666l-49.993818-290.964016 211.373859-205.974527c37.995301-36.995425 16.997898-101.587436-35.395622-109.186496L763.905526 300.38821 633.321676 35.620955c-23.397106-47.194163-91.188723-47.794089-114.785804 0z" /></svg>
|
||||||
|
After Width: | Height: | Size: 615 B |
@@ -2,6 +2,6 @@ import { loader } from '@monaco-editor/react'
|
|||||||
|
|
||||||
loader.config({
|
loader.config({
|
||||||
paths: {
|
paths: {
|
||||||
vs: 'https://unpkg.com/monaco-editor@0.45.0/min/vs'
|
vs: 'https://unpkg.com/monaco-editor@0.48.0/min/vs'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -166,7 +166,13 @@ const FileSelector = ({
|
|||||||
<>
|
<>
|
||||||
<div data-component={'playground-file-selector'} className={'tab'}>
|
<div data-component={'playground-file-selector'} className={'tab'}>
|
||||||
<div className={'multiple'}>
|
<div className={'multiple'}>
|
||||||
<HideScrollbar ref={hideScrollbarRef}>
|
<HideScrollbar
|
||||||
|
ref={hideScrollbarRef}
|
||||||
|
autoHideWaitingTime={800}
|
||||||
|
scrollbarWidth={1}
|
||||||
|
scrollbarAsidePadding={0}
|
||||||
|
scrollbarEdgePadding={0}
|
||||||
|
>
|
||||||
<FlexBox direction={'horizontal'} className={'tab-content'}>
|
<FlexBox direction={'horizontal'} className={'tab-content'}>
|
||||||
{tabs.map((item, index) => (
|
{tabs.map((item, index) => (
|
||||||
<Item
|
<Item
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class Compiler {
|
|||||||
void esbuild
|
void esbuild
|
||||||
.initialize({
|
.initialize({
|
||||||
worker: true,
|
worker: true,
|
||||||
wasmURL: 'https://esm.sh/esbuild-wasm@0.19.12/esbuild.wasm'
|
wasmURL: 'https://esm.sh/esbuild-wasm@0.20.2/esbuild.wasm'
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.init = true
|
this.init = true
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ interface HideScrollbarProps
|
|||||||
minHeight?: string | number
|
minHeight?: string | number
|
||||||
scrollbarWidth?: string | number
|
scrollbarWidth?: string | number
|
||||||
autoHideWaitingTime?: number
|
autoHideWaitingTime?: number
|
||||||
|
scrollbarAsidePadding?: number
|
||||||
|
scrollbarEdgePadding?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HideScrollbarElement {
|
export interface HideScrollbarElement {
|
||||||
@@ -74,6 +76,8 @@ const HideScrollbar = forwardRef<HideScrollbarElement, HideScrollbarProps>(
|
|||||||
children,
|
children,
|
||||||
style,
|
style,
|
||||||
className,
|
className,
|
||||||
|
scrollbarAsidePadding = 12,
|
||||||
|
scrollbarEdgePadding = 4,
|
||||||
...props
|
...props
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
@@ -578,7 +582,8 @@ const HideScrollbar = forwardRef<HideScrollbarElement, HideScrollbarProps>(
|
|||||||
? maskRef.current?.clientLeft +
|
? maskRef.current?.clientLeft +
|
||||||
maskRef.current?.clientWidth -
|
maskRef.current?.clientWidth -
|
||||||
1
|
1
|
||||||
: undefined
|
: undefined,
|
||||||
|
padding: `${scrollbarAsidePadding}px ${scrollbarEdgePadding}px`
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className={'box'} style={{ width: scrollbarWidth }}>
|
<div className={'box'} style={{ width: scrollbarWidth }}>
|
||||||
@@ -620,7 +625,8 @@ const HideScrollbar = forwardRef<HideScrollbarElement, HideScrollbarProps>(
|
|||||||
? maskRef.current?.clientTop +
|
? maskRef.current?.clientTop +
|
||||||
maskRef.current?.clientHeight -
|
maskRef.current?.clientHeight -
|
||||||
1
|
1
|
||||||
: undefined
|
: undefined,
|
||||||
|
padding: `${scrollbarEdgePadding}px ${scrollbarAsidePadding}px`
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className={'box'} style={{ height: scrollbarWidth }}>
|
<div className={'box'} style={{ height: scrollbarWidth }}>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import Icon from '@ant-design/icons'
|
|||||||
import { COLOR_ERROR } from '@/constants/common.constants'
|
import { COLOR_ERROR } from '@/constants/common.constants'
|
||||||
import { getRedirectUrl } from '@/util/route'
|
import { getRedirectUrl } from '@/util/route'
|
||||||
import { getAvatar, getLoginStatus, getNickname, removeToken } from '@/util/auth'
|
import { getAvatar, getLoginStatus, getNickname, removeToken } from '@/util/auth'
|
||||||
|
import { navigateToLogin, navigateToUser } from '@/util/navigation'
|
||||||
import { r_auth_logout } from '@/services/auth'
|
import { r_auth_logout } from '@/services/auth'
|
||||||
|
|
||||||
const Footer = () => {
|
const Footer = () => {
|
||||||
@@ -15,9 +16,9 @@ const Footer = () => {
|
|||||||
|
|
||||||
const handleClickAvatar = () => {
|
const handleClickAvatar = () => {
|
||||||
if (getLoginStatus()) {
|
if (getLoginStatus()) {
|
||||||
navigate('/user')
|
navigateToUser(navigate)
|
||||||
} else {
|
} else {
|
||||||
navigate(getRedirectUrl('/login', `${lastMatch.pathname}${location.search}`))
|
navigateToLogin(navigate, undefined, `${lastMatch.pathname}${location.search}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ import Icon from '@ant-design/icons'
|
|||||||
import Submenu from '@/components/common/Sidebar/Submenu'
|
import Submenu from '@/components/common/Sidebar/Submenu'
|
||||||
|
|
||||||
type ItemProps = {
|
type ItemProps = {
|
||||||
icon?: IconComponent
|
icon?: IconComponent | string
|
||||||
text?: string
|
text?: string
|
||||||
path: string
|
path: string
|
||||||
children?: ReactNode
|
children?: ReactNode
|
||||||
|
extend?: ReactNode
|
||||||
end?: boolean
|
end?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,10 +42,21 @@ const Item = (props: ItemProps) => {
|
|||||||
isPending ? 'pending' : isActive ? 'active' : ''
|
isPending ? 'pending' : isActive ? 'active' : ''
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
{props.icon && (
|
||||||
<div className={'icon-box'}>
|
<div className={'icon-box'}>
|
||||||
{props.icon && <Icon className={'icon'} component={props.icon} />}
|
{typeof props.icon === 'string' ? (
|
||||||
|
<img
|
||||||
|
className={'icon'}
|
||||||
|
src={`data:image/svg+xml;base64,${props.icon}`}
|
||||||
|
alt={'icon'}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Icon className={'icon'} component={props.icon} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
<span className={'text'}>{props.text}</span>
|
<span className={'text'}>{props.text}</span>
|
||||||
|
<div className={'extend'}>{props.extend}</div>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
{props.children && (
|
{props.children && (
|
||||||
|
|||||||
57
src/components/common/UrlCard.tsx
Normal file
57
src/components/common/UrlCard.tsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { DetailedHTMLProps, HTMLAttributes, ReactNode } from 'react'
|
||||||
|
import Icon from '@ant-design/icons'
|
||||||
|
import VanillaTilt, { TiltOptions } from 'vanilla-tilt'
|
||||||
|
import '@/assets/css/components/common/url-card.scss'
|
||||||
|
import Card from '@/components/common/Card'
|
||||||
|
import FlexBox from '@/components/common/FlexBox'
|
||||||
|
|
||||||
|
interface UrlCardProps extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
|
||||||
|
icon: IconComponent
|
||||||
|
description?: ReactNode
|
||||||
|
options?: TiltOptions
|
||||||
|
url?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const UrlCard = ({
|
||||||
|
style,
|
||||||
|
icon,
|
||||||
|
description,
|
||||||
|
options = {
|
||||||
|
reverse: true,
|
||||||
|
max: 8,
|
||||||
|
glare: true,
|
||||||
|
scale: 1.03
|
||||||
|
},
|
||||||
|
url,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: UrlCardProps) => {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const cardRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
cardRef.current && VanillaTilt.init(cardRef.current, options)
|
||||||
|
}, [options])
|
||||||
|
|
||||||
|
const handleCardOnClick = () => {
|
||||||
|
url && navigate(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
data-component={'component-url-card'}
|
||||||
|
style={{ overflow: 'visible', ...style }}
|
||||||
|
{...props}
|
||||||
|
ref={cardRef}
|
||||||
|
onClick={handleCardOnClick}
|
||||||
|
>
|
||||||
|
<FlexBox className={'url-card'}>
|
||||||
|
<Icon component={icon} className={'icon'} />
|
||||||
|
<div className={'text'}>{children}</div>
|
||||||
|
<div className={'description'}>{description}</div>
|
||||||
|
</FlexBox>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UrlCard
|
||||||
27
src/components/dnd/DragHandle.tsx
Normal file
27
src/components/dnd/DragHandle.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { HandleContextInst } from '@/components/dnd/HandleContext'
|
||||||
|
import Icon from '@ant-design/icons'
|
||||||
|
import '@/assets/css/components/dnd/drag-handle.scss'
|
||||||
|
|
||||||
|
interface DragHandleProps {
|
||||||
|
padding?: string | number
|
||||||
|
}
|
||||||
|
|
||||||
|
const DragHandle = ({ padding }: DragHandleProps) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||||
|
const { attributes, listeners, ref } = useContext(HandleContextInst)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
data-component={'component-drag-handle'}
|
||||||
|
style={{ padding }}
|
||||||
|
ref={ref}
|
||||||
|
className={'drag-handle'}
|
||||||
|
{...attributes}
|
||||||
|
{...listeners}
|
||||||
|
>
|
||||||
|
<Icon component={IconOxygenHandle} />
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DragHandle
|
||||||
47
src/components/dnd/Draggable.tsx
Normal file
47
src/components/dnd/Draggable.tsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { CSSProperties, PropsWithChildren } from 'react'
|
||||||
|
import { useDraggable } from '@dnd-kit/core'
|
||||||
|
import { HandleContext, HandleContextInst } from '@/components/dnd/HandleContext'
|
||||||
|
|
||||||
|
interface DraggableProps extends PropsWithChildren {
|
||||||
|
id: string
|
||||||
|
data: ToolMenuItem
|
||||||
|
}
|
||||||
|
|
||||||
|
const Draggable = ({ id, data, children }: DraggableProps) => {
|
||||||
|
const {
|
||||||
|
attributes,
|
||||||
|
isDragging,
|
||||||
|
listeners,
|
||||||
|
setNodeRef: draggableRef,
|
||||||
|
setActivatorNodeRef,
|
||||||
|
transform
|
||||||
|
} = useDraggable({
|
||||||
|
id,
|
||||||
|
data
|
||||||
|
})
|
||||||
|
const context = useMemo<HandleContext>(
|
||||||
|
() => ({
|
||||||
|
attributes,
|
||||||
|
listeners,
|
||||||
|
ref: setActivatorNodeRef
|
||||||
|
}),
|
||||||
|
[attributes, listeners, setActivatorNodeRef]
|
||||||
|
)
|
||||||
|
const style: CSSProperties | undefined = transform
|
||||||
|
? {
|
||||||
|
opacity: isDragging ? 0 : undefined,
|
||||||
|
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
|
||||||
|
zIndex: 10000
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HandleContextInst.Provider value={context}>
|
||||||
|
<div ref={draggableRef} style={style}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</HandleContextInst.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Draggable
|
||||||
22
src/components/dnd/DraggableOverlay.tsx
Normal file
22
src/components/dnd/DraggableOverlay.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { PropsWithChildren } from 'react'
|
||||||
|
import { defaultDropAnimationSideEffects, DragOverlay, DropAnimation } from '@dnd-kit/core'
|
||||||
|
|
||||||
|
interface DraggableOverlayProps extends PropsWithChildren {
|
||||||
|
isDelete?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const dropAnimationConfig: DropAnimation = {
|
||||||
|
sideEffects: defaultDropAnimationSideEffects({
|
||||||
|
styles: {
|
||||||
|
active: {
|
||||||
|
opacity: '0.4'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const DraggableOverlay = ({ children }: DraggableOverlayProps) => {
|
||||||
|
return <DragOverlay dropAnimation={dropAnimationConfig}>{children}</DragOverlay>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DraggableOverlay
|
||||||
14
src/components/dnd/DropMask.tsx
Normal file
14
src/components/dnd/DropMask.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import '@/assets/css/components/dnd/drop-mask.scss'
|
||||||
|
import Icon from '@ant-design/icons'
|
||||||
|
|
||||||
|
const DropMask = () => {
|
||||||
|
return (
|
||||||
|
<div data-component={'component-drop-mask'}>
|
||||||
|
<div className={'drop-mask-border'}>
|
||||||
|
<Icon component={IconOxygenReceive} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DropMask
|
||||||
16
src/components/dnd/Droppable.tsx
Normal file
16
src/components/dnd/Droppable.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { DetailedHTMLProps, HTMLAttributes } from 'react'
|
||||||
|
import { useDroppable } from '@dnd-kit/core'
|
||||||
|
|
||||||
|
interface DroppableProps extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Droppable = ({ id, ...props }: DroppableProps) => {
|
||||||
|
const { setNodeRef: droppableRef } = useDroppable({
|
||||||
|
id
|
||||||
|
})
|
||||||
|
|
||||||
|
return <div {...props} ref={droppableRef} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Droppable
|
||||||
13
src/components/dnd/HandleContext.ts
Normal file
13
src/components/dnd/HandleContext.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { DraggableSyntheticListeners } from '@dnd-kit/core'
|
||||||
|
|
||||||
|
export interface HandleContext {
|
||||||
|
attributes: Record<string, any>
|
||||||
|
listeners: DraggableSyntheticListeners
|
||||||
|
ref(node: HTMLElement | null): void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HandleContextInst = createContext<HandleContext>({
|
||||||
|
attributes: {},
|
||||||
|
listeners: undefined,
|
||||||
|
ref() {}
|
||||||
|
})
|
||||||
54
src/components/dnd/Sortable.tsx
Normal file
54
src/components/dnd/Sortable.tsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { CSSProperties, PropsWithChildren } from 'react'
|
||||||
|
import { useSortable } from '@dnd-kit/sortable'
|
||||||
|
import { HandleContext, HandleContextInst } from '@/components/dnd/HandleContext'
|
||||||
|
|
||||||
|
interface SortableProps extends PropsWithChildren {
|
||||||
|
id: string
|
||||||
|
data: ToolMenuItem
|
||||||
|
isDelete?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const Sortable = ({ id, data, isDelete, children }: SortableProps) => {
|
||||||
|
const {
|
||||||
|
attributes,
|
||||||
|
isDragging,
|
||||||
|
listeners,
|
||||||
|
setNodeRef: draggableRef,
|
||||||
|
setActivatorNodeRef,
|
||||||
|
transform,
|
||||||
|
transition
|
||||||
|
} = useSortable({
|
||||||
|
id,
|
||||||
|
data
|
||||||
|
})
|
||||||
|
const context = useMemo<HandleContext>(
|
||||||
|
() => ({
|
||||||
|
attributes,
|
||||||
|
listeners,
|
||||||
|
ref: setActivatorNodeRef
|
||||||
|
}),
|
||||||
|
[attributes, listeners, setActivatorNodeRef]
|
||||||
|
)
|
||||||
|
const style: CSSProperties | undefined = transform
|
||||||
|
? {
|
||||||
|
opacity: isDragging ? 0.4 : undefined,
|
||||||
|
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
|
||||||
|
zIndex: 10000,
|
||||||
|
transition
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HandleContextInst.Provider value={context}>
|
||||||
|
<div
|
||||||
|
ref={draggableRef}
|
||||||
|
style={style}
|
||||||
|
className={isDragging && isDelete ? 'delete' : undefined}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</HandleContextInst.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Sortable
|
||||||
48
src/components/system/SettingCard.tsx
Normal file
48
src/components/system/SettingCard.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { PropsWithChildren, ReactNode } from 'react'
|
||||||
|
import Icon from '@ant-design/icons'
|
||||||
|
import '@/assets/css/components/system/setting-card.scss'
|
||||||
|
import Card from '@/components/common/Card'
|
||||||
|
import FlexBox from '@/components/common/FlexBox'
|
||||||
|
import Permission from '@/components/common/Permission'
|
||||||
|
import LoadingMask from '@/components/common/LoadingMask'
|
||||||
|
|
||||||
|
interface SettingsCardProps extends PropsWithChildren {
|
||||||
|
icon: IconComponent
|
||||||
|
title: string
|
||||||
|
loading?: boolean
|
||||||
|
modifyOperationCode?: string[]
|
||||||
|
expand?: ReactNode
|
||||||
|
onReset?: () => void
|
||||||
|
onSave?: () => void
|
||||||
|
}
|
||||||
|
export const SettingsCard = (props: SettingsCardProps) => {
|
||||||
|
return (
|
||||||
|
<Card data-component={'component-setting-card'}>
|
||||||
|
<FlexBox className={'settings-card'}>
|
||||||
|
<FlexBox direction={'horizontal'} className={'head'}>
|
||||||
|
<Icon component={props.icon} className={'icon'} />
|
||||||
|
<div className={'title'}>{props.title}</div>
|
||||||
|
{!props.loading && (
|
||||||
|
<Permission operationCode={props.modifyOperationCode}>
|
||||||
|
{props.expand}
|
||||||
|
<AntdButton onClick={props.onReset} title={'重置'}>
|
||||||
|
<Icon component={IconOxygenBack} />
|
||||||
|
</AntdButton>
|
||||||
|
<AntdButton className={'bt-save'} onClick={props.onSave} title={'保存'}>
|
||||||
|
<Icon component={IconOxygenSave} />
|
||||||
|
</AntdButton>
|
||||||
|
</Permission>
|
||||||
|
)}
|
||||||
|
</FlexBox>
|
||||||
|
<LoadingMask
|
||||||
|
maskContent={<AntdSkeleton active paragraph={{ rows: 6 }} />}
|
||||||
|
hidden={!props.loading}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</LoadingMask>
|
||||||
|
</FlexBox>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SettingsCard
|
||||||
35
src/components/system/StatisticsCard.tsx
Normal file
35
src/components/system/StatisticsCard.tsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { PropsWithChildren, ReactNode } from 'react'
|
||||||
|
import Icon from '@ant-design/icons'
|
||||||
|
import '@/assets/css/components/system/statistics-card.scss'
|
||||||
|
import Card from '@/components/common/Card'
|
||||||
|
import FlexBox from '@/components/common/FlexBox'
|
||||||
|
import LoadingMask from '@/components/common/LoadingMask'
|
||||||
|
|
||||||
|
interface StatisticsCardProps extends PropsWithChildren {
|
||||||
|
icon: IconComponent
|
||||||
|
title: ReactNode
|
||||||
|
loading?: boolean
|
||||||
|
expand?: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StatisticsCard = (props: StatisticsCardProps) => {
|
||||||
|
return (
|
||||||
|
<Card data-component={'component-statistics-card'} style={{ overflow: 'visible' }}>
|
||||||
|
<FlexBox className={'statistics-card'}>
|
||||||
|
<FlexBox direction={'horizontal'} className={'head'}>
|
||||||
|
<Icon component={props.icon} className={'icon'} />
|
||||||
|
<div className={'title'}>{props.title}</div>
|
||||||
|
{props.expand}
|
||||||
|
</FlexBox>
|
||||||
|
<LoadingMask
|
||||||
|
hidden={!props.loading}
|
||||||
|
maskContent={<AntdSkeleton active paragraph={{ rows: 6 }} />}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</LoadingMask>
|
||||||
|
</FlexBox>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StatisticsCard
|
||||||
42
src/components/tools/LoadMoreCard.tsx
Normal file
42
src/components/tools/LoadMoreCard.tsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import VanillaTilt from 'vanilla-tilt'
|
||||||
|
import Icon from '@ant-design/icons'
|
||||||
|
import '@/assets/css/components/tools/load-more-card.scss'
|
||||||
|
import FlexBox from '@/components/common/FlexBox'
|
||||||
|
import Card from '@/components/common/Card'
|
||||||
|
|
||||||
|
interface LoadMoreCardProps {
|
||||||
|
onClick: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const LoadMoreCard = ({ onClick }: LoadMoreCardProps) => {
|
||||||
|
const cardRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
cardRef.current &&
|
||||||
|
VanillaTilt.init(cardRef.current, {
|
||||||
|
reverse: true,
|
||||||
|
max: 8,
|
||||||
|
glare: true,
|
||||||
|
['max-glare']: 0.3,
|
||||||
|
scale: 1.03
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
data-component={'component-load-more-card'}
|
||||||
|
style={{ overflow: 'visible' }}
|
||||||
|
ref={cardRef}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<FlexBox className={'load-more-card'}>
|
||||||
|
<div className={'icon'}>
|
||||||
|
<Icon component={IconOxygenMore} />{' '}
|
||||||
|
</div>
|
||||||
|
<div className={'text'}>加载更多</div>
|
||||||
|
</FlexBox>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LoadMoreCard
|
||||||
115
src/components/tools/RepositoryCard.tsx
Normal file
115
src/components/tools/RepositoryCard.tsx
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import { DetailedHTMLProps, HTMLAttributes } from 'react'
|
||||||
|
import VanillaTilt, { TiltOptions } from 'vanilla-tilt'
|
||||||
|
import '@/assets/css/components/tools/repository-card.scss'
|
||||||
|
import Card from '@/components/common/Card'
|
||||||
|
import FlexBox from '@/components/common/FlexBox'
|
||||||
|
import Draggable from '@/components/dnd/Draggable'
|
||||||
|
import DragHandle from '@/components/dnd/DragHandle'
|
||||||
|
|
||||||
|
interface RepositoryCardProps
|
||||||
|
extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
|
||||||
|
icon: string
|
||||||
|
toolName: string
|
||||||
|
toolId: string
|
||||||
|
ver: string
|
||||||
|
platform: Platform
|
||||||
|
options?: TiltOptions
|
||||||
|
onOpen?: () => void
|
||||||
|
onEdit?: () => void
|
||||||
|
onSource?: () => void
|
||||||
|
onPublish?: () => void
|
||||||
|
onCancelReview?: () => void
|
||||||
|
onDelete?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const RepositoryCard = ({
|
||||||
|
style,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
ref,
|
||||||
|
icon,
|
||||||
|
toolName,
|
||||||
|
toolId,
|
||||||
|
ver,
|
||||||
|
platform,
|
||||||
|
options = {
|
||||||
|
reverse: true,
|
||||||
|
max: 8,
|
||||||
|
glare: true,
|
||||||
|
['max-glare']: 0.3,
|
||||||
|
scale: 1.03
|
||||||
|
},
|
||||||
|
onOpen,
|
||||||
|
onEdit,
|
||||||
|
onSource,
|
||||||
|
onPublish,
|
||||||
|
onCancelReview,
|
||||||
|
onDelete,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: RepositoryCardProps) => {
|
||||||
|
const cardRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
cardRef.current && VanillaTilt.init(cardRef.current, options)
|
||||||
|
}, [options])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Draggable
|
||||||
|
id={`!:${toolId}:${ver}:${platform}`}
|
||||||
|
data={{ icon, toolName, toolId, authorUsername: '!', ver, platform }}
|
||||||
|
>
|
||||||
|
<Card
|
||||||
|
data-component={'component-repository-card'}
|
||||||
|
style={{ overflow: 'visible', ...style }}
|
||||||
|
ref={cardRef}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<FlexBox className={'repository-card'}>
|
||||||
|
<div className={'header'}>
|
||||||
|
{children}
|
||||||
|
<DragHandle />
|
||||||
|
</div>
|
||||||
|
<div className={'icon'}>
|
||||||
|
<img src={`data:image/svg+xml;base64,${icon}`} alt={'Icon'} />
|
||||||
|
</div>
|
||||||
|
<div className={'info'}>
|
||||||
|
<div className={'tool-name'}>{toolName}</div>
|
||||||
|
<div className={'tool-id'}>{`ID: ${toolId}`}</div>
|
||||||
|
</div>
|
||||||
|
<div className={'operation'}>
|
||||||
|
{onOpen && (
|
||||||
|
<AntdButton onClick={onOpen} size={'small'} type={'primary'}>
|
||||||
|
打开
|
||||||
|
</AntdButton>
|
||||||
|
)}
|
||||||
|
{onEdit && onPublish && (
|
||||||
|
<div className={'edit'}>
|
||||||
|
<AntdButton.Group size={'small'}>
|
||||||
|
<AntdButton onClick={onEdit}>编辑</AntdButton>
|
||||||
|
<AntdButton onClick={onPublish}>发布</AntdButton>
|
||||||
|
</AntdButton.Group>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{onSource && (
|
||||||
|
<AntdButton size={'small'} onClick={onSource}>
|
||||||
|
源码
|
||||||
|
</AntdButton>
|
||||||
|
)}
|
||||||
|
{onCancelReview && (
|
||||||
|
<AntdButton size={'small'} onClick={onCancelReview}>
|
||||||
|
取消审核
|
||||||
|
</AntdButton>
|
||||||
|
)}
|
||||||
|
{onDelete && (
|
||||||
|
<AntdButton size={'small'} danger onClick={onDelete}>
|
||||||
|
删除
|
||||||
|
</AntdButton>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</FlexBox>
|
||||||
|
</Card>
|
||||||
|
</Draggable>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RepositoryCard
|
||||||
312
src/components/tools/StoreCard.tsx
Normal file
312
src/components/tools/StoreCard.tsx
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
import { DetailedHTMLProps, HTMLAttributes, MouseEvent } from 'react'
|
||||||
|
import VanillaTilt, { TiltOptions } from 'vanilla-tilt'
|
||||||
|
import protocolCheck from 'custom-protocol-check'
|
||||||
|
import Icon from '@ant-design/icons'
|
||||||
|
import '@/assets/css/components/tools/store-card.scss'
|
||||||
|
import { COLOR_BACKGROUND, COLOR_MAIN, COLOR_PRODUCTION } from '@/constants/common.constants'
|
||||||
|
import { checkDesktop, omitText } from '@/util/common'
|
||||||
|
import { getLoginStatus, getUserId } from '@/util/auth'
|
||||||
|
import {
|
||||||
|
navigateToLogin,
|
||||||
|
navigateToSource,
|
||||||
|
navigateToStore,
|
||||||
|
navigateToView
|
||||||
|
} from '@/util/navigation'
|
||||||
|
import { r_tool_add_favorite, r_tool_remove_favorite } from '@/services/tool'
|
||||||
|
import Card from '@/components/common/Card'
|
||||||
|
import FlexBox from '@/components/common/FlexBox'
|
||||||
|
import DragHandle from '@/components/dnd/DragHandle'
|
||||||
|
import Draggable from '@/components/dnd/Draggable'
|
||||||
|
|
||||||
|
interface StoreCardProps extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
|
||||||
|
icon: string
|
||||||
|
toolName: string
|
||||||
|
toolId: string
|
||||||
|
toolDesc: string
|
||||||
|
options?: TiltOptions
|
||||||
|
author: UserWithInfoVo
|
||||||
|
showAuthor?: boolean
|
||||||
|
ver: string
|
||||||
|
platform: Platform
|
||||||
|
supportPlatform: Platform[]
|
||||||
|
favorite: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const StoreCard = ({
|
||||||
|
style,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
ref,
|
||||||
|
icon,
|
||||||
|
toolName,
|
||||||
|
toolId,
|
||||||
|
toolDesc,
|
||||||
|
options = {
|
||||||
|
reverse: true,
|
||||||
|
max: 8,
|
||||||
|
glare: true,
|
||||||
|
['max-glare']: 0.3,
|
||||||
|
scale: 1.03
|
||||||
|
},
|
||||||
|
author,
|
||||||
|
showAuthor = true,
|
||||||
|
ver,
|
||||||
|
platform,
|
||||||
|
supportPlatform,
|
||||||
|
favorite,
|
||||||
|
...props
|
||||||
|
}: StoreCardProps) => {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const [modal, contextHolder] = AntdModal.useModal()
|
||||||
|
const cardRef = useRef<HTMLDivElement>(null)
|
||||||
|
const [favorite_, setFavorite_] = useState<boolean>(favorite)
|
||||||
|
const [userId, setUserId] = useState('')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
cardRef.current && VanillaTilt.init(cardRef.current, options)
|
||||||
|
if (getLoginStatus()) {
|
||||||
|
void getUserId().then((value) => setUserId(value))
|
||||||
|
}
|
||||||
|
}, [options])
|
||||||
|
|
||||||
|
const handleCardOnClick = () => {
|
||||||
|
if (!checkDesktop() && platform === 'DESKTOP') {
|
||||||
|
void message.warning('此应用需要桌面端环境,请在桌面端打开')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (platform === 'ANDROID') {
|
||||||
|
void modal.confirm({
|
||||||
|
icon: <Icon style={{ color: COLOR_MAIN }} component={IconOxygenInfo} />,
|
||||||
|
title: 'Android 端',
|
||||||
|
centered: true,
|
||||||
|
maskClosable: true,
|
||||||
|
content: (
|
||||||
|
<FlexBox className={'android-qrcode'}>
|
||||||
|
<AntdQRCode
|
||||||
|
value={`oxygen://openurl/view/${author.username}/${toolId}`}
|
||||||
|
size={300}
|
||||||
|
/>
|
||||||
|
<AntdTag className={'tag'}>请使用手机端扫描上方二维码</AntdTag>
|
||||||
|
</FlexBox>
|
||||||
|
),
|
||||||
|
okText: '确定',
|
||||||
|
cancelText: '模拟器',
|
||||||
|
onCancel() {
|
||||||
|
navigateToView(navigate, author.username, toolId, platform)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
navigateToView(navigate, author.username, toolId, platform)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOnClickAuthor = (e: MouseEvent<HTMLDivElement>) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
navigateToStore(navigate, author.username)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOnSourceBtnClick = (e: MouseEvent<HTMLDivElement>) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
navigateToSource(navigate, author.username, toolId, platform)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOnStarBtnClick = (e: MouseEvent<HTMLDivElement>) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
if (!getLoginStatus()) {
|
||||||
|
navigateToLogin(navigate, undefined, `${location.pathname}${location.search}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (favorite_) {
|
||||||
|
void r_tool_remove_favorite({
|
||||||
|
authorId: author.id,
|
||||||
|
toolId: toolId,
|
||||||
|
platform: platform
|
||||||
|
}).then((res) => {
|
||||||
|
const response = res.data
|
||||||
|
if (response.success) {
|
||||||
|
setFavorite_(false)
|
||||||
|
} else {
|
||||||
|
void message.error('取消收藏失败,请稍后重试')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
void r_tool_add_favorite({
|
||||||
|
authorId: author.id,
|
||||||
|
toolId: toolId,
|
||||||
|
platform: platform
|
||||||
|
}).then((res) => {
|
||||||
|
const response = res.data
|
||||||
|
if (response.success) {
|
||||||
|
setFavorite_(true)
|
||||||
|
} else {
|
||||||
|
void message.error('收藏失败,请稍后重试')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOnAndroidBtnClick = (e: MouseEvent<HTMLDivElement>) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
void modal.confirm({
|
||||||
|
icon: <Icon style={{ color: COLOR_MAIN }} component={IconOxygenInfo} />,
|
||||||
|
title: 'Android 端',
|
||||||
|
centered: true,
|
||||||
|
maskClosable: true,
|
||||||
|
content: (
|
||||||
|
<FlexBox className={'android-qrcode'}>
|
||||||
|
<AntdQRCode
|
||||||
|
value={`oxygen://openurl/view/${author.username}/${toolId}`}
|
||||||
|
size={300}
|
||||||
|
/>
|
||||||
|
<AntdTag className={'tag'}>请使用手机端扫描上方二维码</AntdTag>
|
||||||
|
</FlexBox>
|
||||||
|
),
|
||||||
|
okText: '确定',
|
||||||
|
cancelText: '模拟器',
|
||||||
|
onCancel() {
|
||||||
|
navigateToView(navigate, author.username, toolId, 'ANDROID')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOnDesktopBtnClick = (e: MouseEvent<HTMLDivElement>) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
if (!checkDesktop()) {
|
||||||
|
void message.loading({ content: '启动桌面端中……', key: 'LOADING', duration: 0 })
|
||||||
|
protocolCheck(
|
||||||
|
`oxygen://openurl/view/${author.username}/${toolId}`,
|
||||||
|
() => {
|
||||||
|
void message.warning('打开失败,此应用需要桌面端环境,请安装桌面端后重试')
|
||||||
|
void message.destroy('LOADING')
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
void message.destroy('LOADING')
|
||||||
|
},
|
||||||
|
2000,
|
||||||
|
() => {
|
||||||
|
void message.warning('打开失败,此应用需要桌面端环境,请安装桌面端后重试')
|
||||||
|
void message.destroy('LOADING')
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
navigateToView(navigate, author.username, toolId, 'DESKTOP')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOnWebBtnClick = (e: MouseEvent<HTMLDivElement>) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
navigateToView(navigate, author.username, toolId, 'WEB')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Draggable
|
||||||
|
id={`${author.username}:${toolId}:${ver}:${platform}`}
|
||||||
|
data={{
|
||||||
|
icon,
|
||||||
|
toolName,
|
||||||
|
toolId,
|
||||||
|
authorUsername: author.username,
|
||||||
|
ver: '',
|
||||||
|
platform: platform
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Card
|
||||||
|
data-component={'component-store-card'}
|
||||||
|
style={{ overflow: 'visible', ...style }}
|
||||||
|
ref={cardRef}
|
||||||
|
{...props}
|
||||||
|
onClick={handleCardOnClick}
|
||||||
|
>
|
||||||
|
<FlexBox className={'store-card'}>
|
||||||
|
<div className={'header'}>
|
||||||
|
<div className={'version'}>
|
||||||
|
<AntdTag>
|
||||||
|
{platform.slice(0, 1)}-{ver}
|
||||||
|
</AntdTag>
|
||||||
|
</div>
|
||||||
|
<div className={'operation'}>
|
||||||
|
{platform !== 'ANDROID' && supportPlatform.includes('ANDROID') && (
|
||||||
|
<AntdTooltip title={'Android 端'}>
|
||||||
|
<Icon
|
||||||
|
component={IconOxygenMobile}
|
||||||
|
onClick={handleOnAndroidBtnClick}
|
||||||
|
/>
|
||||||
|
</AntdTooltip>
|
||||||
|
)}
|
||||||
|
{platform === 'DESKTOP' && supportPlatform.includes('WEB') && (
|
||||||
|
<AntdTooltip title={'Web 端'}>
|
||||||
|
<Icon
|
||||||
|
component={IconOxygenBrowser}
|
||||||
|
onClick={handleOnWebBtnClick}
|
||||||
|
/>
|
||||||
|
</AntdTooltip>
|
||||||
|
)}
|
||||||
|
{platform === 'WEB' && supportPlatform.includes('DESKTOP') && (
|
||||||
|
<AntdTooltip title={'桌面端'}>
|
||||||
|
<Icon
|
||||||
|
component={IconOxygenDesktop}
|
||||||
|
onClick={handleOnDesktopBtnClick}
|
||||||
|
/>
|
||||||
|
</AntdTooltip>
|
||||||
|
)}
|
||||||
|
<AntdTooltip title={'源码'}>
|
||||||
|
<Icon
|
||||||
|
component={IconOxygenCode}
|
||||||
|
onClick={handleOnSourceBtnClick}
|
||||||
|
/>
|
||||||
|
</AntdTooltip>
|
||||||
|
{author.id !== userId && (
|
||||||
|
<AntdTooltip title={favorite_ ? '取消收藏' : '收藏'}>
|
||||||
|
<Icon
|
||||||
|
component={
|
||||||
|
favorite_ ? IconOxygenStarFilled : IconOxygenStar
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
color: favorite_ ? COLOR_PRODUCTION : undefined
|
||||||
|
}}
|
||||||
|
onClick={handleOnStarBtnClick}
|
||||||
|
/>
|
||||||
|
</AntdTooltip>
|
||||||
|
)}
|
||||||
|
<DragHandle />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={'icon'}>
|
||||||
|
<img src={`data:image/svg+xml;base64,${icon}`} alt={'Icon'} />
|
||||||
|
</div>
|
||||||
|
<div className={'info'}>
|
||||||
|
<div className={'tool-name'}>{toolName}</div>
|
||||||
|
<div className={'tool-id'}>{`ID: ${toolId}`}</div>
|
||||||
|
{toolDesc && (
|
||||||
|
<div
|
||||||
|
className={'tool-desc'}
|
||||||
|
title={toolDesc}
|
||||||
|
>{`简介:${omitText(toolDesc, 18)}`}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{showAuthor && (
|
||||||
|
<div className={'author'} onClick={handleOnClickAuthor}>
|
||||||
|
<div className={'avatar'}>
|
||||||
|
<AntdAvatar
|
||||||
|
src={
|
||||||
|
<AntdImage
|
||||||
|
preview={false}
|
||||||
|
src={`data:image/png;base64,${author.userInfo.avatar}`}
|
||||||
|
alt={'Avatar'}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
style={{ background: COLOR_BACKGROUND }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={'author-name'}>{author.userInfo.nickname}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</FlexBox>
|
||||||
|
</Card>
|
||||||
|
</Draggable>
|
||||||
|
{contextHolder}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StoreCard
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
export const PRODUCTION_NAME = 'Oxygen Toolbox'
|
export const PRODUCTION_NAME = 'Oxygen Toolbox'
|
||||||
export const STORAGE_TOKEN_KEY = 'JWT_TOKEN'
|
export const STORAGE_TOKEN_KEY = 'JWT_TOKEN'
|
||||||
export const STORAGE_USER_INFO_KEY = 'USER_INFO'
|
export const STORAGE_USER_INFO_KEY = 'USER_INFO'
|
||||||
|
export const STORAGE_TOOL_MENU_ITEM_KEY = 'TOOL_MENU_ITEM'
|
||||||
export const COLOR_ORIGIN = 'white'
|
export const COLOR_ORIGIN = 'white'
|
||||||
export const COLOR_PRODUCTION = '#4E47BB'
|
export const COLOR_PRODUCTION = '#4E47BB'
|
||||||
export const COLOR_MAIN = COLOR_PRODUCTION
|
export const COLOR_MAIN = COLOR_PRODUCTION
|
||||||
@@ -77,6 +78,7 @@ export const DATABASE_DELETE_FILED = 30035
|
|||||||
export const DATABASE_EXECUTE_ERROR = 30050
|
export const DATABASE_EXECUTE_ERROR = 30050
|
||||||
export const DATABASE_DUPLICATE_KEY = 30051
|
export const DATABASE_DUPLICATE_KEY = 30051
|
||||||
export const DATABASE_NO_RECORD_FOUND = 30052
|
export const DATABASE_NO_RECORD_FOUND = 30052
|
||||||
|
export const DATABASE_RECORD_ALREADY_EXISTS = 30053
|
||||||
|
|
||||||
export const TOOL_SUBMIT_SUCCESS = 40010
|
export const TOOL_SUBMIT_SUCCESS = 40010
|
||||||
export const TOOL_CANCEL_SUCCESS = 40011
|
export const TOOL_CANCEL_SUCCESS = 40011
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export const URL_TOOL_STORE = `${URL_TOOL}/store`
|
|||||||
export const URL_TOOL_TEMPLATE = `${URL_TOOL}/template`
|
export const URL_TOOL_TEMPLATE = `${URL_TOOL}/template`
|
||||||
export const URL_TOOL_CATEGORY = `${URL_TOOL}/category`
|
export const URL_TOOL_CATEGORY = `${URL_TOOL}/category`
|
||||||
export const URL_TOOL_DETAIL = `${URL_TOOL}/detail`
|
export const URL_TOOL_DETAIL = `${URL_TOOL}/detail`
|
||||||
|
export const URL_TOOL_FAVORITE = `${URL_TOOL_STORE}/favorite`
|
||||||
|
|
||||||
export const URL_API_V1 = '/api/v1'
|
export const URL_API_V1 = '/api/v1'
|
||||||
export const URL_API_V1_AVATAR_RANDOM_BASE64 = `${URL_API_V1}/avatar/base64`
|
export const URL_API_V1_AVATAR_RANDOM_BASE64 = `${URL_API_V1}/avatar/base64`
|
||||||
|
|||||||
16
src/global.d.ts
vendored
16
src/global.d.ts
vendored
@@ -604,6 +604,7 @@ interface ToolVo {
|
|||||||
review: 'NONE' | 'PROCESSING' | 'PASS' | 'REJECT'
|
review: 'NONE' | 'PROCESSING' | 'PASS' | 'REJECT'
|
||||||
createTime: string
|
createTime: string
|
||||||
updateTime: string
|
updateTime: string
|
||||||
|
favorite: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ToolCreateParam {
|
interface ToolCreateParam {
|
||||||
@@ -647,3 +648,18 @@ interface ToolManagementPassParam {
|
|||||||
interface ToolStoreGetParam extends PageParam {
|
interface ToolStoreGetParam extends PageParam {
|
||||||
searchValue?: string
|
searchValue?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ToolFavoriteAddRemoveParam {
|
||||||
|
authorId: string
|
||||||
|
toolId: string
|
||||||
|
platform: Platform
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ToolMenuItem {
|
||||||
|
icon: string
|
||||||
|
toolName: string
|
||||||
|
toolId: string
|
||||||
|
authorUsername: string
|
||||||
|
ver: string
|
||||||
|
platform: Platform
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
PERMISSION_USER_NOT_FOUND,
|
PERMISSION_USER_NOT_FOUND,
|
||||||
SYSTEM_INVALID_CAPTCHA_CODE
|
SYSTEM_INVALID_CAPTCHA_CODE
|
||||||
} from '@/constants/common.constants'
|
} from '@/constants/common.constants'
|
||||||
|
import { navigateToLogin } from '@/util/navigation'
|
||||||
import { r_auth_forget, r_auth_retrieve } from '@/services/auth'
|
import { r_auth_forget, r_auth_retrieve } from '@/services/auth'
|
||||||
import FitCenter from '@/components/common/FitCenter'
|
import FitCenter from '@/components/common/FitCenter'
|
||||||
import FlexBox from '@/components/common/FlexBox'
|
import FlexBox from '@/components/common/FlexBox'
|
||||||
@@ -272,7 +273,15 @@ const Forget = () => {
|
|||||||
|
|
||||||
<div className={'footer'}>
|
<div className={'footer'}>
|
||||||
找到了?
|
找到了?
|
||||||
<a onClick={() => navigate(`/login`, { replace: true })}>登录</a>
|
<a
|
||||||
|
onClick={() =>
|
||||||
|
navigateToLogin(navigate, location.search, undefined, {
|
||||||
|
replace: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
登录
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</FlexBox>
|
</FlexBox>
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ import {
|
|||||||
} from '@/constants/common.constants'
|
} from '@/constants/common.constants'
|
||||||
import { getUserInfo, setToken } from '@/util/auth'
|
import { getUserInfo, setToken } from '@/util/auth'
|
||||||
import { utcToLocalTime } from '@/util/datetime'
|
import { utcToLocalTime } from '@/util/datetime'
|
||||||
|
import {
|
||||||
|
navigateToForget,
|
||||||
|
navigateToRedirect,
|
||||||
|
navigateToRegister,
|
||||||
|
navigateToRoot
|
||||||
|
} from '@/util/navigation'
|
||||||
import { r_auth_login } from '@/services/auth'
|
import { r_auth_login } from '@/services/auth'
|
||||||
import { AppContext } from '@/App'
|
import { AppContext } from '@/App'
|
||||||
import FitCenter from '@/components/common/FitCenter'
|
import FitCenter from '@/components/common/FitCenter'
|
||||||
@@ -78,11 +84,7 @@ const SignIn = () => {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
void getUserInfo().then((user) => {
|
void getUserInfo().then((user) => {
|
||||||
refreshRouter()
|
refreshRouter()
|
||||||
if (searchParams.has('redirect')) {
|
navigateToRedirect(navigate, searchParams, '/repository')
|
||||||
navigate(searchParams.get('redirect') ?? '/')
|
|
||||||
} else {
|
|
||||||
navigate('/')
|
|
||||||
}
|
|
||||||
|
|
||||||
notification.success({
|
notification.success({
|
||||||
message: '欢迎回来',
|
message: '欢迎回来',
|
||||||
@@ -110,8 +112,8 @@ const SignIn = () => {
|
|||||||
case PERMISSION_NEED_TWO_FACTOR:
|
case PERMISSION_NEED_TWO_FACTOR:
|
||||||
twoFactorForm.resetFields()
|
twoFactorForm.resetFields()
|
||||||
void modal.confirm({
|
void modal.confirm({
|
||||||
title: '双因素验证',
|
|
||||||
centered: true,
|
centered: true,
|
||||||
|
title: '双因素验证',
|
||||||
footer: (_, { OkBtn, CancelBtn }) => (
|
footer: (_, { OkBtn, CancelBtn }) => (
|
||||||
<>
|
<>
|
||||||
<OkBtn />
|
<OkBtn />
|
||||||
@@ -259,14 +261,14 @@ const SignIn = () => {
|
|||||||
<FlexBox direction={'horizontal'} className={'addition'}>
|
<FlexBox direction={'horizontal'} className={'addition'}>
|
||||||
<a
|
<a
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate('/')
|
navigateToRoot(navigate)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
返回首页
|
返回主页
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(`/forget${location.search}`, { replace: true })
|
navigateToForget(navigate, location.search, { replace: true })
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
忘记密码?
|
忘记密码?
|
||||||
@@ -287,7 +289,7 @@ const SignIn = () => {
|
|||||||
还没有账号?
|
还没有账号?
|
||||||
<a
|
<a
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
navigate(`/register${location.search}`, { replace: true })
|
navigateToRegister(navigate, location.search, { replace: true })
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
注册
|
注册
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
SYSTEM_MATCH_SENSITIVE_WORD
|
SYSTEM_MATCH_SENSITIVE_WORD
|
||||||
} from '@/constants/common.constants'
|
} from '@/constants/common.constants'
|
||||||
import { getLoginStatus, setToken } from '@/util/auth'
|
import { getLoginStatus, setToken } from '@/util/auth'
|
||||||
|
import { navigateToLogin } from '@/util/navigation'
|
||||||
import { r_auth_register, r_auth_resend } from '@/services/auth'
|
import { r_auth_register, r_auth_resend } from '@/services/auth'
|
||||||
import FitCenter from '@/components/common/FitCenter'
|
import FitCenter from '@/components/common/FitCenter'
|
||||||
import FlexBox from '@/components/common/FlexBox'
|
import FlexBox from '@/components/common/FlexBox'
|
||||||
@@ -49,9 +50,7 @@ const SignUp = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (getLoginStatus()) {
|
if (getLoginStatus()) {
|
||||||
navigate(`/login${location.search}`, {
|
navigateToLogin(navigate, location.search, undefined, { replace: true })
|
||||||
replace: true
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}, [location.pathname])
|
}, [location.pathname])
|
||||||
|
|
||||||
@@ -245,7 +244,9 @@ const SignUp = () => {
|
|||||||
已有账号?
|
已有账号?
|
||||||
<a
|
<a
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
navigate(`/login${location.search}`, { replace: true })
|
navigateToLogin(navigate, location.search, undefined, {
|
||||||
|
replace: true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
登录
|
登录
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
SYSTEM_MATCH_SENSITIVE_WORD
|
SYSTEM_MATCH_SENSITIVE_WORD
|
||||||
} from '@/constants/common.constants'
|
} from '@/constants/common.constants'
|
||||||
import { getLoginStatus, getUserInfo, requestUserInfo } from '@/util/auth'
|
import { getLoginStatus, getUserInfo, requestUserInfo } from '@/util/auth'
|
||||||
import { getRedirectUrl } from '@/util/route'
|
import { navigateToLogin, navigateToRedirect, navigateToRepository } from '@/util/navigation'
|
||||||
import { r_auth_resend, r_auth_verify } from '@/services/auth'
|
import { r_auth_resend, r_auth_verify } from '@/services/auth'
|
||||||
import { r_api_avatar_random_base64 } from '@/services/api/avatar'
|
import { r_api_avatar_random_base64 } from '@/services/api/avatar'
|
||||||
import { AppContext } from '@/App'
|
import { AppContext } from '@/App'
|
||||||
@@ -32,7 +32,7 @@ const Verify = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!getLoginStatus()) {
|
if (!getLoginStatus()) {
|
||||||
navigate(getRedirectUrl('/login', `${location.pathname}${location.search}`), {
|
navigateToLogin(navigate, location.search, undefined, {
|
||||||
replace: true
|
replace: true
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@@ -81,7 +81,7 @@ const Verify = () => {
|
|||||||
break
|
break
|
||||||
case PERMISSION_NO_VERIFICATION_REQUIRED:
|
case PERMISSION_NO_VERIFICATION_REQUIRED:
|
||||||
void message.warning('账户已验证')
|
void message.warning('账户已验证')
|
||||||
navigate('/')
|
navigateToRepository(navigate)
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
void message.error('出错了,请稍后重试')
|
void message.error('出错了,请稍后重试')
|
||||||
@@ -128,11 +128,7 @@ const Verify = () => {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
void requestUserInfo().then(() => {
|
void requestUserInfo().then(() => {
|
||||||
refreshRouter()
|
refreshRouter()
|
||||||
if (searchParams.has('redirect')) {
|
navigateToRedirect(navigate, searchParams, '/repository')
|
||||||
navigate(searchParams.get('redirect') ?? '/')
|
|
||||||
} else {
|
|
||||||
navigate('/')
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}, 1500)
|
}, 1500)
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { hasPermission } from '@/util/auth'
|
import { hasPermission } from '@/util/auth'
|
||||||
import { r_sys_settings_base_get, r_sys_settings_base_update } from '@/services/system'
|
import { r_sys_settings_base_get, r_sys_settings_base_update } from '@/services/system'
|
||||||
import { SettingsCard } from '@/pages/System/Settings'
|
import SettingsCard from '@/components/system/SettingCard'
|
||||||
|
|
||||||
const Base = () => {
|
const Base = () => {
|
||||||
const [baseForm] = AntdForm.useForm<BaseSettingsParam>()
|
const [baseForm] = AntdForm.useForm<BaseSettingsParam>()
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
r_sys_settings_mail_send,
|
r_sys_settings_mail_send,
|
||||||
r_sys_settings_mail_update
|
r_sys_settings_mail_update
|
||||||
} from '@/services/system'
|
} from '@/services/system'
|
||||||
import { SettingsCard } from '@/pages/System/Settings'
|
import SettingsCard from '@/components/system/SettingCard'
|
||||||
|
|
||||||
const Mail = () => {
|
const Mail = () => {
|
||||||
const [modal, contextHolder] = AntdModal.useModal()
|
const [modal, contextHolder] = AntdModal.useModal()
|
||||||
@@ -16,9 +16,9 @@ const Mail = () => {
|
|||||||
|
|
||||||
const handleOnTest = () => {
|
const handleOnTest = () => {
|
||||||
void modal.confirm({
|
void modal.confirm({
|
||||||
title: '发送测试邮件',
|
|
||||||
centered: true,
|
centered: true,
|
||||||
maskClosable: true,
|
maskClosable: true,
|
||||||
|
title: '发送测试邮件',
|
||||||
footer: (_, { OkBtn, CancelBtn }) => (
|
footer: (_, { OkBtn, CancelBtn }) => (
|
||||||
<>
|
<>
|
||||||
<OkBtn />
|
<OkBtn />
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
r_sys_settings_sensitive_get,
|
r_sys_settings_sensitive_get,
|
||||||
r_sys_settings_sensitive_update
|
r_sys_settings_sensitive_update
|
||||||
} from '@/services/system'
|
} from '@/services/system'
|
||||||
import { SettingsCard } from '@/pages/System/Settings'
|
import SettingsCard from '@/components/system/SettingCard'
|
||||||
|
|
||||||
const SensitiveWord = () => {
|
const SensitiveWord = () => {
|
||||||
const [dataSource, setDataSource] = useState<SensitiveWordVo[]>()
|
const [dataSource, setDataSource] = useState<SensitiveWordVo[]>()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { hasPermission } from '@/util/auth'
|
import { hasPermission } from '@/util/auth'
|
||||||
import { r_sys_settings_two_factor_get, r_sys_settings_two_factor_update } from '@/services/system'
|
import { r_sys_settings_two_factor_get, r_sys_settings_two_factor_update } from '@/services/system'
|
||||||
import { SettingsCard } from '@/pages/System/Settings'
|
import SettingsCard from '@/components/system/SettingCard'
|
||||||
|
|
||||||
const TwoFactor = () => {
|
const TwoFactor = () => {
|
||||||
const [twoFactorForm] = AntdForm.useForm<TwoFactorSettingsParam>()
|
const [twoFactorForm] = AntdForm.useForm<TwoFactorSettingsParam>()
|
||||||
|
|||||||
@@ -1,55 +1,12 @@
|
|||||||
import { PropsWithChildren, ReactNode } from 'react'
|
|
||||||
import Icon from '@ant-design/icons'
|
|
||||||
import '@/assets/css/pages/system/settings.scss'
|
import '@/assets/css/pages/system/settings.scss'
|
||||||
import FitFullscreen from '@/components/common/FitFullscreen'
|
import FitFullscreen from '@/components/common/FitFullscreen'
|
||||||
import HideScrollbar from '@/components/common/HideScrollbar'
|
import HideScrollbar from '@/components/common/HideScrollbar'
|
||||||
import Card from '@/components/common/Card'
|
|
||||||
import FlexBox from '@/components/common/FlexBox'
|
import FlexBox from '@/components/common/FlexBox'
|
||||||
import LoadingMask from '@/components/common/LoadingMask'
|
|
||||||
import Permission from '@/components/common/Permission'
|
import Permission from '@/components/common/Permission'
|
||||||
import Base from '@/pages/System/Settings/Base'
|
import Base from '@/pages/System/Settings/Base'
|
||||||
import Mail from '@/pages/System/Settings/Mail'
|
import Mail from '@/pages/System/Settings/Mail'
|
||||||
import SensitiveWord from '@/pages/System/Settings/SensitiveWord'
|
import SensitiveWord from '@/pages/System/Settings/SensitiveWord'
|
||||||
import TwoFactor from '@/pages/System/Settings/TwoFactor.tsx'
|
import TwoFactor from '@/pages/System/Settings/TwoFactor'
|
||||||
|
|
||||||
interface SettingsCardProps extends PropsWithChildren {
|
|
||||||
icon: IconComponent
|
|
||||||
title: string
|
|
||||||
loading?: boolean
|
|
||||||
modifyOperationCode?: string[]
|
|
||||||
expand?: ReactNode
|
|
||||||
onReset?: () => void
|
|
||||||
onSave?: () => void
|
|
||||||
}
|
|
||||||
export const SettingsCard = (props: SettingsCardProps) => {
|
|
||||||
return (
|
|
||||||
<Card>
|
|
||||||
<FlexBox className={'settings-card'}>
|
|
||||||
<FlexBox direction={'horizontal'} className={'head'}>
|
|
||||||
<Icon component={props.icon} className={'icon'} />
|
|
||||||
<div className={'title'}>{props.title}</div>
|
|
||||||
{!props.loading && (
|
|
||||||
<Permission operationCode={props.modifyOperationCode}>
|
|
||||||
{props.expand}
|
|
||||||
<AntdButton onClick={props.onReset} title={'重置'}>
|
|
||||||
<Icon component={IconOxygenBack} />
|
|
||||||
</AntdButton>
|
|
||||||
<AntdButton className={'bt-save'} onClick={props.onSave} title={'保存'}>
|
|
||||||
<Icon component={IconOxygenSave} />
|
|
||||||
</AntdButton>
|
|
||||||
</Permission>
|
|
||||||
)}
|
|
||||||
</FlexBox>
|
|
||||||
<LoadingMask
|
|
||||||
maskContent={<AntdSkeleton active paragraph={{ rows: 6 }} />}
|
|
||||||
hidden={!props.loading}
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
</LoadingMask>
|
|
||||||
</FlexBox>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const Settings = () => {
|
const Settings = () => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { getTimesBetweenTwoTimes } from '@/util/datetime'
|
|||||||
import { r_sys_statistics_active } from '@/services/system'
|
import { r_sys_statistics_active } from '@/services/system'
|
||||||
import FlexBox from '@/components/common/FlexBox'
|
import FlexBox from '@/components/common/FlexBox'
|
||||||
import { getTooltipTimeFormatter, lineEChartsBaseOption } from '@/pages/System/Statistics/shared'
|
import { getTooltipTimeFormatter, lineEChartsBaseOption } from '@/pages/System/Statistics/shared'
|
||||||
import { CommonCard } from '@/pages/System/Statistics'
|
import StatisticsCard from '@/components/system/StatisticsCard'
|
||||||
|
|
||||||
const ActiveInfo = () => {
|
const ActiveInfo = () => {
|
||||||
const activeInfoDivRef = useRef<HTMLDivElement>(null)
|
const activeInfoDivRef = useRef<HTMLDivElement>(null)
|
||||||
@@ -147,7 +147,7 @@ const ActiveInfo = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CommonCard
|
<StatisticsCard
|
||||||
icon={IconOxygenAnalysis}
|
icon={IconOxygenAnalysis}
|
||||||
title={
|
title={
|
||||||
<>
|
<>
|
||||||
@@ -183,7 +183,7 @@ const ActiveInfo = () => {
|
|||||||
<FlexBox className={'card-content'} direction={'horizontal'}>
|
<FlexBox className={'card-content'} direction={'horizontal'}>
|
||||||
<div className={'big-chart'} ref={activeInfoDivRef} />
|
<div className={'big-chart'} ref={activeInfoDivRef} />
|
||||||
</FlexBox>
|
</FlexBox>
|
||||||
</CommonCard>
|
</StatisticsCard>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
barEChartsBaseOption,
|
barEChartsBaseOption,
|
||||||
EChartsOption
|
EChartsOption
|
||||||
} from '@/pages/System/Statistics/shared'
|
} from '@/pages/System/Statistics/shared'
|
||||||
import { CommonCard } from '@/pages/System/Statistics'
|
import StatisticsCard from '@/components/system/StatisticsCard'
|
||||||
|
|
||||||
const CPUInfo = () => {
|
const CPUInfo = () => {
|
||||||
const keyDivRef = useRef<HTMLDivElement>(null)
|
const keyDivRef = useRef<HTMLDivElement>(null)
|
||||||
@@ -142,7 +142,7 @@ const CPUInfo = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CommonCard
|
<StatisticsCard
|
||||||
icon={IconOxygenCpu}
|
icon={IconOxygenCpu}
|
||||||
title={'CPU 信息'}
|
title={'CPU 信息'}
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
@@ -172,7 +172,7 @@ const CPUInfo = () => {
|
|||||||
<FlexBox className={'value-chart'} ref={cpuInfoDivRef} />
|
<FlexBox className={'value-chart'} ref={cpuInfoDivRef} />
|
||||||
<FlexBox className={'value-percent'} ref={percentDivRef} />
|
<FlexBox className={'value-percent'} ref={percentDivRef} />
|
||||||
</FlexBox>
|
</FlexBox>
|
||||||
</CommonCard>
|
</StatisticsCard>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { r_sys_statistics_hardware } from '@/services/system'
|
import { r_sys_statistics_hardware } from '@/services/system'
|
||||||
import FlexBox from '@/components/common/FlexBox'
|
import FlexBox from '@/components/common/FlexBox'
|
||||||
import { CommonCard } from '@/pages/System/Statistics'
|
import StatisticsCard from '@/components/system/StatisticsCard'
|
||||||
|
|
||||||
const HardwareInfo = () => {
|
const HardwareInfo = () => {
|
||||||
const [hardwareInfoData, setHardwareInfoData] = useState<HardwareInfoVo>()
|
const [hardwareInfoData, setHardwareInfoData] = useState<HardwareInfoVo>()
|
||||||
@@ -17,7 +17,7 @@ const HardwareInfo = () => {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CommonCard
|
<StatisticsCard
|
||||||
icon={IconOxygenHardware}
|
icon={IconOxygenHardware}
|
||||||
title={'硬件信息'}
|
title={'硬件信息'}
|
||||||
loading={hardwareInfoData === undefined}
|
loading={hardwareInfoData === undefined}
|
||||||
@@ -56,7 +56,7 @@ const HardwareInfo = () => {
|
|||||||
<div title={hardwareInfoData?.disks}>{hardwareInfoData?.disks}</div>
|
<div title={hardwareInfoData?.disks}>{hardwareInfoData?.disks}</div>
|
||||||
</FlexBox>
|
</FlexBox>
|
||||||
</FlexBox>
|
</FlexBox>
|
||||||
</CommonCard>
|
</StatisticsCard>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { getTimesBetweenTwoTimes } from '@/util/datetime'
|
|||||||
import { r_sys_statistics_online } from '@/services/system'
|
import { r_sys_statistics_online } from '@/services/system'
|
||||||
import FlexBox from '@/components/common/FlexBox'
|
import FlexBox from '@/components/common/FlexBox'
|
||||||
import { getTooltipTimeFormatter, lineEChartsBaseOption } from '@/pages/System/Statistics/shared'
|
import { getTooltipTimeFormatter, lineEChartsBaseOption } from '@/pages/System/Statistics/shared'
|
||||||
import { CommonCard } from '@/pages/System/Statistics'
|
import StatisticsCard from '@/components/system/StatisticsCard'
|
||||||
|
|
||||||
const OnlineInfo = () => {
|
const OnlineInfo = () => {
|
||||||
const onlineInfoDivRef = useRef<HTMLDivElement>(null)
|
const onlineInfoDivRef = useRef<HTMLDivElement>(null)
|
||||||
@@ -148,7 +148,7 @@ const OnlineInfo = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CommonCard
|
<StatisticsCard
|
||||||
icon={IconOxygenOnline}
|
icon={IconOxygenOnline}
|
||||||
title={
|
title={
|
||||||
<>
|
<>
|
||||||
@@ -188,7 +188,7 @@ const OnlineInfo = () => {
|
|||||||
<FlexBox className={'card-content'} direction={'horizontal'}>
|
<FlexBox className={'card-content'} direction={'horizontal'}>
|
||||||
<div className={'big-chart'} ref={onlineInfoDivRef} />
|
<div className={'big-chart'} ref={onlineInfoDivRef} />
|
||||||
</FlexBox>
|
</FlexBox>
|
||||||
</CommonCard>
|
</StatisticsCard>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { utcToLocalTime } from '@/util/datetime'
|
import { utcToLocalTime } from '@/util/datetime'
|
||||||
import { r_sys_statistics_software } from '@/services/system'
|
import { r_sys_statistics_software } from '@/services/system'
|
||||||
import FlexBox from '@/components/common/FlexBox'
|
import FlexBox from '@/components/common/FlexBox'
|
||||||
import { CommonCard } from '@/pages/System/Statistics'
|
import StatisticsCard from '@/components/system/StatisticsCard'
|
||||||
|
|
||||||
const SoftwareInfo = () => {
|
const SoftwareInfo = () => {
|
||||||
const [softwareInfoData, setSoftwareInfoData] = useState<SoftwareInfoVo>()
|
const [softwareInfoData, setSoftwareInfoData] = useState<SoftwareInfoVo>()
|
||||||
@@ -18,7 +18,7 @@ const SoftwareInfo = () => {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CommonCard
|
<StatisticsCard
|
||||||
icon={IconOxygenSoftware}
|
icon={IconOxygenSoftware}
|
||||||
title={'软件信息'}
|
title={'软件信息'}
|
||||||
loading={softwareInfoData === undefined}
|
loading={softwareInfoData === undefined}
|
||||||
@@ -61,7 +61,7 @@ const SoftwareInfo = () => {
|
|||||||
</div>
|
</div>
|
||||||
</FlexBox>
|
</FlexBox>
|
||||||
</FlexBox>
|
</FlexBox>
|
||||||
</CommonCard>
|
</StatisticsCard>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
barEChartsBaseOption,
|
barEChartsBaseOption,
|
||||||
EChartsOption
|
EChartsOption
|
||||||
} from '@/pages/System/Statistics/shared'
|
} from '@/pages/System/Statistics/shared'
|
||||||
import { CommonCard } from '@/pages/System/Statistics'
|
import StatisticsCard from '@/components/system/StatisticsCard'
|
||||||
|
|
||||||
const StorageInfo = () => {
|
const StorageInfo = () => {
|
||||||
const keyDivRef = useRef<HTMLDivElement>(null)
|
const keyDivRef = useRef<HTMLDivElement>(null)
|
||||||
@@ -161,7 +161,7 @@ const StorageInfo = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CommonCard
|
<StatisticsCard
|
||||||
icon={IconOxygenMemory}
|
icon={IconOxygenMemory}
|
||||||
title={'内存信息'}
|
title={'内存信息'}
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
@@ -191,7 +191,7 @@ const StorageInfo = () => {
|
|||||||
<FlexBox className={'value-chart'} ref={storageInfoDivRef} />
|
<FlexBox className={'value-chart'} ref={storageInfoDivRef} />
|
||||||
<FlexBox className={'value-percent'} ref={percentDivRef} />
|
<FlexBox className={'value-percent'} ref={percentDivRef} />
|
||||||
</FlexBox>
|
</FlexBox>
|
||||||
</CommonCard>
|
</StatisticsCard>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
import { PropsWithChildren, ReactNode } from 'react'
|
|
||||||
import Icon from '@ant-design/icons'
|
|
||||||
import '@/assets/css/pages/system/statistics.scss'
|
import '@/assets/css/pages/system/statistics.scss'
|
||||||
import Card from '@/components/common/Card'
|
|
||||||
import FlexBox from '@/components/common/FlexBox'
|
import FlexBox from '@/components/common/FlexBox'
|
||||||
import FitFullscreen from '@/components/common/FitFullscreen'
|
import FitFullscreen from '@/components/common/FitFullscreen'
|
||||||
import HideScrollbar from '@/components/common/HideScrollbar'
|
import HideScrollbar from '@/components/common/HideScrollbar'
|
||||||
import LoadingMask from '@/components/common/LoadingMask'
|
|
||||||
import Permission from '@/components/common/Permission'
|
import Permission from '@/components/common/Permission'
|
||||||
import OnlineInfo from '@/pages/System/Statistics/OnlineInfo'
|
import OnlineInfo from '@/pages/System/Statistics/OnlineInfo'
|
||||||
import ActiveInfo from '@/pages/System/Statistics/ActiveInfo'
|
import ActiveInfo from '@/pages/System/Statistics/ActiveInfo'
|
||||||
@@ -14,33 +10,6 @@ import HardwareInfo from '@/pages/System/Statistics/HardwareInfo'
|
|||||||
import CPUInfo from '@/pages/System/Statistics/CPUInfo'
|
import CPUInfo from '@/pages/System/Statistics/CPUInfo'
|
||||||
import StorageInfo from '@/pages/System/Statistics/StorageInfo'
|
import StorageInfo from '@/pages/System/Statistics/StorageInfo'
|
||||||
|
|
||||||
interface CommonCardProps extends PropsWithChildren {
|
|
||||||
icon: IconComponent
|
|
||||||
title: ReactNode
|
|
||||||
loading?: boolean
|
|
||||||
expand?: ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CommonCard = (props: CommonCardProps) => {
|
|
||||||
return (
|
|
||||||
<Card style={{ overflow: 'visible' }}>
|
|
||||||
<FlexBox className={'common-card'}>
|
|
||||||
<FlexBox direction={'horizontal'} className={'head'}>
|
|
||||||
<Icon component={props.icon} className={'icon'} />
|
|
||||||
<div className={'title'}>{props.title}</div>
|
|
||||||
{props.expand}
|
|
||||||
</FlexBox>
|
|
||||||
<LoadingMask
|
|
||||||
hidden={!props.loading}
|
|
||||||
maskContent={<AntdSkeleton active paragraph={{ rows: 6 }} />}
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
</LoadingMask>
|
|
||||||
</FlexBox>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const Statistics = () => {
|
const Statistics = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import Icon from '@ant-design/icons'
|
|||||||
import '@/assets/css/pages/system/tools/code.scss'
|
import '@/assets/css/pages/system/tools/code.scss'
|
||||||
import { DATABASE_NO_RECORD_FOUND, DATABASE_SELECT_SUCCESS } from '@/constants/common.constants'
|
import { DATABASE_NO_RECORD_FOUND, DATABASE_SELECT_SUCCESS } from '@/constants/common.constants'
|
||||||
import { checkDesktop } from '@/util/common'
|
import { checkDesktop } from '@/util/common'
|
||||||
|
import { navigateToExecute, navigateToRepository } from '@/util/navigation'
|
||||||
import { r_sys_tool_get_one } from '@/services/system'
|
import { r_sys_tool_get_one } from '@/services/system'
|
||||||
import { IFiles } from '@/components/Playground/shared'
|
import { IFiles } from '@/components/Playground/shared'
|
||||||
import { base64ToFiles } from '@/components/Playground/files'
|
import { base64ToFiles } from '@/components/Playground/files'
|
||||||
@@ -27,7 +28,7 @@ const Code = () => {
|
|||||||
title: '注意',
|
title: '注意',
|
||||||
content: '运行前请仔细查阅工具代码!',
|
content: '运行前请仔细查阅工具代码!',
|
||||||
onOk: () => {
|
onOk: () => {
|
||||||
navigate(`/system/tools/execute/${id}`)
|
navigateToExecute(navigate, id!)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -61,9 +62,7 @@ const Code = () => {
|
|||||||
break
|
break
|
||||||
case DATABASE_NO_RECORD_FOUND:
|
case DATABASE_NO_RECORD_FOUND:
|
||||||
void message.error('未找到指定工具')
|
void message.error('未找到指定工具')
|
||||||
setTimeout(() => {
|
navigateToRepository(navigate)
|
||||||
navigate('/')
|
|
||||||
}, 3000)
|
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
void message.error('获取工具信息失败,请稍后重试')
|
void message.error('获取工具信息失败,请稍后重试')
|
||||||
@@ -77,7 +76,7 @@ const Code = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getTool()
|
getTool()
|
||||||
}, [])
|
}, [id])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import '@/assets/css/pages/system/tools/execute.scss'
|
import '@/assets/css/pages/system/tools/execute.scss'
|
||||||
import { DATABASE_NO_RECORD_FOUND, DATABASE_SELECT_SUCCESS } from '@/constants/common.constants'
|
import { DATABASE_NO_RECORD_FOUND, DATABASE_SELECT_SUCCESS } from '@/constants/common.constants'
|
||||||
|
import { navigateToTools } from '@/util/navigation'
|
||||||
import { r_sys_tool_get_one } from '@/services/system'
|
import { r_sys_tool_get_one } from '@/services/system'
|
||||||
import FitFullscreen from '@/components/common/FitFullscreen'
|
import FitFullscreen from '@/components/common/FitFullscreen'
|
||||||
import Card from '@/components/common/Card'
|
import Card from '@/components/common/Card'
|
||||||
@@ -24,7 +25,10 @@ const Execute = () => {
|
|||||||
.compile(files, importMap, toolVo.entryPoint)
|
.compile(files, importMap, toolVo.entryPoint)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
const output = result.outputFiles[0].text
|
const output = result.outputFiles[0].text
|
||||||
|
setCompiledCode('')
|
||||||
|
setTimeout(() => {
|
||||||
setCompiledCode(`${output}\n${baseDist}`)
|
setCompiledCode(`${output}\n${baseDist}`)
|
||||||
|
}, 100)
|
||||||
})
|
})
|
||||||
.catch((reason) => {
|
.catch((reason) => {
|
||||||
void message.error(`编译失败:${reason}`)
|
void message.error(`编译失败:${reason}`)
|
||||||
@@ -50,9 +54,7 @@ const Execute = () => {
|
|||||||
break
|
break
|
||||||
case DATABASE_NO_RECORD_FOUND:
|
case DATABASE_NO_RECORD_FOUND:
|
||||||
void message.error('未找到指定工具')
|
void message.error('未找到指定工具')
|
||||||
setTimeout(() => {
|
navigateToTools(navigate)
|
||||||
navigate('/')
|
|
||||||
}, 3000)
|
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
void message.error('获取工具信息失败,请稍后重试')
|
void message.error('获取工具信息失败,请稍后重试')
|
||||||
@@ -66,7 +68,7 @@ const Execute = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getTool()
|
getTool()
|
||||||
}, [])
|
}, [id])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FitFullscreen data-component={'system-tools-execute'}>
|
<FitFullscreen data-component={'system-tools-execute'}>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Icon from '@ant-design/icons'
|
import Icon from '@ant-design/icons'
|
||||||
import '@/assets/css/pages/system/tools/base.scss'
|
import '@/assets/css/pages/system/tools/template.scss'
|
||||||
import {
|
import {
|
||||||
COLOR_PRODUCTION,
|
COLOR_PRODUCTION,
|
||||||
DATABASE_DELETE_SUCCESS,
|
DATABASE_DELETE_SUCCESS,
|
||||||
@@ -1021,7 +1021,7 @@ const Template = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FitFullscreen data-component={'system-tools-base'}>
|
<FitFullscreen data-component={'system-tools-template'}>
|
||||||
<HideScrollbar>
|
<HideScrollbar>
|
||||||
<FlexBox direction={'horizontal'} className={'root-content'}>
|
<FlexBox direction={'horizontal'} className={'root-content'}>
|
||||||
<Card>
|
<Card>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
DATABASE_UPDATE_SUCCESS,
|
DATABASE_UPDATE_SUCCESS,
|
||||||
TOOL_NOT_UNDER_REVIEW
|
TOOL_NOT_UNDER_REVIEW
|
||||||
} from '@/constants/common.constants'
|
} from '@/constants/common.constants'
|
||||||
|
import { navigateToCode } from '@/util/navigation'
|
||||||
import FlexBox from '@/components/common/FlexBox'
|
import FlexBox from '@/components/common/FlexBox'
|
||||||
import Card from '@/components/common/Card'
|
import Card from '@/components/common/Card'
|
||||||
import FitFullscreen from '@/components/common/FitFullscreen'
|
import FitFullscreen from '@/components/common/FitFullscreen'
|
||||||
@@ -196,7 +197,7 @@ const Tools = () => {
|
|||||||
|
|
||||||
const handleOnViewBtnClick = (value: ToolVo) => {
|
const handleOnViewBtnClick = (value: ToolVo) => {
|
||||||
return () => {
|
return () => {
|
||||||
navigate(`/system/tools/code/${value.id}`)
|
navigateToCode(navigate, value.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,65 +1,9 @@
|
|||||||
import { DetailedHTMLProps, HTMLAttributes, ReactNode } from 'react'
|
|
||||||
import Icon from '@ant-design/icons'
|
|
||||||
import VanillaTilt, { TiltOptions } from 'vanilla-tilt'
|
|
||||||
import '@/assets/css/pages/system/index.scss'
|
import '@/assets/css/pages/system/index.scss'
|
||||||
import HideScrollbar from '@/components/common/HideScrollbar'
|
import HideScrollbar from '@/components/common/HideScrollbar'
|
||||||
import FitFullscreen from '@/components/common/FitFullscreen'
|
import FitFullscreen from '@/components/common/FitFullscreen'
|
||||||
import FlexBox from '@/components/common/FlexBox'
|
import FlexBox from '@/components/common/FlexBox'
|
||||||
import Card from '@/components/common/Card'
|
|
||||||
import Permission from '@/components/common/Permission'
|
import Permission from '@/components/common/Permission'
|
||||||
|
import UrlCard from '@/components/common/UrlCard'
|
||||||
interface CommonCardProps
|
|
||||||
extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
|
|
||||||
icon: IconComponent
|
|
||||||
description?: ReactNode
|
|
||||||
options?: TiltOptions
|
|
||||||
url?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const CommonCard = forwardRef<HTMLDivElement, CommonCardProps>(
|
|
||||||
({
|
|
||||||
style,
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
ref,
|
|
||||||
icon,
|
|
||||||
description,
|
|
||||||
options = {
|
|
||||||
reverse: true,
|
|
||||||
max: 8,
|
|
||||||
glare: true,
|
|
||||||
scale: 1.03
|
|
||||||
},
|
|
||||||
url,
|
|
||||||
children,
|
|
||||||
...props
|
|
||||||
}) => {
|
|
||||||
const navigate = useNavigate()
|
|
||||||
const cardRef = useRef<HTMLDivElement>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
cardRef.current && VanillaTilt.init(cardRef.current, options)
|
|
||||||
}, [options])
|
|
||||||
|
|
||||||
const handleCardOnClick = () => {
|
|
||||||
url && navigate(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card
|
|
||||||
style={{ overflow: 'visible', ...style }}
|
|
||||||
ref={cardRef}
|
|
||||||
{...props}
|
|
||||||
onClick={handleCardOnClick}
|
|
||||||
>
|
|
||||||
<FlexBox className={'common-card'}>
|
|
||||||
<Icon component={icon} className={'icon'} />
|
|
||||||
<div className={'text'}>{children}</div>
|
|
||||||
<div className={'description'}>{description}</div>
|
|
||||||
</FlexBox>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const System = () => {
|
const System = () => {
|
||||||
return (
|
return (
|
||||||
@@ -68,54 +12,54 @@ const System = () => {
|
|||||||
<HideScrollbar isShowVerticalScrollbar autoHideWaitingTime={1000}>
|
<HideScrollbar isShowVerticalScrollbar autoHideWaitingTime={1000}>
|
||||||
<FlexBox direction={'horizontal'} className={'root-content'}>
|
<FlexBox direction={'horizontal'} className={'root-content'}>
|
||||||
<Permission path={'/system/statistics'}>
|
<Permission path={'/system/statistics'}>
|
||||||
<CommonCard icon={IconOxygenAnalysis} url={'statistics'}>
|
<UrlCard icon={IconOxygenAnalysis} url={'statistics'}>
|
||||||
系统概况
|
系统概况
|
||||||
</CommonCard>
|
</UrlCard>
|
||||||
</Permission>
|
</Permission>
|
||||||
<Permission path={'/system/settings'}>
|
<Permission path={'/system/settings'}>
|
||||||
<CommonCard icon={IconOxygenOption} url={'settings'}>
|
<UrlCard icon={IconOxygenOption} url={'settings'}>
|
||||||
系统设置
|
系统设置
|
||||||
</CommonCard>
|
</UrlCard>
|
||||||
</Permission>
|
</Permission>
|
||||||
<Permission operationCode={['system:tool:query:tool']}>
|
<Permission operationCode={['system:tool:query:tool']}>
|
||||||
<CommonCard icon={IconOxygenTool} url={'tools'}>
|
<UrlCard icon={IconOxygenTool} url={'tools'}>
|
||||||
工具管理
|
工具管理
|
||||||
</CommonCard>
|
</UrlCard>
|
||||||
</Permission>
|
</Permission>
|
||||||
<Permission operationCode={['system:tool:query:template']}>
|
<Permission operationCode={['system:tool:query:template']}>
|
||||||
<CommonCard icon={IconOxygenTemplate} url={'tools/template'}>
|
<UrlCard icon={IconOxygenTemplate} url={'tools/template'}>
|
||||||
模板管理
|
模板管理
|
||||||
</CommonCard>
|
</UrlCard>
|
||||||
</Permission>
|
</Permission>
|
||||||
<Permission operationCode={['system:tool:query:base']}>
|
<Permission operationCode={['system:tool:query:base']}>
|
||||||
<CommonCard icon={IconOxygenBase} url={'tools/base'}>
|
<UrlCard icon={IconOxygenBase} url={'tools/base'}>
|
||||||
基板管理
|
基板管理
|
||||||
</CommonCard>
|
</UrlCard>
|
||||||
</Permission>
|
</Permission>
|
||||||
<Permission operationCode={['system:tool:query:category']}>
|
<Permission operationCode={['system:tool:query:category']}>
|
||||||
<CommonCard icon={IconOxygenCategory} url={'tools/category'}>
|
<UrlCard icon={IconOxygenCategory} url={'tools/category'}>
|
||||||
类别管理
|
类别管理
|
||||||
</CommonCard>
|
</UrlCard>
|
||||||
</Permission>
|
</Permission>
|
||||||
<Permission path={'/system/user'}>
|
<Permission path={'/system/user'}>
|
||||||
<CommonCard icon={IconOxygenUser} url={'user'}>
|
<UrlCard icon={IconOxygenUser} url={'user'}>
|
||||||
用户管理
|
用户管理
|
||||||
</CommonCard>
|
</UrlCard>
|
||||||
</Permission>
|
</Permission>
|
||||||
<Permission path={'/system/role'}>
|
<Permission path={'/system/role'}>
|
||||||
<CommonCard icon={IconOxygenRole} url={'role'}>
|
<UrlCard icon={IconOxygenRole} url={'role'}>
|
||||||
角色管理
|
角色管理
|
||||||
</CommonCard>
|
</UrlCard>
|
||||||
</Permission>
|
</Permission>
|
||||||
<Permission path={'/system/group'}>
|
<Permission path={'/system/group'}>
|
||||||
<CommonCard icon={IconOxygenGroup} url={'group'}>
|
<UrlCard icon={IconOxygenGroup} url={'group'}>
|
||||||
群组管理
|
群组管理
|
||||||
</CommonCard>
|
</UrlCard>
|
||||||
</Permission>
|
</Permission>
|
||||||
<Permission path={'/system/log'}>
|
<Permission path={'/system/log'}>
|
||||||
<CommonCard icon={IconOxygenLog} url={'log'}>
|
<UrlCard icon={IconOxygenLog} url={'log'}>
|
||||||
系统日志
|
系统日志
|
||||||
</CommonCard>
|
</UrlCard>
|
||||||
</Permission>
|
</Permission>
|
||||||
</FlexBox>
|
</FlexBox>
|
||||||
</HideScrollbar>
|
</HideScrollbar>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
DATABASE_INSERT_SUCCESS,
|
DATABASE_INSERT_SUCCESS,
|
||||||
DATABASE_SELECT_SUCCESS
|
DATABASE_SELECT_SUCCESS
|
||||||
} from '@/constants/common.constants'
|
} from '@/constants/common.constants'
|
||||||
|
import { navigateToEdit } from '@/util/navigation'
|
||||||
import {
|
import {
|
||||||
r_tool_category_get,
|
r_tool_category_get,
|
||||||
r_tool_create,
|
r_tool_create,
|
||||||
@@ -44,9 +45,7 @@ const Create = () => {
|
|||||||
void message.success(
|
void message.success(
|
||||||
`创建工具 ${response.data!.name}<${response.data!.toolId}:${response.data!.platform.slice(0, 1)}${response.data!.platform.slice(1).toLowerCase()}:${response.data!.ver}> 成功`
|
`创建工具 ${response.data!.name}<${response.data!.toolId}:${response.data!.platform.slice(0, 1)}${response.data!.platform.slice(1).toLowerCase()}:${response.data!.ver}> 成功`
|
||||||
)
|
)
|
||||||
navigate(
|
navigateToEdit(navigate, response.data!.toolId, response.data!.platform)
|
||||||
`/edit/${response.data!.toolId}${response.data!.platform !== import.meta.env.VITE_PLATFORM ? `?platform=${response.data!.platform}` : ''}`
|
|
||||||
)
|
|
||||||
break
|
break
|
||||||
case DATABASE_DUPLICATE_KEY:
|
case DATABASE_DUPLICATE_KEY:
|
||||||
void message.warning('已存在相同 ID 的应用')
|
void message.warning('已存在相同 ID 的应用')
|
||||||
@@ -330,7 +329,7 @@ const Create = () => {
|
|||||||
label={'关键字'}
|
label={'关键字'}
|
||||||
tooltip={'工具搜索(每个不超过10个字符)'}
|
tooltip={'工具搜索(每个不超过10个字符)'}
|
||||||
name={'keywords'}
|
name={'keywords'}
|
||||||
rules={[{ required: true, whitespace: true }]}
|
rules={[{ required: true }]}
|
||||||
>
|
>
|
||||||
<AntdSelect
|
<AntdSelect
|
||||||
mode={'tags'}
|
mode={'tags'}
|
||||||
@@ -342,7 +341,7 @@ const Create = () => {
|
|||||||
label={'类别'}
|
label={'类别'}
|
||||||
tooltip={'工具分类'}
|
tooltip={'工具分类'}
|
||||||
name={'categories'}
|
name={'categories'}
|
||||||
rules={[{ required: true, whitespace: true }]}
|
rules={[{ required: true }]}
|
||||||
>
|
>
|
||||||
<AntdSelect
|
<AntdSelect
|
||||||
mode={'multiple'}
|
mode={'multiple'}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
TOOL_HAS_BEEN_PUBLISHED,
|
TOOL_HAS_BEEN_PUBLISHED,
|
||||||
TOOL_UNDER_REVIEW
|
TOOL_UNDER_REVIEW
|
||||||
} from '@/constants/common.constants'
|
} from '@/constants/common.constants'
|
||||||
|
import { navigateToRepository } from '@/util/navigation'
|
||||||
import { r_tool_category_get, r_tool_detail, r_tool_update } from '@/services/tool'
|
import { r_tool_category_get, r_tool_detail, r_tool_update } from '@/services/tool'
|
||||||
import { IFiles, IImportMap, ITsconfig } from '@/components/Playground/shared'
|
import { IFiles, IImportMap, ITsconfig } from '@/components/Playground/shared'
|
||||||
import {
|
import {
|
||||||
@@ -117,15 +118,11 @@ const Edit = () => {
|
|||||||
break
|
break
|
||||||
case TOOL_UNDER_REVIEW:
|
case TOOL_UNDER_REVIEW:
|
||||||
void message.error('保存失败:工具审核中')
|
void message.error('保存失败:工具审核中')
|
||||||
setTimeout(() => {
|
navigateToRepository(navigate)
|
||||||
navigate('/')
|
|
||||||
}, 3000)
|
|
||||||
break
|
break
|
||||||
case TOOL_HAS_BEEN_PUBLISHED:
|
case TOOL_HAS_BEEN_PUBLISHED:
|
||||||
void message.error('保存失败:工具已发布')
|
void message.error('保存失败:工具已发布')
|
||||||
setTimeout(() => {
|
navigateToRepository(navigate)
|
||||||
navigate('/')
|
|
||||||
}, 3000)
|
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
void message.error('保存失败,请稍后重试')
|
void message.error('保存失败,请稍后重试')
|
||||||
@@ -183,15 +180,11 @@ const Edit = () => {
|
|||||||
break
|
break
|
||||||
case TOOL_UNDER_REVIEW:
|
case TOOL_UNDER_REVIEW:
|
||||||
void message.error('保存失败:工具审核中')
|
void message.error('保存失败:工具审核中')
|
||||||
setTimeout(() => {
|
navigateToRepository(navigate)
|
||||||
navigate('/')
|
|
||||||
}, 3000)
|
|
||||||
break
|
break
|
||||||
case TOOL_HAS_BEEN_PUBLISHED:
|
case TOOL_HAS_BEEN_PUBLISHED:
|
||||||
void message.error('保存失败:工具已发布')
|
void message.error('保存失败:工具已发布')
|
||||||
setTimeout(() => {
|
navigateToRepository(navigate)
|
||||||
navigate('/')
|
|
||||||
}, 3000)
|
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
void message.error('保存失败,请稍后重试')
|
void message.error('保存失败,请稍后重试')
|
||||||
@@ -244,22 +237,16 @@ const Edit = () => {
|
|||||||
break
|
break
|
||||||
case 'PROCESSING':
|
case 'PROCESSING':
|
||||||
void message.warning('工具审核中,请勿修改')
|
void message.warning('工具审核中,请勿修改')
|
||||||
setTimeout(() => {
|
navigateToRepository(navigate)
|
||||||
navigate('/')
|
|
||||||
}, 3000)
|
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
void message.warning('请先创建新版本后编辑工具')
|
void message.warning('请先创建新版本后编辑工具')
|
||||||
setTimeout(() => {
|
navigateToRepository(navigate)
|
||||||
navigate('/')
|
|
||||||
}, 3000)
|
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case DATABASE_NO_RECORD_FOUND:
|
case DATABASE_NO_RECORD_FOUND:
|
||||||
void message.error('未找到指定工具')
|
void message.error('未找到指定工具')
|
||||||
setTimeout(() => {
|
navigateToRepository(navigate)
|
||||||
navigate('/')
|
|
||||||
}, 3000)
|
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
void message.error('获取工具信息失败,请稍后重试')
|
void message.error('获取工具信息失败,请稍后重试')
|
||||||
@@ -322,11 +309,11 @@ const Edit = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!['WEB', 'DESKTOP', 'ANDROID'].includes(searchParams.get('platform')!)) {
|
if (!['WEB', 'DESKTOP', 'ANDROID'].includes(searchParams.get('platform')!)) {
|
||||||
navigate('/')
|
navigateToRepository(navigate)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
getTool()
|
getTool()
|
||||||
}, [])
|
}, [toolId, searchParams])
|
||||||
|
|
||||||
const drawerToolbar = (
|
const drawerToolbar = (
|
||||||
<AntdSpace>
|
<AntdSpace>
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import '@/assets/css/pages/tools/source.scss'
|
import '@/assets/css/pages/tools/source.scss'
|
||||||
import { DATABASE_NO_RECORD_FOUND, DATABASE_SELECT_SUCCESS } from '@/constants/common.constants'
|
import { DATABASE_NO_RECORD_FOUND, DATABASE_SELECT_SUCCESS } from '@/constants/common.constants'
|
||||||
|
import { getLoginStatus } from '@/util/auth'
|
||||||
|
import { navigateToRepository, navigateToSource } from '@/util/navigation'
|
||||||
import { r_tool_detail } from '@/services/tool'
|
import { r_tool_detail } from '@/services/tool'
|
||||||
import { IFiles } from '@/components/Playground/shared'
|
import { IFiles } from '@/components/Playground/shared'
|
||||||
import { base64ToFiles } from '@/components/Playground/files'
|
import { base64ToFiles } from '@/components/Playground/files'
|
||||||
import Playground from '@/components/Playground'
|
import Playground from '@/components/Playground'
|
||||||
import FitFullscreen from '@/components/common/FitFullscreen'
|
import FitFullscreen from '@/components/common/FitFullscreen'
|
||||||
import Card from '@/components/common/Card'
|
import Card from '@/components/common/Card'
|
||||||
import { getLoginStatus } from '@/util/auth'
|
|
||||||
|
|
||||||
const Source = () => {
|
const Source = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
@@ -48,9 +49,7 @@ const Source = () => {
|
|||||||
break
|
break
|
||||||
case DATABASE_NO_RECORD_FOUND:
|
case DATABASE_NO_RECORD_FOUND:
|
||||||
void message.error('未找到指定工具')
|
void message.error('未找到指定工具')
|
||||||
setTimeout(() => {
|
navigateToRepository(navigate)
|
||||||
navigate('/')
|
|
||||||
}, 3000)
|
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
void message.error('获取工具信息失败,请稍后重试')
|
void message.error('获取工具信息失败,请稍后重试')
|
||||||
@@ -63,26 +62,25 @@ const Source = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const platform = searchParams.get('platform')!
|
||||||
|
if (!['WEB', 'DESKTOP', 'ANDROID'].includes(platform)) {
|
||||||
|
navigateToRepository(navigate)
|
||||||
|
return
|
||||||
|
}
|
||||||
if (username === '!' && !getLoginStatus()) {
|
if (username === '!' && !getLoginStatus()) {
|
||||||
setTimeout(() => {
|
navigateToRepository(navigate)
|
||||||
navigate('/')
|
|
||||||
}, 3000)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (username !== '!' && ver) {
|
if (username !== '!' && ver) {
|
||||||
navigate(`/source/${username}/${toolId}`)
|
navigateToSource(navigate, username!, toolId!, platform as Platform)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (username === '!' && !ver) {
|
if (username === '!' && !ver) {
|
||||||
navigate(`/source/!/${toolId}/latest`)
|
navigateToSource(navigate, '!', toolId!, platform as Platform, 'latest')
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!['WEB', 'DESKTOP', 'ANDROID'].includes(searchParams.get('platform')!)) {
|
|
||||||
navigate('/')
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
getTool()
|
getTool()
|
||||||
}, [])
|
}, [username, toolId, ver, searchParams])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FitFullscreen data-component={'tools-source'}>
|
<FitFullscreen data-component={'tools-source'}>
|
||||||
|
|||||||
@@ -1,253 +1,13 @@
|
|||||||
import { DetailedHTMLProps, HTMLAttributes, MouseEvent, ReactNode, UIEvent } from 'react'
|
import { UIEvent } from 'react'
|
||||||
import VanillaTilt, { TiltOptions } from 'vanilla-tilt'
|
|
||||||
import protocolCheck from 'custom-protocol-check'
|
|
||||||
import Icon from '@ant-design/icons'
|
|
||||||
import '@/assets/css/pages/tools/store.scss'
|
import '@/assets/css/pages/tools/store.scss'
|
||||||
import { COLOR_BACKGROUND, COLOR_MAIN, DATABASE_SELECT_SUCCESS } from '@/constants/common.constants'
|
import { DATABASE_SELECT_SUCCESS } from '@/constants/common.constants'
|
||||||
import { checkDesktop } from '@/util/common'
|
import { checkDesktop } from '@/util/common'
|
||||||
import { r_tool_store_get } from '@/services/tool'
|
import { r_tool_store_get } from '@/services/tool'
|
||||||
import Card from '@/components/common/Card'
|
|
||||||
import FlexBox from '@/components/common/FlexBox'
|
import FlexBox from '@/components/common/FlexBox'
|
||||||
import FitFullscreen from '@/components/common/FitFullscreen'
|
import FitFullscreen from '@/components/common/FitFullscreen'
|
||||||
import HideScrollbar from '@/components/common/HideScrollbar'
|
import HideScrollbar from '@/components/common/HideScrollbar'
|
||||||
|
import StoreCard from '@/components/tools/StoreCard'
|
||||||
interface CommonCardProps
|
import LoadMoreCard from '@/components/tools/LoadMoreCard'
|
||||||
extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
|
|
||||||
icon: ReactNode
|
|
||||||
toolName: string
|
|
||||||
toolId: string
|
|
||||||
toolDesc: string
|
|
||||||
options?: TiltOptions
|
|
||||||
authorName: string
|
|
||||||
authorAvatar: string
|
|
||||||
authorUsername: string
|
|
||||||
ver: string
|
|
||||||
platform: Platform
|
|
||||||
supportPlatform: Platform[]
|
|
||||||
}
|
|
||||||
|
|
||||||
const CommonCard = ({
|
|
||||||
style,
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
ref,
|
|
||||||
icon,
|
|
||||||
toolName,
|
|
||||||
toolId,
|
|
||||||
toolDesc,
|
|
||||||
options = {
|
|
||||||
reverse: true,
|
|
||||||
max: 8,
|
|
||||||
glare: true,
|
|
||||||
['max-glare']: 0.3,
|
|
||||||
scale: 1.03
|
|
||||||
},
|
|
||||||
authorName,
|
|
||||||
authorAvatar,
|
|
||||||
authorUsername,
|
|
||||||
ver,
|
|
||||||
platform,
|
|
||||||
supportPlatform,
|
|
||||||
...props
|
|
||||||
}: CommonCardProps) => {
|
|
||||||
const navigate = useNavigate()
|
|
||||||
const [modal, contextHolder] = AntdModal.useModal()
|
|
||||||
const cardRef = useRef<HTMLDivElement>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
cardRef.current && VanillaTilt.init(cardRef.current, options)
|
|
||||||
}, [options])
|
|
||||||
|
|
||||||
const handleCardOnClick = () => {
|
|
||||||
if (!checkDesktop() && platform === 'DESKTOP') {
|
|
||||||
void message.warning('此应用需要桌面端环境,请在桌面端打开')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (platform === 'ANDROID') {
|
|
||||||
void modal.info({
|
|
||||||
icon: <Icon style={{ color: COLOR_MAIN }} component={IconOxygenInfo} />,
|
|
||||||
title: 'Android 端',
|
|
||||||
centered: true,
|
|
||||||
maskClosable: true,
|
|
||||||
content: (
|
|
||||||
<FlexBox className={'android-qrcode'}>
|
|
||||||
<AntdQRCode
|
|
||||||
value={`oxygen://openurl/view/${authorUsername}/${toolId}`}
|
|
||||||
size={300}
|
|
||||||
/>
|
|
||||||
<AntdTag className={'tag'}>请使用手机端扫描上方二维码</AntdTag>
|
|
||||||
</FlexBox>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
navigate(
|
|
||||||
`/view/${authorUsername}/${toolId}${platform !== import.meta.env.VITE_PLATFORM ? `?platform=${platform}` : ''}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleOnClickAuthor = (e: MouseEvent<HTMLDivElement>) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
navigate(authorUsername)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleOnSourceBtnClick = (e: MouseEvent<HTMLDivElement>) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
navigate(
|
|
||||||
`/source/${authorUsername}/${toolId}${platform !== import.meta.env.VITE_PLATFORM ? `?platform=${platform}` : ''}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleOnAndroidBtnClick = (e: MouseEvent<HTMLDivElement>) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
void modal.info({
|
|
||||||
icon: <Icon style={{ color: COLOR_MAIN }} component={IconOxygenInfo} />,
|
|
||||||
title: 'Android 端',
|
|
||||||
centered: true,
|
|
||||||
maskClosable: true,
|
|
||||||
content: (
|
|
||||||
<FlexBox className={'android-qrcode'}>
|
|
||||||
<AntdQRCode
|
|
||||||
value={`oxygen://openurl/view/${authorUsername}/${toolId}`}
|
|
||||||
size={300}
|
|
||||||
/>
|
|
||||||
<AntdTag className={'tag'}>请使用手机端扫描上方二维码</AntdTag>
|
|
||||||
</FlexBox>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleOnDesktopBtnClick = (e: MouseEvent<HTMLDivElement>) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
if (!checkDesktop()) {
|
|
||||||
void message.loading({ content: '启动桌面端中……', key: 'LOADING', duration: 0 })
|
|
||||||
protocolCheck(
|
|
||||||
`oxygen://openurl/view/${authorUsername}/${toolId}`,
|
|
||||||
() => {
|
|
||||||
void message.warning('打开失败,此应用需要桌面端环境,请安装桌面端后重试')
|
|
||||||
void message.destroy('LOADING')
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
void message.destroy('LOADING')
|
|
||||||
},
|
|
||||||
2000,
|
|
||||||
() => {
|
|
||||||
void message.warning('打开失败,此应用需要桌面端环境,请安装桌面端后重试')
|
|
||||||
void message.destroy('LOADING')
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
navigate(
|
|
||||||
`/view/${authorUsername}/${toolId}${platform !== import.meta.env.VITE_PLATFORM ? `?platform=${platform}` : ''}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleOnWebBtnClick = (e: MouseEvent<HTMLDivElement>) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
navigate(
|
|
||||||
`/view/${authorUsername}/${toolId}${platform !== import.meta.env.VITE_PLATFORM ? `?platform=${platform}` : ''}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Card
|
|
||||||
style={{ overflow: 'visible', ...style }}
|
|
||||||
ref={cardRef}
|
|
||||||
{...props}
|
|
||||||
onClick={handleCardOnClick}
|
|
||||||
>
|
|
||||||
<FlexBox className={'common-card'}>
|
|
||||||
<div className={'icon'}>{icon}</div>
|
|
||||||
<div className={'version'}>
|
|
||||||
<AntdTag>
|
|
||||||
{platform.slice(0, 1)}-{ver}
|
|
||||||
</AntdTag>
|
|
||||||
</div>
|
|
||||||
<div className={'info'}>
|
|
||||||
<div className={'tool-name'}>{toolName}</div>
|
|
||||||
<div className={'tool-id'}>{`ID: ${toolId}`}</div>
|
|
||||||
{toolDesc && <div className={'tool-desc'}>{`简介:${toolDesc}`}</div>}
|
|
||||||
</div>
|
|
||||||
<div className={'author'} onClick={handleOnClickAuthor}>
|
|
||||||
<div className={'avatar'}>
|
|
||||||
<AntdAvatar
|
|
||||||
src={
|
|
||||||
<AntdImage
|
|
||||||
preview={false}
|
|
||||||
src={`data:image/png;base64,${authorAvatar}`}
|
|
||||||
alt={'Avatar'}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
style={{ background: COLOR_BACKGROUND }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<AntdTooltip title={authorUsername}>
|
|
||||||
<div className={'author-name'}>{authorName}</div>
|
|
||||||
</AntdTooltip>
|
|
||||||
</div>
|
|
||||||
<div className={'operation'}>
|
|
||||||
{platform !== 'ANDROID' && supportPlatform.includes('ANDROID') && (
|
|
||||||
<AntdTooltip title={'Android 端'}>
|
|
||||||
<Icon
|
|
||||||
component={IconOxygenMobile}
|
|
||||||
onClick={handleOnAndroidBtnClick}
|
|
||||||
/>
|
|
||||||
</AntdTooltip>
|
|
||||||
)}
|
|
||||||
{platform === 'DESKTOP' && supportPlatform.includes('WEB') && (
|
|
||||||
<AntdTooltip title={'Web 端'}>
|
|
||||||
<Icon component={IconOxygenBrowser} onClick={handleOnWebBtnClick} />
|
|
||||||
</AntdTooltip>
|
|
||||||
)}
|
|
||||||
{platform === 'WEB' && supportPlatform.includes('DESKTOP') && (
|
|
||||||
<AntdTooltip title={'桌面端'}>
|
|
||||||
<Icon
|
|
||||||
component={IconOxygenDesktop}
|
|
||||||
onClick={handleOnDesktopBtnClick}
|
|
||||||
/>
|
|
||||||
</AntdTooltip>
|
|
||||||
)}
|
|
||||||
<AntdTooltip title={'源码'}>
|
|
||||||
<Icon component={IconOxygenCode} onClick={handleOnSourceBtnClick} />
|
|
||||||
</AntdTooltip>
|
|
||||||
</div>
|
|
||||||
</FlexBox>
|
|
||||||
</Card>
|
|
||||||
{contextHolder}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface LoadMoreCardProps {
|
|
||||||
onClick: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const LoadMoreCard = ({ onClick }: LoadMoreCardProps) => {
|
|
||||||
const cardRef = useRef<HTMLDivElement>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
cardRef.current &&
|
|
||||||
VanillaTilt.init(cardRef.current, {
|
|
||||||
reverse: true,
|
|
||||||
max: 8,
|
|
||||||
glare: true,
|
|
||||||
['max-glare']: 0.3,
|
|
||||||
scale: 1.03
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card style={{ overflow: 'visible' }} ref={cardRef} onClick={onClick}>
|
|
||||||
<FlexBox className={'load-more-card'}>
|
|
||||||
<div className={'icon'}>
|
|
||||||
<Icon component={IconOxygenMore} />{' '}
|
|
||||||
</div>
|
|
||||||
<div className={'text'}>加载更多</div>
|
|
||||||
</FlexBox>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const Store = () => {
|
const Store = () => {
|
||||||
const scrollTopRef = useRef(0)
|
const scrollTopRef = useRef(0)
|
||||||
@@ -372,23 +132,17 @@ const Store = () => {
|
|||||||
: webTool || desktopTool) || androidTool
|
: webTool || desktopTool) || androidTool
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CommonCard
|
<StoreCard
|
||||||
key={firstTool!.id}
|
key={firstTool!.id}
|
||||||
icon={
|
icon={firstTool!.icon}
|
||||||
<img
|
|
||||||
src={`data:image/svg+xml;base64,${firstTool!.icon}`}
|
|
||||||
alt={'Icon'}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
toolName={firstTool!.name}
|
toolName={firstTool!.name}
|
||||||
toolId={firstTool!.toolId}
|
toolId={firstTool!.toolId}
|
||||||
toolDesc={firstTool!.description}
|
toolDesc={firstTool!.description}
|
||||||
authorName={firstTool!.author.userInfo.nickname}
|
author={firstTool!.author}
|
||||||
authorAvatar={firstTool!.author.userInfo.avatar}
|
|
||||||
authorUsername={firstTool!.author.username}
|
|
||||||
ver={firstTool!.ver}
|
ver={firstTool!.ver}
|
||||||
platform={firstTool!.platform}
|
platform={firstTool!.platform}
|
||||||
supportPlatform={tools.map((value) => value.platform)}
|
supportPlatform={tools.map((value) => value.platform)}
|
||||||
|
favorite={firstTool!.favorite}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -1,233 +1,20 @@
|
|||||||
import { DetailedHTMLProps, HTMLAttributes, MouseEvent, ReactNode } from 'react'
|
|
||||||
import VanillaTilt, { TiltOptions } from 'vanilla-tilt'
|
|
||||||
import protocolCheck from 'custom-protocol-check'
|
|
||||||
import Icon from '@ant-design/icons'
|
import Icon from '@ant-design/icons'
|
||||||
import '@/assets/css/pages/tools/user.scss'
|
import '@/assets/css/pages/tools/user.scss'
|
||||||
import {
|
import {
|
||||||
COLOR_BACKGROUND,
|
COLOR_BACKGROUND,
|
||||||
COLOR_MAIN,
|
|
||||||
DATABASE_NO_RECORD_FOUND,
|
DATABASE_NO_RECORD_FOUND,
|
||||||
DATABASE_SELECT_SUCCESS
|
DATABASE_SELECT_SUCCESS
|
||||||
} from '@/constants/common.constants'
|
} from '@/constants/common.constants'
|
||||||
import { checkDesktop } from '@/util/common'
|
import { checkDesktop } from '@/util/common'
|
||||||
|
import { navigateToRoot } from '@/util/navigation'
|
||||||
import { r_sys_user_info_get_basic } from '@/services/system'
|
import { r_sys_user_info_get_basic } from '@/services/system'
|
||||||
import { r_tool_store_get_by_username } from '@/services/tool'
|
import { r_tool_store_get_by_username } from '@/services/tool'
|
||||||
import FitFullscreen from '@/components/common/FitFullscreen'
|
import FitFullscreen from '@/components/common/FitFullscreen'
|
||||||
import HideScrollbar from '@/components/common/HideScrollbar'
|
import HideScrollbar from '@/components/common/HideScrollbar'
|
||||||
import Card from '@/components/common/Card'
|
import Card from '@/components/common/Card'
|
||||||
import FlexBox from '@/components/common/FlexBox'
|
import FlexBox from '@/components/common/FlexBox'
|
||||||
|
import StoreCard from '@/components/tools/StoreCard'
|
||||||
interface CommonCardProps
|
import LoadMoreCard from '@/components/tools/LoadMoreCard'
|
||||||
extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
|
|
||||||
icon: ReactNode
|
|
||||||
toolName: string
|
|
||||||
toolId: string
|
|
||||||
toolDesc: string
|
|
||||||
options?: TiltOptions
|
|
||||||
authorUsername: string
|
|
||||||
ver: string
|
|
||||||
platform: Platform
|
|
||||||
supportPlatform: Platform[]
|
|
||||||
}
|
|
||||||
|
|
||||||
const CommonCard = ({
|
|
||||||
style,
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
ref,
|
|
||||||
icon,
|
|
||||||
toolName,
|
|
||||||
toolId,
|
|
||||||
toolDesc,
|
|
||||||
options = {
|
|
||||||
reverse: true,
|
|
||||||
max: 8,
|
|
||||||
glare: true,
|
|
||||||
['max-glare']: 0.3,
|
|
||||||
scale: 1.03
|
|
||||||
},
|
|
||||||
authorUsername,
|
|
||||||
ver,
|
|
||||||
platform,
|
|
||||||
supportPlatform,
|
|
||||||
...props
|
|
||||||
}: CommonCardProps) => {
|
|
||||||
const navigate = useNavigate()
|
|
||||||
const [modal, contextHolder] = AntdModal.useModal()
|
|
||||||
const cardRef = useRef<HTMLDivElement>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
cardRef.current && VanillaTilt.init(cardRef.current, options)
|
|
||||||
}, [options])
|
|
||||||
|
|
||||||
const handleCardOnClick = () => {
|
|
||||||
if (!checkDesktop() && platform === 'DESKTOP') {
|
|
||||||
void message.warning('此应用需要桌面端环境,请在桌面端打开')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (platform === 'ANDROID') {
|
|
||||||
void modal.info({
|
|
||||||
icon: <Icon style={{ color: COLOR_MAIN }} component={IconOxygenInfo} />,
|
|
||||||
title: 'Android 端',
|
|
||||||
centered: true,
|
|
||||||
maskClosable: true,
|
|
||||||
content: (
|
|
||||||
<FlexBox className={'android-qrcode'}>
|
|
||||||
<AntdQRCode
|
|
||||||
value={`oxygen://openurl/view/${authorUsername}/${toolId}`}
|
|
||||||
size={300}
|
|
||||||
/>
|
|
||||||
<AntdTag className={'tag'}>请使用手机端扫描上方二维码</AntdTag>
|
|
||||||
</FlexBox>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
navigate(
|
|
||||||
`/view/${authorUsername}/${toolId}${platform !== import.meta.env.VITE_PLATFORM ? `?platform=${platform}` : ''}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleOnSourceBtnClick = (e: MouseEvent<HTMLDivElement>) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
navigate(
|
|
||||||
`/source/${authorUsername}/${toolId}${platform !== import.meta.env.VITE_PLATFORM ? `?platform=${platform}` : ''}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleOnAndroidBtnClick = (e: MouseEvent<HTMLDivElement>) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
void modal.info({
|
|
||||||
icon: <Icon style={{ color: COLOR_MAIN }} component={IconOxygenInfo} />,
|
|
||||||
title: 'Android 端',
|
|
||||||
centered: true,
|
|
||||||
maskClosable: true,
|
|
||||||
content: (
|
|
||||||
<FlexBox className={'android-qrcode'}>
|
|
||||||
<AntdQRCode
|
|
||||||
value={`oxygen://openurl/view/${authorUsername}/${toolId}`}
|
|
||||||
size={300}
|
|
||||||
/>
|
|
||||||
<AntdTag className={'tag'}>请使用手机端扫描上方二维码</AntdTag>
|
|
||||||
</FlexBox>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleOnDesktopBtnClick = (e: MouseEvent<HTMLDivElement>) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
if (!checkDesktop()) {
|
|
||||||
void message.loading({ content: '启动桌面端中……', key: 'LOADING', duration: 0 })
|
|
||||||
protocolCheck(
|
|
||||||
`oxygen://openurl/view/${authorUsername}/${toolId}`,
|
|
||||||
() => {
|
|
||||||
void message.warning('打开失败,此应用需要桌面端环境,请安装桌面端后重试')
|
|
||||||
void message.destroy('LOADING')
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
void message.destroy('LOADING')
|
|
||||||
},
|
|
||||||
2000,
|
|
||||||
() => {
|
|
||||||
void message.warning('打开失败,此应用需要桌面端环境,请安装桌面端后重试')
|
|
||||||
void message.destroy('LOADING')
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
navigate(
|
|
||||||
`/view/${authorUsername}/${toolId}${platform !== import.meta.env.VITE_PLATFORM ? `?platform=${platform}` : ''}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleOnWebBtnClick = (e: MouseEvent<HTMLDivElement>) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
navigate(
|
|
||||||
`/view/${authorUsername}/${toolId}${platform !== import.meta.env.VITE_PLATFORM ? `?platform=${platform}` : ''}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Card
|
|
||||||
style={{ overflow: 'visible', ...style }}
|
|
||||||
ref={cardRef}
|
|
||||||
{...props}
|
|
||||||
onClick={handleCardOnClick}
|
|
||||||
>
|
|
||||||
<FlexBox className={'common-card'}>
|
|
||||||
<div className={'icon'}>{icon}</div>
|
|
||||||
<div className={'version'}>
|
|
||||||
<AntdTag>
|
|
||||||
{platform.slice(0, 1)}-{ver}
|
|
||||||
</AntdTag>
|
|
||||||
</div>
|
|
||||||
<div className={'info'}>
|
|
||||||
<div className={'tool-name'}>{toolName}</div>
|
|
||||||
<div className={'tool-id'}>{`ID: ${toolId}`}</div>
|
|
||||||
{toolDesc && <div className={'tool-desc'}>{`简介:${toolDesc}`}</div>}
|
|
||||||
</div>
|
|
||||||
<div className={'operation'}>
|
|
||||||
{platform !== 'ANDROID' && supportPlatform.includes('ANDROID') && (
|
|
||||||
<AntdTooltip title={'Android 端'}>
|
|
||||||
<Icon
|
|
||||||
component={IconOxygenMobile}
|
|
||||||
onClick={handleOnAndroidBtnClick}
|
|
||||||
/>
|
|
||||||
</AntdTooltip>
|
|
||||||
)}
|
|
||||||
{platform === 'DESKTOP' && supportPlatform.includes('WEB') && (
|
|
||||||
<AntdTooltip title={'Web 端'}>
|
|
||||||
<Icon component={IconOxygenBrowser} onClick={handleOnWebBtnClick} />
|
|
||||||
</AntdTooltip>
|
|
||||||
)}
|
|
||||||
{platform === 'WEB' && supportPlatform.includes('DESKTOP') && (
|
|
||||||
<AntdTooltip title={'桌面端'}>
|
|
||||||
<Icon
|
|
||||||
component={IconOxygenDesktop}
|
|
||||||
onClick={handleOnDesktopBtnClick}
|
|
||||||
/>
|
|
||||||
</AntdTooltip>
|
|
||||||
)}
|
|
||||||
<AntdTooltip title={'源码'}>
|
|
||||||
<Icon component={IconOxygenCode} onClick={handleOnSourceBtnClick} />
|
|
||||||
</AntdTooltip>
|
|
||||||
</div>
|
|
||||||
</FlexBox>
|
|
||||||
</Card>
|
|
||||||
{contextHolder}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface LoadMoreCardProps {
|
|
||||||
onClick: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const LoadMoreCard = ({ onClick }: LoadMoreCardProps) => {
|
|
||||||
const cardRef = useRef<HTMLDivElement>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
cardRef.current &&
|
|
||||||
VanillaTilt.init(cardRef.current, {
|
|
||||||
reverse: true,
|
|
||||||
max: 8,
|
|
||||||
glare: true,
|
|
||||||
['max-glare']: 0.3,
|
|
||||||
scale: 1.03
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card style={{ overflow: 'visible' }} ref={cardRef} onClick={onClick}>
|
|
||||||
<FlexBox className={'load-more-card'}>
|
|
||||||
<div className={'icon'}>
|
|
||||||
<Icon component={IconOxygenMore} />{' '}
|
|
||||||
</div>
|
|
||||||
<div className={'text'}>加载更多</div>
|
|
||||||
</FlexBox>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const User = () => {
|
const User = () => {
|
||||||
const { username } = useParams()
|
const { username } = useParams()
|
||||||
@@ -269,7 +56,7 @@ const User = () => {
|
|||||||
case DATABASE_NO_RECORD_FOUND:
|
case DATABASE_NO_RECORD_FOUND:
|
||||||
void message.warning('用户不存在')
|
void message.warning('用户不存在')
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
navigate('/')
|
navigateToRoot(navigate)
|
||||||
}, 3000)
|
}, 3000)
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
@@ -408,21 +195,18 @@ const User = () => {
|
|||||||
: webTool || desktopTool) || androidTool
|
: webTool || desktopTool) || androidTool
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CommonCard
|
<StoreCard
|
||||||
key={firstTool!.id}
|
key={firstTool!.id}
|
||||||
icon={
|
icon={firstTool!.icon}
|
||||||
<img
|
|
||||||
src={`data:image/svg+xml;base64,${firstTool!.icon}`}
|
|
||||||
alt={'Icon'}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
toolName={firstTool!.name}
|
toolName={firstTool!.name}
|
||||||
toolId={firstTool!.toolId}
|
toolId={firstTool!.toolId}
|
||||||
toolDesc={firstTool!.description}
|
toolDesc={firstTool!.description}
|
||||||
authorUsername={firstTool!.author.username}
|
author={firstTool!.author}
|
||||||
|
showAuthor={false}
|
||||||
ver={firstTool!.ver}
|
ver={firstTool!.ver}
|
||||||
platform={firstTool!.platform}
|
platform={firstTool!.platform}
|
||||||
supportPlatform={tools.map((value) => value.platform)}
|
supportPlatform={tools.map((value) => value.platform)}
|
||||||
|
favorite={firstTool!.favorite}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import '@/assets/css/pages/tools/view.scss'
|
import '@/assets/css/pages/tools/view.scss'
|
||||||
import { DATABASE_NO_RECORD_FOUND, DATABASE_SELECT_SUCCESS } from '@/constants/common.constants'
|
import { DATABASE_NO_RECORD_FOUND, DATABASE_SELECT_SUCCESS } from '@/constants/common.constants'
|
||||||
import { getLoginStatus } from '@/util/auth'
|
import { getLoginStatus } from '@/util/auth'
|
||||||
|
import { navigateToRepository, navigateToRoot, navigateToView } from '@/util/navigation'
|
||||||
import { r_tool_detail } from '@/services/tool'
|
import { r_tool_detail } from '@/services/tool'
|
||||||
import compiler from '@/components/Playground/compiler'
|
import compiler from '@/components/Playground/compiler'
|
||||||
import { IImportMap } from '@/components/Playground/shared'
|
import { IImportMap } from '@/components/Playground/shared'
|
||||||
@@ -31,7 +32,10 @@ const View = () => {
|
|||||||
.compile(files, importMap, toolVo.entryPoint)
|
.compile(files, importMap, toolVo.entryPoint)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
const output = result.outputFiles[0].text
|
const output = result.outputFiles[0].text
|
||||||
|
setCompiledCode('')
|
||||||
|
setTimeout(() => {
|
||||||
setCompiledCode(`${output}\n${baseDist}`)
|
setCompiledCode(`${output}\n${baseDist}`)
|
||||||
|
}, 100)
|
||||||
})
|
})
|
||||||
.catch((reason) => {
|
.catch((reason) => {
|
||||||
void message.error(`编译失败:${reason}`)
|
void message.error(`编译失败:${reason}`)
|
||||||
@@ -43,7 +47,10 @@ const View = () => {
|
|||||||
try {
|
try {
|
||||||
const baseDist = base64ToStr(toolVo.base.dist.data!)
|
const baseDist = base64ToStr(toolVo.base.dist.data!)
|
||||||
const dist = base64ToStr(toolVo.dist.data!)
|
const dist = base64ToStr(toolVo.dist.data!)
|
||||||
|
setCompiledCode('')
|
||||||
|
setTimeout(() => {
|
||||||
setCompiledCode(`${dist}\n${baseDist}`)
|
setCompiledCode(`${dist}\n${baseDist}`)
|
||||||
|
}, 100)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
void message.error('载入工具失败')
|
void message.error('载入工具失败')
|
||||||
}
|
}
|
||||||
@@ -71,9 +78,7 @@ const View = () => {
|
|||||||
break
|
break
|
||||||
case DATABASE_NO_RECORD_FOUND:
|
case DATABASE_NO_RECORD_FOUND:
|
||||||
void message.error('未找到指定工具')
|
void message.error('未找到指定工具')
|
||||||
setTimeout(() => {
|
navigateToRepository(navigate)
|
||||||
navigate('/')
|
|
||||||
}, 3000)
|
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
void message.error('获取工具信息失败,请稍后重试')
|
void message.error('获取工具信息失败,请稍后重试')
|
||||||
@@ -86,26 +91,26 @@ const View = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const platform = searchParams.get('platform')!
|
||||||
|
if (!['WEB', 'DESKTOP', 'ANDROID'].includes(platform)) {
|
||||||
|
navigateToRepository(navigate)
|
||||||
|
return
|
||||||
|
}
|
||||||
if (username === '!' && !getLoginStatus()) {
|
if (username === '!' && !getLoginStatus()) {
|
||||||
setTimeout(() => {
|
void message.error('未登录')
|
||||||
navigate('/')
|
navigateToRoot(navigate)
|
||||||
}, 3000)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (username !== '!' && ver) {
|
if (username !== '!' && ver) {
|
||||||
navigate(`/view/${username}/${toolId}`)
|
navigateToView(navigate, username!, toolId!, platform as Platform)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (username === '!' && !ver) {
|
if (username === '!' && !ver) {
|
||||||
navigate(`/view/!/${toolId}/latest`)
|
navigateToView(navigate, '!', toolId!, platform as Platform, 'latest')
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!['WEB', 'DESKTOP', 'ANDROID'].includes(searchParams.get('platform')!)) {
|
|
||||||
navigate('/')
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
getTool()
|
getTool()
|
||||||
}, [])
|
}, [username, toolId, ver, searchParams])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FitFullscreen data-component={'tools-view'}>
|
<FitFullscreen data-component={'tools-view'}>
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { DetailedHTMLProps, HTMLAttributes, ReactNode } from 'react'
|
|
||||||
import Icon from '@ant-design/icons'
|
import Icon from '@ant-design/icons'
|
||||||
import VanillaTilt, { TiltOptions } from 'vanilla-tilt'
|
|
||||||
import '@/assets/css/pages/tools/index.scss'
|
import '@/assets/css/pages/tools/index.scss'
|
||||||
import {
|
import {
|
||||||
DATABASE_DELETE_SUCCESS,
|
DATABASE_DELETE_SUCCESS,
|
||||||
@@ -16,116 +14,22 @@ import {
|
|||||||
} from '@/constants/common.constants'
|
} from '@/constants/common.constants'
|
||||||
import { checkDesktop } from '@/util/common'
|
import { checkDesktop } from '@/util/common'
|
||||||
import { getLoginStatus } from '@/util/auth'
|
import { getLoginStatus } from '@/util/auth'
|
||||||
|
import { navigateToEdit, navigateToSource, navigateToView } from '@/util/navigation'
|
||||||
import {
|
import {
|
||||||
r_tool_cancel,
|
r_tool_cancel,
|
||||||
r_tool_delete,
|
r_tool_delete,
|
||||||
r_tool_get,
|
r_tool_get,
|
||||||
|
r_tool_get_favorite,
|
||||||
r_tool_submit,
|
r_tool_submit,
|
||||||
r_tool_upgrade
|
r_tool_upgrade
|
||||||
} from '@/services/tool'
|
} from '@/services/tool'
|
||||||
import FitFullscreen from '@/components/common/FitFullscreen'
|
import FitFullscreen from '@/components/common/FitFullscreen'
|
||||||
import HideScrollbar from '@/components/common/HideScrollbar'
|
import HideScrollbar from '@/components/common/HideScrollbar'
|
||||||
import FlexBox from '@/components/common/FlexBox'
|
import FlexBox from '@/components/common/FlexBox'
|
||||||
import Card from '@/components/common/Card'
|
import RepositoryCard from '@/components/tools/RepositoryCard'
|
||||||
|
import LoadMoreCard from '@/components/tools/LoadMoreCard'
|
||||||
interface CommonCardProps
|
import StoreCard from '@/components/tools/StoreCard'
|
||||||
extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
|
import UrlCard from '@/components/common/UrlCard'
|
||||||
icon: ReactNode
|
|
||||||
toolName?: string
|
|
||||||
toolId?: string
|
|
||||||
options?: TiltOptions
|
|
||||||
url?: string
|
|
||||||
onOpen?: () => void
|
|
||||||
onEdit?: () => void
|
|
||||||
onSource?: () => void
|
|
||||||
onPublish?: () => void
|
|
||||||
onCancelReview?: () => void
|
|
||||||
onDelete?: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const CommonCard = ({
|
|
||||||
style,
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
ref,
|
|
||||||
icon,
|
|
||||||
toolName,
|
|
||||||
toolId,
|
|
||||||
options = {
|
|
||||||
reverse: true,
|
|
||||||
max: 8,
|
|
||||||
glare: true,
|
|
||||||
['max-glare']: 0.3,
|
|
||||||
scale: 1.03
|
|
||||||
},
|
|
||||||
url,
|
|
||||||
onOpen,
|
|
||||||
onEdit,
|
|
||||||
onSource,
|
|
||||||
onPublish,
|
|
||||||
onCancelReview,
|
|
||||||
onDelete,
|
|
||||||
children,
|
|
||||||
...props
|
|
||||||
}: CommonCardProps) => {
|
|
||||||
const navigate = useNavigate()
|
|
||||||
const cardRef = useRef<HTMLDivElement>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
cardRef.current && VanillaTilt.init(cardRef.current, options)
|
|
||||||
}, [options])
|
|
||||||
|
|
||||||
const handleCardOnClick = () => {
|
|
||||||
url && navigate(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card
|
|
||||||
style={{ overflow: 'visible', ...style }}
|
|
||||||
ref={cardRef}
|
|
||||||
{...props}
|
|
||||||
onClick={handleCardOnClick}
|
|
||||||
>
|
|
||||||
<FlexBox className={'common-card'}>
|
|
||||||
<div className={'icon'}>{icon}</div>
|
|
||||||
<div className={'info'}>
|
|
||||||
{toolName && <div className={'tool-name'}>{toolName}</div>}
|
|
||||||
{toolId && <div className={'tool-id'}>{`ID: ${toolId}`}</div>}
|
|
||||||
</div>
|
|
||||||
<div className={'operation'}>
|
|
||||||
{onOpen && (
|
|
||||||
<AntdButton onClick={onOpen} size={'small'} type={'primary'}>
|
|
||||||
打开
|
|
||||||
</AntdButton>
|
|
||||||
)}
|
|
||||||
{onEdit && onPublish && (
|
|
||||||
<div className={'edit'}>
|
|
||||||
<AntdButton.Group size={'small'}>
|
|
||||||
<AntdButton onClick={onEdit}>编辑</AntdButton>
|
|
||||||
<AntdButton onClick={onPublish}>发布</AntdButton>
|
|
||||||
</AntdButton.Group>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{onSource && (
|
|
||||||
<AntdButton size={'small'} onClick={onSource}>
|
|
||||||
源码
|
|
||||||
</AntdButton>
|
|
||||||
)}
|
|
||||||
{onCancelReview && (
|
|
||||||
<AntdButton size={'small'} onClick={onCancelReview}>
|
|
||||||
取消审核
|
|
||||||
</AntdButton>
|
|
||||||
)}
|
|
||||||
{onDelete && (
|
|
||||||
<AntdButton size={'small'} danger onClick={onDelete}>
|
|
||||||
删除
|
|
||||||
</AntdButton>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{children}
|
|
||||||
</FlexBox>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ToolCardProps {
|
interface ToolCardProps {
|
||||||
tools: ToolVo[]
|
tools: ToolVo[]
|
||||||
@@ -145,8 +49,12 @@ const ToolCard = ({ tools, onDelete, onUpgrade, onSubmit, onCancel }: ToolCardPr
|
|||||||
|
|
||||||
const handleOnOpenTool = () => {
|
const handleOnOpenTool = () => {
|
||||||
if (checkDesktop() || selectedTool.platform !== 'DESKTOP') {
|
if (checkDesktop() || selectedTool.platform !== 'DESKTOP') {
|
||||||
navigate(
|
navigateToView(
|
||||||
`/view/!/${selectedTool.toolId}/${selectedTool.ver}${selectedTool.platform !== import.meta.env.VITE_PLATFORM ? `?platform=${selectedTool.platform}` : ''}`
|
navigate,
|
||||||
|
'!',
|
||||||
|
selectedTool.toolId,
|
||||||
|
selectedTool.platform,
|
||||||
|
selectedTool.ver
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
void message.warning('此应用需要桌面端环境,请在桌面端打开')
|
void message.warning('此应用需要桌面端环境,请在桌面端打开')
|
||||||
@@ -157,9 +65,7 @@ const ToolCard = ({ tools, onDelete, onUpgrade, onSubmit, onCancel }: ToolCardPr
|
|||||||
if (['NONE', 'REJECT'].includes(selectedTool.review)) {
|
if (['NONE', 'REJECT'].includes(selectedTool.review)) {
|
||||||
return () => {
|
return () => {
|
||||||
if (checkDesktop() || selectedTool.platform !== 'DESKTOP') {
|
if (checkDesktop() || selectedTool.platform !== 'DESKTOP') {
|
||||||
navigate(
|
navigateToEdit(navigate, selectedTool.toolId, selectedTool.platform)
|
||||||
`/edit/${selectedTool.toolId}${selectedTool.platform !== import.meta.env.VITE_PLATFORM ? `?platform=${selectedTool.platform}` : ''}`
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
void message.warning('此应用需要桌面端环境,请在桌面端编辑')
|
void message.warning('此应用需要桌面端环境,请在桌面端编辑')
|
||||||
}
|
}
|
||||||
@@ -171,8 +77,12 @@ const ToolCard = ({ tools, onDelete, onUpgrade, onSubmit, onCancel }: ToolCardPr
|
|||||||
const handleOnSourceTool = () => {
|
const handleOnSourceTool = () => {
|
||||||
if (selectedTool.review === 'PASS') {
|
if (selectedTool.review === 'PASS') {
|
||||||
return () => {
|
return () => {
|
||||||
navigate(
|
navigateToSource(
|
||||||
`/source/!/${selectedTool.toolId}/${selectedTool.ver}${selectedTool.platform !== import.meta.env.VITE_PLATFORM ? `?platform=${selectedTool.platform}` : ''}`
|
navigate,
|
||||||
|
'!',
|
||||||
|
selectedTool.toolId,
|
||||||
|
selectedTool.platform,
|
||||||
|
selectedTool.ver
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -255,10 +165,12 @@ const ToolCard = ({ tools, onDelete, onUpgrade, onSubmit, onCancel }: ToolCardPr
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CommonCard
|
<RepositoryCard
|
||||||
icon={<img src={`data:image/svg+xml;base64,${selectedTool.icon}`} alt={'Icon'} />}
|
icon={selectedTool.icon}
|
||||||
toolName={selectedTool.name}
|
toolName={selectedTool.name}
|
||||||
toolId={selectedTool.toolId}
|
toolId={selectedTool.toolId}
|
||||||
|
ver={selectedTool.ver}
|
||||||
|
platform={selectedTool.platform}
|
||||||
onOpen={handleOnOpenTool}
|
onOpen={handleOnOpenTool}
|
||||||
onEdit={handleOnEditTool()}
|
onEdit={handleOnEditTool()}
|
||||||
onSource={handleOnSourceTool()}
|
onSource={handleOnSourceTool()}
|
||||||
@@ -289,7 +201,7 @@ const ToolCard = ({ tools, onDelete, onUpgrade, onSubmit, onCancel }: ToolCardPr
|
|||||||
/>
|
/>
|
||||||
</AntdTooltip>
|
</AntdTooltip>
|
||||||
)}
|
)}
|
||||||
</CommonCard>
|
</RepositoryCard>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,14 +209,20 @@ const Tools = () => {
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [modal, contextHolder] = AntdModal.useModal()
|
const [modal, contextHolder] = AntdModal.useModal()
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [toolData, setToolData] = useState<ToolVo[]>()
|
const [currentPage, setCurrentPage] = useState(0)
|
||||||
|
const [hasNextPage, setHasNextPage] = useState(false)
|
||||||
|
const [toolData, setToolData] = useState<ToolVo[]>([])
|
||||||
const [upgradeForm] = AntdForm.useForm<ToolUpgradeParam>()
|
const [upgradeForm] = AntdForm.useForm<ToolUpgradeParam>()
|
||||||
|
const [currentStarPage, setCurrentStarPage] = useState(0)
|
||||||
|
const [hasNextStarPage, setHasNextStarPage] = useState(false)
|
||||||
|
const [starToolData, setStarToolData] = useState<ToolVo[]>([])
|
||||||
|
|
||||||
const handleOnDeleteTool = (tool: ToolVo) => {
|
const handleOnDeleteTool = (tool: ToolVo) => {
|
||||||
modal
|
modal
|
||||||
.confirm({
|
.confirm({
|
||||||
title: '删除',
|
centered: true,
|
||||||
maskClosable: true,
|
maskClosable: true,
|
||||||
|
title: '删除',
|
||||||
content: `确定删除工具 ${tool.toolId}:${tool.platform.slice(0, 1)}${tool.platform.slice(1).toLowerCase()}:${tool.ver} 吗?`
|
content: `确定删除工具 ${tool.toolId}:${tool.platform.slice(0, 1)}${tool.platform.slice(1).toLowerCase()}:${tool.ver} 吗?`
|
||||||
})
|
})
|
||||||
.then(
|
.then(
|
||||||
@@ -317,7 +235,7 @@ const Tools = () => {
|
|||||||
const response = res.data
|
const response = res.data
|
||||||
if (response.code === DATABASE_DELETE_SUCCESS) {
|
if (response.code === DATABASE_DELETE_SUCCESS) {
|
||||||
void message.success('删除成功')
|
void message.success('删除成功')
|
||||||
getTool()
|
getTool(1)
|
||||||
} else {
|
} else {
|
||||||
void message.error('删除失败,请稍后重试')
|
void message.error('删除失败,请稍后重试')
|
||||||
}
|
}
|
||||||
@@ -333,9 +251,9 @@ const Tools = () => {
|
|||||||
|
|
||||||
const handleOnUpgradeTool = (tool: ToolVo) => {
|
const handleOnUpgradeTool = (tool: ToolVo) => {
|
||||||
void modal.confirm({
|
void modal.confirm({
|
||||||
title: '更新工具',
|
|
||||||
centered: true,
|
centered: true,
|
||||||
maskClosable: true,
|
maskClosable: true,
|
||||||
|
title: '更新工具',
|
||||||
footer: (_, { OkBtn, CancelBtn }) => (
|
footer: (_, { OkBtn, CancelBtn }) => (
|
||||||
<>
|
<>
|
||||||
<OkBtn />
|
<OkBtn />
|
||||||
@@ -406,8 +324,10 @@ const Tools = () => {
|
|||||||
checkDesktop() ||
|
checkDesktop() ||
|
||||||
response.data!.platform !== 'DESKTOP'
|
response.data!.platform !== 'DESKTOP'
|
||||||
) {
|
) {
|
||||||
navigate(
|
navigateToEdit(
|
||||||
`/edit/${response.data!.toolId}${response.data!.platform !== import.meta.env.VITE_PLATFORM ? `?platform=${response.data!.platform}` : ''}`
|
navigate,
|
||||||
|
response.data!.toolId,
|
||||||
|
response.data!.platform
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
resolve()
|
resolve()
|
||||||
@@ -443,8 +363,9 @@ const Tools = () => {
|
|||||||
const handleOnSubmitTool = (tool: ToolVo) => {
|
const handleOnSubmitTool = (tool: ToolVo) => {
|
||||||
modal
|
modal
|
||||||
.confirm({
|
.confirm({
|
||||||
title: '提交审核',
|
centered: true,
|
||||||
maskClosable: true,
|
maskClosable: true,
|
||||||
|
title: '提交审核',
|
||||||
content: `确定提交审核工具 ${tool.name}:${tool.ver} 吗?`
|
content: `确定提交审核工具 ${tool.name}:${tool.ver} 吗?`
|
||||||
})
|
})
|
||||||
.then(
|
.then(
|
||||||
@@ -458,7 +379,7 @@ const Tools = () => {
|
|||||||
switch (response.code) {
|
switch (response.code) {
|
||||||
case TOOL_SUBMIT_SUCCESS:
|
case TOOL_SUBMIT_SUCCESS:
|
||||||
void message.success('提交审核成功')
|
void message.success('提交审核成功')
|
||||||
getTool()
|
getTool(1)
|
||||||
break
|
break
|
||||||
case TOOL_UNDER_REVIEW:
|
case TOOL_UNDER_REVIEW:
|
||||||
void message.warning('工具审核中,请勿重复提交')
|
void message.warning('工具审核中,请勿重复提交')
|
||||||
@@ -482,8 +403,9 @@ const Tools = () => {
|
|||||||
const handleOnCancelTool = (tool: ToolVo) => {
|
const handleOnCancelTool = (tool: ToolVo) => {
|
||||||
modal
|
modal
|
||||||
.confirm({
|
.confirm({
|
||||||
title: '取消审核',
|
centered: true,
|
||||||
maskClosable: true,
|
maskClosable: true,
|
||||||
|
title: '取消审核',
|
||||||
content: `确定取消审核工具 ${tool.name}:${tool.ver} 吗?`
|
content: `确定取消审核工具 ${tool.name}:${tool.ver} 吗?`
|
||||||
})
|
})
|
||||||
.then(
|
.then(
|
||||||
@@ -510,7 +432,7 @@ const Tools = () => {
|
|||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
getTool()
|
getTool(1)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -518,25 +440,93 @@ const Tools = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTool = () => {
|
const handleOnLoadMore = () => {
|
||||||
|
if (loading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
getTool(currentPage + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOnLoadMoreStar = () => {
|
||||||
|
if (loading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
getStarTool(currentStarPage + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTool = (page: number) => {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
void message.loading({ content: '加载工具列表中', key: 'LOADING', duration: 0 })
|
void message.loading({ content: '加载工具列表中', key: 'LOADING', duration: 0 })
|
||||||
|
|
||||||
void r_tool_get()
|
void r_tool_get({ currentPage: page })
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
const response = res.data
|
const response = res.data
|
||||||
|
|
||||||
switch (response.code) {
|
switch (response.code) {
|
||||||
case DATABASE_SELECT_SUCCESS:
|
case DATABASE_SELECT_SUCCESS:
|
||||||
setToolData(response.data!)
|
setCurrentPage(response.data!.current)
|
||||||
|
if (
|
||||||
|
response.data!.current === response.data!.pages ||
|
||||||
|
response.data!.total === 0
|
||||||
|
) {
|
||||||
|
setHasNextPage(false)
|
||||||
|
} else {
|
||||||
|
setHasNextPage(true)
|
||||||
|
}
|
||||||
|
if (response.data!.current === 1) {
|
||||||
|
setToolData(response.data!.records)
|
||||||
|
} else {
|
||||||
|
setToolData([...toolData, ...response.data!.records])
|
||||||
|
}
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
void message.error('获取工具失败,请稍后重试')
|
void message.error('获取工具失败,请稍后重试')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false)
|
||||||
|
message.destroy('LOADING')
|
||||||
|
if (currentStarPage === 0) {
|
||||||
|
getStarTool(1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStarTool = (page: number) => {
|
||||||
|
if (loading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setLoading(true)
|
||||||
|
void message.loading({ content: '加载收藏列表中', key: 'LOADING', duration: 0 })
|
||||||
|
|
||||||
|
void r_tool_get_favorite({ currentPage: page })
|
||||||
|
.then((res) => {
|
||||||
|
const response = res.data
|
||||||
|
|
||||||
|
switch (response.code) {
|
||||||
|
case DATABASE_SELECT_SUCCESS:
|
||||||
|
setCurrentStarPage(response.data!.current)
|
||||||
|
if (
|
||||||
|
response.data!.current === response.data!.pages ||
|
||||||
|
response.data!.total === 0
|
||||||
|
) {
|
||||||
|
setHasNextStarPage(false)
|
||||||
|
} else {
|
||||||
|
setHasNextStarPage(true)
|
||||||
|
}
|
||||||
|
if (response.data!.current === 1) {
|
||||||
|
setStarToolData(response.data!.records)
|
||||||
|
} else {
|
||||||
|
setStarToolData([...starToolData, ...response.data!.records])
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
void message.error('加载失败,请稍后重试')
|
||||||
|
}
|
||||||
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
message.destroy('LOADING')
|
message.destroy('LOADING')
|
||||||
@@ -547,19 +537,18 @@ const Tools = () => {
|
|||||||
if (!getLoginStatus()) {
|
if (!getLoginStatus()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
getTool()
|
getTool(1)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FitFullscreen data-component={'tools'}>
|
<FitFullscreen data-component={'tools'}>
|
||||||
<HideScrollbar isShowVerticalScrollbar autoHideWaitingTime={1000}>
|
<HideScrollbar isShowVerticalScrollbar autoHideWaitingTime={1000}>
|
||||||
<FlexBox direction={'horizontal'} className={'root-content'}>
|
<FlexBox direction={'vertical'} className={'root-content'}>
|
||||||
<CommonCard
|
<FlexBox direction={'horizontal'} className={'own-content'}>
|
||||||
icon={<Icon component={IconOxygenNewProject} />}
|
<UrlCard icon={IconOxygenNewProject} url={'/create'}>
|
||||||
toolName={'创建工具'}
|
创建工具
|
||||||
url={'/create'}
|
</UrlCard>
|
||||||
/>
|
|
||||||
{toolData &&
|
{toolData &&
|
||||||
Object.values(
|
Object.values(
|
||||||
toolData.reduce((result: Record<string, ToolVo[]>, item) => {
|
toolData.reduce((result: Record<string, ToolVo[]>, item) => {
|
||||||
@@ -577,6 +566,73 @@ const Tools = () => {
|
|||||||
onCancel={handleOnCancelTool}
|
onCancel={handleOnCancelTool}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
{hasNextPage && <LoadMoreCard onClick={handleOnLoadMore} />}
|
||||||
|
</FlexBox>
|
||||||
|
{starToolData.length ? (
|
||||||
|
<>
|
||||||
|
<FlexBox className={'favorite-divider'}>
|
||||||
|
<div />
|
||||||
|
<div className={'divider-text'}>收藏</div>
|
||||||
|
<div />
|
||||||
|
</FlexBox>
|
||||||
|
<FlexBox direction={'horizontal'} className={'star-content'}>
|
||||||
|
{starToolData
|
||||||
|
?.reduce((previousValue: ToolVo[], currentValue) => {
|
||||||
|
if (
|
||||||
|
!previousValue.some(
|
||||||
|
(value) =>
|
||||||
|
value.author.id ===
|
||||||
|
currentValue.author.id &&
|
||||||
|
value.toolId === currentValue.toolId
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
previousValue.push(currentValue)
|
||||||
|
}
|
||||||
|
return previousValue
|
||||||
|
}, [])
|
||||||
|
.map((item) => {
|
||||||
|
const tools = starToolData.filter(
|
||||||
|
(value) =>
|
||||||
|
value.author.id === item.author.id &&
|
||||||
|
value.toolId === item.toolId
|
||||||
|
)
|
||||||
|
const webTool = tools.find(
|
||||||
|
(value) => value.platform === 'WEB'
|
||||||
|
)
|
||||||
|
const desktopTool = tools.find(
|
||||||
|
(value) => value.platform === 'DESKTOP'
|
||||||
|
)
|
||||||
|
const androidTool = tools.find(
|
||||||
|
(value) => value.platform === 'ANDROID'
|
||||||
|
)
|
||||||
|
const firstTool =
|
||||||
|
(checkDesktop()
|
||||||
|
? desktopTool || webTool
|
||||||
|
: webTool || desktopTool) || androidTool
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StoreCard
|
||||||
|
key={firstTool!.id}
|
||||||
|
icon={firstTool!.icon}
|
||||||
|
toolName={firstTool!.name}
|
||||||
|
toolId={firstTool!.toolId}
|
||||||
|
toolDesc={firstTool!.description}
|
||||||
|
author={firstTool!.author}
|
||||||
|
ver={firstTool!.ver}
|
||||||
|
platform={firstTool!.platform}
|
||||||
|
supportPlatform={tools.map(
|
||||||
|
(value) => value.platform
|
||||||
|
)}
|
||||||
|
favorite={firstTool!.favorite}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
{hasNextStarPage && (
|
||||||
|
<LoadMoreCard onClick={handleOnLoadMoreStar} />
|
||||||
|
)}
|
||||||
|
</FlexBox>
|
||||||
|
</>
|
||||||
|
) : undefined}
|
||||||
</FlexBox>
|
</FlexBox>
|
||||||
</HideScrollbar>
|
</HideScrollbar>
|
||||||
</FitFullscreen>
|
</FitFullscreen>
|
||||||
|
|||||||
@@ -1,57 +1,181 @@
|
|||||||
|
import { DndContext, DragOverEvent, DragStartEvent } from '@dnd-kit/core'
|
||||||
|
import { arrayMove, SortableContext } from '@dnd-kit/sortable'
|
||||||
|
import type { DragEndEvent } from '@dnd-kit/core/dist/types'
|
||||||
import '@/assets/css/pages/tools-framework.scss'
|
import '@/assets/css/pages/tools-framework.scss'
|
||||||
import { tools } from '@/router/tools'
|
import { tools } from '@/router/tools'
|
||||||
|
import { checkDesktop, getToolMenuItem, saveToolMenuItem } from '@/util/common'
|
||||||
|
import { getViewPath } from '@/util/navigation'
|
||||||
import FitFullscreen from '@/components/common/FitFullscreen'
|
import FitFullscreen from '@/components/common/FitFullscreen'
|
||||||
import Sidebar from '@/components/common/Sidebar'
|
import Sidebar from '@/components/common/Sidebar'
|
||||||
import FullscreenLoadingMask from '@/components/common/FullscreenLoadingMask'
|
import FullscreenLoadingMask from '@/components/common/FullscreenLoadingMask'
|
||||||
|
import Sortable from '@/components/dnd/Sortable'
|
||||||
|
import DragHandle from '@/components/dnd/DragHandle'
|
||||||
|
import DraggableOverlay from '@/components/dnd/DraggableOverlay'
|
||||||
|
import DropMask from '@/components/dnd/DropMask'
|
||||||
|
import Droppable from '@/components/dnd/Droppable'
|
||||||
|
|
||||||
const ToolsFramework = () => {
|
const ToolsFramework = () => {
|
||||||
|
const [isDelete, setIsDelete] = useState(false)
|
||||||
|
const [toolMenuItem, setToolMenuItem] = useState<ToolMenuItem[]>(getToolMenuItem)
|
||||||
|
const [activeItem, setActiveItem] = useState<ToolMenuItem | null>(null)
|
||||||
|
const [showDropMask, setShowDropMask] = useState(false)
|
||||||
|
|
||||||
|
const handleOnDragStart = ({ active }: DragStartEvent) => {
|
||||||
|
setActiveItem(active.data.current as ToolMenuItem)
|
||||||
|
if (!active.data.current?.sortable) {
|
||||||
|
setShowDropMask(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOnDragOver = ({ over }: DragOverEvent) => {
|
||||||
|
setIsDelete(over === null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOnDragEnd = ({ active, over }: DragEndEvent) => {
|
||||||
|
if (over && active.id !== over?.id) {
|
||||||
|
const activeIndex = toolMenuItem.findIndex(
|
||||||
|
({ authorUsername, toolId, ver, platform }) =>
|
||||||
|
`${authorUsername}:${toolId}:${ver}:${platform}` === active.id
|
||||||
|
)
|
||||||
|
const overIndex = toolMenuItem.findIndex(
|
||||||
|
({ authorUsername, toolId, ver, platform }) =>
|
||||||
|
`${authorUsername}:${toolId}:${ver}:${platform}` === over.id
|
||||||
|
)
|
||||||
|
setToolMenuItem(arrayMove(toolMenuItem, activeIndex, overIndex))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!over) {
|
||||||
|
setToolMenuItem(
|
||||||
|
toolMenuItem.filter(
|
||||||
|
({ authorUsername, toolId, ver, platform }) =>
|
||||||
|
`${authorUsername}:${toolId}:${ver}:${platform}` !== active?.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!active.data.current?.sortable && over) {
|
||||||
|
const newItem = active.data.current as ToolMenuItem
|
||||||
|
if (
|
||||||
|
toolMenuItem.findIndex(
|
||||||
|
({ authorUsername, toolId, ver }) =>
|
||||||
|
authorUsername === newItem.authorUsername &&
|
||||||
|
toolId === newItem.toolId &&
|
||||||
|
ver === newItem.ver
|
||||||
|
) === -1
|
||||||
|
) {
|
||||||
|
if (!checkDesktop() && newItem.platform === 'DESKTOP') {
|
||||||
|
void message.warning('暂不支持添加桌面端应用')
|
||||||
|
setToolMenuItem(toolMenuItem)
|
||||||
|
} else {
|
||||||
|
setToolMenuItem([...toolMenuItem, newItem])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setActiveItem(null)
|
||||||
|
setShowDropMask(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOnDragCancel = () => {
|
||||||
|
setActiveItem(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
saveToolMenuItem(toolMenuItem)
|
||||||
|
}, [toolMenuItem])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FitFullscreen data-component={'tools-framework'} className={'flex-horizontal'}>
|
<FitFullscreen data-component={'tools-framework'} className={'flex-horizontal'}>
|
||||||
|
<DndContext
|
||||||
|
onDragStart={handleOnDragStart}
|
||||||
|
onDragOver={handleOnDragOver}
|
||||||
|
onDragEnd={handleOnDragEnd}
|
||||||
|
onDragCancel={handleOnDragCancel}
|
||||||
|
>
|
||||||
<div className={'left-panel'}>
|
<div className={'left-panel'}>
|
||||||
<Sidebar title={'氧工具'}>
|
<Sidebar title={'氧工具'}>
|
||||||
<Sidebar.ItemList>
|
<Sidebar.ItemList>
|
||||||
<Sidebar.Item
|
<Sidebar.Item
|
||||||
end
|
end
|
||||||
path={'/'}
|
path={'/store'}
|
||||||
icon={tools[0].icon}
|
icon={tools[0].icon}
|
||||||
text={tools[0].name}
|
text={tools[0].name}
|
||||||
/>
|
/>
|
||||||
<Sidebar.Item
|
<Sidebar.Item
|
||||||
end
|
end
|
||||||
path={'/store'}
|
path={'/repository'}
|
||||||
icon={tools[1].icon}
|
icon={tools[1].icon}
|
||||||
text={tools[1].name}
|
text={tools[1].name}
|
||||||
/>
|
/>
|
||||||
</Sidebar.ItemList>
|
</Sidebar.ItemList>
|
||||||
<Sidebar.Separate style={{ marginBottom: 0 }} />
|
<Sidebar.Separate />
|
||||||
|
<Droppable id={'menu'} className={'menu-droppable'}>
|
||||||
<Sidebar.Scroll>
|
<Sidebar.Scroll>
|
||||||
<Sidebar.ItemList>
|
<Sidebar.ItemList>
|
||||||
{tools.map((tool) => {
|
<SortableContext
|
||||||
return tool.menu &&
|
items={toolMenuItem.map(
|
||||||
tool.id !== 'tools' &&
|
({ authorUsername, toolId, ver, platform }) =>
|
||||||
tool.id !== 'tools-store' ? (
|
`${authorUsername}:${toolId}:${ver}:${platform}`
|
||||||
<Sidebar.Item
|
)}
|
||||||
path={tool.absolutePath}
|
>
|
||||||
icon={tool.icon}
|
{toolMenuItem.map(
|
||||||
text={tool.name}
|
({
|
||||||
key={tool.id}
|
icon,
|
||||||
|
toolName,
|
||||||
|
toolId,
|
||||||
|
authorUsername,
|
||||||
|
ver,
|
||||||
|
platform
|
||||||
|
}) => (
|
||||||
|
<Sortable
|
||||||
|
id={`${authorUsername}:${toolId}:${ver}:${platform}`}
|
||||||
|
data={{
|
||||||
|
icon,
|
||||||
|
toolName,
|
||||||
|
toolId,
|
||||||
|
authorUsername,
|
||||||
|
ver,
|
||||||
|
platform
|
||||||
|
}}
|
||||||
|
isDelete={isDelete}
|
||||||
>
|
>
|
||||||
{tool.children &&
|
|
||||||
tool.children.map((subTool) => {
|
|
||||||
return (
|
|
||||||
<Sidebar.Item
|
<Sidebar.Item
|
||||||
path={subTool.absolutePath}
|
path={getViewPath(
|
||||||
text={subTool.name}
|
authorUsername,
|
||||||
key={subTool.id}
|
toolId,
|
||||||
|
platform,
|
||||||
|
ver
|
||||||
|
)}
|
||||||
|
icon={icon}
|
||||||
|
text={toolName}
|
||||||
|
key={`${authorUsername}:${toolId}:${ver}:${platform}`}
|
||||||
|
extend={<DragHandle padding={10} />}
|
||||||
/>
|
/>
|
||||||
|
</Sortable>
|
||||||
)
|
)
|
||||||
})}
|
)}
|
||||||
</Sidebar.Item>
|
</SortableContext>
|
||||||
) : undefined
|
<DraggableOverlay>
|
||||||
})}
|
{activeItem && (
|
||||||
|
<Sidebar.Item
|
||||||
|
path={getViewPath(
|
||||||
|
activeItem.authorUsername,
|
||||||
|
activeItem.toolId,
|
||||||
|
import.meta.env.VITE_PLATFORM,
|
||||||
|
activeItem.ver
|
||||||
|
)}
|
||||||
|
icon={activeItem.icon}
|
||||||
|
text={activeItem.toolName}
|
||||||
|
key={`${activeItem.authorUsername}:${activeItem.toolId}:${activeItem.ver}`}
|
||||||
|
extend={<DragHandle padding={10} />}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</DraggableOverlay>
|
||||||
</Sidebar.ItemList>
|
</Sidebar.ItemList>
|
||||||
</Sidebar.Scroll>
|
</Sidebar.Scroll>
|
||||||
|
{showDropMask && <DropMask />}
|
||||||
|
</Droppable>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
</div>
|
</div>
|
||||||
<div className={'right-panel'}>
|
<div className={'right-panel'}>
|
||||||
@@ -65,6 +189,7 @@ const ToolsFramework = () => {
|
|||||||
<Outlet />
|
<Outlet />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
|
</DndContext>
|
||||||
</FitFullscreen>
|
</FitFullscreen>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -89,6 +89,8 @@ const User = () => {
|
|||||||
changePasswordForm.resetFields()
|
changePasswordForm.resetFields()
|
||||||
|
|
||||||
void modal.confirm({
|
void modal.confirm({
|
||||||
|
centered: true,
|
||||||
|
maskClosable: true,
|
||||||
icon: <></>,
|
icon: <></>,
|
||||||
title: (
|
title: (
|
||||||
<>
|
<>
|
||||||
@@ -99,8 +101,6 @@ const User = () => {
|
|||||||
修改密码
|
修改密码
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
centered: true,
|
|
||||||
maskClosable: true,
|
|
||||||
footer: (_, { OkBtn, CancelBtn }) => (
|
footer: (_, { OkBtn, CancelBtn }) => (
|
||||||
<>
|
<>
|
||||||
<OkBtn />
|
<OkBtn />
|
||||||
@@ -220,10 +220,10 @@ const User = () => {
|
|||||||
twoFactorForm.resetFields()
|
twoFactorForm.resetFields()
|
||||||
if (enable) {
|
if (enable) {
|
||||||
void modal.confirm({
|
void modal.confirm({
|
||||||
title: '双因素',
|
|
||||||
centered: true,
|
centered: true,
|
||||||
maskClosable: true,
|
maskClosable: true,
|
||||||
focusTriggerAfterClose: false,
|
focusTriggerAfterClose: false,
|
||||||
|
title: '双因素',
|
||||||
footer: (_, { OkBtn, CancelBtn }) => (
|
footer: (_, { OkBtn, CancelBtn }) => (
|
||||||
<>
|
<>
|
||||||
<OkBtn />
|
<OkBtn />
|
||||||
@@ -233,9 +233,9 @@ const User = () => {
|
|||||||
content: '确定解除双因素?',
|
content: '确定解除双因素?',
|
||||||
onOk: () => {
|
onOk: () => {
|
||||||
void modal.confirm({
|
void modal.confirm({
|
||||||
title: '解除双因素',
|
|
||||||
centered: true,
|
centered: true,
|
||||||
maskClosable: true,
|
maskClosable: true,
|
||||||
|
title: '解除双因素',
|
||||||
footer: (_, { OkBtn, CancelBtn }) => (
|
footer: (_, { OkBtn, CancelBtn }) => (
|
||||||
<>
|
<>
|
||||||
<OkBtn />
|
<OkBtn />
|
||||||
@@ -313,9 +313,9 @@ const User = () => {
|
|||||||
const response = res.data
|
const response = res.data
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
void modal.confirm({
|
void modal.confirm({
|
||||||
title: '绑定双因素',
|
|
||||||
centered: true,
|
centered: true,
|
||||||
maskClosable: true,
|
maskClosable: true,
|
||||||
|
title: '绑定双因素',
|
||||||
footer: (_, { OkBtn, CancelBtn }) => (
|
footer: (_, { OkBtn, CancelBtn }) => (
|
||||||
<>
|
<>
|
||||||
<OkBtn />
|
<OkBtn />
|
||||||
|
|||||||
@@ -1,14 +1,4 @@
|
|||||||
export const tools: RouteJsonObject[] = [
|
export const tools: RouteJsonObject[] = [
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
absolutePath: '/',
|
|
||||||
id: 'tools',
|
|
||||||
component: lazy(() => import('@/pages/Tools')),
|
|
||||||
name: '主页',
|
|
||||||
icon: lazy(() => import('~icons/oxygen/home')),
|
|
||||||
menu: true,
|
|
||||||
auth: false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: 'store',
|
path: 'store',
|
||||||
absolutePath: '/store',
|
absolutePath: '/store',
|
||||||
@@ -19,6 +9,21 @@ export const tools: RouteJsonObject[] = [
|
|||||||
icon: lazy(() => import('~icons/oxygen/store')),
|
icon: lazy(() => import('~icons/oxygen/store')),
|
||||||
menu: true
|
menu: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'repository',
|
||||||
|
absolutePath: '/repository',
|
||||||
|
id: 'tools-repository',
|
||||||
|
component: lazy(() => import('@/pages/Tools')),
|
||||||
|
name: '个人仓库',
|
||||||
|
titlePostfix: ' - 仓库',
|
||||||
|
icon: lazy(() => import('~icons/oxygen/home')),
|
||||||
|
menu: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
absolutePath: '/',
|
||||||
|
element: <Navigate to="/store" replace />
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'store/:username',
|
path: 'store/:username',
|
||||||
absolutePath: '/store',
|
absolutePath: '/store',
|
||||||
@@ -34,7 +39,6 @@ export const tools: RouteJsonObject[] = [
|
|||||||
name: '创建工具',
|
name: '创建工具',
|
||||||
titlePostfix: ' - 创建新工具',
|
titlePostfix: ' - 创建新工具',
|
||||||
icon: lazy(() => import('~icons/oxygen/newProject')),
|
icon: lazy(() => import('~icons/oxygen/newProject')),
|
||||||
menu: false,
|
|
||||||
auth: true
|
auth: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
URL_TOOL,
|
URL_TOOL,
|
||||||
URL_TOOL_CATEGORY,
|
URL_TOOL_CATEGORY,
|
||||||
URL_TOOL_DETAIL,
|
URL_TOOL_DETAIL,
|
||||||
|
URL_TOOL_FAVORITE,
|
||||||
URL_TOOL_STORE,
|
URL_TOOL_STORE,
|
||||||
URL_TOOL_TEMPLATE
|
URL_TOOL_TEMPLATE
|
||||||
} from '@/constants/urls.constants'
|
} from '@/constants/urls.constants'
|
||||||
@@ -19,7 +20,7 @@ export const r_tool_create = (param: ToolCreateParam) => request.post<ToolVo>(UR
|
|||||||
|
|
||||||
export const r_tool_upgrade = (param: ToolUpgradeParam) => request.patch<ToolVo>(URL_TOOL, param)
|
export const r_tool_upgrade = (param: ToolUpgradeParam) => request.patch<ToolVo>(URL_TOOL, param)
|
||||||
|
|
||||||
export const r_tool_get = () => request.get<ToolVo[]>(URL_TOOL)
|
export const r_tool_get = (param: PageParam) => request.get<PageVo<ToolVo>>(URL_TOOL, param)
|
||||||
|
|
||||||
export const r_tool_detail = (username: string, toolId: string, ver: string, platform: Platform) =>
|
export const r_tool_detail = (username: string, toolId: string, ver: string, platform: Platform) =>
|
||||||
request.get<ToolVo>(`${URL_TOOL_DETAIL}/${username}/${toolId}/${ver}`, { platform })
|
request.get<ToolVo>(`${URL_TOOL_DETAIL}/${username}/${toolId}/${ver}`, { platform })
|
||||||
@@ -37,3 +38,12 @@ export const r_tool_store_get = (param: ToolStoreGetParam) =>
|
|||||||
|
|
||||||
export const r_tool_store_get_by_username = (username: string, param: ToolStoreGetParam) =>
|
export const r_tool_store_get_by_username = (username: string, param: ToolStoreGetParam) =>
|
||||||
request.get<PageVo<ToolVo>>(`${URL_TOOL_STORE}/${username}`, param)
|
request.get<PageVo<ToolVo>>(`${URL_TOOL_STORE}/${username}`, param)
|
||||||
|
|
||||||
|
export const r_tool_add_favorite = (param: ToolFavoriteAddRemoveParam) =>
|
||||||
|
request.post(URL_TOOL_FAVORITE, param)
|
||||||
|
|
||||||
|
export const r_tool_remove_favorite = (param: ToolFavoriteAddRemoveParam) =>
|
||||||
|
request.delete(URL_TOOL_FAVORITE, param)
|
||||||
|
|
||||||
|
export const r_tool_get_favorite = (param: PageParam) =>
|
||||||
|
request.get<PageVo<ToolVo>>(URL_TOOL_FAVORITE, param)
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client'
|
||||||
import FullscreenLoadingMask from '@/components/common/FullscreenLoadingMask'
|
|
||||||
import { floor } from 'lodash'
|
import { floor } from 'lodash'
|
||||||
|
import { STORAGE_TOOL_MENU_ITEM_KEY } from '@/constants/common.constants'
|
||||||
|
import { getLocalStorage, setLocalStorage } from '@/util/browser'
|
||||||
|
import FullscreenLoadingMask from '@/components/common/FullscreenLoadingMask'
|
||||||
|
|
||||||
export const randomInt = (start: number, end: number) => {
|
export const randomInt = (start: number, end: number) => {
|
||||||
if (start > end) {
|
if (start > end) {
|
||||||
@@ -133,3 +135,22 @@ const formatByte = (size: number, unit: ByteUnit): string => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const checkDesktop = () => import.meta.env.VITE_PLATFORM === 'DESKTOP'
|
export const checkDesktop = () => import.meta.env.VITE_PLATFORM === 'DESKTOP'
|
||||||
|
|
||||||
|
export const saveToolMenuItem = (toolMenuItem: ToolMenuItem[]) => {
|
||||||
|
setLocalStorage(STORAGE_TOOL_MENU_ITEM_KEY, JSON.stringify(toolMenuItem))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getToolMenuItem = (): ToolMenuItem[] => {
|
||||||
|
const s = getLocalStorage(STORAGE_TOOL_MENU_ITEM_KEY)
|
||||||
|
if (!s) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return JSON.parse(s) as ToolMenuItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const omitText = (text: string, length: number) => {
|
||||||
|
if (text.length <= length) {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
return `${text.substring(0, length)}...`
|
||||||
|
}
|
||||||
|
|||||||
127
src/util/navigation.tsx
Normal file
127
src/util/navigation.tsx
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import { NavigateFunction, NavigateOptions } from 'react-router'
|
||||||
|
import { getRedirectUrl } from '@/util/route'
|
||||||
|
|
||||||
|
export const navigateToRoot = (navigate: NavigateFunction, options?: NavigateOptions) => {
|
||||||
|
navigate('/', options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const navigateToStore = (
|
||||||
|
navigate: NavigateFunction,
|
||||||
|
username?: string,
|
||||||
|
options?: NavigateOptions
|
||||||
|
) => {
|
||||||
|
navigate(`/store${username ? `/${username}` : ''}`, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const navigateToRepository = (navigate: NavigateFunction, options?: NavigateOptions) => {
|
||||||
|
navigate('/repository', options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const navigateToLogin = (
|
||||||
|
navigate: NavigateFunction,
|
||||||
|
locationSearch?: string,
|
||||||
|
redirectUrl?: string,
|
||||||
|
options?: NavigateOptions
|
||||||
|
) => {
|
||||||
|
navigate(
|
||||||
|
redirectUrl ? getRedirectUrl('/login', redirectUrl) : `/login${locationSearch}`,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const navigateToView = (
|
||||||
|
navigate: NavigateFunction,
|
||||||
|
username: string,
|
||||||
|
toolId: string,
|
||||||
|
platform: Platform,
|
||||||
|
version?: string,
|
||||||
|
options?: NavigateOptions
|
||||||
|
) => {
|
||||||
|
navigate(
|
||||||
|
`/view/${username}/${toolId}${version ? `/${version}` : ''}${platform !== import.meta.env.VITE_PLATFORM ? `?platform=${platform}` : ''}`,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const navigateToSource = (
|
||||||
|
navigate: NavigateFunction,
|
||||||
|
username: string,
|
||||||
|
toolId: string,
|
||||||
|
platform: Platform,
|
||||||
|
version?: string,
|
||||||
|
options?: NavigateOptions
|
||||||
|
) => {
|
||||||
|
navigate(
|
||||||
|
`/source/${username}/${toolId}${version ? `/${version}` : ''}${platform !== import.meta.env.VITE_PLATFORM ? `?platform=${platform}` : ''}`,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const navigateToRedirect = (
|
||||||
|
navigate: NavigateFunction,
|
||||||
|
searchParams: URLSearchParams,
|
||||||
|
defaultUrl: '/repository' | '/',
|
||||||
|
options?: NavigateOptions
|
||||||
|
) => {
|
||||||
|
navigate(searchParams.get('redirect') ?? defaultUrl, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const navigateToForget = (
|
||||||
|
navigate: NavigateFunction,
|
||||||
|
locationSearch: string,
|
||||||
|
options?: NavigateOptions
|
||||||
|
) => {
|
||||||
|
navigate(`/forget/${locationSearch}`, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const navigateToRegister = (
|
||||||
|
navigate: NavigateFunction,
|
||||||
|
locationSearch: string,
|
||||||
|
options?: NavigateOptions
|
||||||
|
) => {
|
||||||
|
navigate(`/register/${locationSearch}`, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const navigateToExecute = (
|
||||||
|
navigate: NavigateFunction,
|
||||||
|
toolId: string,
|
||||||
|
options?: NavigateOptions
|
||||||
|
) => {
|
||||||
|
navigate(`/system/tools/execute/${toolId}`, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const navigateToCode = (
|
||||||
|
navigate: NavigateFunction,
|
||||||
|
toolId: string,
|
||||||
|
options?: NavigateOptions
|
||||||
|
) => {
|
||||||
|
navigate(`/system/tools/code/${toolId}`, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const navigateToEdit = (
|
||||||
|
navigate: NavigateFunction,
|
||||||
|
toolId: string,
|
||||||
|
platform: Platform,
|
||||||
|
options?: NavigateOptions
|
||||||
|
) => {
|
||||||
|
navigate(
|
||||||
|
`/edit/${toolId}${platform !== import.meta.env.VITE_PLATFORM ? `?platform=${platform}` : ''}`,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const navigateToUser = (navigate: NavigateFunction, options?: NavigateOptions) => {
|
||||||
|
navigate('/user', options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const navigateToTools = (navigate: NavigateFunction, options?: NavigateOptions) => {
|
||||||
|
navigate('/system/tools', options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getViewPath = (
|
||||||
|
username: string,
|
||||||
|
toolId: string,
|
||||||
|
platform: Platform,
|
||||||
|
version?: string
|
||||||
|
) =>
|
||||||
|
`/view/${username}/${toolId}${version ? `/${version}` : ''}${platform !== import.meta.env.VITE_PLATFORM ? `?platform=${platform}` : ''}`
|
||||||
Reference in New Issue
Block a user