diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index 8d0ca95..522cb9d 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -24,11 +24,10 @@ module.exports = {
rules: {
'no-cond-assign': 'error',
'eqeqeq': 'error',
- 'indent': ['error', 4, { 'SwitchCase': 1 }],
'prettier/prettier': [
'error',
{
- endOfLine: 'auto'
+ endOfLine: 'auto',
}
],
'react-refresh/only-export-components': [
diff --git a/index.html b/index.html
index fdacc4a..93b7658 100644
--- a/index.html
+++ b/index.html
@@ -2,9 +2,9 @@
-
+
- Vite + React + TS
+ FatWeb
diff --git a/package-lock.json b/package-lock.json
index 16c4295..c67c05d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,8 +8,8 @@
"name": "fatweb-ui",
"version": "0.0.0",
"dependencies": {
- "@ant-design/icons": "^5.2.5",
- "antd": "^5.8.5",
+ "@ant-design/icons": "^5.2.6",
+ "antd": "^5.8.6",
"axios": "^1.5.0",
"jwt-decode": "^3.1.2",
"localforage": "^1.10.0",
@@ -18,19 +18,18 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router": "^6.15.0",
- "react-router-dom": "^6.15.0",
- "sort-by": "^0.0.2"
+ "react-router-dom": "^6.15.0"
},
"devDependencies": {
"@svgr/core": "^8.1.0",
"@svgr/plugin-jsx": "^8.1.0",
"@types/jsdom": "^21.1.2",
- "@types/lodash": "^4.14.197",
- "@types/node": "^20.5.7",
+ "@types/lodash": "^4.14.198",
+ "@types/node": "^20.5.9",
"@types/react": "^18.2.21",
"@types/react-dom": "^18.2.7",
- "@typescript-eslint/eslint-plugin": "^6.5.0",
- "@typescript-eslint/parser": "^6.5.0",
+ "@typescript-eslint/eslint-plugin": "^6.6.0",
+ "@typescript-eslint/parser": "^6.6.0",
"@vitejs/plugin-react": "^4.0.4",
"eslint": "^8.48.0",
"eslint-config-prettier": "^9.0.0",
@@ -42,10 +41,11 @@
"eslint-plugin-react-refresh": "^0.4.3",
"jsdom": "^22.1.0",
"prettier": "^3.0.3",
+ "sass": "^1.66.1",
"stylelint-config-prettier": "^9.0.5",
"typescript": "^5.2.2",
"unplugin-auto-import": "^0.16.6",
- "unplugin-icons": "^0.16.6",
+ "unplugin-icons": "^0.17.0",
"vite": "^4.4.9"
}
},
@@ -1508,7 +1508,7 @@
},
"node_modules/@types/json-schema": {
"version": "7.0.12",
- "resolved": "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.12.tgz",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz",
"integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==",
"dev": true
},
@@ -1519,9 +1519,9 @@
"dev": true
},
"node_modules/@types/lodash": {
- "version": "4.14.197",
- "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.197.tgz",
- "integrity": "sha512-BMVOiWs0uNxHVlHBgzTIqJYmj+PgCo4euloGF+5m4okL3rEYzM2EEv78mw8zWSMM57dM7kVIgJ2QDvwHSoCI5g==",
+ "version": "4.14.198",
+ "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.198.tgz",
+ "integrity": "sha512-trNJ/vtMZYMLhfN45uLq4ShQSw0/S7xCTLLVM+WM1rmFpba/VS42jVUgaO3w/NOLiWR/09lnYk0yMaA/atdIsg==",
"dev": true
},
"node_modules/@types/minimist": {
@@ -1585,7 +1585,7 @@
},
"node_modules/@types/semver": {
"version": "7.5.1",
- "resolved": "https://registry.npmmirror.com/@types/semver/-/semver-7.5.1.tgz",
+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.1.tgz",
"integrity": "sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg==",
"dev": true
},
@@ -1596,16 +1596,16 @@
"dev": true
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "6.5.0",
- "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.5.0.tgz",
- "integrity": "sha512-2pktILyjvMaScU6iK3925uvGU87E+N9rh372uGZgiMYwafaw9SXq86U04XPq3UH6tzRvNgBsub6x2DacHc33lw==",
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.6.0.tgz",
+ "integrity": "sha512-CW9YDGTQnNYMIo5lMeuiIG08p4E0cXrXTbcZ2saT/ETE7dWUrNxlijsQeU04qAAKkILiLzdQz+cGFxCJjaZUmA==",
"dev": true,
"dependencies": {
"@eslint-community/regexpp": "^4.5.1",
- "@typescript-eslint/scope-manager": "6.5.0",
- "@typescript-eslint/type-utils": "6.5.0",
- "@typescript-eslint/utils": "6.5.0",
- "@typescript-eslint/visitor-keys": "6.5.0",
+ "@typescript-eslint/scope-manager": "6.6.0",
+ "@typescript-eslint/type-utils": "6.6.0",
+ "@typescript-eslint/utils": "6.6.0",
+ "@typescript-eslint/visitor-keys": "6.6.0",
"debug": "^4.3.4",
"graphemer": "^1.4.0",
"ignore": "^5.2.4",
@@ -1616,6 +1616,10 @@
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
"peerDependencies": {
"@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha",
"eslint": "^7.0.0 || ^8.0.0"
@@ -1660,20 +1664,24 @@
"dev": true
},
"node_modules/@typescript-eslint/parser": {
- "version": "6.5.0",
- "resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-6.5.0.tgz",
- "integrity": "sha512-LMAVtR5GN8nY0G0BadkG0XIe4AcNMeyEy3DyhKGAh9k4pLSMBO7rF29JvDBpZGCmp5Pgz5RLHP6eCpSYZJQDuQ==",
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.6.0.tgz",
+ "integrity": "sha512-setq5aJgUwtzGrhW177/i+DMLqBaJbdwGj2CPIVFFLE0NCliy5ujIdLHd2D1ysmlmsjdL2GWW+hR85neEfc12w==",
"dev": true,
"dependencies": {
- "@typescript-eslint/scope-manager": "6.5.0",
- "@typescript-eslint/types": "6.5.0",
- "@typescript-eslint/typescript-estree": "6.5.0",
- "@typescript-eslint/visitor-keys": "6.5.0",
+ "@typescript-eslint/scope-manager": "6.6.0",
+ "@typescript-eslint/types": "6.6.0",
+ "@typescript-eslint/typescript-estree": "6.6.0",
+ "@typescript-eslint/visitor-keys": "6.6.0",
"debug": "^4.3.4"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
"peerDependencies": {
"eslint": "^7.0.0 || ^8.0.0"
},
@@ -1684,32 +1692,40 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "6.5.0",
- "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-6.5.0.tgz",
- "integrity": "sha512-A8hZ7OlxURricpycp5kdPTH3XnjG85UpJS6Fn4VzeoH4T388gQJ/PGP4ole5NfKt4WDVhmLaQ/dBLNDC4Xl/Kw==",
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.6.0.tgz",
+ "integrity": "sha512-pT08u5W/GT4KjPUmEtc2kSYvrH8x89cVzkA0Sy2aaOUIw6YxOIjA8ilwLr/1fLjOedX1QAuBpG9XggWqIIfERw==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "6.5.0",
- "@typescript-eslint/visitor-keys": "6.5.0"
+ "@typescript-eslint/types": "6.6.0",
+ "@typescript-eslint/visitor-keys": "6.6.0"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "6.5.0",
- "resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-6.5.0.tgz",
- "integrity": "sha512-f7OcZOkRivtujIBQ4yrJNIuwyCQO1OjocVqntl9dgSIZAdKqicj3xFDqDOzHDlGCZX990LqhLQXWRnQvsapq8A==",
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.6.0.tgz",
+ "integrity": "sha512-8m16fwAcEnQc69IpeDyokNO+D5spo0w1jepWWY2Q6y5ZKNuj5EhVQXjtVAeDDqvW6Yg7dhclbsz6rTtOvcwpHg==",
"dev": true,
"dependencies": {
- "@typescript-eslint/typescript-estree": "6.5.0",
- "@typescript-eslint/utils": "6.5.0",
+ "@typescript-eslint/typescript-estree": "6.6.0",
+ "@typescript-eslint/utils": "6.6.0",
"debug": "^4.3.4",
"ts-api-utils": "^1.0.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
"peerDependencies": {
"eslint": "^7.0.0 || ^8.0.0"
},
@@ -1720,22 +1736,26 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "6.5.0",
- "resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-6.5.0.tgz",
- "integrity": "sha512-eqLLOEF5/lU8jW3Bw+8auf4lZSbbljHR2saKnYqON12G/WsJrGeeDHWuQePoEf9ro22+JkbPfWQwKEC5WwLQ3w==",
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.6.0.tgz",
+ "integrity": "sha512-CB6QpJQ6BAHlJXdwUmiaXDBmTqIE2bzGTDLADgvqtHWuhfNP3rAOK7kAgRMAET5rDRr9Utt+qAzRBdu3AhR3sg==",
"dev": true,
"engines": {
"node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "6.5.0",
- "resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.5.0.tgz",
- "integrity": "sha512-q0rGwSe9e5Kk/XzliB9h2LBc9tmXX25G0833r7kffbl5437FPWb2tbpIV9wAATebC/018pGa9fwPDuvGN+LxWQ==",
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.6.0.tgz",
+ "integrity": "sha512-hMcTQ6Al8MP2E6JKBAaSxSVw5bDhdmbCEhGW/V8QXkb9oNsFkA4SBuOMYVPxD3jbtQ4R/vSODBsr76R6fP3tbA==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "6.5.0",
- "@typescript-eslint/visitor-keys": "6.5.0",
+ "@typescript-eslint/types": "6.6.0",
+ "@typescript-eslint/visitor-keys": "6.6.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -1745,6 +1765,10 @@
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
"peerDependenciesMeta": {
"typescript": {
"optional": true
@@ -1753,7 +1777,7 @@
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": {
"version": "6.0.0",
- "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
@@ -1765,7 +1789,7 @@
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
"version": "7.5.4",
- "resolved": "https://registry.npmmirror.com/semver/-/semver-7.5.4.tgz",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"dependencies": {
@@ -1780,34 +1804,38 @@
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": {
"version": "4.0.0",
- "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/@typescript-eslint/utils": {
- "version": "6.5.0",
- "resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-6.5.0.tgz",
- "integrity": "sha512-9nqtjkNykFzeVtt9Pj6lyR9WEdd8npPhhIPM992FWVkZuS6tmxHfGVnlUcjpUP2hv8r4w35nT33mlxd+Be1ACQ==",
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.6.0.tgz",
+ "integrity": "sha512-mPHFoNa2bPIWWglWYdR0QfY9GN0CfvvXX1Sv6DlSTive3jlMTUy+an67//Gysc+0Me9pjitrq0LJp0nGtLgftw==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0",
- "@typescript-eslint/scope-manager": "6.5.0",
- "@typescript-eslint/types": "6.5.0",
- "@typescript-eslint/typescript-estree": "6.5.0",
+ "@typescript-eslint/scope-manager": "6.6.0",
+ "@typescript-eslint/types": "6.6.0",
+ "@typescript-eslint/typescript-estree": "6.6.0",
"semver": "^7.5.4"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
"peerDependencies": {
"eslint": "^7.0.0 || ^8.0.0"
}
},
"node_modules/@typescript-eslint/utils/node_modules/lru-cache": {
"version": "6.0.0",
- "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
@@ -1819,7 +1847,7 @@
},
"node_modules/@typescript-eslint/utils/node_modules/semver": {
"version": "7.5.4",
- "resolved": "https://registry.npmmirror.com/semver/-/semver-7.5.4.tgz",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"dependencies": {
@@ -1834,21 +1862,25 @@
},
"node_modules/@typescript-eslint/utils/node_modules/yallist": {
"version": "4.0.0",
- "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "6.5.0",
- "resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.5.0.tgz",
- "integrity": "sha512-yCB/2wkbv3hPsh02ZS8dFQnij9VVQXJMN/gbQsaaY+zxALkZnxa/wagvLEFsAWMPv7d7lxQmNsIzGU1w/T/WyA==",
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.6.0.tgz",
+ "integrity": "sha512-L61uJT26cMOfFQ+lMZKoJNbAEckLe539VhTxiGHrWl5XSKQgA0RTBZJW2HFPy5T0ZvPVSD93QsrTKDkfNwJGyQ==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "6.5.0",
+ "@typescript-eslint/types": "6.6.0",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@vitejs/plugin-react": {
@@ -4018,6 +4050,12 @@
"resolved": "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
},
+ "node_modules/immutable": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmmirror.com/immutable/-/immutable-4.3.4.tgz",
+ "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==",
+ "dev": true
+ },
"node_modules/import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -6444,6 +6482,23 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
},
+ "node_modules/sass": {
+ "version": "1.66.1",
+ "resolved": "https://registry.npmmirror.com/sass/-/sass-1.66.1.tgz",
+ "integrity": "sha512-50c+zTsZOJVgFfTgwwEzkjA3/QACgdNsKueWPyAR0mRINIvLAStVQBbPg14iuqEQ74NPDbXzJARJ/O4SI1zftA==",
+ "dev": true,
+ "dependencies": {
+ "chokidar": ">=3.0.0 <4.0.0",
+ "immutable": "^4.0.0",
+ "source-map-js": ">=0.6.2 <2.0.0"
+ },
+ "bin": {
+ "sass": "sass.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/saxes": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/saxes/-/saxes-6.0.0.tgz",
@@ -6592,11 +6647,6 @@
"tslib": "^2.0.3"
}
},
- "node_modules/sort-by": {
- "version": "0.0.2",
- "resolved": "https://registry.npmmirror.com/sort-by/-/sort-by-0.0.2.tgz",
- "integrity": "sha512-iOX5oHA4a0eqTMFiWrHYqv924UeRKFBLhym7iwSVG37Egg2wApgZKAjyzM9WZjMwKv6+8Zi+nIaJ7FYsO9EkoA=="
- },
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz",
@@ -7340,9 +7390,9 @@
}
},
"node_modules/unplugin-icons": {
- "version": "0.16.6",
- "resolved": "https://registry.npmmirror.com/unplugin-icons/-/unplugin-icons-0.16.6.tgz",
- "integrity": "sha512-jL70sAC7twp4hI/MTfm+vyvTRtHqiEIzf3XOjJz7yzhMEEQnk5Ey5YIXRAU03Mc4BF99ITvvnBzfyRZee86OeA==",
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/unplugin-icons/-/unplugin-icons-0.17.0.tgz",
+ "integrity": "sha512-gMv66eY/Hj64heM55XrfDH3LUCWI51mtkBVUPVl9VkpvLgAYhdVe9nRuzu6p+idmCLSQVq7xiPxQcD4aXCgW5A==",
"dev": true,
"dependencies": {
"@antfu/install-pkg": "^0.1.1",
@@ -7353,6 +7403,9 @@
"local-pkg": "^0.4.3",
"unplugin": "^1.4.0"
},
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
"peerDependencies": {
"@svgr/core": ">=7.0.0",
"@svgx/core": "^1.0.1",
diff --git a/package.json b/package.json
index d5ad9f7..6ec90f0 100644
--- a/package.json
+++ b/package.json
@@ -6,14 +6,15 @@
"scripts": {
"dev": "vite",
"dev-host": "vite --host 0.0.0.0",
- "build": "vite build && tsc && vite build",
+ "build": "vite build",
+ "clean": "rimraf dist .eslintrc-auto-import.json auto-imports.d.ts",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"format": "prettier --write src/",
"preview": "vite preview"
},
"dependencies": {
- "@ant-design/icons": "^5.2.5",
- "antd": "^5.8.5",
+ "@ant-design/icons": "^5.2.6",
+ "antd": "^5.8.6",
"axios": "^1.5.0",
"jwt-decode": "^3.1.2",
"localforage": "^1.10.0",
@@ -22,19 +23,18 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router": "^6.15.0",
- "react-router-dom": "^6.15.0",
- "sort-by": "^0.0.2"
+ "react-router-dom": "^6.15.0"
},
"devDependencies": {
"@svgr/core": "^8.1.0",
"@svgr/plugin-jsx": "^8.1.0",
"@types/jsdom": "^21.1.2",
- "@types/lodash": "^4.14.197",
- "@types/node": "^20.5.7",
+ "@types/lodash": "^4.14.198",
+ "@types/node": "^20.5.9",
"@types/react": "^18.2.21",
"@types/react-dom": "^18.2.7",
- "@typescript-eslint/eslint-plugin": "^6.5.0",
- "@typescript-eslint/parser": "^6.5.0",
+ "@typescript-eslint/eslint-plugin": "^6.6.0",
+ "@typescript-eslint/parser": "^6.6.0",
"@vitejs/plugin-react": "^4.0.4",
"eslint": "^8.48.0",
"eslint-config-prettier": "^9.0.0",
@@ -46,10 +46,11 @@
"eslint-plugin-react-refresh": "^0.4.3",
"jsdom": "^22.1.0",
"prettier": "^3.0.3",
+ "sass": "^1.66.1",
"stylelint-config-prettier": "^9.0.5",
"typescript": "^5.2.2",
"unplugin-auto-import": "^0.16.6",
- "unplugin-icons": "^0.16.6",
+ "unplugin-icons": "^0.17.0",
"vite": "^4.4.9"
}
}
diff --git a/public/logo.png b/public/logo.png
new file mode 100644
index 0000000..f3703eb
Binary files /dev/null and b/public/logo.png differ
diff --git a/public/logo.svg b/public/logo.svg
new file mode 100644
index 0000000..e2769cb
--- /dev/null
+++ b/public/logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/vite.svg b/public/vite.svg
deleted file mode 100644
index e7b8dfb..0000000
--- a/public/vite.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/App.css b/src/App.css
deleted file mode 100644
index b9d355d..0000000
--- a/src/App.css
+++ /dev/null
@@ -1,42 +0,0 @@
-#root {
- max-width: 1280px;
- margin: 0 auto;
- padding: 2rem;
- text-align: center;
-}
-
-.logo {
- height: 6em;
- padding: 1.5em;
- will-change: filter;
- transition: filter 300ms;
-}
-.logo:hover {
- filter: drop-shadow(0 0 2em #646cffaa);
-}
-.logo.react:hover {
- filter: drop-shadow(0 0 2em #61dafbaa);
-}
-
-@keyframes logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
-
-@media (prefers-reduced-motion: no-preference) {
- a:nth-of-type(2) .logo {
- animation: logo-spin infinite 20s linear;
- }
-}
-
-.card {
- padding: 2em;
-}
-
-.read-the-docs {
- color: #888;
-}
diff --git a/src/App.tsx b/src/App.tsx
index 3561770..869499d 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,10 +1,13 @@
import React from 'react'
import router from '@/router'
+import LoadingMask from '@/components/common/LoadingMask.tsx'
const App: React.FC = () => {
return (
<>
-
+ }>
+
+
>
)
}
diff --git a/src/assets/css/_mixins.scss b/src/assets/css/_mixins.scss
new file mode 100644
index 0000000..47308ed
--- /dev/null
+++ b/src/assets/css/_mixins.scss
@@ -0,0 +1,22 @@
+@mixin keyframes($animationName) {
+ @-webkit-keyframes #{$animationName} {
+ @content
+ }
+ @-moz-keyframes #{$animationName} {
+ @content
+ }
+ @-o-keyframes #{$animationName} {
+ @content
+ }
+ @keyframes #{$animationName} {
+ @content
+ }
+}
+
+@mixin unique-keyframes {
+ $animationName: unique-id();
+ animation-name: $animationName;
+ @include keyframes($animationName) {
+ @content
+ }
+}
\ No newline at end of file
diff --git a/src/assets/css/base.css b/src/assets/css/base.css
deleted file mode 100644
index 1426524..0000000
--- a/src/assets/css/base.css
+++ /dev/null
@@ -1,63 +0,0 @@
-* {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
-}
-
-html {
- scroll-behavior: smooth;
-}
-
-em,
-i {
- font-style: normal
-}
-
-li {
- list-style: none
-}
-
-img {
- border: 0;
- vertical-align: middle
-}
-
-button {
- cursor: pointer
-}
-
-a {
- color: #666;
- text-decoration: none
-}
-
-button,
-input {
- font-family: Microsoft YaHei, Heiti SC, tahoma, arial, Hiragino Sans GB, "\5B8B\4F53", sans-serif;
- border: 0;
- outline: none;
-}
-
-body {
- -webkit-font-smoothing: antialiased;
- background-color: #fff;
- font: 12px/1.5 Microsoft YaHei, Heiti SC, tahoma, arial, Hiragino Sans GB, "\5B8B\4F53", sans-serif;
- color: #666
-}
-
-.hide,
-.none {
- display: none
-}
-
-.clearfix:after {
- visibility: hidden;
- clear: both;
- display: block;
- content: ".";
- height: 0
-}
-
-.clearfix {
- *zoom: 1
-}
\ No newline at end of file
diff --git a/src/assets/css/base.scss b/src/assets/css/base.scss
new file mode 100644
index 0000000..8bb5df0
--- /dev/null
+++ b/src/assets/css/base.scss
@@ -0,0 +1,63 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+html {
+ scroll-behavior: smooth;
+}
+
+em,
+i {
+ font-style: normal
+}
+
+li {
+ list-style: none
+}
+
+img {
+ border: 0;
+ vertical-align: middle
+}
+
+button {
+ cursor: pointer
+}
+
+a {
+ color: #666;
+ text-decoration: none
+}
+
+button,
+input {
+ font-family: Microsoft YaHei, Heiti SC, tahoma, arial, Hiragino Sans GB, "\5B8B\4F53", sans-serif;
+ border: 0;
+ outline: none;
+}
+
+body {
+ -webkit-font-smoothing: antialiased;
+ background-color: #fff;
+ font: 12px/1.5 Microsoft YaHei, Heiti SC, tahoma, arial, Hiragino Sans GB, "\5B8B\4F53", sans-serif;
+ color: #666
+}
+
+.hide,
+.none {
+ display: none
+}
+
+.clearfix:after {
+ visibility: hidden;
+ clear: both;
+ display: block;
+ content: ".";
+ height: 0
+}
+
+.clearfix {
+ *zoom: 1
+}
\ No newline at end of file
diff --git a/src/assets/css/common.css b/src/assets/css/common.css
deleted file mode 100644
index 1435e89..0000000
--- a/src/assets/css/common.css
+++ /dev/null
@@ -1,106 +0,0 @@
-:root {
- --main-color: #00D4FF;
- --background-color: #F5F5F5;
- --font-main-color: #4D4D4D;
- --font-secondary-color: #9E9E9E;
-}
-
-.body {
- color: var(--font-main-color);
- user-select: none;
- min-width: 1800px;
- min-height: 400px;
-}
-
-.fill {
- height: 100%;
- width: 100%;
-}
-
-.fill-with {
- width: 100%;
-}
-
-.fill-height {
- height: 100%;
-}
-
-.background-white {
- background-color: white;
-}
-
-.center-box {
- display: flex;
- justify-content: center;
- align-items: center;
-}
-
-.vertical-center-box {
- display: flex;
- align-items: center;
-}
-
-.horizontal-center-box {
- display: flex;
- justify-content: center;
-}
-
-.icon-size-xs {
- width: 16px;
- height: 16px;
-}
-
-.icon-size-xs > use {
- width: 16px;
- height: 16px;
-}
-
-.icon-size-sm {
- width: 20px;
- height: 20px;
-}
-
-.icon-size-sm > use {
- width: 20px;
- height: 20px;
-}
-
-.icon-size-md {
- width: 24px;
- height: 24px;
-}
-
-.icon-size-md > use {
- width: 24px;
- height: 24px;
-}
-
-.icon-size-lg {
- width: 32px;
- height: 32px;
-}
-
-.icon-size-lg > use {
- width: 32px;
- height: 32px;
-}
-
-.icon-size-xl {
- width: 64px;
- height: 64px;
-}
-
-.icon-size-xl > use {
- width: 64px;
- height: 64px;
-}
-
-.icon-size-menu {
- width: 23px;
- height: 23px;
-}
-
-.icon-size-menu > use {
- width: 23px;
- height: 23px;
-}
diff --git a/src/assets/css/common.scss b/src/assets/css/common.scss
new file mode 100644
index 0000000..e1716f7
--- /dev/null
+++ b/src/assets/css/common.scss
@@ -0,0 +1,115 @@
+@use '@/assets/css/constants.scss' as constants;
+
+#root {
+ height: 100vh;
+ width: 100vw;
+}
+
+.body {
+ color: constants.$font-main-color;
+ user-select: none;
+ min-width: 900px;
+ min-height: 400px;
+}
+
+.fill {
+ height: 100%;
+ width: 100%;
+}
+
+.fill-with {
+ width: 100%;
+}
+
+.fill-height {
+ height: 100%;
+}
+
+.background-white {
+ background-color: white;
+}
+
+.center-box {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.vertical-center-box {
+ display: flex;
+ align-items: center;
+}
+
+.horizontal-center-box {
+ display: flex;
+ justify-content: center;
+}
+
+.icon-size-xs {
+ width: 16px;
+ height: 16px;
+
+ > use {
+ width: 16px;
+ height: 16px;
+ }
+}
+
+.icon-size-sm {
+ width: 20px;
+ height: 20px;
+
+ > use {
+ width: 20px;
+ height: 20px;
+ }
+}
+
+.icon-size-md {
+ width: 24px;
+ height: 24px;
+
+ > use {
+ width: 24px;
+ height: 24px;
+ }
+}
+
+.icon-size-lg {
+ width: 32px;
+ height: 32px;
+
+ > use {
+
+ width: 32px;
+ height: 32px;
+ }
+}
+
+.icon-size-xl {
+ width: 64px;
+ height: 64px;
+
+ > use {
+ width: 64px;
+ height: 64px;
+ }
+}
+
+.icon-size-menu {
+ width: 23px;
+ height: 23px;
+
+ > use {
+ width: 23px;
+ height: 23px;
+ }
+}
+
+.flex-horizontal {
+ flex-direction: row;
+}
+
+.flex-vertical {
+ flex-direction: column;
+}
\ No newline at end of file
diff --git a/src/assets/css/components/common/fit-center.scss b/src/assets/css/components/common/fit-center.scss
new file mode 100644
index 0000000..fd07971
--- /dev/null
+++ b/src/assets/css/components/common/fit-center.scss
@@ -0,0 +1,7 @@
+.fit-center {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 100%;
+ height: 100%;
+}
\ No newline at end of file
diff --git a/src/assets/css/components/common/fit-fullscreen.scss b/src/assets/css/components/common/fit-fullscreen.scss
new file mode 100644
index 0000000..83d0ff6
--- /dev/null
+++ b/src/assets/css/components/common/fit-fullscreen.scss
@@ -0,0 +1,5 @@
+.fit-fullscreen {
+ position: relative;
+ width: 100%;
+ height: 100vh;
+}
\ No newline at end of file
diff --git a/src/assets/css/components/common/hide-scrollbar.scss b/src/assets/css/components/common/hide-scrollbar.scss
new file mode 100644
index 0000000..799a993
--- /dev/null
+++ b/src/assets/css/components/common/hide-scrollbar.scss
@@ -0,0 +1,68 @@
+@use '@/assets/css/constants' as constants;
+
+.hide-scrollbar-mask {
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+
+ .hide-scrollbar-selection {
+ position: relative;
+ overflow: scroll;
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+
+ .hide-scrollbar-content {
+ display: inline-block;
+ width: 100%;
+ min-width: 900px;
+ }
+ }
+
+ ::-webkit-scrollbar {
+ display: none;
+ }
+
+ .scrollbar {
+ position: absolute;
+ z-index: 1000;
+ opacity: .5;
+ touch-action: none;
+
+ .box {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ border-radius: 8px;
+ overflow: hidden;
+
+
+ .block {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ border-radius: 8px;
+ background-color: constants.$font-secondary-color;
+ transition: background-color .2s;
+ }
+ :hover {
+ background-color: constants.$font-main-color;
+ }
+ }
+ }
+
+ .vertical-scrollbar {
+ padding: 12px 4px;
+ width: 16px;
+ height: 100%;
+ right: 0;
+ top: 0;
+ }
+
+ .horizontal-scrollbar {
+ padding: 4px 12px;
+ width: 100%;
+ height: 16px;
+ left: 0;
+ bottom: 0;
+ }
+}
\ No newline at end of file
diff --git a/src/assets/css/components/common/indicator.scss b/src/assets/css/components/common/indicator.scss
new file mode 100644
index 0000000..efe8e76
--- /dev/null
+++ b/src/assets/css/components/common/indicator.scss
@@ -0,0 +1,34 @@
+@use '@/assets/css/constants.scss' as constants;
+
+.dot-list {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100%;
+ width: 100%;
+
+ .item {
+ flex: auto;
+ cursor: pointer;
+
+ .dot {
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ border: {
+ width: 2px;
+ color: constants.$font-secondary-color;
+ style: solid;
+ };
+ transition: all .2s;
+ }
+
+ :hover {
+ background-color: constants.$focus-color;
+ }
+ }
+
+ .active > * {
+ background-color: constants.$font-secondary-color !important;
+ }
+}
\ No newline at end of file
diff --git a/src/assets/css/components/common/loading-mask.scss b/src/assets/css/components/common/loading-mask.scss
new file mode 100644
index 0000000..ad20dde
--- /dev/null
+++ b/src/assets/css/components/common/loading-mask.scss
@@ -0,0 +1,10 @@
+.loading-mask {
+ position: absolute;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 100%;
+ height: 100%;
+ z-index: 100;
+ background-color: rgba(200, 200, 200, 0.2);
+}
\ No newline at end of file
diff --git a/src/assets/css/components/home/footer.scss b/src/assets/css/components/home/footer.scss
new file mode 100644
index 0000000..7abf726
--- /dev/null
+++ b/src/assets/css/components/home/footer.scss
@@ -0,0 +1,19 @@
+.icons {
+ display: flex;
+ gap: 20px;
+
+ .icon {
+ font-size: 8em;
+ color: white;
+ }
+}
+
+.links {
+ font-size: 2em;
+ text-decoration: underline;
+ color: white;
+
+ > * {
+ color: white;
+ }
+}
\ No newline at end of file
diff --git a/src/assets/css/components/home/home.scss b/src/assets/css/components/home/home.scss
new file mode 100644
index 0000000..f339e4f
--- /dev/null
+++ b/src/assets/css/components/home/home.scss
@@ -0,0 +1,11 @@
+.indicator {
+ position: fixed;
+ margin: {
+ right: 20px;
+ };
+ width: 20px;
+ height: 100px;
+ right: 0;
+ top: 50%;
+ transform: translateY(-50%);
+}
\ No newline at end of file
diff --git a/src/assets/css/components/home/slogan.scss b/src/assets/css/components/home/slogan.scss
new file mode 100644
index 0000000..018eec3
--- /dev/null
+++ b/src/assets/css/components/home/slogan.scss
@@ -0,0 +1,63 @@
+@use "@/assets/css/mixins" as mixins;
+
+.center-box {
+ display: flex;
+ flex-direction: column;
+}
+
+.big-logo {
+ font: {
+ size: 5em;
+ weight: bold;
+ };
+ color: #666;
+}
+
+.slogan {
+ font: {
+ size: 1.3em;
+ style: italic;
+ };
+ color: #666;
+
+ .cursor {
+ font-style: normal;
+ animation: 1s infinite;
+
+ @include mixins.unique-keyframes {
+ 0% {
+ opacity: 0;
+ }
+ 50% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0;
+ }
+ }
+ }
+}
+
+.scroll-down {
+ position: absolute;
+ bottom: 10px;
+ padding: 20px;
+ cursor: pointer;
+ animation: 1.5s infinite;
+ @include mixins.unique-keyframes {
+ 0%,
+ 100% {
+ -ms-filter: none;
+ filter: none;
+ opacity: 1;
+ transform: translateY(10px);
+ }
+
+ 50% {
+ -ms-filter: alpha(opacity=40);
+ filter: alpha(opacity=40);
+ opacity: .4;
+ transform: translateY(-10px);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/assets/css/constants.scss b/src/assets/css/constants.scss
new file mode 100644
index 0000000..edf5734
--- /dev/null
+++ b/src/assets/css/constants.scss
@@ -0,0 +1,8 @@
+$main-color: #00D4FF;
+$background-color: #F5F5F5;
+$font-main-color: #4D4D4D;
+$font-secondary-color: #9E9E9E;
+$focus-color: #DDDDDD;
+$border-color: rgba(204, 204, 204, 0.33);
+$url-color: rgba(102, 102, 102, .8);
+$url-active-color: #ccc
\ No newline at end of file
diff --git a/src/assets/css/login.css b/src/assets/css/login.css
deleted file mode 100644
index fd5fe6e..0000000
--- a/src/assets/css/login.css
+++ /dev/null
@@ -1,63 +0,0 @@
-.login-background {
- display: flex;
- height: 100vh;
- width: 100vw;
- justify-content: center;
- align-items: center;
- background-color: #B3E5FC;
-}
-
-.login-box {
- display: flex;
- width: 800px;
- height: 450px;
- background-color: #448AFF;
-}
-
-.login-box-left {
- display: flex;
- justify-content: center;
- align-items: center;
- height: 100%;
- flex: 2;
-}
-
-.login-box-left-text {
- font-size: 3rem;
- color: white;
- font-weight: bold;
-}
-
-.login-box-left-text>div:last-child {
- font-size: 1.8rem;
- font-weight: normal;
-}
-
-.login-box-right {
- position: relative;
- height: 100%;
- flex: 3;
- background-color: white;
-}
-
-.login-from-text {
- position: absolute;
- top: 60px;
- left: 40px;
- font-weight: bold;
- font-size: 2rem;
-}
-
-.login-from {
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- margin-top: 30px;
- width: 100%;
- height: 100%;
-}
-
-.login-from-item {
- width: 80%;
-}
\ No newline at end of file
diff --git a/src/assets/css/manager.css b/src/assets/css/manager.css
deleted file mode 100644
index 17c4829..0000000
--- a/src/assets/css/manager.css
+++ /dev/null
@@ -1,35 +0,0 @@
-.top-bar {
- display: flex;
- justify-content: right;
- background-color: #317ece;
- padding: 10px 20px;
-}
-
-.top-bar > button:hover {
- color: #F5F5F5;
- border-color: #F5F5F5;
-}
-
-.search-row {
- display: flex;
- gap: 20px;
- margin: 10px 10px;
-}
-
-.search-row > * {
- display: flex;
- gap: 5px;
- align-items: center;
- flex: 1;
- font-size: 1rem;
-}
-
-.search-row > *:not(.operation-buttons) > *:last-child {
- flex: 1;
-}
-
-.operation-buttons {
- display: flex;
- justify-content: right;
- gap: 10px;
-}
\ No newline at end of file
diff --git a/src/assets/css/pages/header.scss b/src/assets/css/pages/header.scss
new file mode 100644
index 0000000..eedd195
--- /dev/null
+++ b/src/assets/css/pages/header.scss
@@ -0,0 +1,164 @@
+@use "@/assets/css/mixins" as mixins;
+@use "@/assets/css/constants" as constants;
+
+.nav {
+ display: flex;
+ position: fixed;
+ align-items: center;
+ z-index: 1;
+ width: 100%;
+ height: 70px;
+ background-color: white;
+ border: {
+ bottom: {
+ width: 1px;
+ style: solid;
+ color: constants.$border-color;
+ }
+ }
+ animation: .5s ease both;
+
+ @include mixins.unique-keyframes {
+ 0% {
+ transform: translateY(-100%);
+ }
+ 100% {
+ transform: translateY(0);
+ }
+ }
+
+ .logo {
+ padding: 0 40px;
+
+ .title {
+ font-size: 2.8em;
+ font-family: century gothic, texgyreadventor, stheiti, sans-serif;
+ }
+ }
+
+ nav {
+ display: flex;
+ justify-content: end;
+ flex: 1;
+ transition: {
+ property: all;
+ duration: .5s;
+ };
+
+ .menu {
+ padding: 0 30px;
+
+ .item {
+ display: inline-block;
+ font-size: 1.5em;
+ transition: {
+ property: all;
+ duration: .3s;
+ };
+
+ a {
+ padding: 5px 20px;
+ color: constants.$url-color;
+ }
+ }
+
+ .active {
+ border: {
+ bottom: {
+ width: 2px;
+ style: solid;
+ color: constants.$url-active-color;
+ };
+ };
+ }
+
+ :hover {
+ transform: translateY(-5px);
+ }
+ }
+
+ .dropdown-menu-button {
+ display: none;
+ margin: 0 20px;
+ width: 45px;
+ height: 45px;
+ justify-content: center;
+ align-items: center;
+ border-radius: 6px;
+ }
+
+ .dropdown-menu-button.active {
+ background-color: transparentize(constants.$focus-color, 0.8);
+ border: {
+ width: 1px;
+ color: constants.$focus-color;
+ style: solid;
+ };
+ }
+
+ @media screen and (max-width: 900px) {
+ .menu {
+ display: none;
+ }
+
+ .dropdown-menu-button {
+ display: flex;
+ }
+ }
+ }
+}
+
+.dropdown-menu-content {
+ display: none;
+
+ @media screen and (max-width: 900px) {
+ display: none;
+ position: fixed;
+ width: 100%;
+ top: 70px;
+ border: {
+ width: 1px;
+ color: constants.$border-color;
+ style: solid;
+ };
+ background-color: white;
+ z-index: 1;
+
+ ul {
+ li {
+ font-size: 1.2em;
+ text-align: center;
+
+ a {
+ display: block;
+ width: 100%;
+ height: 100%;
+ padding: 10px;
+ }
+
+ :hover {
+ background-color: constants.$focus-color;
+ }
+ }
+ }
+ }
+}
+
+@media screen and (max-width: 900px){
+ .dropdown-menu-content.show {
+ display: block;
+ }
+}
+
+.nav.hide {
+ animation: .5s ease both;
+
+ @include mixins.unique-keyframes {
+ 0% {
+ transform: translateY(0);
+ }
+ 100% {
+ transform: translateY(-100%);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/assets/css/pages/login.scss b/src/assets/css/pages/login.scss
new file mode 100644
index 0000000..22629a8
--- /dev/null
+++ b/src/assets/css/pages/login.scss
@@ -0,0 +1,63 @@
+.login-background {
+ display: flex;
+ height: 100vh;
+ width: 100vw;
+ justify-content: center;
+ align-items: center;
+ background-color: #B3E5FC;
+}
+
+.login-box {
+ display: flex;
+ width: 800px;
+ height: 450px;
+ background-color: #448AFF;
+}
+
+.login-box-left {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100%;
+ flex: 2;
+}
+
+.login-box-left-text {
+ font-size: 3rem;
+ color: white;
+ font-weight: bold;
+
+ > div:last-child {
+ font-size: 1.8rem;
+ font-weight: normal;
+ }
+}
+
+.login-box-right {
+ position: relative;
+ height: 100%;
+ flex: 3;
+ background-color: white;
+}
+
+.login-from-text {
+ position: absolute;
+ top: 60px;
+ left: 40px;
+ font-weight: bold;
+ font-size: 2rem;
+}
+
+.login-from {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ margin-top: 30px;
+ width: 100%;
+ height: 100%;
+}
+
+.login-from-item {
+ width: 80%;
+}
\ No newline at end of file
diff --git a/src/assets/svg/down.svg b/src/assets/svg/down.svg
new file mode 100644
index 0000000..872d226
--- /dev/null
+++ b/src/assets/svg/down.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/svg/github.svg b/src/assets/svg/github.svg
new file mode 100644
index 0000000..fdf2ea5
--- /dev/null
+++ b/src/assets/svg/github.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/svg/home.svg b/src/assets/svg/home.svg
deleted file mode 100644
index 689ddce..0000000
--- a/src/assets/svg/home.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/assets/svg/jenkins.svg b/src/assets/svg/jenkins.svg
new file mode 100644
index 0000000..51fbbc6
--- /dev/null
+++ b/src/assets/svg/jenkins.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/svg/loading.svg b/src/assets/svg/loading.svg
new file mode 100644
index 0000000..a7c3d67
--- /dev/null
+++ b/src/assets/svg/loading.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/svg/logo.svg b/src/assets/svg/logo.svg
new file mode 100644
index 0000000..243d161
--- /dev/null
+++ b/src/assets/svg/logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/svg/menu.svg b/src/assets/svg/menu.svg
new file mode 100644
index 0000000..46ac707
--- /dev/null
+++ b/src/assets/svg/menu.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/components/common/FitCenter.tsx b/src/components/common/FitCenter.tsx
new file mode 100644
index 0000000..b33d8e6
--- /dev/null
+++ b/src/components/common/FitCenter.tsx
@@ -0,0 +1,23 @@
+import React from 'react'
+import '@/assets/css/components/common/fit-center.scss'
+
+interface FitCenterProps
+ extends React.DetailedHTMLProps, HTMLDivElement> {
+ vertical?: boolean
+}
+
+const FitCenter: React.FC = (props) => {
+ const { className, vertical, ..._props } = props
+ return (
+ <>
+
+ >
+ )
+}
+
+export default FitCenter
diff --git a/src/components/common/FitFullScreen.tsx b/src/components/common/FitFullScreen.tsx
new file mode 100644
index 0000000..c61515d
--- /dev/null
+++ b/src/components/common/FitFullScreen.tsx
@@ -0,0 +1,30 @@
+import React from 'react'
+import '@/assets/css/components/common/fit-fullscreen.scss'
+
+interface FitFullscreenProps
+ extends React.DetailedHTMLProps, HTMLDivElement> {
+ zIndex?: number
+ backgroundColor?: string
+}
+
+const FitFullScreen = forwardRef((props, ref) => {
+ const { zIndex, backgroundColor, className, style, ..._props } = props
+ return (
+ <>
+
+ {props.children}
+
+ >
+ )
+})
+
+export default FitFullScreen
diff --git a/src/components/common/HideScrollbar.tsx b/src/components/common/HideScrollbar.tsx
new file mode 100644
index 0000000..de3a10a
--- /dev/null
+++ b/src/components/common/HideScrollbar.tsx
@@ -0,0 +1,541 @@
+import React from 'react'
+import '@/assets/css/components/common/hide-scrollbar.scss'
+
+interface HideScrollbarProps
+ extends React.DetailedHTMLProps, HTMLDivElement> {
+ isPreventScroll?: boolean
+ isPreventVerticalScroll?: boolean
+ isPreventHorizontalScroll?: boolean
+ isShowVerticalScrollbar?: boolean
+ isHiddenVerticalScrollbarWhenFull?: boolean
+ isShowHorizontalScrollbar?: boolean
+ isHiddenHorizontalScrollbarWhenFull?: boolean
+}
+
+export interface HideScrollbarElement {
+ scrollTo(x: number, y: number, smooth?: boolean): void
+ scrollX(x: number, smooth?: boolean): void
+ scrollY(y: number, smooth?: boolean): void
+ scrollLeft(length: number, smooth?: boolean): void
+ scrollRight(length: number, smooth?: boolean): void
+ scrollUp(length: number, smooth?: boolean): void
+ scrollDown(length: number, smooth?: boolean): void
+ getX(): number
+ getY(): number
+ addEventListenerWithType(
+ type: K,
+ listener: (this: HTMLDivElement, ev: HTMLElementEventMap[K]) => never,
+ options?: boolean | AddEventListenerOptions
+ ): void
+ addEventListener(
+ type: string,
+ listener: EventListenerOrEventListenerObject,
+ options?: boolean | AddEventListenerOptions
+ ): void
+ removeEventListenerWithType(
+ type: K,
+ listener: (this: HTMLDivElement, ev: HTMLElementEventMap[K]) => never,
+ options?: boolean | EventListenerOptions
+ ): void
+ removeEventListener(
+ type: string,
+ listener: EventListenerOrEventListenerObject,
+ options?: boolean | EventListenerOptions
+ ): void
+}
+
+const HideScrollbar = forwardRef((props, ref) => {
+ useImperativeHandle(
+ ref,
+ () => {
+ return {
+ scrollTo(x, y, smooth?: boolean) {
+ rootRef.current?.scrollTo({
+ left: x,
+ top: y,
+ behavior: smooth === false ? 'instant' : 'smooth'
+ })
+ },
+ scrollX(x, smooth?: boolean) {
+ rootRef.current?.scrollTo({
+ left: x,
+ behavior: smooth === false ? 'instant' : 'smooth'
+ })
+ },
+ scrollY(y, smooth?: boolean) {
+ rootRef.current?.scrollTo({
+ top: y,
+ behavior: smooth === false ? 'instant' : 'smooth'
+ })
+ },
+ scrollLeft(length, smooth?: boolean) {
+ rootRef.current?.scrollTo({
+ left: rootRef.current?.scrollLeft - length,
+ behavior: smooth === false ? 'instant' : 'smooth'
+ })
+ },
+ scrollRight(length, smooth?: boolean) {
+ rootRef.current?.scrollTo({
+ left: rootRef.current?.scrollLeft + length,
+ behavior: smooth === false ? 'instant' : 'smooth'
+ })
+ },
+ scrollUp(length, smooth?: boolean) {
+ rootRef.current?.scrollTo({
+ top: rootRef.current?.scrollTop - length,
+ behavior: smooth === false ? 'instant' : 'smooth'
+ })
+ },
+ scrollDown(length, smooth?: boolean) {
+ rootRef.current?.scrollTo({
+ top: rootRef.current?.scrollTop + length,
+ behavior: smooth === false ? 'instant' : 'smooth'
+ })
+ },
+ getX() {
+ return rootRef.current?.scrollLeft ?? 0
+ },
+ getY() {
+ return rootRef.current?.scrollTop ?? 0
+ },
+ addEventListenerWithType(
+ type: K,
+ listener: (this: HTMLDivElement, ev: HTMLElementEventMap[K]) => never,
+ options?: boolean | AddEventListenerOptions
+ ): void {
+ rootRef.current?.addEventListener(type, listener, options)
+ },
+ addEventListener(
+ type: string,
+ listener: EventListenerOrEventListenerObject,
+ options?: boolean | AddEventListenerOptions
+ ): void {
+ rootRef.current?.addEventListener(type, listener, options)
+ },
+ removeEventListenerWithType(
+ type: K,
+ listener: (this: HTMLDivElement, ev: HTMLElementEventMap[K]) => never,
+ options?: boolean | EventListenerOptions
+ ): void {
+ rootRef.current?.removeEventListener(type, listener, options)
+ },
+ removeEventListener(
+ type: string,
+ listener: EventListenerOrEventListenerObject,
+ options?: boolean | EventListenerOptions
+ ): void {
+ rootRef.current?.removeEventListener(type, listener, options)
+ }
+ }
+ },
+ []
+ )
+
+ const maskRef = useRef(null)
+ const rootRef = useRef(null)
+ const contentRef = useRef(null)
+ const wheelListenerRef = useRef<(event: WheelEvent) => void>(() => undefined)
+ const lastScrollbarClickPositionRef = useRef({ x: -1, y: -1 })
+ const lastScrollbarTouchPositionRef = useRef({ x: -1, y: -1 })
+ const lastTouchPositionRef = useRef({ x: -1, y: -1 })
+ const [verticalScrollbarWidth, setVerticalScrollbarWidth] = useState(0)
+ const [verticalScrollbarLength, setVerticalScrollbarLength] = useState(100)
+ const [verticalScrollbarPosition, setVerticalScrollbarPosition] = useState(0)
+ const [verticalScrollbarOnClick, setVerticalScrollbarOnClick] = useState(false)
+ const [verticalScrollbarOnTouch, setVerticalScrollbarOnTouch] = useState(false)
+ const [horizontalScrollbarWidth, setHorizontalScrollbarWidth] = useState(0)
+ const [horizontalScrollbarLength, setHorizontalScrollbarLength] = useState(100)
+ const [horizontalScrollbarPosition, setHorizontalScrollbarPosition] = useState(0)
+ const [horizontalScrollbarOnClick, setHorizontalScrollbarOnClick] = useState(false)
+ const [horizontalScrollbarOnTouch, setHorizontalScrollbarOnTouch] = useState(false)
+
+ const {
+ isPreventScroll,
+ isPreventVerticalScroll,
+ isPreventHorizontalScroll,
+ isShowVerticalScrollbar,
+ isHiddenVerticalScrollbarWhenFull,
+ isShowHorizontalScrollbar,
+ isHiddenHorizontalScrollbarWhenFull,
+ ..._props
+ } = props
+
+ const isPreventAnyScroll =
+ isPreventScroll || isPreventVerticalScroll || isPreventHorizontalScroll
+
+ const handleDefaultTouchStart = useCallback(
+ (event: React.TouchEvent) => {
+ if (event.touches.length !== 1 || isPreventScroll) {
+ lastTouchPositionRef.current = { x: -1, y: -1 }
+ return
+ }
+
+ const { clientX, clientY } = event.touches[0]
+ lastTouchPositionRef.current = { x: clientX, y: clientY }
+ },
+ [isPreventScroll]
+ )
+
+ const handleDefaultTouchmove = useCallback(
+ (event: React.TouchEvent) => {
+ if (event.touches.length !== 1 || isPreventScroll) {
+ lastTouchPositionRef.current = { x: -1, y: -1 }
+ return
+ }
+ const { clientX, clientY } = event.touches[0]
+
+ if (!isPreventVerticalScroll) {
+ rootRef.current?.scrollTo({
+ top: rootRef.current?.scrollTop + (lastTouchPositionRef.current.y - clientY),
+ behavior: 'instant'
+ })
+ }
+
+ if (!isPreventHorizontalScroll) {
+ rootRef.current?.scrollTo({
+ left: rootRef.current?.scrollLeft + (lastTouchPositionRef.current.x - clientX),
+ behavior: 'instant'
+ })
+ }
+
+ lastTouchPositionRef.current = { x: clientX, y: clientY }
+ },
+ [isPreventHorizontalScroll, isPreventScroll, isPreventVerticalScroll]
+ )
+
+ const handleDefaultMouseDown = (event: React.MouseEvent) => {
+ if (isPreventAnyScroll)
+ if (event.button === 1) {
+ event.preventDefault()
+ }
+ }
+
+ const handleDefaultKeyDown = useCallback(
+ (event: React.KeyboardEvent) => {
+ if (
+ isPreventScroll &&
+ [
+ 'ArrowUp',
+ 'ArrowDown',
+ 'ArrowLeft',
+ 'ArrowRight',
+ ' ',
+ '',
+ 'PageUp',
+ 'PageDown',
+ 'Home',
+ 'End'
+ ].find((value) => value === event.key)
+ ) {
+ event.preventDefault()
+ }
+ if (
+ isPreventVerticalScroll &&
+ ['ArrowUp', 'ArrowDown', ' ', '', 'PageUp', 'PageDown', 'Home', 'End'].find(
+ (value) => value === event.key
+ )
+ ) {
+ event.preventDefault()
+ }
+ if (
+ isPreventHorizontalScroll &&
+ ['ArrowLeft', 'ArrowRight'].find((value) => value === event.key)
+ ) {
+ event.preventDefault()
+ }
+ },
+ [isPreventHorizontalScroll, isPreventScroll, isPreventVerticalScroll]
+ )
+
+ const handleScrollbarMouseEvent = (eventFlag: string, scrollbarFlag: string) => {
+ return (event: React.MouseEvent) => {
+ switch (eventFlag) {
+ case 'down':
+ lastScrollbarClickPositionRef.current = { x: event.clientX, y: event.clientY }
+ switch (scrollbarFlag) {
+ case 'vertical':
+ setVerticalScrollbarOnClick(true)
+ break
+ case 'horizontal':
+ setHorizontalScrollbarOnClick(true)
+ break
+ }
+ break
+ case 'up':
+ case 'leave':
+ setVerticalScrollbarOnClick(false)
+ setHorizontalScrollbarOnClick(false)
+ break
+ case 'move':
+ if (verticalScrollbarOnClick) {
+ rootRef.current?.scrollTo({
+ top:
+ rootRef.current?.scrollTop +
+ ((event.clientY - lastScrollbarClickPositionRef.current.y) /
+ (rootRef.current?.clientHeight ?? 1)) *
+ (contentRef.current?.clientHeight ?? 0),
+ behavior: 'instant'
+ })
+ }
+ if (horizontalScrollbarOnClick) {
+ rootRef.current?.scrollTo({
+ left:
+ rootRef.current?.scrollLeft +
+ ((event.clientX - lastScrollbarClickPositionRef.current.x) /
+ (rootRef.current?.clientWidth ?? 1)) *
+ (contentRef.current?.clientWidth ?? 0),
+ behavior: 'instant'
+ })
+ }
+ lastScrollbarClickPositionRef.current = {
+ x: event.clientX,
+ y: event.clientY
+ }
+ }
+ }
+ }
+
+ const handleScrollbarTouchEvent = (eventFlag: string, scrollbarFlag: string) => {
+ return (event: React.TouchEvent) => {
+ switch (eventFlag) {
+ case 'start':
+ if (event.touches.length !== 1) {
+ return
+ }
+ lastScrollbarTouchPositionRef.current = {
+ x: event.touches[0].clientX,
+ y: event.touches[0].clientY
+ }
+ switch (scrollbarFlag) {
+ case 'vertical':
+ setVerticalScrollbarOnTouch(true)
+ break
+ case 'horizontal':
+ setHorizontalScrollbarOnTouch(true)
+ break
+ }
+ break
+ case 'end':
+ case 'cancel':
+ setVerticalScrollbarOnTouch(false)
+ setHorizontalScrollbarOnTouch(false)
+ break
+ case 'move':
+ if (event.touches.length !== 1) {
+ return
+ }
+ if (verticalScrollbarOnTouch) {
+ rootRef.current?.scrollTo({
+ top:
+ rootRef.current?.scrollTop +
+ ((event.touches[0].clientY -
+ lastScrollbarClickPositionRef.current.y) /
+ (rootRef.current?.clientHeight ?? 1)) *
+ (contentRef.current?.clientHeight ?? 0),
+ behavior: 'instant'
+ })
+ }
+ if (horizontalScrollbarOnTouch) {
+ rootRef.current?.scrollTo({
+ left:
+ rootRef.current?.scrollLeft +
+ ((event.touches[0].clientX -
+ lastScrollbarClickPositionRef.current.x) /
+ (rootRef.current?.clientWidth ?? 1)) *
+ (contentRef.current?.clientWidth ?? 0),
+ behavior: 'instant'
+ })
+ }
+ lastScrollbarClickPositionRef.current = {
+ x: event.touches[0].clientX,
+ y: event.touches[0].clientY
+ }
+ }
+ }
+ }
+
+ const handleDefaultScroll = () => {
+ setVerticalScrollbarPosition(
+ ((rootRef.current?.scrollTop ?? 0) / (contentRef.current?.clientHeight ?? 1)) * 100
+ )
+ setHorizontalScrollbarPosition(
+ ((rootRef.current?.scrollLeft ?? 0) / (contentRef.current?.clientWidth ?? 1)) * 100
+ )
+ }
+
+ useEffect(() => {
+ const windowResizeListener = () => {
+ setVerticalScrollbarWidth(
+ (rootRef.current?.offsetWidth ?? 0) - (rootRef.current?.clientWidth ?? 0)
+ )
+ setHorizontalScrollbarWidth(
+ (rootRef.current?.offsetHeight ?? 0) - (rootRef.current?.clientHeight ?? 0)
+ )
+
+ rootRef.current &&
+ setVerticalScrollbarLength(
+ (rootRef.current.clientHeight / (contentRef.current?.clientHeight ?? 0)) * 100
+ )
+
+ rootRef.current &&
+ setHorizontalScrollbarLength(
+ (rootRef.current.clientWidth / (contentRef.current?.clientWidth ?? 0)) * 100
+ )
+
+ return windowResizeListener
+ }
+ setTimeout(() => {
+ windowResizeListener()
+ }, 1000)
+ window.addEventListener('resize', windowResizeListener())
+
+ rootRef.current?.removeEventListener('wheel', wheelListenerRef.current)
+ if (isPreventAnyScroll) {
+ const handleDefaultWheel = (event: WheelEvent) => {
+ if (!event.altKey && !event.ctrlKey) {
+ if (isPreventScroll) {
+ event.preventDefault()
+ return
+ }
+ if (isPreventVerticalScroll && !event.shiftKey && !event.deltaX) {
+ event.preventDefault()
+ return
+ }
+ if (isPreventHorizontalScroll && (event.shiftKey || !event.deltaY)) {
+ event.preventDefault()
+ return
+ }
+ }
+ }
+ wheelListenerRef.current = handleDefaultWheel
+ rootRef.current?.addEventListener('wheel', handleDefaultWheel, { passive: false })
+ }
+
+ rootRef.current &&
+ setVerticalScrollbarLength(
+ (rootRef.current.clientHeight / (contentRef.current?.clientHeight ?? 0)) * 100
+ )
+
+ rootRef.current &&
+ setHorizontalScrollbarLength(
+ (rootRef.current.clientWidth / (contentRef.current?.clientWidth ?? 0)) * 100
+ )
+
+ return () => {
+ window.removeEventListener('resize', windowResizeListener)
+ }
+ }, [
+ horizontalScrollbarLength,
+ isPreventAnyScroll,
+ isPreventHorizontalScroll,
+ isPreventScroll,
+ isPreventVerticalScroll
+ ])
+
+ return (
+ <>
+
+ >
+ )
+})
+
+export default HideScrollbar
diff --git a/src/components/common/Indicator.tsx b/src/components/common/Indicator.tsx
new file mode 100644
index 0000000..e234fe9
--- /dev/null
+++ b/src/components/common/Indicator.tsx
@@ -0,0 +1,39 @@
+import React from 'react'
+import _ from 'lodash'
+import '@/assets/css/components/common/indicator.scss'
+
+interface IndicatorProps {
+ total: number
+ current: number
+ onSwitch?: (index: number) => void
+}
+
+const Indicator: React.FC = (props) => {
+ const { total, current, onSwitch } = props
+
+ const handleClick = (index: number) => {
+ return () => {
+ onSwitch && onSwitch(index)
+ }
+ }
+
+ return (
+ <>
+
+ {_.range(0, total).map((_value, index) => {
+ return (
+ -
+
+
+ )
+ })}
+
+ >
+ )
+}
+
+export default Indicator
diff --git a/src/components/common/LoadingMask.tsx b/src/components/common/LoadingMask.tsx
new file mode 100644
index 0000000..84c09e8
--- /dev/null
+++ b/src/components/common/LoadingMask.tsx
@@ -0,0 +1,28 @@
+import React from 'react'
+import Icon from '@ant-design/icons'
+import '@/assets/css/components/common/loading-mask.scss'
+import FitFullScreen from '@/components/common/FitFullScreen'
+import { COLOR_FONT_MAIN } from '@/constants/Common.constants'
+
+const LoadingMask: React.FC = () => {
+ const loadingIcon = (
+ <>
+
+ >
+ )
+ return (
+ <>
+
+
+
+ >
+ )
+}
+
+export default LoadingMask
diff --git a/src/components/home/Footer.tsx b/src/components/home/Footer.tsx
new file mode 100644
index 0000000..47c8f62
--- /dev/null
+++ b/src/components/home/Footer.tsx
@@ -0,0 +1,30 @@
+import React from 'react'
+import FitCenter from '@/components/common/FitCenter.tsx'
+import Icon from '@ant-design/icons'
+import '@/assets/css/components/home/footer.scss'
+import FitFullScreen from '@/components/common/FitFullScreen.tsx'
+import { NavLink } from 'react-router-dom'
+
+const Footer: React.FC = () => {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ Mail
+
+
+
+ >
+ )
+}
+
+export default Footer
diff --git a/src/components/home/OxygenToolbox.tsx b/src/components/home/OxygenToolbox.tsx
new file mode 100644
index 0000000..6423e3b
--- /dev/null
+++ b/src/components/home/OxygenToolbox.tsx
@@ -0,0 +1,17 @@
+import React from 'react'
+import FitCenter from '@/components/common/FitCenter.tsx'
+
+const OxygenToolbox: React.FC = () => {
+ return (
+ <>
+
+
+
Oxygen Toolbox
+
is coming soon...
+
+
+ >
+ )
+}
+
+export default OxygenToolbox
diff --git a/src/components/home/Slogan.tsx b/src/components/home/Slogan.tsx
new file mode 100644
index 0000000..0c32a6e
--- /dev/null
+++ b/src/components/home/Slogan.tsx
@@ -0,0 +1,50 @@
+import React from 'react'
+import Icon from '@ant-design/icons'
+import '@/assets/css/components/home/slogan.scss'
+import FitCenter from '@/components/common/FitCenter.tsx'
+
+interface SloganProps {
+ onClickScrollDown: (event: React.MouseEvent) => void
+}
+
+const Slogan: React.FC = (props) => {
+ const [slogan, setSlogan] = useState('')
+ const [sloganType, setSloganType] = useState(true)
+
+ const typeText = '因为热爱 所以折腾'
+ if (sloganType) {
+ setTimeout(() => {
+ if (slogan.length < typeText.length) {
+ setSlogan(typeText.substring(0, slogan.length + 1))
+ } else {
+ setSloganType(false)
+ }
+ }, 250)
+ } else {
+ setTimeout(() => {
+ if (slogan.length > 0) {
+ setSlogan(typeText.substring(0, slogan.length - 1))
+ } else {
+ setSloganType(true)
+ }
+ }, 100)
+ }
+
+ return (
+ <>
+
+
+
FatWeb
+
+ /* {slogan || <> >} | */
+
+
+
+
+
+
+ >
+ )
+}
+
+export default Slogan
diff --git a/src/components/home/index.tsx b/src/components/home/index.tsx
new file mode 100644
index 0000000..0598636
--- /dev/null
+++ b/src/components/home/index.tsx
@@ -0,0 +1,173 @@
+import React from 'react'
+import '@/assets/css/components/home/home.scss'
+import FitFullScreen from '@/components/common/FitFullScreen'
+import { MainFrameworkContext } from '@/pages/MainFramework'
+import Slogan from '@/components/home/Slogan'
+import OxygenToolbox from '@/components/home/OxygenToolbox'
+import Indicator from '@/components/common/Indicator.tsx'
+import Footer from '@/components/home/Footer'
+
+const Home: React.FC = () => {
+ const {
+ hideScrollbarRef,
+ navbarHiddenState: { navbarHidden, setNavbarHidden },
+ showDropdownMenuState: { setShowDropdownMenu },
+ preventScrollState: { setPreventScroll }
+ } = useContext(MainFrameworkContext)
+
+ const fitFullScreenRef = useRef(null)
+ const scrollTimeout = useRef(0)
+ const clickStart = useRef(0)
+
+ const [currentContent, setCurrentContent] = useState(0)
+
+ useEffect(() => {
+ setTimeout(() => {
+ setNavbarHidden(true)
+ setPreventScroll(true)
+ })
+ }, [setNavbarHidden, setPreventScroll])
+
+ useLayoutEffect(() => {
+ const handleWindowResize = () => {
+ handleScrollToContent(currentContent)()
+ }
+ window.addEventListener('resize', handleWindowResize)
+ return () => window.removeEventListener('resize', handleWindowResize)
+ })
+
+ const handleScrollToContent = (index: number) => {
+ return () => {
+ setShowDropdownMenu(false)
+ if (!index) {
+ setNavbarHidden(true)
+ hideScrollbarRef.current?.scrollY(0)
+ } else {
+ hideScrollbarRef.current?.scrollY(
+ (fitFullScreenRef.current?.offsetHeight ?? 0) * index
+ )
+ setNavbarHidden(false)
+ }
+ }
+ }
+
+ const handleScrollUp = () => {
+ if (currentContent <= 0) {
+ return
+ }
+ handleScrollToContent(currentContent - 1)()
+ clearTimeout(scrollTimeout.current)
+ scrollTimeout.current = setTimeout(() => {
+ setCurrentContent(currentContent - 1)
+ }, 500)
+ }
+
+ const handleScrollDown = () => {
+ if (currentContent >= content.length - 1) {
+ return
+ }
+ handleScrollToContent(currentContent + 1)()
+ clearTimeout(scrollTimeout.current)
+ scrollTimeout.current = setTimeout(() => {
+ setCurrentContent(currentContent + 1)
+ }, 500)
+ }
+
+ const handleWheel = (event: React.WheelEvent) => {
+ if (event.altKey || event.ctrlKey || event.shiftKey || event.deltaY === 0) {
+ return
+ }
+
+ if (event.deltaY > 0) {
+ handleScrollDown()
+ } else {
+ handleScrollUp()
+ }
+ }
+
+ const handleTouchStart = (event: React.TouchEvent) => {
+ clickStart.current = event.touches[0].clientY
+ }
+
+ const handleTouchEnd = (event: React.TouchEvent) => {
+ const moveLength = clickStart.current - event.changedTouches[0].clientY
+ if (Math.abs(moveLength) < 100) {
+ return
+ }
+
+ if (moveLength > 0) {
+ handleScrollDown()
+ } else {
+ handleScrollUp()
+ }
+ }
+
+ const handleMouseDown = (event: React.MouseEvent) => {
+ clickStart.current = event.clientY
+ }
+
+ const handleMouseUp = (event: React.MouseEvent) => {
+ const moveLength = clickStart.current - event.clientY
+ if (Math.abs(moveLength) < 100) {
+ return
+ }
+
+ if (moveLength > 0) {
+ handleScrollDown()
+ } else {
+ handleScrollUp()
+ }
+ }
+
+ const handleKeyDown = (event: React.KeyboardEvent) => {
+ if (event.key === 'ArrowUp') {
+ handleScrollUp()
+ }
+ if (event.key === 'ArrowDown') {
+ handleScrollDown()
+ }
+ }
+
+ const handleIndicatorSwitch = (index: number) => {
+ setCurrentContent(index)
+ handleScrollToContent(index)()
+ }
+
+ const content = [
+ {
+ backgroundColor: '#FBFBFB',
+ ref: fitFullScreenRef,
+ children:
+ },
+ { children: },
+ { children: }
+ ]
+
+ return (
+ <>
+
+ {content.map((element, index) => {
+ return
+ })}
+
+
+
+
+
+ >
+ )
+}
+
+export default Home
diff --git a/src/constants/Common.constants.ts b/src/constants/Common.constants.ts
index e58a345..34b9302 100644
--- a/src/constants/Common.constants.ts
+++ b/src/constants/Common.constants.ts
@@ -1,4 +1,4 @@
-const PRODUCTION_NAME = 'Pinnacle OA'
+const PRODUCTION_NAME = 'FatWeb'
const TOKEN_NAME = 'JWT_TOKEN'
const COLOR_PRODUCTION = '#00D4FF'
const COLOR_BACKGROUND = '#F5F5F5'
diff --git a/src/index.css b/src/index.css
deleted file mode 100644
index 2c3fac6..0000000
--- a/src/index.css
+++ /dev/null
@@ -1,69 +0,0 @@
-:root {
- font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
- line-height: 1.5;
- font-weight: 400;
-
- color-scheme: light dark;
- color: rgba(255, 255, 255, 0.87);
- background-color: #242424;
-
- font-synthesis: none;
- text-rendering: optimizeLegibility;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
- -webkit-text-size-adjust: 100%;
-}
-
-a {
- font-weight: 500;
- color: #646cff;
- text-decoration: inherit;
-}
-a:hover {
- color: #535bf2;
-}
-
-body {
- margin: 0;
- display: flex;
- place-items: center;
- min-width: 320px;
- min-height: 100vh;
-}
-
-h1 {
- font-size: 3.2em;
- line-height: 1.1;
-}
-
-button {
- border-radius: 8px;
- border: 1px solid transparent;
- padding: 0.6em 1.2em;
- font-size: 1em;
- font-weight: 500;
- font-family: inherit;
- background-color: #1a1a1a;
- cursor: pointer;
- transition: border-color 0.25s;
-}
-button:hover {
- border-color: #646cff;
-}
-button:focus,
-button:focus-visible {
- outline: 4px auto -webkit-focus-ring-color;
-}
-
-@media (prefers-color-scheme: light) {
- :root {
- color: #213547;
- background-color: #ffffff;
- }
- a:hover {
- color: #747bff;
- }
- button {
- background-color: #f9f9f9;
- }
-}
diff --git a/src/main.tsx b/src/main.tsx
index 89528f3..e102176 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,9 +1,9 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
-import App from './App.tsx'
-import '@/assets/css/base.css'
-import '@/assets/css/common.css'
import zh_CN from 'antd/locale/zh_CN'
+import '@/assets/css/base.scss'
+import '@/assets/css/common.scss'
+import App from './App.tsx'
ReactDOM.createRoot(document.getElementById('root')!).render(
diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx
deleted file mode 100644
index 28044c9..0000000
--- a/src/pages/Home.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import React from 'react'
-
-const Home: React.FC = () => {
- return (
- <>
- FatWeb
- >
- )
-}
-
-export default Home
diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx
index 40f71af..efa6ee9 100644
--- a/src/pages/Login.tsx
+++ b/src/pages/Login.tsx
@@ -2,7 +2,7 @@ import React from 'react'
import { login } from '@/utils/auth.ts'
import { LOGIN_SUCCESS, LOGIN_USERNAME_PASSWORD_ERROR } from '@/constants/Common.constants.ts'
import { setToken } from '@/utils/common.ts'
-import '@/assets/css/login.css'
+import '@/assets/css/pages/login.scss'
const Login: React.FC = () => {
const [messageApi, contextHolder] = message.useMessage()
diff --git a/src/pages/MainFramework.tsx b/src/pages/MainFramework.tsx
new file mode 100644
index 0000000..24bcc24
--- /dev/null
+++ b/src/pages/MainFramework.tsx
@@ -0,0 +1,181 @@
+import React from 'react'
+import '@/assets/css/pages/header.scss'
+import router from '@/router'
+import LoadingMask from '@/components/common/LoadingMask'
+import HideScrollbar, { HideScrollbarElement } from '@/components/common/HideScrollbar'
+import Icon from '@ant-design/icons'
+import { COLOR_FONT_SECONDARY } from '@/constants/Common.constants.ts'
+
+export const MainFrameworkContext = createContext<{
+ navbarHiddenState: {
+ navbarHidden: boolean
+ setNavbarHidden: (newValue: boolean) => void
+ }
+ preventScrollState: {
+ preventScroll: boolean
+ setPreventScroll: (newValue: boolean) => void
+ }
+ showVerticalScrollbarState: {
+ showVerticalScrollbar: boolean
+ setShowVerticalScrollbar: (newValue: boolean) => void
+ }
+ showHorizontalScrollbarState: {
+ showHorizontalScrollbar: boolean
+ setShowHorizontalScrollbar: (newValue: boolean) => void
+ }
+ showDropdownMenuState: {
+ showDropdownMenu: boolean
+ setShowDropdownMenu: (newValue: boolean) => void
+ }
+ hideScrollbarRef: React.RefObject
+}>({
+ navbarHiddenState: {
+ navbarHidden: false,
+ setNavbarHidden: () => undefined
+ },
+ preventScrollState: {
+ preventScroll: false,
+ setPreventScroll: () => undefined
+ },
+ showVerticalScrollbarState: {
+ showVerticalScrollbar: false,
+ setShowVerticalScrollbar: () => undefined
+ },
+ showHorizontalScrollbarState: {
+ showHorizontalScrollbar: false,
+ setShowHorizontalScrollbar: () => undefined
+ },
+ showDropdownMenuState: {
+ showDropdownMenu: false,
+ setShowDropdownMenu: () => undefined
+ },
+ hideScrollbarRef: createRef()
+})
+
+const MainFramework: React.FC = () => {
+ const routeId = useMatches()[1].id
+ const routeChildren = router.routes[0].children?.find((value) => value.id === routeId)?.children
+
+ const pathname = useLocation().pathname
+
+ const hideScrollbarRef = useRef(null)
+
+ const [navbarHidden, setNavbarHidden] = useState(true)
+ const [preventScroll, setPreventScroll] = useState(false)
+ const [showVerticalScrollbar, setShowVerticalScrollbar] = useState(false)
+ const [showHorizontalScrollbar, setShowHorizontalScrollbar] = useState(false)
+ const [showDropdownMenu, setShowDropdownMenu] = useState(false)
+
+ useEffect(() => {
+ setNavbarHidden(false)
+ }, [pathname])
+
+ const handleMenuDropdownButtonClick = () => {
+ setShowDropdownMenu(!showDropdownMenu)
+ }
+
+ return (
+ <>
+
+
+
+
+
+ FatWeb
+
+
+
+
+
+ {routeChildren?.map((route) => {
+ return (
+ -
+
+ isPending ? 'pending' : isActive ? 'active' : ''
+ }
+ >
+ {(route.handle as RouteHandle).name}
+
+
+ )
+ })}
+
+
+
+
+
+
+
+ >
+ }
+ >
+
+
+
+
+
+ >
+ )
+}
+
+export default MainFramework
diff --git a/src/router/index.tsx b/src/router/index.tsx
index 316eacc..808527a 100644
--- a/src/router/index.tsx
+++ b/src/router/index.tsx
@@ -10,20 +10,36 @@ const routes: RouteObject[] = [
id: 'login',
Component: React.lazy(() => import('@/pages/Login'))
},
+ {
+ path: '/loading',
+ id: 'loading',
+ Component: React.lazy(() => import('@/components/common/LoadingMask'))
+ },
{
path: '',
- id: 'manager',
- Component: React.lazy(() => import('@/pages/Home')),
+ id: 'mainFramework',
+ Component: React.lazy(() => import('@/pages/MainFramework')),
children: [
{
- id: 'manager-sub',
- path: 'sub',
- Component: React.lazy(() => import('@/pages/Home'))
+ path: '',
+ id: 'home',
+ Component: React.lazy(() => import('@/components/home')),
+ handle: {
+ name: '主页',
+ menu: true,
+ auth: false
+ }
+ },
+ {
+ path: 'https://blog.fatweb.top',
+ id: 'blog',
+ handle: {
+ name: '博客',
+ menu: true,
+ auth: false
+ }
}
- ],
- handle: {
- auth: false
- }
+ ]
},
{
path: '*',
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
index 2bc780f..2cb4cf2 100644
--- a/src/vite-env.d.ts
+++ b/src/vite-env.d.ts
@@ -1,12 +1,9 @@
///
-type Captcha = {
- value: string
- base64Src: string
-}
-
type RouteHandle = {
- auth: boolean
+ name?: string
+ menu?: boolean
+ auth?: boolean
}
type _Response = {
@@ -15,6 +12,11 @@ type _Response = {
data: T | null
}
+type Captcha = {
+ value: string
+ base64Src: string
+}
+
type Token = {
token: string
}
diff --git a/vite.config.ts b/vite.config.ts
index 42e70a7..a4acaba 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -26,6 +26,7 @@ export default defineConfig({
'react-router',
'react-router-dom',
{
+ react: ['Suspense', 'createContext'],
'react-router': ['useMatches', 'RouterProvider'],
'react-router-dom': ['createBrowserRouter'],
antd: ['message']