mirror of
https://github.com/fantastic-admin/basic.git
synced 2026-10-07 02:54:05 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c1fca69dc | ||
|
|
f3993b8563 | ||
|
|
d39f5c3af6 | ||
|
|
86df38a142 | ||
|
|
548eac59f2 | ||
|
|
8dee916774 | ||
|
|
59ffda20e7 | ||
|
|
d908f3ce26 | ||
|
|
cc9226f19e | ||
|
|
30a303195f | ||
|
|
75d815d315 | ||
|
|
2b8a198aaf | ||
|
|
e015f29e9e | ||
|
|
795d428ed8 | ||
|
|
8adcb2654b | ||
|
|
fbef475459 | ||
|
|
8311afa712 | ||
|
|
dc17038185 |
@@ -1,3 +1,5 @@
|
||||
# 应用配置面板
|
||||
VITE_APP_SETTING = true
|
||||
# 页面标题
|
||||
VITE_APP_TITLE = Fantastic-admin 基础版
|
||||
# 接口请求地址,会设置到 axios 的 baseURL 参数上
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 应用配置面板
|
||||
VITE_APP_SETTING = false
|
||||
# 页面标题
|
||||
VITE_APP_TITLE = 页面标题
|
||||
# 接口请求地址,会设置到 axios 的 baseURL 参数上
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 应用配置面板
|
||||
VITE_APP_SETTING = false
|
||||
# 页面标题
|
||||
VITE_APP_TITLE = 页面标题(test)
|
||||
# 接口请求地址,会设置到 axios 的 baseURL 参数上
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"*.{ts,tsx,vue}": "eslint . --cache --fix",
|
||||
"*.{css,scss,vue}": "stylelint src/**/*.{css,scss,vue} --cache --fix --allow-empty-input"
|
||||
"*.{ts,tsx,vue}": "eslint --cache --fix",
|
||||
"*.{css,scss,vue}": "stylelint --cache --fix"
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
dist
|
||||
node_modules
|
||||
src/assets/sprites
|
||||
public/tinymce
|
||||
@@ -1,45 +0,0 @@
|
||||
{
|
||||
"extends": [
|
||||
"stylelint-stylistic/config",
|
||||
"stylelint-config-standard-scss",
|
||||
"stylelint-config-standard-vue/scss"
|
||||
],
|
||||
"plugins": [
|
||||
"stylelint-scss"
|
||||
],
|
||||
"rules": {
|
||||
"stylistic/max-line-length": null,
|
||||
"stylistic/block-closing-brace-newline-after": [
|
||||
"always",
|
||||
{
|
||||
"ignoreAtRules": ["if", "else"]
|
||||
}
|
||||
],
|
||||
"at-rule-no-unknown": null,
|
||||
"no-descending-specificity": null,
|
||||
"property-no-unknown": null,
|
||||
"font-family-no-missing-generic-family-keyword": null,
|
||||
"selector-class-pattern": null,
|
||||
"function-no-unknown": [
|
||||
true,
|
||||
{
|
||||
"ignoreFunctions": [
|
||||
"v-bind",
|
||||
"map-get",
|
||||
"lighten",
|
||||
"darken"
|
||||
]
|
||||
}
|
||||
],
|
||||
"selector-pseudo-element-no-unknown": [
|
||||
true,
|
||||
{
|
||||
"ignorePseudoElements": [
|
||||
"/^view-transition/"
|
||||
]
|
||||
}
|
||||
],
|
||||
"scss/double-slash-comment-empty-line-before": null,
|
||||
"scss/no-global-function-names": null
|
||||
}
|
||||
}
|
||||
+28
-26
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"type": "module",
|
||||
"version": "4.2.0",
|
||||
"version": "4.4.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc && vite build",
|
||||
@@ -13,7 +13,7 @@
|
||||
"lint": "npm-run-all -s lint:tsc lint:eslint lint:stylelint",
|
||||
"lint:tsc": "vue-tsc",
|
||||
"lint:eslint": "eslint . --cache --fix",
|
||||
"lint:stylelint": "stylelint src/**/*.{css,scss,vue} --cache --fix --allow-empty-input",
|
||||
"lint:stylelint": "stylelint \"src/**/*.{css,scss,vue}\" --cache --fix",
|
||||
"postinstall": "simple-git-hooks",
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"commit": "git cz",
|
||||
@@ -21,20 +21,20 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/vue": "^1.7.16",
|
||||
"@vueuse/core": "^10.7.0",
|
||||
"@vueuse/integrations": "^10.7.0",
|
||||
"axios": "^1.6.2",
|
||||
"@vueuse/core": "^10.7.1",
|
||||
"@vueuse/integrations": "^10.7.1",
|
||||
"axios": "^1.6.3",
|
||||
"dayjs": "^1.11.10",
|
||||
"defu": "^6.1.3",
|
||||
"element-plus": "^2.4.4",
|
||||
"eruda": "^3.0.1",
|
||||
"floating-vue": "2.0.0-beta.24",
|
||||
"hotkeys-js": "^3.13.2",
|
||||
"hotkeys-js": "^3.13.3",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mitt": "^3.0.1",
|
||||
"mockjs": "^1.1.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"overlayscrollbars": "^2.4.5",
|
||||
"overlayscrollbars": "^2.4.6",
|
||||
"overlayscrollbars-vue": "^0.5.6",
|
||||
"path-browserify": "^1.0.1",
|
||||
"path-to-regexp": "^6.2.1",
|
||||
@@ -42,55 +42,57 @@
|
||||
"qs": "^6.11.2",
|
||||
"scule": "^1.1.1",
|
||||
"vconsole": "^3.15.1",
|
||||
"vue": "^3.3.11",
|
||||
"vue": "^3.4.4",
|
||||
"vue-m-message": "^4.0.2",
|
||||
"vue-router": "^4.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "2.4.6",
|
||||
"@iconify/json": "^2.2.158",
|
||||
"@antfu/eslint-config": "2.6.1",
|
||||
"@iconify/json": "^2.2.164",
|
||||
"@iconify/vue": "^4.1.1",
|
||||
"@stylistic/stylelint-config": "^1.0.1",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/mockjs": "^1.0.10",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@types/path-browserify": "^1.0.2",
|
||||
"@types/qs": "^6.9.10",
|
||||
"@unocss/eslint-plugin": "^0.58.0",
|
||||
"@types/qs": "^6.9.11",
|
||||
"@unocss/eslint-plugin": "^0.58.3",
|
||||
"@vitejs/plugin-legacy": "^5.2.0",
|
||||
"@vitejs/plugin-vue": "^4.5.2",
|
||||
"@vitejs/plugin-vue": "^5.0.2",
|
||||
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"bumpp": "^9.2.1",
|
||||
"cz-git": "^1.8.0",
|
||||
"eslint": "^8.55.0",
|
||||
"eslint": "^8.56.0",
|
||||
"esno": "^4.0.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"http-server": "^14.1.1",
|
||||
"inquirer": "^9.2.12",
|
||||
"lint-staged": "^15.2.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"plop": "^4.0.0",
|
||||
"sass": "^1.69.5",
|
||||
"plop": "^4.0.1",
|
||||
"sass": "^1.69.7",
|
||||
"simple-git-hooks": "^2.9.0",
|
||||
"stylelint": "^15.11.0",
|
||||
"stylelint-config-standard-scss": "^11.1.0",
|
||||
"stylelint": "^16.1.0",
|
||||
"stylelint-config-recess-order": "^4.4.0",
|
||||
"stylelint-config-standard-scss": "^12.0.0",
|
||||
"stylelint-config-standard-vue": "^1.0.0",
|
||||
"stylelint-scss": "^5.3.1",
|
||||
"stylelint-stylistic": "^0.4.3",
|
||||
"svgo": "^3.1.0",
|
||||
"stylelint-scss": "^6.0.0",
|
||||
"svgo": "^3.2.0",
|
||||
"terser": "^5.26.0",
|
||||
"typescript": "^5.3.3",
|
||||
"unocss": "^0.58.0",
|
||||
"unplugin-auto-import": "^0.17.2",
|
||||
"unocss": "^0.58.3",
|
||||
"unplugin-auto-import": "^0.17.3",
|
||||
"unplugin-vue-components": "^0.26.0",
|
||||
"vite": "^5.0.10",
|
||||
"vite-plugin-banner": "^0.7.1",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-mock": "^2.9.8",
|
||||
"vite-plugin-fake-server": "^2.0.1",
|
||||
"vite-plugin-pages": "^0.32.0",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"vite-plugin-vue-inspector": "^4.0.2",
|
||||
"vite-plugin-vue-meta-layouts": "^0.3.4",
|
||||
"vue-tsc": "^1.8.25"
|
||||
"vite-plugin-vue-meta-layouts": "^0.4.1",
|
||||
"vue-tsc": "^1.8.27"
|
||||
},
|
||||
"simple-git-hooks": {
|
||||
"pre-commit": "pnpm lint-staged",
|
||||
|
||||
Generated
+1181
-1399
File diff suppressed because it is too large
Load Diff
@@ -1,20 +1,20 @@
|
||||
#browser-upgrade {
|
||||
position: absolute;
|
||||
z-index: 10001;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 10001;
|
||||
display: none;
|
||||
user-select: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #736477;
|
||||
user-select: none;
|
||||
background-color: snow;
|
||||
}
|
||||
|
||||
#browser-upgrade .title {
|
||||
margin: 40px 0;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#browser-upgrade .browsers {
|
||||
@@ -24,8 +24,8 @@
|
||||
#browser-upgrade .browsers .browser {
|
||||
display: inline-block;
|
||||
margin: 0 20px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#browser-upgrade .browsers .browser .browser-icon {
|
||||
@@ -37,10 +37,10 @@
|
||||
}
|
||||
|
||||
#browser-upgrade .browsers .browser .browser-name {
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
padding-bottom: 2px;
|
||||
margin-top: 10px;
|
||||
color: #736477;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid transparent;
|
||||
}
|
||||
|
||||
|
||||
+15
-15
@@ -4,43 +4,43 @@
|
||||
|
||||
.fantastic-admin-home {
|
||||
position: absolute;
|
||||
z-index: 10000;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 10000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
user-select: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #736477;
|
||||
user-select: none;
|
||||
background-color: snow;
|
||||
}
|
||||
|
||||
.fantastic-admin-home .loading {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.fantastic-admin-home .loading .square {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.fantastic-admin-home .loading .square::before {
|
||||
content: "";
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 15%;
|
||||
content: "";
|
||||
border: 3px solid #8c858f;
|
||||
border-radius: 15%;
|
||||
animation: square-to-dot-animation 2s linear infinite;
|
||||
}
|
||||
|
||||
@@ -63,30 +63,30 @@
|
||||
@keyframes square-to-dot-animation {
|
||||
15%,
|
||||
25% {
|
||||
border-radius: 100%;
|
||||
width: 0;
|
||||
height: 0;
|
||||
margin: 5px;
|
||||
border-width: 5px;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
40% {
|
||||
border-radius: 15%;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin: initial;
|
||||
border-width: 3px;
|
||||
border-radius: 15%;
|
||||
}
|
||||
}
|
||||
|
||||
.fantastic-admin-home .text {
|
||||
position: relative;
|
||||
font-size: 24px;
|
||||
margin-top: 20px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.fantastic-admin-home .text::after {
|
||||
content: "…";
|
||||
position: absolute;
|
||||
padding-left: 5px;
|
||||
content: "…";
|
||||
}
|
||||
|
||||
+3
-3
@@ -2,8 +2,8 @@
|
||||
import eruda from 'eruda'
|
||||
import VConsole from 'vconsole'
|
||||
import hotkeys from 'hotkeys-js'
|
||||
import elementPlusLocaleZhCN from 'element-plus/es/locale/lang/zh-cn.mjs'
|
||||
import eventBus from './utils/eventBus'
|
||||
import Provider from './ui-provider/index.vue'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
@@ -59,7 +59,7 @@ import.meta.env.VITE_APP_DEBUG_TOOL === 'vconsole' && new VConsole()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElConfigProvider :locale="elementPlusLocaleZhCN" :button="{ autoInsertSpace: true }">
|
||||
<Provider>
|
||||
<RouterView
|
||||
v-slot="{ Component, route }"
|
||||
:style="{
|
||||
@@ -71,5 +71,5 @@ import.meta.env.VITE_APP_DEBUG_TOOL === 'vconsole' && new VConsole()
|
||||
<NotAllowed v-else />
|
||||
</RouterView>
|
||||
<SystemInfo />
|
||||
</ElConfigProvider>
|
||||
</Provider>
|
||||
</template>
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
:root {
|
||||
&::view-transition-old(root),
|
||||
&::view-transition-new(root) {
|
||||
animation: none;
|
||||
mix-blend-mode: normal;
|
||||
animation: none;
|
||||
}
|
||||
|
||||
&::view-transition-old(root) {
|
||||
@@ -70,10 +70,10 @@ body {
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background-color: var(--g-container-bg);
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
background-color: var(--g-container-bg);
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
|
||||
&.overflow-hidden {
|
||||
@@ -88,8 +88,8 @@ body {
|
||||
// 右侧内容区针对fixed元素,有横向铺满的需求,可在fixed元素上设置 [data-fixed-calc-width]
|
||||
[data-fixed-calc-width] {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
right: 0;
|
||||
left: 50%;
|
||||
width: calc(100% - var(--g-main-sidebar-actual-width) - var(--g-sub-sidebar-actual-width));
|
||||
transform: translateX(-50%) translateX(calc(var(--g-main-sidebar-actual-width) / 2)) translateX(calc(var(--g-sub-sidebar-actual-width) / 2));
|
||||
}
|
||||
|
||||
@@ -3,48 +3,48 @@
|
||||
|
||||
.bar {
|
||||
position: fixed;
|
||||
z-index: 1031;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1031;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: rgb(var(--ui-primary));
|
||||
}
|
||||
|
||||
.peg {
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
display: block;
|
||||
width: 100px;
|
||||
height: 100%;
|
||||
box-shadow: 0 0 10px rgb(var(--ui-primary)), 0 0 5px rgb(var(--ui-primary));
|
||||
opacity: 1;
|
||||
transform: rotate(3deg) translate(0, -4px);
|
||||
box-shadow: 0 0 10px rgb(var(--ui-primary)), 0 0 5px rgb(var(--ui-primary));
|
||||
}
|
||||
|
||||
.spinner {
|
||||
display: block;
|
||||
position: fixed;
|
||||
z-index: 1031;
|
||||
top: 11px;
|
||||
right: 14px;
|
||||
z-index: 1031;
|
||||
display: block;
|
||||
|
||||
.spinner-icon {
|
||||
box-sizing: border-box;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
box-sizing: border-box;
|
||||
border: solid 2px transparent;
|
||||
border-radius: 50%;
|
||||
border-top-color: rgb(var(--ui-primary));
|
||||
border-left-color: rgb(var(--ui-primary));
|
||||
border-radius: 50%;
|
||||
animation: nprogress-spinner 400ms linear infinite;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nprogress-custom-parent {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
#nprogress .spinner,
|
||||
#nprogress .bar {
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
}
|
||||
|
||||
@if $type == xy {
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
}
|
||||
}
|
||||
@@ -46,8 +46,8 @@
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: "";
|
||||
display: block;
|
||||
clear: both;
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,10 +28,18 @@ function onScroll() {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="bottom-0 z-4 bg-[var(--g-container-bg)] p-5 text-center transition" :class="[
|
||||
isBottom ? 'shadow-[0_0_1px_0_var(--g-box-shadow-color)]' : 'shadow-[0_-10px_10px_-10px_var(--g-box-shadow-color)]',
|
||||
]" data-fixed-calc-width
|
||||
class="fixed-action-bar bottom-0 z-4 bg-[var(--g-container-bg)] p-5 text-center transition" :class="{ shadow: !isBottom }" data-fixed-calc-width
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fixed-action-bar {
|
||||
box-shadow: 0 0 1px 0 var(--g-box-shadow-color);
|
||||
|
||||
&.shadow {
|
||||
box-shadow: 0 -10px 10px -10px var(--g-box-shadow-color);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -36,14 +36,14 @@ const realHeight = computed(() => {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-image {
|
||||
border-radius: 5px;
|
||||
background-color: var(--el-fill-color);
|
||||
border-radius: 5px;
|
||||
box-shadow: var(--el-box-shadow-light);
|
||||
transition: background-color 0.3s, var(--el-transition-box-shadow);
|
||||
|
||||
:deep(.el-image__inner) {
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.2);
|
||||
@@ -52,12 +52,12 @@ const realHeight = computed(() => {
|
||||
|
||||
:deep(.image-slot) {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #909399;
|
||||
font-size: 30px;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -146,33 +146,33 @@ const onSuccess: UploadProps['onSuccess'] = (res) => {
|
||||
|
||||
.image {
|
||||
position: relative;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
border-radius: 6px;
|
||||
|
||||
.mask {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--el-overlay-color-lighter);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
|
||||
.actions {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
|
||||
@include position-center(xy);
|
||||
|
||||
span {
|
||||
width: 50%;
|
||||
color: var(--el-color-white);
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
color: var(--el-color-white);
|
||||
transition: color 0.1s, transform 0.1s;
|
||||
|
||||
&:hover {
|
||||
@@ -207,8 +207,8 @@ const onSuccess: UploadProps['onSuccess'] = (res) => {
|
||||
|
||||
.image-slot {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: var(--el-text-color-placeholder);
|
||||
@@ -224,12 +224,12 @@ const onSuccess: UploadProps['onSuccess'] = (res) => {
|
||||
top: 0;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
content: "";
|
||||
background-color: var(--el-overlay-color-lighter);
|
||||
}
|
||||
|
||||
|
||||
@@ -169,34 +169,34 @@ const onSuccess: UploadProps['onSuccess'] = (res) => {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
overflow: hidden;
|
||||
border: 1px dashed var(--el-border-color);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
|
||||
.mask {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--el-overlay-color-lighter);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
|
||||
.actions {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
|
||||
@include position-center(xy);
|
||||
|
||||
span {
|
||||
width: 50%;
|
||||
color: var(--el-color-white);
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
color: var(--el-color-white);
|
||||
transition: color 0.1s, transform 0.1s;
|
||||
|
||||
&.disabled {
|
||||
@@ -236,8 +236,8 @@ const onSuccess: UploadProps['onSuccess'] = (res) => {
|
||||
|
||||
.image-slot {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: var(--el-text-color-placeholder);
|
||||
@@ -253,12 +253,12 @@ const onSuccess: UploadProps['onSuccess'] = (res) => {
|
||||
top: 0;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
content: "";
|
||||
background-color: var(--el-overlay-color-lighter);
|
||||
}
|
||||
|
||||
|
||||
@@ -176,11 +176,26 @@ function handleCopy() {
|
||||
<div class="divider">
|
||||
工具栏
|
||||
</div>
|
||||
<div v-if="settingsStore.mode === 'pc'" class="setting-item">
|
||||
<div class="label">
|
||||
面包屑导航
|
||||
</div>
|
||||
<HToggle v-model="settingsStore.settings.toolbar.breadcrumb" />
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="label">
|
||||
导航搜索
|
||||
<HTooltip text="对导航进行快捷搜索">
|
||||
<SvgIcon name="ri:question-line" />
|
||||
</HTooltip>
|
||||
</div>
|
||||
<HToggle v-model="settingsStore.settings.toolbar.navSearch" />
|
||||
</div>
|
||||
<div v-if="settingsStore.mode === 'pc'" class="setting-item">
|
||||
<div class="label">
|
||||
全屏
|
||||
</div>
|
||||
<HToggle v-model="settingsStore.settings.toolbar.enableFullscreen" />
|
||||
<HToggle v-model="settingsStore.settings.toolbar.fullscreen" />
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="label">
|
||||
@@ -189,7 +204,7 @@ function handleCopy() {
|
||||
<SvgIcon name="ri:question-line" />
|
||||
</HTooltip>
|
||||
</div>
|
||||
<HToggle v-model="settingsStore.settings.toolbar.enablePageReload" />
|
||||
<HToggle v-model="settingsStore.settings.toolbar.pageReload" />
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="label">
|
||||
@@ -198,16 +213,7 @@ function handleCopy() {
|
||||
<SvgIcon name="ri:question-line" />
|
||||
</HTooltip>
|
||||
</div>
|
||||
<HToggle v-model="settingsStore.settings.toolbar.enableColorScheme" />
|
||||
</div>
|
||||
<div v-if="settingsStore.mode === 'pc'" class="divider">
|
||||
面包屑导航
|
||||
</div>
|
||||
<div v-if="settingsStore.mode === 'pc'" class="setting-item">
|
||||
<div class="label">
|
||||
是否启用
|
||||
</div>
|
||||
<HToggle v-model="settingsStore.settings.breadcrumb.enable" />
|
||||
<HToggle v-model="settingsStore.settings.toolbar.colorScheme" />
|
||||
</div>
|
||||
<div class="divider">
|
||||
页面
|
||||
@@ -221,20 +227,11 @@ function handleCopy() {
|
||||
<div class="divider">
|
||||
导航搜索
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="label">
|
||||
是否启用
|
||||
<HTooltip text="对导航进行快捷搜索">
|
||||
<SvgIcon name="ri:question-line" />
|
||||
</HTooltip>
|
||||
</div>
|
||||
<HToggle v-model="settingsStore.settings.navSearch.enable" />
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="label">
|
||||
是否启用快捷键
|
||||
</div>
|
||||
<HToggle v-model="settingsStore.settings.navSearch.enableHotkeys" :disabled="!settingsStore.settings.navSearch.enable" />
|
||||
<HToggle v-model="settingsStore.settings.navSearch.enableHotkeys" :disabled="!settingsStore.settings.toolbar.navSearch" />
|
||||
</div>
|
||||
<div class="divider">
|
||||
底部版权
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import Logo from '../Logo/index.vue'
|
||||
import Tools from '../Tools/index.vue'
|
||||
import ToolbarRightSide from '../Topbar/Toolbar/rightSide.vue'
|
||||
import useMenuStore from '@/store/modules/menu'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
|
||||
@@ -55,7 +55,7 @@ function handlerMouserScroll(event: WheelEvent) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Tools />
|
||||
<ToolbarRightSide />
|
||||
</div>
|
||||
</header>
|
||||
</Transition>
|
||||
@@ -64,32 +64,32 @@ function handlerMouserScroll(event: WheelEvent) {
|
||||
<style lang="scss" scoped>
|
||||
header {
|
||||
position: fixed;
|
||||
z-index: 2000;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 2000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
width: 100%;
|
||||
height: var(--g-header-height);
|
||||
padding: 0 20px;
|
||||
margin: 0 auto;
|
||||
color: var(--g-header-color);
|
||||
background-color: var(--g-header-bg);
|
||||
box-shadow: -1px 0 0 0 var(--g-border-color), 1px 0 0 0 var(--g-border-color), 0 1px 0 0 var(--g-border-color);
|
||||
transition: background-color 0.3s;
|
||||
|
||||
.header-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0 auto;
|
||||
|
||||
.main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
@@ -110,16 +110,16 @@ header {
|
||||
|
||||
span {
|
||||
font-size: 20px;
|
||||
letter-spacing: 1px;
|
||||
color: var(--g-header-color);
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-container {
|
||||
flex: 1;
|
||||
margin: 0 30px;
|
||||
padding: 0 20px;
|
||||
height: 100%;
|
||||
padding: 0 20px;
|
||||
margin: 0 30px;
|
||||
overflow-x: auto;
|
||||
mask-image: linear-gradient(to right, transparent, #000 20px, #000 calc(100% - 20px), transparent);
|
||||
|
||||
@@ -152,10 +152,6 @@ header {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.tools) {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 头部动画
|
||||
|
||||
@@ -31,7 +31,7 @@ onMounted(() => {
|
||||
<HKbd>I</HKbd>
|
||||
查看系统信息
|
||||
</li>
|
||||
<li v-if="settingsStore.settings.navSearch.enable && settingsStore.settings.navSearch.enableHotkeys" class="py-1">
|
||||
<li v-if="settingsStore.settings.toolbar.navSearch && settingsStore.settings.navSearch.enableHotkeys" class="py-1">
|
||||
<HKbd>{{ settingsStore.os === 'mac' ? '⌥' : 'Alt' }}</HKbd>
|
||||
<HKbd>S</HKbd>
|
||||
唤起导航搜索
|
||||
|
||||
@@ -46,10 +46,10 @@ const { switchTo } = useMenu()
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.main-sidebar-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: var(--g-main-sidebar-width);
|
||||
color: var(--g-main-sidebar-menu-color);
|
||||
background-color: var(--g-main-sidebar-bg);
|
||||
|
||||
@@ -43,7 +43,7 @@ defineExpose({
|
||||
active: isItemActive,
|
||||
}"
|
||||
>
|
||||
<router-link v-slot="{ href }" custom :to="uniqueKey.at(-1) ?? ''">
|
||||
<router-link v-slot="{ href, navigate }" custom :to="uniqueKey.at(-1) ?? ''">
|
||||
<component
|
||||
:is="subMenu ? 'div' : 'a'" v-bind="{
|
||||
...(!subMenu && {
|
||||
@@ -54,7 +54,11 @@ defineExpose({
|
||||
}" class="group menu-item-container h-full w-full flex cursor-pointer items-center justify-between gap-1 px-5 py-4 text-[var(--g-sub-sidebar-menu-color)] transition-all hover:(bg-[var(--g-sub-sidebar-menu-hover-bg)] text-[var(--g-sub-sidebar-menu-hover-color)])" :class="{
|
||||
'text-[var(--g-sub-sidebar-menu-active-color)]! bg-[var(--g-sub-sidebar-menu-active-bg)]!': isItemActive,
|
||||
'px-3!': rootMenu.isMenuPopup && level === 0,
|
||||
}" :title="typeof item.meta?.title === 'function' ? item.meta?.title() : item.meta?.title"
|
||||
}" :title="typeof item.meta?.title === 'function' ? item.meta?.title() : item.meta?.title" v-on="{
|
||||
...(!subMenu && {
|
||||
click: navigate,
|
||||
}),
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="inline-flex flex-1 items-center justify-center gap-[12px]" :class="{
|
||||
|
||||
@@ -132,14 +132,14 @@ onMounted(() => {
|
||||
isShow.value = !isShow.value
|
||||
})
|
||||
hotkeys('alt+s', (e) => {
|
||||
if (settingsStore.settings.navSearch.enable && settingsStore.settings.navSearch.enableHotkeys) {
|
||||
if (settingsStore.settings.toolbar.navSearch && settingsStore.settings.navSearch.enableHotkeys) {
|
||||
e.preventDefault()
|
||||
initSourceList()
|
||||
isShow.value = true
|
||||
}
|
||||
})
|
||||
hotkeys('esc', (e) => {
|
||||
if (settingsStore.settings.navSearch.enable && settingsStore.settings.navSearch.enableHotkeys) {
|
||||
if (settingsStore.settings.toolbar.navSearch && settingsStore.settings.navSearch.enableHotkeys) {
|
||||
e.preventDefault()
|
||||
isShow.value = false
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { useElementSize } from '@vueuse/core'
|
||||
import Logo from '../Logo/index.vue'
|
||||
import Menu from '../Menu/index.vue'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
@@ -13,11 +14,30 @@ const route = useRoute()
|
||||
const settingsStore = useSettingsStore()
|
||||
const menuStore = useMenuStore()
|
||||
|
||||
const sidebarScrollTop = ref(0)
|
||||
|
||||
function onSidebarScroll(e: Event) {
|
||||
sidebarScrollTop.value = (e.target as HTMLElement).scrollTop
|
||||
const subSidebarRef = ref()
|
||||
const showShadowTop = ref(false)
|
||||
const showShadowBottom = ref(false)
|
||||
function onSidebarScroll() {
|
||||
const scrollTop = subSidebarRef.value.scrollTop
|
||||
showShadowTop.value = scrollTop > 0
|
||||
const clientHeight = subSidebarRef.value.clientHeight
|
||||
const scrollHeight = subSidebarRef.value.scrollHeight
|
||||
showShadowBottom.value = Math.ceil(scrollTop + clientHeight) < scrollHeight
|
||||
}
|
||||
|
||||
const menuRef = ref()
|
||||
|
||||
onMounted(() => {
|
||||
onSidebarScroll()
|
||||
const { height } = useElementSize(menuRef)
|
||||
watch(() => height.value, () => {
|
||||
if (height.value > 0) {
|
||||
onSidebarScroll()
|
||||
}
|
||||
}, {
|
||||
immediate: true,
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -32,29 +52,37 @@ function onSidebarScroll(e: Event) {
|
||||
}"
|
||||
/>
|
||||
<div
|
||||
class="sub-sidebar flex-1 transition-shadow-300" :class="{
|
||||
shadow: sidebarScrollTop,
|
||||
ref="subSidebarRef" class="sub-sidebar flex-1 transition-shadow-300" :class="{
|
||||
'shadow-top': showShadowTop,
|
||||
'shadow-bottom': showShadowBottom,
|
||||
}" @scroll="onSidebarScroll"
|
||||
>
|
||||
<TransitionGroup name="sub-sidebar">
|
||||
<template v-for="(mainItem, mainIndex) in menuStore.allMenus" :key="mainIndex">
|
||||
<div v-show="mainIndex === menuStore.actived">
|
||||
<Menu :menu="mainItem.children" :value="route.meta.activeMenu || route.path" :default-openeds="menuStore.defaultOpenedPaths" :accordion="settingsStore.settings.menu.subMenuUniqueOpened" :collapse="settingsStore.mode === 'pc' && settingsStore.settings.menu.subMenuCollapse" class="menu" />
|
||||
</div>
|
||||
</template>
|
||||
</TransitionGroup>
|
||||
<div ref="menuRef">
|
||||
<TransitionGroup name="sub-sidebar">
|
||||
<template v-for="(mainItem, mainIndex) in menuStore.allMenus" :key="mainIndex">
|
||||
<div v-show="mainIndex === menuStore.actived">
|
||||
<Menu :menu="mainItem.children" :value="route.meta.activeMenu || route.path" :default-openeds="menuStore.defaultOpenedPaths" :accordion="settingsStore.settings.menu.subMenuUniqueOpened" :collapse="settingsStore.mode === 'pc' && settingsStore.settings.menu.subMenuCollapse" class="menu" />
|
||||
</div>
|
||||
</template>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="settingsStore.mode === 'pc'" class="relative flex items-center px-4 py-3" :class="[settingsStore.settings.menu.subMenuCollapse ? 'justify-center' : 'justify-end']">
|
||||
<span v-show="settingsStore.settings.menu.enableSubMenuCollapseButton" class="flex-center cursor-pointer rounded bg-stone-1 p-2 transition dark:bg-stone-9 hover:bg-stone-2 dark:hover:bg-stone-8" :class="{ '-rotate-z-180': settingsStore.settings.menu.subMenuCollapse }" @click="settingsStore.toggleSidebarCollapse()">
|
||||
<SvgIcon name="toolbar-collapse" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.sub-sidebar-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: var(--g-sub-sidebar-width);
|
||||
background-color: var(--g-sub-sidebar-bg);
|
||||
transition: background-color 0.3s, left 0.3s, width 0.3s;
|
||||
@@ -98,8 +126,16 @@ function onSidebarScroll(e: Event) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.shadow {
|
||||
box-shadow: inset 0 10px 10px -10px var(--g-box-shadow-color);
|
||||
&.shadow-top {
|
||||
box-shadow: inset 0 10px 10px -10px var(--g-box-shadow-color), inset 0 0 0 transparent;
|
||||
}
|
||||
|
||||
&.shadow-bottom {
|
||||
box-shadow: inset 0 0 0 transparent, inset 0 -10px 10px -10px var(--g-box-shadow-color);
|
||||
}
|
||||
|
||||
&.shadow-top.shadow-bottom {
|
||||
box-shadow: inset 0 10px 10px -10px var(--g-box-shadow-color), inset 0 -10px 10px -10px var(--g-box-shadow-color);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { useFullscreen } from '@vueuse/core'
|
||||
import eventBus from '@/utils/eventBus'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
|
||||
defineOptions({
|
||||
name: 'Tools',
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const mainPage = useMainPage()
|
||||
const { isFullscreen, toggle } = useFullscreen()
|
||||
|
||||
function toggleColorScheme(event: MouseEvent) {
|
||||
const { startViewTransition } = useViewTransition(() => {
|
||||
settingsStore.setColorScheme(settingsStore.settings.app.colorScheme === 'dark' ? 'light' : 'dark')
|
||||
})
|
||||
startViewTransition()?.ready.then(() => {
|
||||
const x = event.clientX
|
||||
const y = event.clientY
|
||||
const endRadius = Math.hypot(
|
||||
Math.max(x, innerWidth - x),
|
||||
Math.max(y, innerHeight - y),
|
||||
)
|
||||
const clipPath = [
|
||||
`circle(0px at ${x}px ${y}px)`,
|
||||
`circle(${endRadius}px at ${x}px ${y}px)`,
|
||||
]
|
||||
document.documentElement.animate(
|
||||
{
|
||||
clipPath: settingsStore.settings.app.colorScheme !== 'dark' ? clipPath : clipPath.reverse(),
|
||||
},
|
||||
{
|
||||
duration: 300,
|
||||
easing: 'ease-out',
|
||||
pseudoElement: settingsStore.settings.app.colorScheme !== 'dark' ? '::view-transition-new(root)' : '::view-transition-old(root)',
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const avatarError = ref(false)
|
||||
watch(() => userStore.avatar, () => {
|
||||
if (avatarError.value) {
|
||||
avatarError.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="tools flex items-center gap-4 whitespace-nowrap px-4">
|
||||
<span v-if="settingsStore.settings.navSearch.enable && settingsStore.mode === 'pc'" class="group inline-flex cursor-pointer items-center gap-1 rounded-2 bg-stone-1 px-2 py-1.5 text-dark ring-stone-3 ring-inset transition dark:bg-stone-9 dark:text-white hover:ring-1 dark:ring-stone-7" @click="eventBus.emit('global-search-toggle')">
|
||||
<SvgIcon name="ri:search-line" />
|
||||
<span class="text-sm text-stone-5 transition group-hover:text-dark dark:group-hover:text-white">搜索</span>
|
||||
<HKbd v-if="settingsStore.settings.navSearch.enableHotkeys" class="ml-2">{{ settingsStore.os === 'mac' ? '⌥' : 'Alt' }} S</HKbd>
|
||||
</span>
|
||||
<div class="flex items-center empty:hidden">
|
||||
<span v-if="settingsStore.settings.navSearch.enable && settingsStore.mode === 'mobile'" class="item" @click="eventBus.emit('global-search-toggle')">
|
||||
<SvgIcon name="ri:search-line" />
|
||||
</span>
|
||||
<span v-if="settingsStore.mode === 'pc' && settingsStore.settings.toolbar.enableFullscreen" class="item" @click="toggle">
|
||||
<SvgIcon :name="isFullscreen ? 'ri:fullscreen-exit-line' : 'ri:fullscreen-line'" />
|
||||
</span>
|
||||
<span v-if="settingsStore.settings.toolbar.enablePageReload" class="item" @click="mainPage.reload()">
|
||||
<SvgIcon name="iconoir:refresh-double" />
|
||||
</span>
|
||||
<span v-if="settingsStore.settings.toolbar.enableColorScheme" class="item" @click="toggleColorScheme">
|
||||
<SvgIcon :name="settingsStore.settings.app.colorScheme === 'light' ? 'ri:sun-line' : 'ri:moon-line'" />
|
||||
</span>
|
||||
</div>
|
||||
<HDropdownMenu
|
||||
:items="[
|
||||
[
|
||||
{ label: settingsStore.settings.home.title, handle: () => router.push({ path: settingsStore.settings.home.fullPath }), hide: !settingsStore.settings.home.enable },
|
||||
{ label: '个人设置', handle: () => router.push({ name: 'personalSetting' }) },
|
||||
],
|
||||
[
|
||||
{ label: '快捷键介绍', handle: () => eventBus.emit('global-hotkeys-intro-toggle'), hide: settingsStore.mode !== 'pc' },
|
||||
],
|
||||
[
|
||||
{ label: '退出登录', handle: () => userStore.logout() },
|
||||
],
|
||||
]"
|
||||
>
|
||||
<div flex-center cursor-pointer gap-1>
|
||||
<img v-if="userStore.avatar && !avatarError" :src="userStore.avatar" :onerror="() => (avatarError = true)" class="h-[24px] w-[24px] rounded-full">
|
||||
<SvgIcon v-else name="carbon:user-avatar-filled-alt" :size="24" class="text-gray-400" />
|
||||
{{ userStore.account }}
|
||||
<SvgIcon name="ep:caret-bottom" />
|
||||
</div>
|
||||
</HDropdownMenu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.item {
|
||||
--at-apply: flex px-2 py-1 cursor-pointer;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,58 @@
|
||||
<script setup lang="ts">
|
||||
import { compile } from 'path-to-regexp'
|
||||
import Breadcrumb from '../../../Breadcrumb/index.vue'
|
||||
import BreadcrumbItem from '../../../Breadcrumb/item.vue'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
|
||||
const breadcrumbList = computed(() => {
|
||||
const breadcrumbList = []
|
||||
if (settingsStore.settings.home.enable) {
|
||||
breadcrumbList.push({
|
||||
path: settingsStore.settings.home.fullPath,
|
||||
title: settingsStore.settings.home.title,
|
||||
})
|
||||
}
|
||||
if (route.meta.breadcrumbNeste) {
|
||||
route.meta.breadcrumbNeste.forEach((item) => {
|
||||
if (item.hide === false) {
|
||||
breadcrumbList.push({
|
||||
path: item.path,
|
||||
title: item.title,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
return breadcrumbList
|
||||
})
|
||||
|
||||
function pathCompile(path: string) {
|
||||
const toPath = compile(path)
|
||||
return toPath(route.params)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Breadcrumb v-if="settingsStore.mode === 'pc' && settingsStore.settings.app.routeBaseOn !== 'filesystem'" class="breadcrumb whitespace-nowrap px-2">
|
||||
<TransitionGroup name="breadcrumb">
|
||||
<BreadcrumbItem v-for="(item, index) in breadcrumbList" :key="`${index}_${item.path}_${item.title}`" :to="index < breadcrumbList.length - 1 && item.path !== '' ? pathCompile(item.path) : ''">
|
||||
{{ item.title }}
|
||||
</BreadcrumbItem>
|
||||
</TransitionGroup>
|
||||
</Breadcrumb>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// 面包屑动画
|
||||
.breadcrumb-enter-active {
|
||||
transition: transform 0.3s, opacity 0.3s;
|
||||
}
|
||||
|
||||
.breadcrumb-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateX(30px) skewX(-50deg);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
|
||||
defineOptions({
|
||||
name: 'ColorScheme',
|
||||
})
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
|
||||
function toggleColorScheme(event: MouseEvent) {
|
||||
const { startViewTransition } = useViewTransition(() => {
|
||||
settingsStore.setColorScheme(settingsStore.settings.app.colorScheme === 'dark' ? 'light' : 'dark')
|
||||
})
|
||||
startViewTransition()?.ready.then(() => {
|
||||
const x = event.clientX
|
||||
const y = event.clientY
|
||||
const endRadius = Math.hypot(
|
||||
Math.max(x, innerWidth - x),
|
||||
Math.max(y, innerHeight - y),
|
||||
)
|
||||
const clipPath = [
|
||||
`circle(0px at ${x}px ${y}px)`,
|
||||
`circle(${endRadius}px at ${x}px ${y}px)`,
|
||||
]
|
||||
document.documentElement.animate(
|
||||
{
|
||||
clipPath: settingsStore.settings.app.colorScheme !== 'dark' ? clipPath : clipPath.reverse(),
|
||||
},
|
||||
{
|
||||
duration: 300,
|
||||
easing: 'ease-out',
|
||||
pseudoElement: settingsStore.settings.app.colorScheme !== 'dark' ? '::view-transition-new(root)' : '::view-transition-old(root)',
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span class="flex-center cursor-pointer px-2 py-1" @click="toggleColorScheme">
|
||||
<SvgIcon :name="settingsStore.settings.app.colorScheme === 'light' ? 'ri:sun-line' : 'ri:moon-line'" />
|
||||
</span>
|
||||
</template>
|
||||
@@ -0,0 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import { useFullscreen } from '@vueuse/core'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
|
||||
defineOptions({
|
||||
name: 'Fullscreen',
|
||||
})
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
|
||||
const { isFullscreen, toggle } = useFullscreen()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span v-if="settingsStore.mode === 'pc'" class="flex-center cursor-pointer px-2 py-1" @click="toggle">
|
||||
<SvgIcon :name="isFullscreen ? 'ri:fullscreen-exit-line' : 'ri:fullscreen-line'" />
|
||||
</span>
|
||||
</template>
|
||||
@@ -0,0 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import eventBus from '@/utils/eventBus'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
|
||||
defineOptions({
|
||||
name: 'ToolbarRightSide',
|
||||
})
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span class="flex-center cursor-pointer px-2 py-1" @click="eventBus.emit('global-search-toggle')">
|
||||
<SvgIcon v-if="settingsStore.mode === 'mobile'" name="ri:search-line" />
|
||||
<span v-else class="group inline-flex cursor-pointer items-center gap-1 whitespace-nowrap rounded-2 bg-stone-1 px-2 py-1.5 text-dark ring-stone-3 ring-inset transition dark:bg-stone-9 dark:text-white hover:ring-1 dark:ring-stone-7">
|
||||
<SvgIcon name="ri:search-line" />
|
||||
<span class="text-sm text-stone-5 transition group-hover:text-dark dark:group-hover:text-white">搜索</span>
|
||||
<HKbd v-if="settingsStore.settings.navSearch.enableHotkeys" class="ml-2">{{ settingsStore.os === 'mac' ? '⌥' : 'Alt' }} S</HKbd>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'PageReload',
|
||||
})
|
||||
|
||||
const mainPage = useMainPage()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span class="flex-center cursor-pointer px-2 py-1" @click="mainPage.reload()">
|
||||
<SvgIcon name="iconoir:refresh-double" />
|
||||
</span>
|
||||
</template>
|
||||
@@ -1,98 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import { compile } from 'path-to-regexp'
|
||||
import Breadcrumb from '../../Breadcrumb/index.vue'
|
||||
import BreadcrumbItem from '../../Breadcrumb/item.vue'
|
||||
import Tools from '../../Tools/index.vue'
|
||||
import LeftSide from './leftSide.vue'
|
||||
import RightSide from './rightSide.vue'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
|
||||
defineOptions({
|
||||
name: 'Toolbar',
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
|
||||
const enableSubMenuCollapseButton = computed(() => {
|
||||
return settingsStore.mode === 'mobile' || settingsStore.settings.menu.enableSubMenuCollapseButton
|
||||
})
|
||||
|
||||
const breadcrumbList = computed(() => {
|
||||
const breadcrumbList = []
|
||||
if (settingsStore.settings.home.enable) {
|
||||
breadcrumbList.push({
|
||||
path: settingsStore.settings.home.fullPath,
|
||||
title: settingsStore.settings.home.title,
|
||||
})
|
||||
}
|
||||
if (route.meta.breadcrumbNeste) {
|
||||
route.meta.breadcrumbNeste.forEach((item) => {
|
||||
if (item.hide === false) {
|
||||
breadcrumbList.push({
|
||||
path: item.path,
|
||||
title: item.title,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
return breadcrumbList
|
||||
})
|
||||
|
||||
function pathCompile(path: string) {
|
||||
const toPath = compile(path)
|
||||
return toPath(route.params)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="toolbar-container">
|
||||
<div class="left-box">
|
||||
<div v-if="enableSubMenuCollapseButton" class="flex-center cursor-pointer px-2 py-1 transition-transform" :class="{ '-rotate-z-180': settingsStore.settings.menu.subMenuCollapse }" @click="settingsStore.toggleSidebarCollapse()">
|
||||
<SvgIcon name="toolbar-collapse" />
|
||||
</div>
|
||||
<Breadcrumb v-if="settingsStore.mode === 'pc' && settingsStore.settings.breadcrumb.enable && settingsStore.settings.app.routeBaseOn !== 'filesystem'" class="breadcrumb">
|
||||
<TransitionGroup name="breadcrumb">
|
||||
<BreadcrumbItem v-for="(item, index) in breadcrumbList" :key="`${index}_${item.path}_${item.title}`" :to="index < breadcrumbList.length - 1 && item.path !== '' ? pathCompile(item.path) : ''">
|
||||
{{ item.title }}
|
||||
</BreadcrumbItem>
|
||||
</TransitionGroup>
|
||||
</Breadcrumb>
|
||||
<div class="toolbar-container flex items-center justify-between">
|
||||
<div class="h-full flex items-center of-hidden pl-2 pr-16" style="mask-image: linear-gradient(90deg, #000 0%, #000 calc(100% - 50px), transparent);">
|
||||
<LeftSide />
|
||||
</div>
|
||||
<div v-show="['side', 'single'].includes(settingsStore.settings.menu.menuMode)" class="h-full flex items-center px-2">
|
||||
<RightSide />
|
||||
</div>
|
||||
<Tools />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.toolbar-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: var(--g-toolbar-height);
|
||||
background-color: var(--g-container-bg);
|
||||
transition: background-color 0.3s;
|
||||
|
||||
.left-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 10px;
|
||||
padding-right: 50px;
|
||||
overflow: hidden;
|
||||
mask-image: linear-gradient(90deg, #000 0%, #000 calc(100% - 50px), transparent);
|
||||
|
||||
.breadcrumb {
|
||||
padding-left: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 面包屑动画
|
||||
.breadcrumb-enter-active {
|
||||
transition: transform 0.3s, opacity 0.3s;
|
||||
}
|
||||
|
||||
.breadcrumb-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateX(30px) skewX(-50deg);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import Breadcrumb from './Breadcrumb/index.vue'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
|
||||
defineOptions({
|
||||
name: 'ToolbarLeftSide',
|
||||
})
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center">
|
||||
<div v-if="settingsStore.mode === 'mobile'" class="flex-center cursor-pointer px-2 py-1 -rotate-z-180" @click="settingsStore.toggleSidebarCollapse()">
|
||||
<SvgIcon name="toolbar-collapse" />
|
||||
</div>
|
||||
<Breadcrumb v-if="settingsStore.settings.toolbar.breadcrumb" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,54 @@
|
||||
<script setup lang="ts">
|
||||
import NavSearch from './NavSearch/index.vue'
|
||||
import Fullscreen from './Fullscreen/index.vue'
|
||||
import PageReload from './PageReload/index.vue'
|
||||
import ColorScheme from './ColorScheme/index.vue'
|
||||
import eventBus from '@/utils/eventBus'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
|
||||
defineOptions({
|
||||
name: 'Tools',
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const avatarError = ref(false)
|
||||
watch(() => userStore.avatar, () => {
|
||||
if (avatarError.value) {
|
||||
avatarError.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center">
|
||||
<NavSearch v-if="settingsStore.settings.toolbar.navSearch" />
|
||||
<Fullscreen v-if="settingsStore.settings.toolbar.fullscreen" />
|
||||
<PageReload v-if="settingsStore.settings.toolbar.pageReload" />
|
||||
<ColorScheme v-if="settingsStore.settings.toolbar.colorScheme" />
|
||||
<HDropdownMenu
|
||||
:items="[
|
||||
[
|
||||
{ label: settingsStore.settings.home.title, handle: () => router.push({ path: settingsStore.settings.home.fullPath }), hide: !settingsStore.settings.home.enable },
|
||||
],
|
||||
[
|
||||
{ label: '快捷键介绍', handle: () => eventBus.emit('global-hotkeys-intro-toggle'), hide: settingsStore.mode !== 'pc' },
|
||||
],
|
||||
[
|
||||
{ label: '退出登录', handle: () => userStore.logout() },
|
||||
],
|
||||
]" class="flex-center cursor-pointer px-2"
|
||||
>
|
||||
<div class="flex-center gap-1">
|
||||
<img v-if="userStore.avatar && !avatarError" :src="userStore.avatar" :onerror="() => (avatarError = true)" class="h-[24px] w-[24px] rounded-full">
|
||||
<SvgIcon v-else name="carbon:user-avatar-filled-alt" :size="24" class="text-gray-400" />
|
||||
{{ userStore.account }}
|
||||
<SvgIcon name="ep:caret-bottom" />
|
||||
</div>
|
||||
</HDropdownMenu>
|
||||
</div>
|
||||
</template>
|
||||
@@ -10,8 +10,8 @@ const settingsStore = useSettingsStore()
|
||||
|
||||
const enableToolbar = computed(() => {
|
||||
return !(
|
||||
settingsStore.settings.menu.menuMode === 'head' && !settingsStore.settings.menu.enableSubMenuCollapseButton && (
|
||||
!settingsStore.settings.breadcrumb.enable || settingsStore.settings.app.routeBaseOn === 'filesystem'
|
||||
settingsStore.settings.menu.menuMode === 'head' && (
|
||||
!settingsStore.settings.toolbar.breadcrumb || settingsStore.settings.app.routeBaseOn === 'filesystem'
|
||||
)
|
||||
)
|
||||
})
|
||||
@@ -51,8 +51,8 @@ watch(scrollTop, (val, oldVal) => {
|
||||
<style lang="scss" scoped>
|
||||
.topbar-container {
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
top: 0;
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 1px 0 0 var(--g-border-color);
|
||||
|
||||
@@ -54,12 +54,12 @@ function open() {
|
||||
}
|
||||
|
||||
.slide-right-enter-from {
|
||||
opacity: 0;
|
||||
margin-left: -20px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.slide-right-leave-to {
|
||||
opacity: 0;
|
||||
margin-left: 20px;
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
+17
-19
@@ -51,7 +51,7 @@ watch(() => routeInfo.path, () => {
|
||||
|
||||
onMounted(() => {
|
||||
hotkeys('f5', (e) => {
|
||||
if (settingsStore.settings.toolbar.enablePageReload) {
|
||||
if (settingsStore.settings.toolbar.pageReload) {
|
||||
e.preventDefault()
|
||||
mainPage.reload()
|
||||
}
|
||||
@@ -67,6 +67,8 @@ onUnmounted(() => {
|
||||
hotkeys.unbind('f5')
|
||||
hotkeys.unbind('alt+`')
|
||||
})
|
||||
|
||||
const enableAppSetting = import.meta.env.VITE_APP_SETTING === 'true'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -97,7 +99,7 @@ onUnmounted(() => {
|
||||
</div>
|
||||
<Search />
|
||||
<HotkeysIntro />
|
||||
<template v-if="settingsStore.settings.app.enableAppSetting">
|
||||
<template v-if="enableAppSetting">
|
||||
<div class="app-setting" @click="eventBus.emit('global-app-setting-toggle')">
|
||||
<SvgIcon name="uiw:setting-o" class="icon" />
|
||||
</div>
|
||||
@@ -150,9 +152,9 @@ onUnmounted(() => {
|
||||
|
||||
.sidebar-container {
|
||||
position: fixed;
|
||||
z-index: 1010;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 1010;
|
||||
display: flex;
|
||||
width: calc(var(--g-main-sidebar-actual-width) + var(--g-sub-sidebar-actual-width));
|
||||
box-shadow: -1px 0 0 0 var(--g-border-color), 1px 0 0 0 var(--g-border-color);
|
||||
@@ -161,21 +163,21 @@ onUnmounted(() => {
|
||||
|
||||
.sidebar-mask {
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
visibility: hidden;
|
||||
background-image: radial-gradient(transparent 1px, rgb(0 0 0 / 30%) 1px);
|
||||
background-size: 4px 4px;
|
||||
backdrop-filter: saturate(50%) blur(4px);
|
||||
transition: all 0.2s;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.2s;
|
||||
|
||||
&.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,9 +195,9 @@ onUnmounted(() => {
|
||||
transition: margin-left 0.3s, background-color 0.3s, box-shadow 0.3s;
|
||||
|
||||
.main {
|
||||
height: 100%;
|
||||
flex: auto;
|
||||
position: relative;
|
||||
flex: auto;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
transition: 0.3s;
|
||||
}
|
||||
@@ -220,10 +222,6 @@ header:not(.header-leave-active) + .wrapper {
|
||||
.main-container {
|
||||
.topbar-container {
|
||||
top: var(--g-header-height);
|
||||
|
||||
:deep(.tools) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -232,18 +230,18 @@ header:not(.header-leave-active) + .wrapper {
|
||||
--at-apply: text-white dark:text-dark bg-ui-primary;
|
||||
|
||||
position: fixed;
|
||||
z-index: 10;
|
||||
right: 0;
|
||||
top: calc(50% + 250px);
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
|
||||
.icon {
|
||||
animation: rotate 5s linear infinite;
|
||||
@@ -270,12 +268,12 @@ header:not(.header-leave-active) + .wrapper {
|
||||
}
|
||||
|
||||
.slide-right-enter-from {
|
||||
opacity: 0;
|
||||
margin-left: -20px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.slide-right-leave-to {
|
||||
opacity: 0;
|
||||
margin-left: 20px;
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
+2
-5
@@ -2,10 +2,6 @@ import '@/utils/baidu'
|
||||
|
||||
import '@/utils/system.copyright'
|
||||
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||
|
||||
import FloatingVue from 'floating-vue'
|
||||
import 'floating-vue/dist/style.css'
|
||||
|
||||
@@ -17,6 +13,7 @@ import 'overlayscrollbars/overlayscrollbars.css'
|
||||
import App from './App.vue'
|
||||
import pinia from './store'
|
||||
import router from './router'
|
||||
import ui from './ui-provider'
|
||||
|
||||
// 自定义指令
|
||||
import directive from '@/utils/directive'
|
||||
@@ -34,13 +31,13 @@ import 'virtual:uno.css'
|
||||
import '@/assets/styles/globals.scss'
|
||||
|
||||
const app = createApp(App)
|
||||
app.use(ElementPlus)
|
||||
app.use(FloatingVue, {
|
||||
distance: 12,
|
||||
})
|
||||
app.use(Message)
|
||||
app.use(pinia)
|
||||
app.use(router)
|
||||
app.use(ui)
|
||||
directive(app)
|
||||
if (icons.isOfflineUse) {
|
||||
for (const info of icons.collections) {
|
||||
|
||||
+4
-2
@@ -1,4 +1,6 @@
|
||||
export default [
|
||||
import { defineFakeRoute } from 'vite-plugin-fake-server/client'
|
||||
|
||||
export default defineFakeRoute([
|
||||
{
|
||||
url: '/mock/app/route/list',
|
||||
method: 'get',
|
||||
@@ -149,4 +151,4 @@ export default [
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
])
|
||||
|
||||
+19
-14
@@ -1,26 +1,29 @@
|
||||
export default [
|
||||
import { defineFakeRoute } from 'vite-plugin-fake-server/client'
|
||||
import Mock from 'mockjs'
|
||||
|
||||
export default defineFakeRoute([
|
||||
{
|
||||
url: '/mock/user/login',
|
||||
method: 'post',
|
||||
response: ({ body }: any) => {
|
||||
response: ({ body }) => {
|
||||
return {
|
||||
error: '',
|
||||
status: 1,
|
||||
data: {
|
||||
data: Mock.mock({
|
||||
account: body.account,
|
||||
token: `${body.account}_@string`,
|
||||
failure_time: Math.ceil(new Date().getTime() / 1000) + 24 * 60 * 60,
|
||||
avatar: '',
|
||||
},
|
||||
}),
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
url: '/mock/user/permission',
|
||||
method: 'get',
|
||||
response: ({ headers }: any) => {
|
||||
response: ({ headers }) => {
|
||||
let permissions: string[] = []
|
||||
if (headers.token.indexOf('admin') === 0) {
|
||||
if (headers.token?.indexOf('admin') === 0) {
|
||||
permissions = [
|
||||
'permission.browse',
|
||||
'permission.create',
|
||||
@@ -28,7 +31,7 @@ export default [
|
||||
'permission.remove',
|
||||
]
|
||||
}
|
||||
else if (headers.token.indexOf('test') === 0) {
|
||||
else if (headers.token?.indexOf('test') === 0) {
|
||||
permissions = [
|
||||
'permission.browse',
|
||||
]
|
||||
@@ -45,12 +48,14 @@ export default [
|
||||
{
|
||||
url: '/mock/user/password/edit',
|
||||
method: 'post',
|
||||
response: {
|
||||
error: '',
|
||||
status: 1,
|
||||
data: {
|
||||
isSuccess: true,
|
||||
},
|
||||
response: () => {
|
||||
return {
|
||||
error: '',
|
||||
status: 1,
|
||||
data: {
|
||||
isSuccess: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
])
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'
|
||||
|
||||
const mocks: any[] = []
|
||||
const mockContext = import.meta.glob('./mock/*.ts', { eager: true })
|
||||
|
||||
Object.keys(mockContext).forEach((v) => {
|
||||
mocks.push(...(mockContext[v] as any).default)
|
||||
})
|
||||
|
||||
export function setupProdMockServer() {
|
||||
createProdMockServer(mocks)
|
||||
}
|
||||
@@ -9,7 +9,6 @@ const globalSettingsDefault: RecursiveRequired<Settings.all> = {
|
||||
enableProgress: true,
|
||||
enableDynamicTitle: false,
|
||||
routeBaseOn: 'frontend',
|
||||
enableAppSetting: false,
|
||||
},
|
||||
home: {
|
||||
enable: true,
|
||||
@@ -32,18 +31,16 @@ const globalSettingsDefault: RecursiveRequired<Settings.all> = {
|
||||
mode: 'static',
|
||||
},
|
||||
toolbar: {
|
||||
enableFullscreen: false,
|
||||
enablePageReload: false,
|
||||
enableColorScheme: false,
|
||||
},
|
||||
breadcrumb: {
|
||||
enable: true,
|
||||
breadcrumb: true,
|
||||
navSearch: true,
|
||||
fullscreen: false,
|
||||
pageReload: false,
|
||||
colorScheme: false,
|
||||
},
|
||||
mainPage: {
|
||||
enableHotkeys: true,
|
||||
},
|
||||
navSearch: {
|
||||
enable: true,
|
||||
enableHotkeys: true,
|
||||
},
|
||||
copyright: {
|
||||
|
||||
@@ -70,16 +70,17 @@ const useSettingsStore = defineStore(
|
||||
}
|
||||
}
|
||||
|
||||
// 次导航是否收起(用于记录 pc 模式下最后的状态)
|
||||
const subMenuCollapseLastStatus = ref(settingsDefault.menu.subMenuCollapse)
|
||||
// 切换侧边栏导航展开/收起
|
||||
function toggleSidebarCollapse() {
|
||||
settings.value.menu.subMenuCollapse = !settings.value.menu.subMenuCollapse
|
||||
if (mode.value === 'pc') {
|
||||
subMenuCollapseLastStatus.value = !subMenuCollapseLastStatus.value
|
||||
}
|
||||
}
|
||||
|
||||
// 次导航是否收起(用于记录 pc 模式下最后的状态)
|
||||
const subMenuCollapseLastStatus = ref(settingsDefault.menu.subMenuCollapse)
|
||||
watch(() => settings.value.menu.subMenuCollapse, (val) => {
|
||||
if (mode.value === 'pc') {
|
||||
subMenuCollapseLastStatus.value = val
|
||||
}
|
||||
})
|
||||
watch(mode, (val) => {
|
||||
switch (val) {
|
||||
case 'pc':
|
||||
|
||||
Vendored
+13
-22
@@ -38,11 +38,6 @@ declare namespace Settings {
|
||||
* @可选值 `'filesystem'` 文件系统
|
||||
*/
|
||||
routeBaseOn?: 'frontend' | 'backend' | 'filesystem'
|
||||
/**
|
||||
* 是否开启应用配置,强烈建议在生产环境中关闭
|
||||
* @默认值 `false`
|
||||
*/
|
||||
enableAppSetting?: boolean
|
||||
}
|
||||
interface home {
|
||||
/**
|
||||
@@ -118,28 +113,31 @@ declare namespace Settings {
|
||||
mode?: 'static' | 'fixed' | 'sticky'
|
||||
}
|
||||
interface toolbar {
|
||||
/**
|
||||
* 是否开启面包屑导航
|
||||
* @默认值 `true`
|
||||
*/
|
||||
breadcrumb?: boolean
|
||||
/**
|
||||
* 是否开启导航搜索
|
||||
* @默认值 `true`
|
||||
*/
|
||||
navSearch?: boolean
|
||||
/**
|
||||
* 是否开启全屏
|
||||
* @默认值 `false`
|
||||
*/
|
||||
enableFullscreen?: boolean
|
||||
fullscreen?: boolean
|
||||
/**
|
||||
* 是否开启页面刷新
|
||||
* @默认值 `false`
|
||||
*/
|
||||
enablePageReload?: boolean
|
||||
pageReload?: boolean
|
||||
/**
|
||||
* 是否开启颜色主题
|
||||
* @默认值 `false`
|
||||
*/
|
||||
enableColorScheme?: boolean
|
||||
}
|
||||
interface breadcrumb {
|
||||
/**
|
||||
* 是否开启面包屑导航
|
||||
* @默认值 `true`
|
||||
*/
|
||||
enable?: boolean
|
||||
colorScheme?: boolean
|
||||
}
|
||||
interface mainPage {
|
||||
/**
|
||||
@@ -149,11 +147,6 @@ declare namespace Settings {
|
||||
enableHotkeys?: boolean
|
||||
}
|
||||
interface navSearch {
|
||||
/**
|
||||
* 是否开启导航搜索
|
||||
* @默认值 `true`
|
||||
*/
|
||||
enable?: boolean
|
||||
/**
|
||||
* 是否开启导航搜索快捷键
|
||||
* @默认值 `true`
|
||||
@@ -200,8 +193,6 @@ declare namespace Settings {
|
||||
topbar?: topbar
|
||||
/** 工具栏设置 */
|
||||
toolbar?: toolbar
|
||||
/** 面包屑导航设置 */
|
||||
breadcrumb?: breadcrumb
|
||||
/** 页面设置 */
|
||||
mainPage?: mainPage
|
||||
/** 导航搜索设置 */
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { App } from 'vue'
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||
|
||||
function install(app: App) {
|
||||
app.use(ElementPlus)
|
||||
}
|
||||
|
||||
export default { install }
|
||||
@@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import zhCN from 'element-plus/es/locale/lang/zh-cn.mjs'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElConfigProvider :locale="zhCN" :button="{ autoInsertSpace: true }">
|
||||
<slot />
|
||||
</ElConfigProvider>
|
||||
</template>
|
||||
+2
-2
@@ -141,7 +141,7 @@ function open(url: string) {
|
||||
</template>
|
||||
<ElCarousel trigger="click" indicator-position="none" :interval="5000" height="250px">
|
||||
<ElCarouselItem v-for="(item, index) in fantasticAdminInfo.data" :key="item">
|
||||
<ElImage :src="item" fit="cover" style="cursor: pointer; width: 100%; height: 250px; margin: auto;" @click="fantasticAdminInfo.imageVisible = true; fantasticAdminInfo.index = index" />
|
||||
<ElImage :src="item" fit="cover" style="width: 100%; height: 250px; margin: auto; cursor: pointer;" @click="fantasticAdminInfo.imageVisible = true; fantasticAdminInfo.index = index" />
|
||||
</ElCarouselItem>
|
||||
</ElCarousel>
|
||||
<ElImageViewer v-if="fantasticAdminInfo.imageVisible" :url-list="fantasticAdminInfo.data" :initial-index="fantasticAdminInfo.index" @close="fantasticAdminInfo.imageVisible = false" />
|
||||
@@ -168,7 +168,7 @@ function open(url: string) {
|
||||
</template>
|
||||
<ElCarousel trigger="click" indicator-position="none" :interval="5000" height="250px">
|
||||
<ElCarouselItem v-for="(item, index) in oneStepAdminInfo.data" :key="item">
|
||||
<ElImage :src="item" fit="cover" style="cursor: pointer; width: 100%; height: 250px; margin: auto;" @click="oneStepAdminInfo.imageVisible = true; oneStepAdminInfo.index = index" />
|
||||
<ElImage :src="item" fit="cover" style="width: 100%; height: 250px; margin: auto; cursor: pointer;" @click="oneStepAdminInfo.imageVisible = true; oneStepAdminInfo.index = index" />
|
||||
</ElCarouselItem>
|
||||
</ElCarousel>
|
||||
<ElImageViewer v-if="oneStepAdminInfo.imageVisible" :url-list="oneStepAdminInfo.data" :initial-index="oneStepAdminInfo.index" @close="oneStepAdminInfo.imageVisible = false" />
|
||||
|
||||
+15
-15
@@ -306,15 +306,15 @@ function testAccount(account: string) {
|
||||
[data-mode="mobile"] {
|
||||
#login-box {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: inherit;
|
||||
left: inherit;
|
||||
transform: translateX(0) translateY(0);
|
||||
flex-direction: column;
|
||||
justify-content: start;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
transform: translateX(0) translateY(0);
|
||||
|
||||
.login-banner {
|
||||
width: 100%;
|
||||
@@ -322,12 +322,12 @@ function testAccount(account: string) {
|
||||
|
||||
.banner {
|
||||
position: relative;
|
||||
top: inherit;
|
||||
right: inherit;
|
||||
display: inherit;
|
||||
width: 100%;
|
||||
max-width: 375px;
|
||||
margin: 0 auto;
|
||||
display: inherit;
|
||||
top: inherit;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
@@ -357,22 +357,22 @@ function testAccount(account: string) {
|
||||
}
|
||||
|
||||
#login-box {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
background-color: var(--g-container-bg);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: var(--el-box-shadow);
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
|
||||
.login-banner {
|
||||
position: relative;
|
||||
width: 450px;
|
||||
background-color: var(--g-bg);
|
||||
overflow: hidden;
|
||||
background-color: var(--g-bg);
|
||||
|
||||
.banner {
|
||||
width: 100%;
|
||||
@@ -394,8 +394,8 @@ function testAccount(account: string) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
min-height: 500px;
|
||||
width: 500px;
|
||||
min-height: 500px;
|
||||
padding: 50px;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -403,10 +403,10 @@ function testAccount(account: string) {
|
||||
position: relative;
|
||||
|
||||
.title {
|
||||
font-size: 1.3em;
|
||||
color: var(--el-text-color-primary);
|
||||
margin: 0 auto 30px;
|
||||
font-size: 1.3em;
|
||||
font-weight: bold;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -415,9 +415,9 @@ function testAccount(account: string) {
|
||||
margin-bottom: 24px;
|
||||
|
||||
:deep(.el-input) {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
line-height: inherit;
|
||||
width: 100%;
|
||||
|
||||
input {
|
||||
height: 48px;
|
||||
@@ -467,8 +467,8 @@ function testAccount(account: string) {
|
||||
.copyright {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
export default {
|
||||
extends: [
|
||||
'stylelint-config-standard-scss',
|
||||
'stylelint-config-standard-vue/scss',
|
||||
'stylelint-config-recess-order',
|
||||
'@stylistic/stylelint-config',
|
||||
],
|
||||
plugins: [
|
||||
'stylelint-scss',
|
||||
],
|
||||
rules: {
|
||||
'at-rule-no-unknown': null,
|
||||
'no-descending-specificity': null,
|
||||
'property-no-unknown': null,
|
||||
'font-family-no-missing-generic-family-keyword': null,
|
||||
'selector-class-pattern': null,
|
||||
'function-no-unknown': [
|
||||
true,
|
||||
{
|
||||
ignoreFunctions: [
|
||||
'v-bind',
|
||||
'map-get',
|
||||
'lighten',
|
||||
'darken',
|
||||
],
|
||||
},
|
||||
],
|
||||
'selector-pseudo-element-no-unknown': [
|
||||
true,
|
||||
{
|
||||
ignorePseudoElements: [
|
||||
'/^view-transition/',
|
||||
],
|
||||
},
|
||||
],
|
||||
'scss/double-slash-comment-empty-line-before': null,
|
||||
'scss/no-global-function-names': null,
|
||||
'@stylistic/max-line-length': null,
|
||||
'@stylistic/block-closing-brace-newline-after': [
|
||||
'always',
|
||||
{
|
||||
ignoreAtRules: ['if', 'else'],
|
||||
},
|
||||
],
|
||||
},
|
||||
allowEmptyInput: true,
|
||||
ignoreFiles: [
|
||||
'node_modules/**/*',
|
||||
'dist*/**/*',
|
||||
],
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
import { viteMockServe } from 'vite-plugin-mock'
|
||||
import { vitePluginFakeServer } from 'vite-plugin-fake-server'
|
||||
|
||||
export default function createMock(env, isBuild) {
|
||||
const { VITE_BUILD_MOCK } = env
|
||||
return viteMockServe({
|
||||
mockPath: 'src/mock',
|
||||
localEnabled: !isBuild,
|
||||
prodEnabled: isBuild && VITE_BUILD_MOCK === 'true',
|
||||
injectCode: `
|
||||
import { setupProdMockServer } from './mockProdServer';
|
||||
setupProdMockServer();
|
||||
`,
|
||||
return vitePluginFakeServer({
|
||||
logger: !isBuild,
|
||||
include: 'src/mock',
|
||||
infixName: false,
|
||||
enableProd: isBuild && VITE_BUILD_MOCK === 'true',
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user