@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,193 @@
|
|
1
|
+
<template>
|
2
|
+
|
3
|
+
<!-- 用户面板按钮 -->
|
4
|
+
<div class="scx-user-panel-btn" @click="changeUserPanel()">
|
5
|
+
<el-avatar :src="getAvatar()" class="scx-user-panel-btn-avatar" shape="circle">
|
6
|
+
<img :src="defaultAvatar"/>
|
7
|
+
</el-avatar>
|
8
|
+
<span class="scx-user-panel-btn-label">
|
9
|
+
{{ userInfo.username }}
|
10
|
+
</span>
|
11
|
+
</div>
|
12
|
+
|
13
|
+
<!-- 用户面板 -->
|
14
|
+
<scx-panel v-model="showUserPanelDialog" class='scx-user-panel' transition-type="top">
|
15
|
+
|
16
|
+
<scx-panel-item v-for="i in userPanelItemList" :active="activeMenu===i.name" @click="handleGoToRoute(i.name)">
|
17
|
+
<scx-icon v-if="i.meta && i.meta.icon" :icon="i.meta.icon"/>
|
18
|
+
<span>{{ getRouteTitle(i) }}</span>
|
19
|
+
</scx-panel-item>
|
20
|
+
|
21
|
+
<!-- 分割线 -->
|
22
|
+
<div v-if="userPanelItemList.length > 0" style="border-bottom: 2px solid #9a9696;"></div>
|
23
|
+
|
24
|
+
<scx-panel-item class="scx-dialog-item" @click="logout">
|
25
|
+
<scx-icon icon="outlined-poweroff"/>
|
26
|
+
<span>退出登录</span>
|
27
|
+
</scx-panel-item>
|
28
|
+
</scx-panel>
|
29
|
+
|
30
|
+
</template>
|
31
|
+
|
32
|
+
<script>
|
33
|
+
import {computed, ref, watch} from "vue";
|
34
|
+
import {useRoute, useRouter} from "vue-router";
|
35
|
+
import defaultAvatar from "./img/default-avatar.gif";
|
36
|
+
import {ElMessage} from "element-plus";
|
37
|
+
import {ScxPanel, ScxPanelItem,} from "@scx-js/scx-ui";
|
38
|
+
import {useScxFSS} from "@scx-js/scx-app-x";
|
39
|
+
import {getRouteTitle, routeNoNeedLogin, useScxAuth, useScxRouter, useScxUserInfo} from "../scx/index.js";
|
40
|
+
|
41
|
+
export default {
|
42
|
+
name: "scx-user-panel",
|
43
|
+
components: {
|
44
|
+
ScxPanel,
|
45
|
+
ScxPanelItem
|
46
|
+
},
|
47
|
+
setup() {
|
48
|
+
//获取路由对象
|
49
|
+
const route = useRoute();
|
50
|
+
const router = useRouter();
|
51
|
+
|
52
|
+
const auth = useScxAuth();
|
53
|
+
const fss = useScxFSS();
|
54
|
+
const userInfo = useScxUserInfo();
|
55
|
+
const scxRouter = useScxRouter();
|
56
|
+
//当前路由
|
57
|
+
const activeMenu = computed(() => route.name);
|
58
|
+
|
59
|
+
//在用户面板中显示的菜单
|
60
|
+
const userPanelItemList = scxRouter.getRoutes().filter(s => s.children.length === 0 && (s.meta && s.meta.showInUserPanel));
|
61
|
+
//是否显示用户面板弹窗
|
62
|
+
const showUserPanelDialog = ref(false);
|
63
|
+
|
64
|
+
//点击 跳转路由
|
65
|
+
function handleGoToRoute(item) {
|
66
|
+
showUserPanelDialog.value = false;
|
67
|
+
router.push({name: item});
|
68
|
+
}
|
69
|
+
|
70
|
+
/**
|
71
|
+
* 退出登录
|
72
|
+
*/
|
73
|
+
function logout() {
|
74
|
+
auth.logout().then(() => {
|
75
|
+
debugger;
|
76
|
+
ElMessage.success("退出登录成功 !!!");
|
77
|
+
//查看当前页面是否在未登录即可访问的白名单中 , 在的话不做任何处理 , 不在的话重定向到登录
|
78
|
+
if (!routeNoNeedLogin(route)) {
|
79
|
+
//再退出到登录页面时 携带当前页面的参数
|
80
|
+
router.push(scxRouter.getLoginRoute(route));
|
81
|
+
}
|
82
|
+
}).catch(error => {
|
83
|
+
console.error(error);
|
84
|
+
}).finally(() => {
|
85
|
+
showUserPanelDialog.value = false;
|
86
|
+
});
|
87
|
+
}
|
88
|
+
|
89
|
+
/**
|
90
|
+
* 关闭 用户面板
|
91
|
+
* @param evt
|
92
|
+
*/
|
93
|
+
function closeUserPanelDialog(evt) {
|
94
|
+
const parentA = evt.target.closest(".scx-user-panel-btn");
|
95
|
+
const parentB = evt.target.closest(".scx-user-panel");
|
96
|
+
if (!parentA && !parentB) {
|
97
|
+
showUserPanelDialog.value = false;
|
98
|
+
}
|
99
|
+
}
|
100
|
+
|
101
|
+
function changeUserPanel() {
|
102
|
+
showUserPanelDialog.value = !showUserPanelDialog.value;
|
103
|
+
}
|
104
|
+
|
105
|
+
//监听
|
106
|
+
watch(showUserPanelDialog, (value) => {
|
107
|
+
if (value) {
|
108
|
+
window.addEventListener("click", closeUserPanelDialog);
|
109
|
+
} else {
|
110
|
+
window.removeEventListener("click", closeUserPanelDialog);
|
111
|
+
}
|
112
|
+
});
|
113
|
+
|
114
|
+
function getAvatar() {
|
115
|
+
if (userInfo.avatar) {
|
116
|
+
return fss.joinImageURL(userInfo.avatar, {
|
117
|
+
w: 200,
|
118
|
+
h: 200
|
119
|
+
});
|
120
|
+
} else {
|
121
|
+
return defaultAvatar;
|
122
|
+
}
|
123
|
+
}
|
124
|
+
|
125
|
+
return {
|
126
|
+
userInfo,
|
127
|
+
userPanelItemList,
|
128
|
+
showUserPanelDialog,
|
129
|
+
defaultAvatar,
|
130
|
+
activeMenu,
|
131
|
+
logout,
|
132
|
+
handleGoToRoute,
|
133
|
+
getAvatar,
|
134
|
+
changeUserPanel,
|
135
|
+
getRouteTitle
|
136
|
+
};
|
137
|
+
|
138
|
+
}
|
139
|
+
|
140
|
+
};
|
141
|
+
</script>
|
142
|
+
<style>
|
143
|
+
|
144
|
+
/*用户面板按钮*/
|
145
|
+
.scx-user-panel-btn {
|
146
|
+
position: relative;
|
147
|
+
display: flex;
|
148
|
+
height: 48px;
|
149
|
+
align-items: center;
|
150
|
+
justify-content: start;
|
151
|
+
transition: background 0.3s;
|
152
|
+
user-select: none;
|
153
|
+
cursor: pointer;
|
154
|
+
column-gap: 5px;
|
155
|
+
padding-left: 10px;
|
156
|
+
padding-right: 10px;
|
157
|
+
min-width: 80px;
|
158
|
+
max-width: 150px;
|
159
|
+
margin-right: 10px;
|
160
|
+
}
|
161
|
+
|
162
|
+
.scx-user-panel-btn:hover {
|
163
|
+
background: var(--scx-theme-bg);
|
164
|
+
}
|
165
|
+
|
166
|
+
.scx-user-panel-btn-label {
|
167
|
+
width: 100%;
|
168
|
+
overflow: hidden;
|
169
|
+
white-space: nowrap;
|
170
|
+
text-overflow: ellipsis
|
171
|
+
}
|
172
|
+
|
173
|
+
.scx-user-panel-btn-avatar {
|
174
|
+
transition: transform 200ms;
|
175
|
+
flex-shrink: 0;
|
176
|
+
}
|
177
|
+
|
178
|
+
.scx-user-panel-btn-avatar:active {
|
179
|
+
transform: scale(0.9);
|
180
|
+
}
|
181
|
+
|
182
|
+
/*弹窗*/
|
183
|
+
.scx-user-panel {
|
184
|
+
position: fixed;
|
185
|
+
z-index: 200;
|
186
|
+
top: calc(50px + 6px);
|
187
|
+
right: 10px;
|
188
|
+
width: 150px;
|
189
|
+
display: grid;
|
190
|
+
grid-row-gap: 10px;
|
191
|
+
}
|
192
|
+
|
193
|
+
</style>
|
package/package.json
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
{
|
2
|
+
"name": "@scx-js/scx-admin",
|
3
|
+
"version": "0.0.3",
|
4
|
+
"description": "SCX Admin",
|
5
|
+
"license": "MIT",
|
6
|
+
"author": "scx567888",
|
7
|
+
"main": "index.js",
|
8
|
+
"type": "module",
|
9
|
+
"scripts": {
|
10
|
+
"dev:test": "vite _test --host",
|
11
|
+
"build:test": "vite build _test",
|
12
|
+
"preview:test": "vite preview _test --host"
|
13
|
+
},
|
14
|
+
"repository": {
|
15
|
+
"type": "git",
|
16
|
+
"url": "https://github.com/scx567888/scx-js.git"
|
17
|
+
},
|
18
|
+
"dependencies": {
|
19
|
+
"@scx-js/scx-app-x": "0.0.3",
|
20
|
+
"@scx-js/scx-common": "0.0.3",
|
21
|
+
"@scx-js/scx-dom": "0.0.3",
|
22
|
+
"element-plus": "^2.9.3",
|
23
|
+
"nprogress": "^0.2.0"
|
24
|
+
},
|
25
|
+
"devDependencies": {
|
26
|
+
"@vitejs/plugin-vue": "^5.0.0",
|
27
|
+
"vite": "^6.0.0",
|
28
|
+
"vue-router": "^4.0.0"
|
29
|
+
}
|
30
|
+
}
|
package/routes.js
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
const PathMatchRoute = {
|
2
|
+
path: "/:pathMatch(.*)*",
|
3
|
+
redirect: {name: "not-found"},
|
4
|
+
meta: {
|
5
|
+
hiddenInLauncher: true,
|
6
|
+
},
|
7
|
+
};
|
8
|
+
|
9
|
+
const NotFoundRoute = {
|
10
|
+
path: "/not-found",
|
11
|
+
name: "not-found",
|
12
|
+
component: () => import("./views/not-found.vue"),
|
13
|
+
meta: {
|
14
|
+
title: "页面未找到",
|
15
|
+
hiddenInLauncher: true,
|
16
|
+
noNeedLogin: true,
|
17
|
+
},
|
18
|
+
};
|
19
|
+
|
20
|
+
const NoPermRoute = {
|
21
|
+
path: "/no-perm",
|
22
|
+
name: "no-perm",
|
23
|
+
component: () => import("./views/no-perm.vue"),
|
24
|
+
meta: {
|
25
|
+
hiddenInLauncher: true,
|
26
|
+
noNeedPerm: true,
|
27
|
+
},
|
28
|
+
};
|
29
|
+
|
30
|
+
|
31
|
+
const LoginAndRegisterRoute = {
|
32
|
+
path: "/login",
|
33
|
+
name: "login",
|
34
|
+
component: () => import("./views/login/login-and-register.vue"),
|
35
|
+
meta: {
|
36
|
+
title: "登录",
|
37
|
+
icon: "dashboard",
|
38
|
+
hiddenInLauncher: true,
|
39
|
+
noNeedLogin: true,
|
40
|
+
},
|
41
|
+
};
|
42
|
+
|
43
|
+
|
44
|
+
const LoginRoute = {
|
45
|
+
path: "/login",
|
46
|
+
name: "login",
|
47
|
+
component: () => import("./views/login/login.vue"),
|
48
|
+
meta: {
|
49
|
+
title: "登录",
|
50
|
+
icon: "dashboard",
|
51
|
+
hiddenInLauncher: true,
|
52
|
+
noNeedLogin: true,
|
53
|
+
},
|
54
|
+
};
|
55
|
+
|
56
|
+
|
57
|
+
export {PathMatchRoute, NotFoundRoute, NoPermRoute, LoginRoute, LoginAndRegisterRoute};
|
package/scx/ali-oss.js
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
import {formatFileSize, getChunkAndHash} from "@scx-js/scx-app-x";
|
2
|
+
import dayjs from "dayjs/esm/index.js";
|
3
|
+
import {inject} from "vue";
|
4
|
+
|
5
|
+
class AliOSS {
|
6
|
+
|
7
|
+
aliOssClient;
|
8
|
+
|
9
|
+
baseURL;
|
10
|
+
|
11
|
+
constructor(baseURL, aliOssClient) {
|
12
|
+
this.baseURL = baseURL;
|
13
|
+
this.aliOssClient = aliOssClient;
|
14
|
+
}
|
15
|
+
|
16
|
+
upload(needUploadFile, progress) {
|
17
|
+
const onProgress = (state, value) => {
|
18
|
+
//前 50% 是校验 md5 后 50% 才是真正的文件上传
|
19
|
+
if (state === "checking") {
|
20
|
+
progress(value * 0.5, "校验中");
|
21
|
+
} else if (state === "uploading") {
|
22
|
+
progress(50 + value * 0.5, "上传中");
|
23
|
+
}
|
24
|
+
};
|
25
|
+
return new Promise((resolve, reject) => {
|
26
|
+
if (needUploadFile == null || !(needUploadFile instanceof File)) {
|
27
|
+
reject("文件不能为空并且类型必须为文件 !!!");
|
28
|
+
return;
|
29
|
+
}
|
30
|
+
getChunkAndHash(needUploadFile, onProgress, needUploadFile.size).then(chunkAndMD5 => {
|
31
|
+
const now = new Date();
|
32
|
+
const path = []; //这里路径和 scx-fss 保持一致 年+月+日+MD5+真实文件名
|
33
|
+
path.push(now.getFullYear());
|
34
|
+
path.push(now.getMonth() + 1);
|
35
|
+
path.push(now.getDate());
|
36
|
+
path.push(chunkAndMD5.hash);
|
37
|
+
path.push(needUploadFile.name);
|
38
|
+
onProgress("uploading", 0);
|
39
|
+
this.aliOssClient.put(path.join("/"), needUploadFile)
|
40
|
+
.then(res => {
|
41
|
+
onProgress("uploading", 100);
|
42
|
+
resolve(res.name);
|
43
|
+
});
|
44
|
+
}).catch(e => reject(e));
|
45
|
+
});
|
46
|
+
}
|
47
|
+
|
48
|
+
info(fileID) {
|
49
|
+
const previewURL = this.joinURL(fileID) + "?x-oss-process=image/resize,w_150";
|
50
|
+
const downloadURL = this.joinURL(fileID);
|
51
|
+
const fileName = fileID.substring(fileID.lastIndexOf("/") + 1);
|
52
|
+
|
53
|
+
return new Promise((resolve, reject) => {
|
54
|
+
this.aliOssClient.getObjectMeta(fileID).then(m => {
|
55
|
+
const headers = m.res.headers;
|
56
|
+
const fileSizeDisplay = formatFileSize(headers["content-length"]);
|
57
|
+
const uploadTime = dayjs(headers["last-modified"]).format("YYYY-MM-DD HH:mm:ss");
|
58
|
+
resolve({fileName, previewURL, downloadURL, uploadTime, fileSizeDisplay});
|
59
|
+
});
|
60
|
+
});
|
61
|
+
}
|
62
|
+
|
63
|
+
joinURL(fileName) {
|
64
|
+
return joinURL(this.baseURL, fileName);
|
65
|
+
}
|
66
|
+
|
67
|
+
install(app) {
|
68
|
+
app.provide(aliOSSKey, this);
|
69
|
+
}
|
70
|
+
|
71
|
+
}
|
72
|
+
|
73
|
+
/**
|
74
|
+
*
|
75
|
+
* @type {string}
|
76
|
+
*/
|
77
|
+
const aliOSSKey = "ali-oss";
|
78
|
+
|
79
|
+
/**
|
80
|
+
*
|
81
|
+
* @returns {AliOSS}
|
82
|
+
*/
|
83
|
+
function useAliOSS() {
|
84
|
+
return inject(aliOSSKey);
|
85
|
+
}
|
86
|
+
|
87
|
+
export {AliOSS, useAliOSS};
|
@@ -0,0 +1,68 @@
|
|
1
|
+
import {ResponseNotOKError, ScxFetch, ScxFetchResponseType} from "@scx-js/scx-http";
|
2
|
+
import {ElMessage, ElMessageBox} from "element-plus";
|
3
|
+
|
4
|
+
class AuthFetch extends ScxFetch {
|
5
|
+
|
6
|
+
scxAuth;
|
7
|
+
|
8
|
+
/**
|
9
|
+
* 防止弹出多个提示框
|
10
|
+
*/
|
11
|
+
messageLock;
|
12
|
+
|
13
|
+
constructor(baseURL, scxAuth) {
|
14
|
+
super(baseURL);
|
15
|
+
this.scxAuth = scxAuth;
|
16
|
+
this.defaultOptions.responseType = ScxFetchResponseType.JSON;
|
17
|
+
this.defaultOptions.usePreInterceptor = true;
|
18
|
+
this.defaultOptions.usePostInterceptor = true;
|
19
|
+
}
|
20
|
+
|
21
|
+
/**
|
22
|
+
* 这里重写前置拦截器以便传送请求头
|
23
|
+
* @param request
|
24
|
+
* @returns {RequestInit}
|
25
|
+
*/
|
26
|
+
preInterceptor(request) {
|
27
|
+
const authHeaders = this.scxAuth.AuthHeaders();
|
28
|
+
for (let key in authHeaders) {
|
29
|
+
request.headers.set(key, authHeaders[key]);
|
30
|
+
}
|
31
|
+
return request;
|
32
|
+
}
|
33
|
+
|
34
|
+
postInterceptor(response) {
|
35
|
+
return new Promise((resolve, reject) => response.then(v => resolve(v)).catch(error => {
|
36
|
+
if (error instanceof ResponseNotOKError) {
|
37
|
+
//此处针对一些常见的 错误进行处理 例如 权限问题
|
38
|
+
const status = error.cause.status;
|
39
|
+
if (status === 401) {
|
40
|
+
if (!this.messageLock) {
|
41
|
+
this.messageLock = true;
|
42
|
+
ElMessageBox.confirm("您已经被登出 !!!", "您已经被登出 !!!", {
|
43
|
+
confirmButtonText: "重新登录", cancelButtonText: "取消", type: "warning",
|
44
|
+
}).then(() => {
|
45
|
+
this.scxAuth.removeToken();
|
46
|
+
this.scxAuth.resetUserInfo();
|
47
|
+
location.reload();
|
48
|
+
}).catch(e => {
|
49
|
+
console.error(e);
|
50
|
+
}).finally(() => {
|
51
|
+
this.messageLock = false;
|
52
|
+
});
|
53
|
+
}
|
54
|
+
} else if (status === 403) {
|
55
|
+
ElMessage.warning("您没有权限进行该操作 !!!");
|
56
|
+
} else if (status === 500) {
|
57
|
+
ElMessage.error("服务器端发生错误 !!!");
|
58
|
+
}
|
59
|
+
}
|
60
|
+
reject(error);
|
61
|
+
}));
|
62
|
+
}
|
63
|
+
|
64
|
+
}
|
65
|
+
|
66
|
+
export {
|
67
|
+
AuthFetch,
|
68
|
+
};
|