@scx-js/scx-admin 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/crud/crud-add-button.vue +28 -0
- package/components/crud/crud-batch-delete.vue +27 -0
- package/components/crud/crud-edit-dialog.vue +36 -0
- package/components/crud/crud-edit-form.vue +28 -0
- package/components/crud/crud-form-footer.vue +28 -0
- package/components/crud/crud-pagination.vue +51 -0
- package/components/crud/crud-reset-button.vue +19 -0
- package/components/crud/crud-search-button.vue +25 -0
- package/components/crud/crud-table-delete-button.vue +31 -0
- package/components/crud/crud-table-edit-button.vue +26 -0
- package/components/crud/crud-table.vue +131 -0
- package/components/crud/index.css +46 -0
- package/components/crud/index.vue +37 -0
- package/components/easy-ckeditor/default-editor-config.js +199 -0
- package/components/easy-ckeditor/easy-ckeditor-lazy.css +17 -0
- package/components/easy-ckeditor/easy-ckeditor-lazy.vue +106 -0
- package/components/easy-ckeditor/index.css +3 -0
- package/components/easy-ckeditor/index.vue +58 -0
- package/components/easy-ckeditor/plugins/scx-upload-adapter.js +39 -0
- package/components/easy-form-item/index.vue +168 -0
- package/components/easy-image/index.css +24 -0
- package/components/easy-image/index.vue +75 -0
- package/components/easy-monaco-editor/index.css +8 -0
- package/components/easy-monaco-editor/index.vue +70 -0
- package/components/easy-monaco-editor/use-worker.js +27 -0
- package/components/easy-select/index.vue +29 -0
- package/components/easy-upload/index.vue +94 -0
- package/components/easy-upload-list/index.vue +107 -0
- package/components/index.js +69 -0
- package/components/left-tree/index.css +74 -0
- package/components/left-tree/index.vue +130 -0
- package/components/scx-container/index.css +19 -0
- package/components/scx-container/index.vue +22 -0
- package/components/user-profile/change-password-dialog.vue +100 -0
- package/components/user-profile/change-user-avatar.vue +43 -0
- package/components/user-profile/change-username-dialog.vue +82 -0
- package/components/user-profile/index.css +8 -0
- package/components/user-profile/index.vue +77 -0
- package/index.js +4 -0
- package/layout/img/default-avatar.gif +0 -0
- package/layout/index.vue +24 -0
- package/layout/scx-app.vue +110 -0
- package/layout/scx-input.vue +84 -0
- package/layout/scx-logo.vue +65 -0
- package/layout/scx-main.vue +48 -0
- package/layout/scx-menu-item.vue +47 -0
- package/layout/scx-menu-toggle.vue +69 -0
- package/layout/scx-menu.vue +122 -0
- package/layout/scx-navbar.vue +47 -0
- package/layout/scx-notice.vue +211 -0
- package/layout/scx-sidebar.vue +70 -0
- package/layout/scx-theme-switch.vue +54 -0
- package/layout/scx-user-panel.vue +193 -0
- package/package.json +30 -0
- package/routes.js +57 -0
- package/scx/ali-oss.js +87 -0
- package/scx/auth-fetch.js +68 -0
- package/scx/crud-context.js +522 -0
- package/scx/easy-option.js +131 -0
- package/scx/index.js +8 -0
- package/scx/scx-auth-info.js +48 -0
- package/scx/scx-auth.js +197 -0
- package/scx/scx-config-manager.js +105 -0
- package/scx/scx-router.js +273 -0
- package/styles/index.css +37 -0
- package/util/cities.js +350 -0
- package/util/duration-format.js +27 -0
- package/util/element-plus-helper.js +114 -0
- package/util/get-order-number.js +7 -0
- package/util/index.js +4 -0
- package/util/nations.js +16 -0
- package/util/provinces.js +41 -0
- package/views/error-page.vue +79 -0
- package/views/login/index.css +95 -0
- package/views/login/login-and-register.vue +66 -0
- package/views/login/login-bg.vue +121 -0
- package/views/login/login-form-bg.vue +61 -0
- package/views/login/login-form.vue +137 -0
- package/views/login/login-message.js +28 -0
- package/views/login/login.vue +29 -0
- package/views/login/register-form.vue +148 -0
- package/views/no-perm.vue +7 -0
- package/views/not-found.vue +7 -0
- package/views/rocket.vue +84 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
<template>
|
2
|
+
|
3
|
+
<el-sub-menu v-if="isSubMenu() && routeCanShowInMenu(data)" :index="data.name">
|
4
|
+
<template #title>
|
5
|
+
<scx-icon v-if="data.meta && data.meta.icon" :icon="data.meta.icon"/>
|
6
|
+
<span>{{ getRouteTitle(data) }}</span>
|
7
|
+
</template>
|
8
|
+
<scx-menu-item v-for="i in canAccessRoutes" :data="i"/>
|
9
|
+
</el-sub-menu>
|
10
|
+
|
11
|
+
<el-menu-item v-else-if="routeCanShowInMenu(data)" :index="data.name" :route="data">
|
12
|
+
<scx-icon v-if="data.meta && data.meta.icon" :icon="data.meta.icon"/>
|
13
|
+
<span>{{ getRouteTitle(data) }}</span>
|
14
|
+
</el-menu-item>
|
15
|
+
|
16
|
+
</template>
|
17
|
+
|
18
|
+
<script>
|
19
|
+
import {getRouteTitle, routeCanShowInMenu, useScxRouter} from "../scx/index.js";
|
20
|
+
import {computed} from "vue";
|
21
|
+
|
22
|
+
export default {
|
23
|
+
name: "scx-menu-item",
|
24
|
+
methods: {getRouteTitle},
|
25
|
+
props: {
|
26
|
+
data: {}
|
27
|
+
},
|
28
|
+
setup(props, ctx) {
|
29
|
+
|
30
|
+
const scxRouter = useScxRouter();
|
31
|
+
|
32
|
+
const allRoutes = props.data.children ? props.data.children : [];
|
33
|
+
|
34
|
+
const canAccessRoutes = computed(() => allRoutes.filter((c) => scxRouter.canAccessThisRoute(c)));
|
35
|
+
|
36
|
+
function isSubMenu() {
|
37
|
+
return canAccessRoutes.value.length > 0;
|
38
|
+
}
|
39
|
+
|
40
|
+
return {
|
41
|
+
isSubMenu,
|
42
|
+
routeCanShowInMenu,
|
43
|
+
canAccessRoutes
|
44
|
+
};
|
45
|
+
}
|
46
|
+
};
|
47
|
+
</script>
|
@@ -0,0 +1,69 @@
|
|
1
|
+
<template>
|
2
|
+
<div class="scx-menu-toggle" @click="changeMenuCollapse()">
|
3
|
+
<scx-icon :class="{'isOpened':userConfig.menuCollapse}" class="scx-menu-toggle-icon" icon="outlined-menu-fold"/>
|
4
|
+
</div>
|
5
|
+
</template>
|
6
|
+
|
7
|
+
<script>
|
8
|
+
import {useScxConfigManager, useScxUserConfig} from "../scx/index.js";
|
9
|
+
|
10
|
+
export default {
|
11
|
+
name: "scx-menu-toggle",
|
12
|
+
setup() {
|
13
|
+
|
14
|
+
const userConfig = useScxUserConfig();
|
15
|
+
const scxConfigManager = useScxConfigManager();
|
16
|
+
|
17
|
+
function changeMenuCollapse() {
|
18
|
+
userConfig.menuCollapse = !userConfig.menuCollapse;
|
19
|
+
scxConfigManager.updateUserConfig({menuCollapse: userConfig.menuCollapse});
|
20
|
+
}
|
21
|
+
|
22
|
+
return {
|
23
|
+
userConfig,
|
24
|
+
changeMenuCollapse
|
25
|
+
};
|
26
|
+
|
27
|
+
}
|
28
|
+
};
|
29
|
+
</script>
|
30
|
+
<style>
|
31
|
+
.scx-menu-toggle {
|
32
|
+
display: flex;
|
33
|
+
align-items: center;
|
34
|
+
height: 100%;
|
35
|
+
cursor: pointer;
|
36
|
+
transition: all 200ms;
|
37
|
+
width: 54px;
|
38
|
+
flex-shrink: 0;
|
39
|
+
justify-content: center;
|
40
|
+
}
|
41
|
+
|
42
|
+
.scx-menu-toggle:hover {
|
43
|
+
background-color: var(--scx-theme-bg);
|
44
|
+
}
|
45
|
+
|
46
|
+
.scx-menu-toggle-icon {
|
47
|
+
display: inline-block;
|
48
|
+
vertical-align: middle;
|
49
|
+
transition: all 200ms;
|
50
|
+
width: 40%;
|
51
|
+
height: 40%;
|
52
|
+
}
|
53
|
+
|
54
|
+
.scx-menu-toggle:hover > .scx-menu-toggle-icon {
|
55
|
+
fill: var(--scx-theme);
|
56
|
+
}
|
57
|
+
|
58
|
+
.scx-menu-toggle > .scx-menu-toggle-icon.isOpened {
|
59
|
+
transform: rotateY(180deg);
|
60
|
+
}
|
61
|
+
|
62
|
+
.scx-menu-toggle:active > .scx-menu-toggle-icon {
|
63
|
+
transform: scale(0.9);
|
64
|
+
}
|
65
|
+
|
66
|
+
.scx-menu-toggle:active > .scx-menu-toggle-icon.isOpened {
|
67
|
+
transform: rotateY(180deg) scale(0.9);
|
68
|
+
}
|
69
|
+
</style>
|
@@ -0,0 +1,122 @@
|
|
1
|
+
<template>
|
2
|
+
|
3
|
+
<el-menu :collapse="userConfig.menuCollapse" :collapse-transition="false" :default-active="activeMenu"
|
4
|
+
:router="true">
|
5
|
+
<scx-menu-item v-for="i in canAccessRoutes" :data="i"/>
|
6
|
+
</el-menu>
|
7
|
+
|
8
|
+
</template>
|
9
|
+
<script>
|
10
|
+
import {getRouteTitle, useScxRouter, useScxSystemConfig, useScxUserConfig} from "../scx/index.js";
|
11
|
+
import {useRoute} from "vue-router";
|
12
|
+
import {computed} from "vue";
|
13
|
+
import ScxMenuItem from "./scx-menu-item.vue";
|
14
|
+
|
15
|
+
export default {
|
16
|
+
name: "scx-menu",
|
17
|
+
components: {
|
18
|
+
ScxMenuItem,
|
19
|
+
},
|
20
|
+
setup() {
|
21
|
+
const systemConfig = useScxSystemConfig();
|
22
|
+
const userConfig = useScxUserConfig();
|
23
|
+
const scxRouter = useScxRouter();
|
24
|
+
|
25
|
+
const route = useRoute();
|
26
|
+
|
27
|
+
//所有能够被展示的路由 1, 是最终节点(没有 children) 2, 没有开启 hiddenInLauncher
|
28
|
+
const appRoute = scxRouter.getRoutes().find(c => c.name === "app");
|
29
|
+
|
30
|
+
const allRoutes = appRoute.children;
|
31
|
+
|
32
|
+
//当前路由
|
33
|
+
const activeMenu = computed(() => route.name);
|
34
|
+
//用户可以访问的路由
|
35
|
+
const canAccessRoutes = computed(() => allRoutes.filter((c) => scxRouter.canAccessThisRoute(c)));
|
36
|
+
|
37
|
+
return {
|
38
|
+
systemConfig,
|
39
|
+
activeMenu,
|
40
|
+
getRouteTitle,
|
41
|
+
scxRouter,
|
42
|
+
allRoutes,
|
43
|
+
userConfig,
|
44
|
+
canAccessRoutes
|
45
|
+
};
|
46
|
+
}
|
47
|
+
};
|
48
|
+
</script>
|
49
|
+
<style>
|
50
|
+
.el-menu .scx-icon {
|
51
|
+
fill: currentColor;
|
52
|
+
flex-shrink: 0;
|
53
|
+
}
|
54
|
+
|
55
|
+
.collapse .el-menu {
|
56
|
+
width: 54px;
|
57
|
+
}
|
58
|
+
|
59
|
+
.el-sub-menu__title {
|
60
|
+
padding-right: 0;
|
61
|
+
}
|
62
|
+
|
63
|
+
.el-menu {
|
64
|
+
box-sizing: border-box;
|
65
|
+
border-right: unset;
|
66
|
+
width: 200px;
|
67
|
+
|
68
|
+
--el-menu-active-color: var(--scx-theme);
|
69
|
+
--el-menu-hover-text-color: var(--scx-theme);
|
70
|
+
--el-menu-bg-color: var(--scx-overlay-bg);
|
71
|
+
--el-menu-hover-bg-color: var(--scx-theme-bg);
|
72
|
+
--el-menu-item-height: 40px;
|
73
|
+
--el-menu-sub-item-height: 40px;
|
74
|
+
--el-menu-horizontal-sub-item-height: 36px;
|
75
|
+
}
|
76
|
+
|
77
|
+
.el-menu-item.is-active {
|
78
|
+
background-color: var(--scx-theme-bg);
|
79
|
+
}
|
80
|
+
|
81
|
+
.el-sub-menu .el-menu {
|
82
|
+
background-color: var(--scx-bg);
|
83
|
+
}
|
84
|
+
|
85
|
+
.el-sub-menu.is-active > .el-sub-menu__title,
|
86
|
+
.el-menu-item.is-active {
|
87
|
+
font-weight: 600;
|
88
|
+
color: var(--scx-theme);
|
89
|
+
}
|
90
|
+
|
91
|
+
/* 菜单文字 */
|
92
|
+
.el-sub-menu__title,
|
93
|
+
.el-menu-item {
|
94
|
+
transition: background-color 100ms;
|
95
|
+
column-gap: 20px;
|
96
|
+
}
|
97
|
+
|
98
|
+
/* 左侧火柴条 */
|
99
|
+
.el-sub-menu__title::before,
|
100
|
+
.el-menu-item::before {
|
101
|
+
content: "";
|
102
|
+
position: absolute;
|
103
|
+
top: 50%;
|
104
|
+
left: 0;
|
105
|
+
height: 0;
|
106
|
+
width: 100%;
|
107
|
+
transition: all 150ms;
|
108
|
+
}
|
109
|
+
|
110
|
+
/* 左侧火柴条 */
|
111
|
+
.el-menu-item.is-active::before,
|
112
|
+
/* 父菜单展开时 不需要有火柴条 */
|
113
|
+
.el-sub-menu.is-active:not(.is-opened) > .el-sub-menu__title::before,
|
114
|
+
/* 处理折叠时展开 */
|
115
|
+
.el-menu--collapse .el-sub-menu.is-active > .el-sub-menu__title::before,
|
116
|
+
.el-sub-menu__title:hover::before,
|
117
|
+
.el-menu-item:hover::before {
|
118
|
+
top: 0;
|
119
|
+
border-left: 2px solid var(--scx-theme);
|
120
|
+
height: 100%;
|
121
|
+
}
|
122
|
+
</style>
|
@@ -0,0 +1,47 @@
|
|
1
|
+
<template>
|
2
|
+
<div class="scx-navbar">
|
3
|
+
<div class="scx-navbar-left">
|
4
|
+
<slot name="left"/>
|
5
|
+
</div>
|
6
|
+
<div class="scx-navbar-right">
|
7
|
+
<slot name="right"/>
|
8
|
+
</div>
|
9
|
+
</div>
|
10
|
+
</template>
|
11
|
+
<script>
|
12
|
+
|
13
|
+
export default {
|
14
|
+
name: "scx-navbar",
|
15
|
+
components: {},
|
16
|
+
setup() {
|
17
|
+
|
18
|
+
}
|
19
|
+
};
|
20
|
+
</script>
|
21
|
+
<style>
|
22
|
+
.scx-navbar {
|
23
|
+
height: 50px;
|
24
|
+
width: 100%;
|
25
|
+
top: 0;
|
26
|
+
flex-shrink: 0;
|
27
|
+
box-shadow: var(--scx-box-shadow-bottom);
|
28
|
+
z-index: 4;
|
29
|
+
background-color: var(--scx-overlay-bg);
|
30
|
+
display: flex;
|
31
|
+
justify-content: space-between;
|
32
|
+
}
|
33
|
+
|
34
|
+
.scx-navbar-left {
|
35
|
+
display: flex;
|
36
|
+
align-items: center;
|
37
|
+
height: 100%;
|
38
|
+
}
|
39
|
+
|
40
|
+
.scx-navbar-right {
|
41
|
+
display: flex;
|
42
|
+
align-items: center;
|
43
|
+
justify-content: center;
|
44
|
+
height: 100%;
|
45
|
+
column-gap: 20px;
|
46
|
+
}
|
47
|
+
</style>
|
@@ -0,0 +1,211 @@
|
|
1
|
+
<template>
|
2
|
+
|
3
|
+
<div v-drag="dragEvent" :class="{'show-notice':showNotice}" class="scx-notice-button-wrapper">
|
4
|
+
<div class="scx-notice-button">
|
5
|
+
<scx-icon :icon="showNotice?'outlined-close':'outlined-bell'"/>
|
6
|
+
<div v-if="$slots['tip']" class="tip">
|
7
|
+
<slot name="tip"></slot>
|
8
|
+
</div>
|
9
|
+
</div>
|
10
|
+
</div>
|
11
|
+
|
12
|
+
<div :class="{'show-notice':showNotice}" class="scx-notice">
|
13
|
+
<slot name="default"></slot>
|
14
|
+
</div>
|
15
|
+
|
16
|
+
</template>
|
17
|
+
|
18
|
+
<script>
|
19
|
+
import {ref, watch} from "vue";
|
20
|
+
|
21
|
+
export default {
|
22
|
+
name: "scx-notice",
|
23
|
+
setup() {
|
24
|
+
|
25
|
+
const showNotice = ref(false);
|
26
|
+
|
27
|
+
function closeSidebar(evt) {
|
28
|
+
const parentA = evt.target.closest(".scx-notice");
|
29
|
+
const parentB = evt.target.closest(".scx-notice-button");
|
30
|
+
if (!parentA && !parentB) {
|
31
|
+
showNotice.value = false;
|
32
|
+
window.removeEventListener("click", closeSidebar);
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
const dragEvent = {
|
37
|
+
bounds: (e) => {
|
38
|
+
if (showNotice.value) {
|
39
|
+
e.right = e.right - 260;
|
40
|
+
}
|
41
|
+
return e;
|
42
|
+
},
|
43
|
+
onClick: (el) => {
|
44
|
+
showNotice.value = !showNotice.value;
|
45
|
+
},
|
46
|
+
onDrag: (el) => {
|
47
|
+
el.classList.add("dragging");
|
48
|
+
},
|
49
|
+
onDragEnd: (el) => {
|
50
|
+
el.classList.add("drag-moving");
|
51
|
+
let nowMatrix = new DOMMatrix(window.getComputedStyle(el).transform);
|
52
|
+
//清除我们不需要的坐标
|
53
|
+
nowMatrix = nowMatrix.translate(-nowMatrix.e, 0);
|
54
|
+
el.style.transform = nowMatrix.toString();
|
55
|
+
el.addEventListener("transitionend", (e) => {
|
56
|
+
if (e.target === el) {
|
57
|
+
el.classList.remove("drag-moving");
|
58
|
+
el.classList.remove("dragging");
|
59
|
+
}
|
60
|
+
});
|
61
|
+
}
|
62
|
+
};
|
63
|
+
|
64
|
+
watch(showNotice, (value) => {
|
65
|
+
value && window.addEventListener("click", closeSidebar);
|
66
|
+
});
|
67
|
+
|
68
|
+
return {
|
69
|
+
dragEvent,
|
70
|
+
showNotice,
|
71
|
+
};
|
72
|
+
}
|
73
|
+
};
|
74
|
+
</script>
|
75
|
+
<style>
|
76
|
+
.scx-notice-button-wrapper {
|
77
|
+
z-index: 10;
|
78
|
+
height: 48px;
|
79
|
+
width: 48px;
|
80
|
+
position: fixed;
|
81
|
+
right: 0;
|
82
|
+
top: 35%;
|
83
|
+
transition: right 0.25s cubic-bezier(0.7, 0.3, 0.1, 1)
|
84
|
+
}
|
85
|
+
|
86
|
+
/*右侧操作按钮*/
|
87
|
+
.scx-notice-button-wrapper.show-notice {
|
88
|
+
right: 260px;
|
89
|
+
}
|
90
|
+
|
91
|
+
/*右侧操作按钮*/
|
92
|
+
.scx-notice-button {
|
93
|
+
height: 100%;
|
94
|
+
width: 100%;
|
95
|
+
border-radius: 2px 0 0 2px;
|
96
|
+
pointer-events: auto;
|
97
|
+
cursor: pointer;
|
98
|
+
color: #fff;
|
99
|
+
transition: background 0.3s ease-in, transform 0.25s cubic-bezier(0.7, 0.3, 0.1, 1);
|
100
|
+
background: var(--scx-theme-bg, #3a8ee6);
|
101
|
+
display: flex;
|
102
|
+
user-select: none;
|
103
|
+
justify-content: center;
|
104
|
+
z-index: 300;
|
105
|
+
}
|
106
|
+
|
107
|
+
.dragging {
|
108
|
+
transition: unset;
|
109
|
+
}
|
110
|
+
|
111
|
+
.dragging .scx-notice-button {
|
112
|
+
transform: scale(0.9);
|
113
|
+
border-radius: 2px;
|
114
|
+
cursor: move;
|
115
|
+
}
|
116
|
+
|
117
|
+
:not(.show-notice) .scx-notice-button:hover svg {
|
118
|
+
animation: ringing 2500ms ease infinite;
|
119
|
+
}
|
120
|
+
|
121
|
+
.show-notice .scx-notice-button:hover svg {
|
122
|
+
animation: rotating 1700ms linear infinite;
|
123
|
+
}
|
124
|
+
|
125
|
+
.scx-notice-button:hover {
|
126
|
+
background: var(--scx-theme, #ffaaff);
|
127
|
+
}
|
128
|
+
|
129
|
+
.scx-notice-button svg {
|
130
|
+
pointer-events: none;
|
131
|
+
fill: whitesmoke;
|
132
|
+
margin-top: 25%;
|
133
|
+
font-size: 24px;
|
134
|
+
}
|
135
|
+
|
136
|
+
.scx-notice {
|
137
|
+
position: fixed;
|
138
|
+
width: 260px;
|
139
|
+
height: 100%;
|
140
|
+
top: 0;
|
141
|
+
right: 0;
|
142
|
+
transform: translateX(260px);
|
143
|
+
background: var(--scx-glass-bg);
|
144
|
+
transition: all 0.25s cubic-bezier(0.7, 0.3, 0.1, 1);
|
145
|
+
backdrop-filter: var(--scx-glass-bg-filter);
|
146
|
+
display: flex;
|
147
|
+
padding: 16px;
|
148
|
+
justify-content: center;
|
149
|
+
align-items: center;
|
150
|
+
word-wrap: break-word;
|
151
|
+
box-sizing: border-box;
|
152
|
+
overflow-y: auto;
|
153
|
+
overflow-x: hidden;
|
154
|
+
z-index: 5;
|
155
|
+
}
|
156
|
+
|
157
|
+
.scx-notice.show-notice {
|
158
|
+
transform: translateX(0);
|
159
|
+
box-shadow: var(--scx-box-shadow-left);
|
160
|
+
}
|
161
|
+
|
162
|
+
.drag-moving {
|
163
|
+
transition: transform 600ms ease-out;
|
164
|
+
}
|
165
|
+
|
166
|
+
/* 旋转动画 */
|
167
|
+
@keyframes rotating {
|
168
|
+
0% {
|
169
|
+
transform: rotate(0)
|
170
|
+
}
|
171
|
+
100% {
|
172
|
+
transform: rotate(360deg)
|
173
|
+
}
|
174
|
+
}
|
175
|
+
|
176
|
+
/* 铃铛动画 */
|
177
|
+
@keyframes ringing {
|
178
|
+
0%, 50%, 100% {
|
179
|
+
transform: rotate(0deg);
|
180
|
+
}
|
181
|
+
5%, 10%, 15%, 20%, 30%, 35%, 40% {
|
182
|
+
transform: rotate(6deg);
|
183
|
+
}
|
184
|
+
45% {
|
185
|
+
transform: rotate(4deg);
|
186
|
+
}
|
187
|
+
7.5%, 12.5%, 17.5%, 22.5%, 27.5%, 32.5%, 37.5%, 42.5% {
|
188
|
+
transform: rotate(-6deg);
|
189
|
+
}
|
190
|
+
47.5% {
|
191
|
+
transform: rotate(-2deg);
|
192
|
+
}
|
193
|
+
}
|
194
|
+
|
195
|
+
.tip {
|
196
|
+
position: absolute;
|
197
|
+
top: -10px;
|
198
|
+
right: 4px;
|
199
|
+
font-size: 10px;
|
200
|
+
width: 22px;
|
201
|
+
height: 22px;
|
202
|
+
display: flex;
|
203
|
+
justify-content: center;
|
204
|
+
align-items: center;
|
205
|
+
background: #ff5454;
|
206
|
+
border-radius: 50%;
|
207
|
+
box-sizing: border-box;
|
208
|
+
padding: 2px;
|
209
|
+
border: 2px solid #ff8888;
|
210
|
+
}
|
211
|
+
</style>
|
@@ -0,0 +1,70 @@
|
|
1
|
+
<template>
|
2
|
+
<div class="scx-sidebar">
|
3
|
+
<!-- 顶部 -->
|
4
|
+
<div v-if="$slots['top']" class="scx-sidebar-top">
|
5
|
+
<slot name="top"/>
|
6
|
+
</div>
|
7
|
+
<!-- 中心 -->
|
8
|
+
<div class="scx-sidebar-center">
|
9
|
+
<slot/>
|
10
|
+
</div>
|
11
|
+
<!-- 底部 -->
|
12
|
+
<div v-if="$slots['bottom']" class="scx-sidebar-bottom">
|
13
|
+
<slot name="bottom"/>
|
14
|
+
</div>
|
15
|
+
</div>
|
16
|
+
</template>
|
17
|
+
|
18
|
+
<script>
|
19
|
+
|
20
|
+
export default {
|
21
|
+
name: "scx-sidebar",
|
22
|
+
components: {}
|
23
|
+
};
|
24
|
+
</script>
|
25
|
+
|
26
|
+
<style>
|
27
|
+
.scx-sidebar {
|
28
|
+
z-index: 5;
|
29
|
+
flex-shrink: 0;
|
30
|
+
display: flex;
|
31
|
+
height: 100%;
|
32
|
+
flex-direction: column;
|
33
|
+
overflow: hidden;
|
34
|
+
user-select: none;
|
35
|
+
box-shadow: var(--scx-box-shadow-right);
|
36
|
+
background: var(--scx-overlay-bg);
|
37
|
+
/*使切换主题时颜色过渡平滑一些*/
|
38
|
+
transition: background-color 100ms;
|
39
|
+
}
|
40
|
+
|
41
|
+
.scx-sidebar-top {
|
42
|
+
z-index: 1;
|
43
|
+
display: flex;
|
44
|
+
align-items: center;
|
45
|
+
cursor: pointer;
|
46
|
+
transition: background 0.3s;
|
47
|
+
flex-shrink: 0;
|
48
|
+
justify-content: center;
|
49
|
+
box-shadow: var(--scx-box-shadow-bottom);
|
50
|
+
width: 100%;
|
51
|
+
}
|
52
|
+
|
53
|
+
.scx-sidebar-center {
|
54
|
+
height: 100%;
|
55
|
+
overflow: hidden;
|
56
|
+
}
|
57
|
+
|
58
|
+
.scx-sidebar-center:hover {
|
59
|
+
overflow-y: auto;
|
60
|
+
}
|
61
|
+
|
62
|
+
.scx-sidebar-bottom {
|
63
|
+
position: relative;
|
64
|
+
display: flex;
|
65
|
+
flex-shrink: 0;
|
66
|
+
align-items: center;
|
67
|
+
justify-content: center;
|
68
|
+
box-shadow: var(--scx-box-shadow-top);
|
69
|
+
}
|
70
|
+
</style>
|
@@ -0,0 +1,54 @@
|
|
1
|
+
<template>
|
2
|
+
<scx-switch v-model="userConfig.dark" class="scx-theme-switch" @change="onChange">
|
3
|
+
<template #icon>
|
4
|
+
<scx-icon :icon="userConfig.dark ? 'filled-moon':'filled-sun'"/>
|
5
|
+
</template>
|
6
|
+
<template #label>
|
7
|
+
{{ userConfig.dark ? "黑暗" : "明亮" }}
|
8
|
+
</template>
|
9
|
+
</scx-switch>
|
10
|
+
</template>
|
11
|
+
|
12
|
+
<script>
|
13
|
+
import {changeTheme} from "@scx-js/scx-ui/style/changeTheme.js";
|
14
|
+
import {useScxConfigManager, useScxUserConfig} from "../scx/index.js";
|
15
|
+
|
16
|
+
export default {
|
17
|
+
name: "scx-theme-switch",
|
18
|
+
setup() {
|
19
|
+
const userConfig = useScxUserConfig();
|
20
|
+
const scxConfigManager = useScxConfigManager();
|
21
|
+
|
22
|
+
changeTheme(userConfig.dark);
|
23
|
+
|
24
|
+
function onChange(v) {
|
25
|
+
changeTheme(v);
|
26
|
+
scxConfigManager.updateUserConfig({dark: v});
|
27
|
+
}
|
28
|
+
|
29
|
+
return {
|
30
|
+
userConfig,
|
31
|
+
onChange
|
32
|
+
};
|
33
|
+
}
|
34
|
+
};
|
35
|
+
</script>
|
36
|
+
|
37
|
+
<style>
|
38
|
+
.scx-theme-switch {
|
39
|
+
flex-shrink: 0;
|
40
|
+
width: 60px;
|
41
|
+
}
|
42
|
+
|
43
|
+
.scx-theme-switch.scx-switch {
|
44
|
+
background: var(--scx-bg);
|
45
|
+
}
|
46
|
+
|
47
|
+
.scx-theme-switch.scx-switch > .scx-switch-icon {
|
48
|
+
background: var(--scx-overlay-bg);
|
49
|
+
}
|
50
|
+
|
51
|
+
.scx-theme-switch.scx-switch > .scx-switch-icon > .scx-icon {
|
52
|
+
fill: var(--scx-text-color);
|
53
|
+
}
|
54
|
+
</style>
|