Compare commits
66
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc9b67d467 | ||
|
|
2c95f86963 | ||
|
|
1272e8fe7d | ||
|
|
58d7e50de3 | ||
|
|
c045276a7c | ||
|
|
6a7ec81168 | ||
|
|
183e0e9212 | ||
|
|
c020c1affc | ||
|
|
9673380f60 | ||
|
|
9b28d14bc3 | ||
|
|
01a4dd0b45 | ||
|
|
e1023bb331 | ||
|
|
5c701801db | ||
|
|
9b4692b625 | ||
|
|
aaec0f7bfe | ||
|
|
25e2e0e018 | ||
|
|
5165541780 | ||
|
|
b3c1b95437 | ||
|
|
d84fee9319 | ||
|
|
d034236ce3 | ||
|
|
9e89ab5bae | ||
|
|
6e6062e824 | ||
|
|
35bebf403c | ||
|
|
0a8c33c3cb | ||
|
|
8a7de17b58 | ||
|
|
f65eb2aee5 | ||
|
|
efb53d4bcb | ||
|
|
ad2134f07d | ||
|
|
caa9d0fcdc | ||
|
|
b829ba4aa0 | ||
|
|
a232437abc | ||
|
|
f2372d3243 | ||
|
|
a8a334b3c3 | ||
|
|
1e3f18ce22 | ||
|
|
631f338be2 | ||
|
|
bf977fe00a | ||
|
|
5bc7044032 | ||
|
|
6ad0ed5316 | ||
|
|
ccb2ecc5ee | ||
|
|
ec0cb472df | ||
|
|
cd0a459c2b | ||
|
|
18fea86a2c | ||
|
|
66bfff450c | ||
|
|
ec34c759ec | ||
|
|
c1c5ea5e9d | ||
|
|
6fe269efc7 | ||
|
|
920e079727 | ||
|
|
a2f13185e1 | ||
|
|
078bb50130 | ||
|
|
4854d8df00 | ||
|
|
ecf062605d | ||
|
|
b989b07121 | ||
|
|
bf957f06a0 | ||
|
|
aed8c89ded | ||
|
|
4085811d24 | ||
|
|
fe384a3020 | ||
|
|
c6f3cd068e | ||
|
|
99a6b56233 | ||
|
|
8ed32775cb | ||
|
|
395db813b5 | ||
|
|
ab1a7204fa | ||
|
|
92b2b489a4 | ||
|
|
956d2ad495 | ||
|
|
cb6cbc4f40 | ||
|
|
14538ce399 | ||
|
|
6c16d023f4 |
@@ -20,6 +20,11 @@ VITE_APP_PORT = 80
|
||||
|
||||
# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
|
||||
VITE_APP_RSA_PUBLIC_KEY = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
|
||||
# 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换
|
||||
VITE_APP_RSA_PRIVATE_KEY = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE='
|
||||
|
||||
# 客户端id
|
||||
VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e'
|
||||
|
||||
# websocket 开关(开发环境默认关闭ws 因vite的bug导致如ws无法连接则会崩溃)
|
||||
VITE_APP_WEBSOCKET = false
|
||||
|
||||
@@ -23,6 +23,11 @@ VITE_APP_PORT = 80
|
||||
|
||||
# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
|
||||
VITE_APP_RSA_PUBLIC_KEY = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
|
||||
# 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换
|
||||
VITE_APP_RSA_PRIVATE_KEY = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE='
|
||||
|
||||
# 客户端id
|
||||
VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e'
|
||||
|
||||
# websocket 开关
|
||||
VITE_APP_WEBSOCKET = true
|
||||
|
||||
@@ -17,7 +17,9 @@ npm install --registry=https://registry.npmmirror.com
|
||||
# 启动服务
|
||||
npm run dev
|
||||
|
||||
# 构建生产环境 yarn build:prod
|
||||
# 构建生产环境
|
||||
npm run build:prod
|
||||
|
||||
# 前端访问地址 http://localhost:80
|
||||
```
|
||||
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ruoyi-vue-plus",
|
||||
"version": "5.1.0",
|
||||
"version": "5.1.2",
|
||||
"description": "RuoYi-Vue-Plus多租户管理系统",
|
||||
"author": "LionLi",
|
||||
"license": "MIT",
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "2.1.0",
|
||||
"@vueup/vue-quill": "1.1.0",
|
||||
"@vueup/vue-quill": "1.2.0",
|
||||
"@vueuse/core": "9.5.0",
|
||||
"animate.css": "4.1.1",
|
||||
"await-to-js": "^3.0.0",
|
||||
|
||||
+6
-1
@@ -29,6 +29,11 @@ export function login(data: LoginData): AxiosPromise<LoginResult> {
|
||||
|
||||
// 注册方法
|
||||
export function register(data: any) {
|
||||
const params = {
|
||||
...data,
|
||||
clientId: clientId,
|
||||
grantType: 'password'
|
||||
};
|
||||
return request({
|
||||
url: '/auth/register',
|
||||
headers: {
|
||||
@@ -36,7 +41,7 @@ export function register(data: any) {
|
||||
isEncrypt: true
|
||||
},
|
||||
method: 'post',
|
||||
data: data
|
||||
data: params
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export interface OperLogQuery extends PageQuery {
|
||||
operIp: string;
|
||||
title: string;
|
||||
operName: string;
|
||||
businessType: string;
|
||||
|
||||
@@ -20,7 +20,7 @@ export function getConfig(configId: string | number): AxiosPromise<ConfigVO> {
|
||||
}
|
||||
|
||||
// 根据参数键名查询参数值
|
||||
export function getConfigKey(configKey: string): AxiosPromise<ConfigVO> {
|
||||
export function getConfigKey(configKey: string): AxiosPromise<String> {
|
||||
return request({
|
||||
url: '/system/config/configKey/' + configKey,
|
||||
method: 'get'
|
||||
|
||||
@@ -136,7 +136,7 @@ export const updateUserPwd = (oldPassword: string, newPassword: string) => {
|
||||
headers: {
|
||||
isEncrypt: true
|
||||
},
|
||||
params: data
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -227,23 +227,4 @@
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
// the scroll bar appears when the sub-menu is too long
|
||||
> .el-menu--popup {
|
||||
max-height: 100vh;
|
||||
overflow-y: auto;
|
||||
|
||||
&::-webkit-scrollbar-track-piece {
|
||||
background: #d3dce6;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #99a9bf;
|
||||
border-radius: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
<div>
|
||||
<template v-for="(item, index) in options">
|
||||
<template v-if="values.includes(item.value)">
|
||||
<span v-if="item.elTagType == 'default' || item.elTagType == ''" :key="item.value" :index="index" :class="item.elTagClass">
|
||||
<span v-if="(item.elTagType === 'default' || item.elTagType === '') && (item.elTagClass === '' || item.elTagClass == null)"
|
||||
:key="item.value" :index="index" :class="item.elTagClass">
|
||||
{{ item.label + " " }}
|
||||
</span>
|
||||
<el-tag
|
||||
@@ -10,7 +11,7 @@
|
||||
:disable-transitions="true"
|
||||
:key="item.value + ''"
|
||||
:index="index"
|
||||
:type="item.elTagType === 'primary' ? '' : item.elTagType"
|
||||
:type="(item.elTagType === 'primary' || item.elTagType === 'default')? '' : item.elTagType"
|
||||
:class="item.elTagClass"
|
||||
>
|
||||
{{ item.label + " " }}
|
||||
@@ -37,46 +38,33 @@ const props = defineProps({
|
||||
value: [Number, String, Array] as PropType<number | string | Array<number | string>>,
|
||||
// 当未找到匹配的数据时,显示value
|
||||
showValue: propTypes.bool.def(true),
|
||||
separator: propTypes.string.def(","),
|
||||
});
|
||||
|
||||
const values = computed(() => {
|
||||
if (props.value !== null && typeof props.value !== "undefined") {
|
||||
return Array.isArray(props.value) ? props.value : [String(props.value)];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
if (props.value === '' || props.value === null || typeof props.value === "undefined") return []
|
||||
return Array.isArray(props.value) ? props.value.map(item => '' + item) : String(props.value).split(props.separator);
|
||||
});
|
||||
|
||||
const unmatch = computed(() => {
|
||||
if (props.value !== null && typeof props.value !== "undefined") {
|
||||
if (props.options?.length == 0 || props.value === '' || props.value === null || typeof props.value === "undefined") return false
|
||||
// 传入值为非数组
|
||||
if (!Array.isArray(props.value)) {
|
||||
if (props.options.some((v) => v.value == props.value)) {
|
||||
return false;
|
||||
values.value.forEach(item => {
|
||||
if (!props.options.some(v => v.value === item)) {
|
||||
return true // 如果有未匹配项,将标志设置为true
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// 没有value不显示
|
||||
return false;
|
||||
})
|
||||
return false // 返回标志的值
|
||||
});
|
||||
|
||||
const unmatchArray = computed(() => {
|
||||
// 记录未匹配的项
|
||||
const itemUnmatchArray: Array<string | number> = [];
|
||||
if (props.value !== null && typeof props.value !== "undefined") {
|
||||
// 传入值为非数组
|
||||
if (!Array.isArray(props.value)) {
|
||||
itemUnmatchArray.push(props.value);
|
||||
} else {
|
||||
// 传入值为Array
|
||||
props.value.forEach((item) => {
|
||||
if (!props.options.some((v) => v.value == item)) {
|
||||
if (props.value !== '' && props.value !== null && typeof props.value !== "undefined") {
|
||||
values.value.forEach(item => {
|
||||
if (!props.options.some(v => v.value === item)) {
|
||||
itemUnmatchArray.push(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
// 没有value不显示
|
||||
return handleArray(itemUnmatchArray);
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
name="file"
|
||||
:show-file-list="false"
|
||||
:headers="upload.headers"
|
||||
style="display: none"
|
||||
ref="uploadRef"
|
||||
v-if="type === 'url'"
|
||||
>
|
||||
</el-upload>
|
||||
<div class="editor">
|
||||
<quill-editor
|
||||
ref="myQuillEditor"
|
||||
ref="quillEditorRef"
|
||||
v-model:content="content"
|
||||
contentType="html"
|
||||
@textChange="(e: any) => $emit('update:modelValue', content)"
|
||||
@@ -53,7 +53,7 @@ const upload = reactive<UploadOption>({
|
||||
headers: globalHeaders(),
|
||||
url: import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload'
|
||||
})
|
||||
const myQuillEditor = ref();
|
||||
const quillEditorRef = ref();
|
||||
|
||||
const options = ref({
|
||||
theme: "snow",
|
||||
@@ -86,7 +86,7 @@ const options = ref({
|
||||
},
|
||||
}
|
||||
},
|
||||
placeholder: '请输入内容',
|
||||
placeholder: "请输入内容",
|
||||
readOnly: props.readOnly,
|
||||
});
|
||||
|
||||
@@ -110,10 +110,10 @@ watch(() => props.modelValue, (v) => {
|
||||
|
||||
// 图片上传成功返回图片地址
|
||||
const handleUploadSuccess = (res: any) => {
|
||||
// 获取富文本实例
|
||||
let quill = toRaw(myQuillEditor.value).getQuill();
|
||||
// 如果上传成功
|
||||
if (res.code === 200) {
|
||||
// 获取富文本实例
|
||||
let quill = toRaw(quillEditorRef.value).getQuill();
|
||||
// 获取光标位置
|
||||
let length = quill.selection.savedRange.index;
|
||||
// 插入图片,res为服务器返回的图片链接地址
|
||||
@@ -129,6 +129,13 @@ const handleUploadSuccess = (res: any) => {
|
||||
|
||||
// 图片上传前拦截
|
||||
const handleBeforeUpload = (file: any) => {
|
||||
const type = ["image/jpeg", "image/jpg", "image/png", "image/svg"];
|
||||
const isJPG = type.includes(file.type);
|
||||
//检验文件格式
|
||||
if (!isJPG) {
|
||||
proxy?.$modal.msgError(`图片格式错误!`);
|
||||
return false;
|
||||
}
|
||||
// 校检文件大小
|
||||
if (props.fileSize) {
|
||||
const isLt = file.size / 1024 / 1024 < props.fileSize;
|
||||
@@ -149,95 +156,80 @@ const handleUploadError = (err: any) => {
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.editor-img-uploader {
|
||||
display: none;
|
||||
}
|
||||
.editor,
|
||||
.ql-toolbar {
|
||||
white-space: pre-wrap !important;
|
||||
line-height: normal !important;
|
||||
}
|
||||
|
||||
.quill-img {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ql-snow .ql-tooltip[data-mode="link"]::before {
|
||||
content: "请输入链接地址:";
|
||||
}
|
||||
|
||||
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
|
||||
border-right: 0;
|
||||
content: "保存";
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.ql-snow .ql-tooltip[data-mode="video"]::before {
|
||||
content: "请输入视频地址:";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
|
||||
content: "14px";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"]::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before {
|
||||
content: "10px";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"]::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before {
|
||||
content: "18px";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before {
|
||||
content: "32px";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
|
||||
content: "文本";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
|
||||
content: "标题1";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
|
||||
content: "标题2";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
|
||||
content: "标题3";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
|
||||
content: "标题4";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
|
||||
content: "标题5";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
|
||||
content: "标题6";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
|
||||
content: "标准字体";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="serif"]::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before {
|
||||
content: "衬线字体";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="monospace"]::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before {
|
||||
content: "等宽字体";
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
class="header-search-select"
|
||||
@change="change"
|
||||
>
|
||||
<el-option v-for="option in options" :key="option.item.path" :value="option.item" :label="option.item.title.join(' > ')" />
|
||||
<el-option v-for="option in options" :key="option.item.path" :value="option.item"
|
||||
:label="option.item.title.join(' > ')"/>
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
@@ -51,13 +52,18 @@ const close = () => {
|
||||
}
|
||||
const change = (val: any) => {
|
||||
const path = val.path;
|
||||
const query = val.query;
|
||||
if (isHttp(path)) {
|
||||
// http(s):// 路径新窗口打开
|
||||
const pindex = path.indexOf("http");
|
||||
window.open(path.substr(pindex, path.length), "_blank");
|
||||
} else {
|
||||
if (query) {
|
||||
router.push({ path: path, query: JSON.parse(query) });
|
||||
} else {
|
||||
router.push(path)
|
||||
}
|
||||
}
|
||||
search.value = ''
|
||||
options.value = []
|
||||
nextTick(() => {
|
||||
@@ -90,7 +96,8 @@ const generateRoutes = (routes: RouteOption[], basePath = '', prefixTitle: strin
|
||||
const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path;
|
||||
const data = {
|
||||
path: !isHttp(r.path) ? getNormalPath(basePath + p) : r.path,
|
||||
title: [...prefixTitle]
|
||||
title: [...prefixTitle],
|
||||
query: ''
|
||||
}
|
||||
if (r.meta && r.meta.title) {
|
||||
data.title = [...data.title, r.meta.title];
|
||||
@@ -100,6 +107,11 @@ const generateRoutes = (routes: RouteOption[], basePath = '', prefixTitle: strin
|
||||
res.push(data);
|
||||
}
|
||||
}
|
||||
|
||||
if (r.query) {
|
||||
data.query = r.query
|
||||
}
|
||||
|
||||
// recursive child routes
|
||||
if (r.children) {
|
||||
const tempRoutes = generateRoutes(r.children, data.path, data.title);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="relative" :style="{ width: width }">
|
||||
<el-input v-model="modelValue" readonly @click="visible = !visible" placeholder="点击选择图标">
|
||||
<template #prepend>
|
||||
<svg-icon :icon-class="modelValue as string" />
|
||||
<svg-icon :icon-class="modelValue" />
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
|
||||
@@ -7,13 +7,25 @@
|
||||
<el-tooltip class="item" effect="dark" content="刷新" placement="top">
|
||||
<el-button circle icon="Refresh" @click="refresh()" />
|
||||
</el-tooltip>
|
||||
<el-tooltip class="item" effect="dark" content="显隐列" placement="top" v-if="columns">
|
||||
<el-button circle icon="Menu" @click="showColumn()" />
|
||||
<el-tooltip class="item" effect="dark" content="显示/隐藏列" placement="top" v-if="columns">
|
||||
<div class="show-btn">
|
||||
<el-popover placement="bottom" trigger="click">
|
||||
<div class="tree-header">显示/隐藏列</div>
|
||||
<el-tree
|
||||
ref="columnRef"
|
||||
:data="columns"
|
||||
show-checkbox
|
||||
@check="columnChange"
|
||||
node-key="key"
|
||||
:props="{ label: 'label', children: 'children' }"
|
||||
></el-tree>
|
||||
<template #reference>
|
||||
<el-button circle icon="Menu" />
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</el-row>
|
||||
<el-dialog :title="title" v-model="open" append-to-body>
|
||||
<el-transfer :titles="['显示', '隐藏']" v-model="value" :data="columns" @change="dataChange"></el-transfer>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -29,15 +41,9 @@ const props = defineProps({
|
||||
gutter: propTypes.number.def(10),
|
||||
})
|
||||
|
||||
const columnRef = ref<ElTreeInstance>();
|
||||
const emits = defineEmits(['update:showSearch', 'queryTable']);
|
||||
|
||||
// 显隐数据
|
||||
const value = ref<Array<number>>([]);
|
||||
// 弹出层标题
|
||||
const title = ref("显示/隐藏");
|
||||
// 是否显示弹出层
|
||||
const open = ref(false);
|
||||
|
||||
const style = computed(() => {
|
||||
const ret: any = {};
|
||||
if (props.gutter) {
|
||||
@@ -56,23 +62,19 @@ function refresh() {
|
||||
emits("queryTable");
|
||||
}
|
||||
|
||||
// 右侧列表元素变化
|
||||
function dataChange(data: TransferKey[]) {
|
||||
// 更改数据列的显示和隐藏
|
||||
function columnChange(...args: any[]) {
|
||||
props.columns?.forEach((item) => {
|
||||
item.visible = !data.includes(item.key);
|
||||
item.visible = args[1].checkedKeys.includes(item.key);
|
||||
})
|
||||
}
|
||||
|
||||
// 打开显隐列dialog
|
||||
const showColumn = () => {
|
||||
open.value = true;
|
||||
}
|
||||
|
||||
// 显隐列初始默认隐藏列
|
||||
onMounted(() => {
|
||||
props.columns?.forEach((item) => {
|
||||
if (!item.visible) {
|
||||
value.value.push(item.key);
|
||||
if (item.visible) {
|
||||
columnRef.value?.setChecked(item.key, true, false);
|
||||
// value.value.push(item.key);
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -91,4 +93,12 @@ onMounted(() => {
|
||||
.my-el-transfer {
|
||||
text-align: center;
|
||||
}
|
||||
.tree-header{
|
||||
width: 100%;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
.show-btn {
|
||||
margin-left: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -18,6 +18,7 @@ export default {
|
||||
language: 'Language',
|
||||
dashboard: 'Dashboard',
|
||||
document: 'Document',
|
||||
message: 'Message',
|
||||
layoutSize: 'Layout Size',
|
||||
selectTenant: 'Select Tenant',
|
||||
layoutSetting: 'Layout Setting',
|
||||
|
||||
@@ -17,6 +17,7 @@ export default {
|
||||
language: '语言',
|
||||
dashboard: '首页',
|
||||
document: '项目文档',
|
||||
message: '消息',
|
||||
layoutSize: '布局大小',
|
||||
selectTenant: '选择租户',
|
||||
layoutSetting: '布局设置',
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
:key="item.path"
|
||||
:iframeId="'iframe' + index"
|
||||
v-show="route.path === item.path"
|
||||
:src="item.meta ? item.meta.link : ''"
|
||||
:src="iframeUrl(item.meta ? item.meta.link : '', item.query)"
|
||||
></inner-link>
|
||||
</transition-group>
|
||||
</template>
|
||||
@@ -15,5 +15,13 @@ import InnerLink from "../InnerLink/index.vue";
|
||||
import useTagsViewStore from '@/store/modules/tagsView';
|
||||
|
||||
const route = useRoute();
|
||||
const tagsViewStore = useTagsViewStore()
|
||||
const tagsViewStore = useTagsViewStore();
|
||||
|
||||
function iframeUrl(url: string, query: any) {
|
||||
if (Object.keys(query).length > 0) {
|
||||
let params = Object.keys(query).map((key) => key + "=" + query[key]).join("&");
|
||||
return url + "?" + params;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -27,6 +27,21 @@
|
||||
<svg-icon class-name="search-icon" icon-class="search" />
|
||||
</div>
|
||||
</el-tooltip>
|
||||
<!-- 消息 -->
|
||||
<el-tooltip :content="$t('navbar.message')" effect="dark" placement="bottom">
|
||||
<div>
|
||||
<el-popover placement="bottom" trigger="click" transition="el-zoom-in-top" :width="300" :persistent="false">
|
||||
<template #reference>
|
||||
<el-badge :value="newNotice > 0 ? newNotice : ''" :max="99">
|
||||
<svg-icon icon-class="message" />
|
||||
</el-badge>
|
||||
</template>
|
||||
<template #default>
|
||||
<notice></notice>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="Github" effect="dark" placement="bottom">
|
||||
<ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" />
|
||||
</el-tooltip>
|
||||
@@ -81,10 +96,14 @@ import { getTenantList } from "@/api/login";
|
||||
import { dynamicClear, dynamicTenant } from "@/api/system/tenant";
|
||||
import { ComponentInternalInstance } from "vue";
|
||||
import { TenantVO } from "@/api/types";
|
||||
import notice from './notice/index.vue';
|
||||
import useNoticeStore from '@/store/modules/notice';
|
||||
|
||||
const appStore = useAppStore();
|
||||
const userStore = useUserStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
const noticeStore = storeToRefs(useNoticeStore());
|
||||
const newNotice = ref(<number>0);
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
@@ -161,6 +180,11 @@ const handleCommand = (command: string) => {
|
||||
commandMap[command]();
|
||||
}
|
||||
}
|
||||
|
||||
//用深度监听 消息
|
||||
watch(() => noticeStore.state.value.notices, (newVal, oldVal) => {
|
||||
newNotice.value = newVal.filter((item: any) => !item.read).length;
|
||||
}, { deep: true });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -169,6 +193,10 @@ const handleCommand = (command: string) => {
|
||||
height:30px;
|
||||
}
|
||||
|
||||
:deep(.el-badge__content.is-fixed){
|
||||
top: 12px;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div v-if="!item.hidden">
|
||||
<template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow">
|
||||
<template v-if="hasOneShowingChild(item, item.children) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow">
|
||||
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)">
|
||||
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
|
||||
<svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" />
|
||||
@@ -18,10 +18,10 @@
|
||||
</template>
|
||||
|
||||
<sidebar-item
|
||||
v-for="child in item.children"
|
||||
:key="child.path"
|
||||
v-for="(child, index) in item.children"
|
||||
:key="child.path + index"
|
||||
:is-nest="true"
|
||||
:item="child as RouteOption"
|
||||
:item="child"
|
||||
:base-path="resolvePath(child.path)"
|
||||
class="nest-menu"
|
||||
/>
|
||||
@@ -34,7 +34,6 @@ import { isExternal } from '@/utils/validate'
|
||||
import AppLink from './Link.vue'
|
||||
import { getNormalPath } from '@/utils/ruoyi'
|
||||
import { RouteOption } from "vue-router";
|
||||
import { PropType } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
// route object
|
||||
@@ -54,7 +53,7 @@ const props = defineProps({
|
||||
|
||||
const onlyOneChild = ref<any>({});
|
||||
|
||||
const hasOneShowingChild = (children:RouteOption[] = [], parent: RouteOption) => {
|
||||
const hasOneShowingChild = (parent: RouteOption, children?:RouteOption[]) => {
|
||||
if (!children) {
|
||||
children = [];
|
||||
}
|
||||
@@ -76,9 +75,13 @@ const hasOneShowingChild = (children:RouteOption[] = [], parent: RouteOption) =>
|
||||
// Show parent if there are no child router to display
|
||||
if (showingChildren.length === 0) {
|
||||
onlyOneChild.value = { ...parent, path: '', noShowingChildren: true }
|
||||
if (parent.name === '2222') {
|
||||
console.log(onlyOneChild.value)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
return false
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<el-scrollbar :class="sideTheme" wrap-class="scrollbar-wrapper">
|
||||
<transition :enter-active-class="proxy?.animate.menuSearchAnimate.enter" mode="out-in">
|
||||
<el-menu
|
||||
:default-active="activeMenu as string"
|
||||
:default-active="activeMenu"
|
||||
:collapse="isCollapse"
|
||||
:background-color="bgColor"
|
||||
:text-color="textColor"
|
||||
@@ -27,6 +27,7 @@ import variables from '@/assets/styles/variables.module.scss'
|
||||
import useAppStore from '@/store/modules/app'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
import usePermissionStore from '@/store/modules/permission'
|
||||
import { RouteOption } from "vue-router";
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
const route = useRoute();
|
||||
@@ -34,7 +35,7 @@ const appStore = useAppStore()
|
||||
const settingsStore = useSettingsStore()
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
const sidebarRouters = computed(() => permissionStore.sidebarRouters);
|
||||
const sidebarRouters = computed<RouteOption[]>(() => permissionStore.sidebarRouters);
|
||||
const showLogo = computed(() => settingsStore.sidebarLogo);
|
||||
const sideTheme = computed(() => settingsStore.sideTheme);
|
||||
const theme = computed(() => settingsStore.theme);
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<div class="layout-navbars-breadcrumb-user-news" v-loading="state.loading">
|
||||
<div class="head-box">
|
||||
<div class="head-box-title">通知公告</div>
|
||||
<div class="head-box-btn" @click="readAll">全部已读</div>
|
||||
</div>
|
||||
<div class="content-box" v-loading="state.loading">
|
||||
<template v-if="newsList.length > 0">
|
||||
<div class="content-box-item" v-for="(v, k) in newsList" :key="k" @click="onNewsClick(k)">
|
||||
<div class="item-conten">
|
||||
<div>{{ v.message }}</div>
|
||||
<div class="content-box-msg"></div>
|
||||
<div class="content-box-time">{{ v.time }}</div>
|
||||
</div>
|
||||
<!-- 已读/未读 -->
|
||||
<span v-if="v.read" class="el-tag el-tag--success el-tag--mini read">已读</span>
|
||||
<span v-else class="el-tag el-tag--danger el-tag--mini read">未读</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-empty :description="'消息为空'" v-else></el-empty>
|
||||
</div>
|
||||
<div class="foot-box" @click="onGoToGiteeClick" v-if="newsList.length > 0">前往gitee</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="layoutBreadcrumbUserNews">
|
||||
import { ref } from "vue";
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { nextTick, onMounted, reactive } from "vue";
|
||||
import useNoticeStore from '@/store/modules/notice';
|
||||
|
||||
const noticeStore = storeToRefs(useNoticeStore());
|
||||
const {readAll} = useNoticeStore();
|
||||
|
||||
// 定义变量内容
|
||||
const state = reactive({
|
||||
loading: false,
|
||||
});
|
||||
const newsList =ref([]) as any;
|
||||
|
||||
/**
|
||||
* 初始化数据
|
||||
* @returns
|
||||
*/
|
||||
const getTableData = async () => {
|
||||
state.loading = true;
|
||||
newsList.value = noticeStore.state.value.notices;
|
||||
state.loading = false;
|
||||
};
|
||||
|
||||
|
||||
//点击消息,写入已读
|
||||
const onNewsClick = (item: any) => {
|
||||
newsList.value[item].read = true;
|
||||
//并且写入pinia
|
||||
noticeStore.state.value.notices = newsList.value;
|
||||
};
|
||||
|
||||
// 前往通知中心点击
|
||||
const onGoToGiteeClick = () => {
|
||||
window.open("https://gitee.com/dromara/RuoYi-Vue-Plus/tree/5.X/");
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
getTableData();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.layout-navbars-breadcrumb-user-news {
|
||||
.head-box {
|
||||
display: flex;
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
box-sizing: border-box;
|
||||
color: var(--el-text-color-primary);
|
||||
justify-content: space-between;
|
||||
height: 35px;
|
||||
align-items: center;
|
||||
.head-box-btn {
|
||||
color: var(--el-color-primary);
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.content-box {
|
||||
height: 300px;
|
||||
overflow: auto;
|
||||
font-size: 13px;
|
||||
.content-box-item {
|
||||
padding-top: 12px;
|
||||
display: flex;
|
||||
&:last-of-type {
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
.content-box-msg {
|
||||
color: var(--el-text-color-secondary);
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.content-box-time {
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
.item-conten {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
.foot-box {
|
||||
height: 35px;
|
||||
color: var(--el-color-primary);
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-top: 1px solid var(--el-border-color-lighter);
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
:deep(.el-empty__description p) {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -20,6 +20,8 @@ router.beforeEach(async (to, from, next) => {
|
||||
if (to.path === '/login') {
|
||||
next({ path: '/' });
|
||||
NProgress.done();
|
||||
} else if (whiteList.indexOf(to.path) !== -1) {
|
||||
next()
|
||||
} else {
|
||||
if (useUserStore().roles.length === 0) {
|
||||
isRelogin.show = true;
|
||||
|
||||
+1
-1
@@ -139,7 +139,7 @@ export const dynamicRoutes: RouteOption[] = [
|
||||
path: '/system/oss-config',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
permissions: ['system:oss:list'],
|
||||
permissions: ['system:ossConfig:list'],
|
||||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
interface NoticeItem {
|
||||
title?: string;
|
||||
read: boolean;
|
||||
message: any;
|
||||
time: string;
|
||||
}
|
||||
|
||||
export const useNoticeStore = defineStore('notice', () => {
|
||||
const state = reactive({
|
||||
notices: [] as NoticeItem[]
|
||||
});
|
||||
|
||||
const addNotice = (notice: NoticeItem) => {
|
||||
state.notices.push(notice);
|
||||
};
|
||||
|
||||
const removeNotice = (notice: NoticeItem) => {
|
||||
state.notices.splice(state.notices.indexOf(notice), 1);
|
||||
};
|
||||
|
||||
//实现全部已读
|
||||
const readAll = () => {
|
||||
state.notices.forEach((item) => {
|
||||
item.read = true;
|
||||
});
|
||||
};
|
||||
|
||||
const clearNotice = () => {
|
||||
state.notices = [];
|
||||
};
|
||||
return {
|
||||
state,
|
||||
addNotice,
|
||||
removeNotice,
|
||||
readAll,
|
||||
clearNotice
|
||||
};
|
||||
});
|
||||
|
||||
export default useNoticeStore;
|
||||
@@ -100,6 +100,10 @@ export const usePermissionStore = defineStore('permission', () => {
|
||||
}
|
||||
if (lastRouter) {
|
||||
el.path = lastRouter.path + '/' + el.path;
|
||||
if (el.children && el.children.length) {
|
||||
children = children.concat(filterChildren(el.children, el))
|
||||
return
|
||||
}
|
||||
}
|
||||
children = children.concat(el);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TagView } from 'vue-router';
|
||||
import { TagView, RouteRecordNormalized } from 'vue-router';
|
||||
|
||||
export const useTagsViewStore = defineStore('tagsView', () => {
|
||||
const visitedViews = ref<TagView[]>([]);
|
||||
@@ -35,7 +35,9 @@ export const useTagsViewStore = defineStore('tagsView', () => {
|
||||
const delView = (view: TagView): Promise<{ visitedViews: TagView[]; cachedViews: string[] }> => {
|
||||
return new Promise((resolve) => {
|
||||
delVisitedView(view);
|
||||
if (!isDynamicRoute(view)) {
|
||||
delCachedView(view);
|
||||
}
|
||||
resolve({
|
||||
visitedViews: [...visitedViews.value],
|
||||
cachedViews: [...cachedViews.value]
|
||||
@@ -177,6 +179,11 @@ export const useTagsViewStore = defineStore('tagsView', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const isDynamicRoute = (view: any): boolean => {
|
||||
// 检查匹配的路由记录中是否有动态段
|
||||
return view.matched.some((m: RouteRecordNormalized) => m.path.includes(':'));
|
||||
};
|
||||
|
||||
return {
|
||||
visitedViews,
|
||||
cachedViews,
|
||||
|
||||
@@ -63,6 +63,10 @@ export const useUserStore = defineStore('user', () => {
|
||||
removeToken();
|
||||
};
|
||||
|
||||
const setAvatar = (value: string) => {
|
||||
avatar.value = value;
|
||||
};
|
||||
|
||||
return {
|
||||
userId,
|
||||
token,
|
||||
@@ -72,7 +76,8 @@ export const useUserStore = defineStore('user', () => {
|
||||
permissions,
|
||||
login,
|
||||
getInfo,
|
||||
logout
|
||||
logout,
|
||||
setAvatar
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
Vendored
-1
@@ -24,7 +24,6 @@ declare global {
|
||||
declare type ElColorPickerInstance = InstanceType<typeof ep.ElColorPicker>;
|
||||
declare type ElRateInstance = InstanceType<typeof ep.ElRate>;
|
||||
declare type ElSliderInstance = InstanceType<typeof ep.ElSlider>;
|
||||
declare type ElUploadInstance = InstanceType<typeof ep.ElUpload>;
|
||||
declare type ElScrollbarInstance = InstanceType<typeof ep.ElScrollbar>;
|
||||
|
||||
declare type TransferKey = ep.TransferKey;
|
||||
|
||||
Vendored
+2
@@ -68,7 +68,9 @@ interface ImportMetaEnv {
|
||||
VITE_APP_POWERJOB_ADMIN: string;
|
||||
VITE_APP_ENV: string;
|
||||
VITE_APP_RSA_PUBLIC_KEY: string;
|
||||
VITE_APP_RSA_PRIVATE_KEY: string;
|
||||
VITE_APP_CLIENT_ID: string;
|
||||
VITE_APP_WEBSOCKET: string;
|
||||
}
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
|
||||
Vendored
+1
@@ -13,6 +13,7 @@ declare global {
|
||||
key: number;
|
||||
label: string;
|
||||
visible: boolean;
|
||||
children?: Array<FieldOption>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,6 +30,13 @@ export const encryptBase64 = (str: CryptoJS.lib.WordArray) => {
|
||||
return CryptoJS.enc.Base64.stringify(str);
|
||||
};
|
||||
|
||||
/**
|
||||
* 解密base64
|
||||
*/
|
||||
export const decryptBase64 = (str: string) => {
|
||||
return CryptoJS.enc.Base64.parse(str);
|
||||
};
|
||||
|
||||
/**
|
||||
* 使用密钥对数据进行加密
|
||||
* @param message
|
||||
@@ -43,3 +50,17 @@ export const encryptWithAes = (message: string, aesKey: CryptoJS.lib.WordArray)
|
||||
});
|
||||
return encrypted.toString();
|
||||
};
|
||||
|
||||
/**
|
||||
* 使用密钥对数据进行解密
|
||||
* @param message
|
||||
* @param aesKey
|
||||
* @returns {string}
|
||||
*/
|
||||
export const decryptWithAes = (message: string, aesKey: CryptoJS.lib.WordArray) => {
|
||||
const decrypted = CryptoJS.AES.decrypt(message, aesKey, {
|
||||
mode: CryptoJS.mode.ECB,
|
||||
padding: CryptoJS.pad.Pkcs7
|
||||
});
|
||||
return decrypted.toString(CryptoJS.enc.Utf8);
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import JSEncrypt from 'jsencrypt';
|
||||
const publicKey = import.meta.env.VITE_APP_RSA_PUBLIC_KEY;
|
||||
|
||||
// 前端不建议存放私钥 不建议解密数据 因为都是透明的意义不大
|
||||
const privateKey = '**********';
|
||||
const privateKey = import.meta.env.VITE_APP_RSA_PRIVATE_KEY;
|
||||
|
||||
// 加密
|
||||
export const encrypt = (txt: string) => {
|
||||
|
||||
+18
-3
@@ -8,9 +8,10 @@ import { errorCode } from '@/utils/errorCode';
|
||||
import { LoadingInstance } from 'element-plus/es/components/loading/src/loading';
|
||||
import FileSaver from 'file-saver';
|
||||
import { getLanguage } from '@/lang';
|
||||
import { encryptBase64, encryptWithAes, generateAesKey } from '@/utils/crypto';
|
||||
import { encrypt } from '@/utils/jsencrypt';
|
||||
import { encryptBase64, encryptWithAes, generateAesKey, decryptWithAes, decryptBase64 } from '@/utils/crypto';
|
||||
import { encrypt, decrypt } from '@/utils/jsencrypt';
|
||||
|
||||
const encryptHeader = 'encrypt-key';
|
||||
let downloadLoadingInstance: LoadingInstance;
|
||||
// 是否显示重新登录
|
||||
export const isRelogin = { show: false };
|
||||
@@ -78,7 +79,7 @@ service.interceptors.request.use(
|
||||
if (isEncrypt && (config.method === 'post' || config.method === 'put')) {
|
||||
// 生成一个 AES 密钥
|
||||
const aesKey = generateAesKey();
|
||||
config.headers['encrypt-key'] = encrypt(encryptBase64(aesKey));
|
||||
config.headers[encryptHeader] = encrypt(encryptBase64(aesKey));
|
||||
config.data = typeof config.data === 'object' ? encryptWithAes(JSON.stringify(config.data), aesKey) : encryptWithAes(config.data, aesKey);
|
||||
}
|
||||
// FormData数据去请求头Content-Type
|
||||
@@ -96,6 +97,20 @@ service.interceptors.request.use(
|
||||
// 响应拦截器
|
||||
service.interceptors.response.use(
|
||||
(res: AxiosResponse) => {
|
||||
// 加密后的 AES 秘钥
|
||||
const keyStr = res.headers[encryptHeader];
|
||||
// 加密
|
||||
if (keyStr != null && keyStr != '') {
|
||||
const data = res.data;
|
||||
// 请求体 AES 解密
|
||||
const base64Str = decrypt(keyStr);
|
||||
// base64 解码 得到请求头的 AES 秘钥
|
||||
const aesKey = decryptBase64(base64Str.toString());
|
||||
// aesKey 解码 data
|
||||
const decryptData = decryptWithAes(data, aesKey);
|
||||
// 将结果 (得到的是 JSON 字符串) 转为 JSON
|
||||
res.data = JSON.parse(decryptData);
|
||||
}
|
||||
// 未设置状态码则默认成功状态
|
||||
const code = res.data.code || HttpStatus.SUCCESS;
|
||||
// 获取错误信息
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* @module initWebSocket 初始化
|
||||
* @module websocketonopen 连接成功
|
||||
* @module websocketonerror 连接失败
|
||||
* @module websocketclose 断开连接
|
||||
* @module resetHeart 重置心跳
|
||||
* @module sendSocketHeart 心跳发送
|
||||
* @module reconnect 重连
|
||||
* @module sendMsg 发送数据
|
||||
* @module websocketonmessage 接收数据
|
||||
* @module test 测试收到消息传递
|
||||
* @description socket 通信
|
||||
* @param {any} url socket地址
|
||||
* @param {any} websocket websocket 实例
|
||||
* @param {any} heartTime 心跳定时器实例
|
||||
* @param {number} socketHeart 心跳次数
|
||||
* @param {number} HeartTimeOut 心跳超时时间
|
||||
* @param {number} socketError 错误次数
|
||||
*/
|
||||
|
||||
import { getToken } from '@/utils/auth';
|
||||
import useNoticeStore from '@/store/modules/notice';
|
||||
import { ElNotification } from "element-plus";
|
||||
|
||||
const { addNotice } = useNoticeStore();
|
||||
|
||||
let socketUrl: any = ''; // socket地址
|
||||
let websocket: any = null; // websocket 实例
|
||||
let heartTime: any = null; // 心跳定时器实例
|
||||
let socketHeart = 0 as number; // 心跳次数
|
||||
const HeartTimeOut = 10000; // 心跳超时时间 10000 = 10s
|
||||
let socketError = 0 as number; // 错误次数
|
||||
|
||||
// 初始化socket
|
||||
export const initWebSocket = (url: any) => {
|
||||
if (import.meta.env.VITE_APP_WEBSOCKET === 'false') {
|
||||
return;
|
||||
}
|
||||
socketUrl = url;
|
||||
// 初始化 websocket
|
||||
websocket = new WebSocket(url + '?Authorization=Bearer ' + getToken() + '&clientid=' + import.meta.env.VITE_APP_CLIENT_ID);
|
||||
websocketonopen();
|
||||
websocketonmessage();
|
||||
websocketonerror();
|
||||
websocketclose();
|
||||
sendSocketHeart();
|
||||
return websocket;
|
||||
};
|
||||
|
||||
// socket 连接成功
|
||||
export const websocketonopen = () => {
|
||||
websocket.onopen = function () {
|
||||
console.log('连接 websocket 成功');
|
||||
resetHeart();
|
||||
};
|
||||
};
|
||||
|
||||
// socket 连接失败
|
||||
export const websocketonerror = () => {
|
||||
websocket.onerror = function (e: any) {
|
||||
console.log('连接 websocket 失败', e);
|
||||
};
|
||||
};
|
||||
|
||||
// socket 断开链接
|
||||
export const websocketclose = () => {
|
||||
websocket.onclose = function (e: any) {
|
||||
console.log('断开连接', e);
|
||||
};
|
||||
};
|
||||
|
||||
// socket 重置心跳
|
||||
export const resetHeart = () => {
|
||||
socketHeart = 0;
|
||||
socketError = 0;
|
||||
clearInterval(heartTime);
|
||||
sendSocketHeart();
|
||||
};
|
||||
|
||||
// socket心跳发送
|
||||
export const sendSocketHeart = () => {
|
||||
heartTime = setInterval(() => {
|
||||
// 如果连接正常则发送心跳
|
||||
if (websocket.readyState == 1) {
|
||||
// if (socketHeart <= 30) {
|
||||
websocket.send(
|
||||
JSON.stringify({
|
||||
type: 'ping'
|
||||
})
|
||||
);
|
||||
socketHeart = socketHeart + 1;
|
||||
} else {
|
||||
// 重连
|
||||
reconnect();
|
||||
}
|
||||
}, HeartTimeOut);
|
||||
};
|
||||
|
||||
// socket重连
|
||||
export const reconnect = () => {
|
||||
if (socketError <= 2) {
|
||||
clearInterval(heartTime);
|
||||
initWebSocket(socketUrl);
|
||||
socketError = socketError + 1;
|
||||
// eslint-disable-next-line prettier/prettier
|
||||
console.log('socket重连', socketError);
|
||||
} else {
|
||||
// eslint-disable-next-line prettier/prettier
|
||||
console.log('重试次数已用完');
|
||||
clearInterval(heartTime);
|
||||
}
|
||||
};
|
||||
|
||||
// socket 发送数据
|
||||
export const sendMsg = (data: any) => {
|
||||
websocket.send(data);
|
||||
};
|
||||
|
||||
// socket 接收数据
|
||||
export const websocketonmessage = () => {
|
||||
websocket.onmessage = function (e: any) {
|
||||
if (e.data.indexOf('heartbeat') > 0) {
|
||||
resetHeart();
|
||||
}
|
||||
if (e.data.indexOf('ping') > 0) {
|
||||
return;
|
||||
}
|
||||
addNotice({
|
||||
message: e.data,
|
||||
read: false,
|
||||
time: new Date().toLocaleString()
|
||||
});
|
||||
ElNotification({
|
||||
title: '消息',
|
||||
message: e.data,
|
||||
type: 'success',
|
||||
duration: 3000
|
||||
})
|
||||
return e.data;
|
||||
};
|
||||
};
|
||||
@@ -5,19 +5,19 @@
|
||||
<el-card shadow="hover">
|
||||
<el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
|
||||
<el-form-item label="部门id" prop="deptId">
|
||||
<el-input v-model="queryParams.deptId" placeholder="请输入部门id" clearable @keyup.enter="handleQuery" />
|
||||
<el-input v-model="queryParams.deptId" placeholder="请输入部门id" clearable style="width: 240px" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="用户id" prop="userId">
|
||||
<el-input v-model="queryParams.userId" placeholder="请输入用户id" clearable @keyup.enter="handleQuery" />
|
||||
<el-input v-model="queryParams.userId" placeholder="请输入用户id" clearable style="width: 240px" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序号" prop="orderNum">
|
||||
<el-input v-model="queryParams.orderNum" placeholder="请输入排序号" clearable @keyup.enter="handleQuery" />
|
||||
<el-input v-model="queryParams.orderNum" placeholder="请输入排序号" clearable style="width: 240px" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="key键" prop="testKey">
|
||||
<el-input v-model="queryParams.testKey" placeholder="请输入key键" clearable @keyup.enter="handleQuery" />
|
||||
<el-input v-model="queryParams.testKey" placeholder="请输入key键" clearable style="width: 240px" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="值" prop="value">
|
||||
<el-input v-model="queryParams.value" placeholder="请输入值" clearable @keyup.enter="handleQuery" />
|
||||
<el-input v-model="queryParams.value" placeholder="请输入值" clearable style="width: 240px" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<el-card shadow="hover">
|
||||
<el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
|
||||
<el-form-item label="树节点名" prop="treeName">
|
||||
<el-input v-model="queryParams.treeName" placeholder="请输入树节点名" clearable @keyup.enter="handleQuery" />
|
||||
<el-input v-model="queryParams.treeName" placeholder="请输入树节点名" clearable style="width: 240px" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
|
||||
+8
-2
@@ -33,7 +33,7 @@
|
||||
* 部署方式 Docker 容器编排 一键部署业务集群<br />
|
||||
* 国际化 SpringMessage Spring标准国际化方案<br />
|
||||
</p>
|
||||
<p><b>当前版本:</b> <span>v5.1.0</span></p>
|
||||
<p><b>当前版本:</b> <span>v5.1.2</span></p>
|
||||
<p>
|
||||
<el-tag type="danger">¥免费开源</el-tag>
|
||||
</p>
|
||||
@@ -78,7 +78,7 @@
|
||||
* 分布式监控 Prometheus、Grafana 全方位性能监控<br />
|
||||
* 其余与 Vue 版本一致<br />
|
||||
</p>
|
||||
<p><b>当前版本:</b> <span>v2.1.0</span></p>
|
||||
<p><b>当前版本:</b> <span>v2.1.2</span></p>
|
||||
<p>
|
||||
<el-tag type="danger">¥免费开源</el-tag>
|
||||
</p>
|
||||
@@ -96,6 +96,12 @@
|
||||
</template>
|
||||
|
||||
<script setup name="Index" lang="ts">
|
||||
import { initWebSocket } from '@/utils/websocket';
|
||||
|
||||
onMounted(() => {
|
||||
let protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://'
|
||||
initWebSocket(protocol + window.location.host + import.meta.env.VITE_APP_BASE_API + "/resource/websocket");
|
||||
});
|
||||
|
||||
const goTarget = (url:string) => {
|
||||
window.open(url, '__blank')
|
||||
|
||||
@@ -125,6 +125,7 @@ const handleLogin = () => {
|
||||
const [err] = await to(userStore.login(loginForm.value));
|
||||
if (!err) {
|
||||
await router.push({ path: redirect.value || '/' });
|
||||
loading.value = false;
|
||||
} else {
|
||||
loading.value = false;
|
||||
// 重新获取验证码
|
||||
|
||||
@@ -76,6 +76,12 @@
|
||||
sortable="custom"
|
||||
:sort-orders="['descending', 'ascending']"
|
||||
/>
|
||||
<el-table-column label="客户端" align="center" prop="clientKey" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="设备类型" align="center">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="sys_device_type" :value="scope.row.deviceType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="地址" align="center" prop="ipaddr" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="登录地点" align="center" prop="loginLocation" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="操作系统" align="center" prop="os" :show-overflow-tooltip="true" />
|
||||
@@ -103,6 +109,7 @@ import { list, delLoginInfo, cleanLoginInfo, unlockLoginInfo } from "@/api/monit
|
||||
import { LoginInfoQuery, LoginInfoVO } from "@/api/monitor/loginInfo/types";
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { sys_device_type } = toRefs<any>(proxy?.useDict("sys_device_type"));
|
||||
const { sys_common_status } = toRefs<any>(proxy?.useDict("sys_common_status"));
|
||||
|
||||
const loginInfoList = ref<LoginInfoVO[]>([]);
|
||||
|
||||
@@ -29,6 +29,12 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="会话编号" align="center" prop="tokenId" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="登录名称" align="center" prop="userName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="客户端" align="center" prop="clientKey" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="设备类型" align="center">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="sys_device_type" :value="scope.row.deviceType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="所属部门" align="center" prop="deptName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="主机" align="center" prop="ipaddr" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="登录地点" align="center" prop="loginLocation" :show-overflow-tooltip="true" />
|
||||
@@ -57,8 +63,11 @@
|
||||
<script setup name="Online" lang="ts">
|
||||
import { forceLogout, list as initData } from "@/api/monitor/online";
|
||||
import { OnlineQuery, OnlineVO } from "@/api/monitor/online/types";
|
||||
import api from "@/api/system/user";
|
||||
import {to} from "await-to-js";
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { sys_device_type } = toRefs<any>(proxy?.useDict("sys_device_type"));
|
||||
|
||||
const onlineList = ref<OnlineVO[]>([]);
|
||||
const loading = ref(true);
|
||||
@@ -93,11 +102,13 @@ const resetQuery = () => {
|
||||
}
|
||||
/** 强退按钮操作 */
|
||||
const handleForceLogout = async (row: OnlineVO) => {
|
||||
await proxy?.$modal.confirm('是否确认强退名称为"' + row.userName + '"的用户?');
|
||||
const [err] = await to(proxy?.$modal.confirm('是否确认强退名称为"' + row.userName + '"的用户?') as any);
|
||||
if (!err) {
|
||||
await forceLogout(row.tokenId);
|
||||
await getList();
|
||||
proxy?.$modal.msgSuccess("删除成功");
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
<div class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
|
||||
<el-form-item label="操作地址" prop="operIp">
|
||||
<el-input v-model="queryParams.operIp" placeholder="请输入操作地址" clearable style="width: 240px;" @keyup.enter="handleQuery"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="系统模块" prop="title">
|
||||
<el-input v-model="queryParams.title" placeholder="请输入系统模块" clearable style="width: 240px;" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
@@ -83,7 +86,8 @@
|
||||
sortable="custom"
|
||||
:sort-orders="['descending', 'ascending']"
|
||||
/>
|
||||
<el-table-column label="主机" align="center" prop="operIp" width="130" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="部门" align="center" prop="deptName" width="130" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="操作地址" align="center" prop="operIp" width="130" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="操作状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="sys_common_status" :value="scope.row.status" />
|
||||
@@ -122,13 +126,14 @@
|
||||
<el-dialog title="操作日志详细" v-model="dialog.visible" width="700px" append-to-body>
|
||||
<el-form :model="form" label-width="100px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="操作模块:">{{ form.title }} / {{ typeFormat(form) }}</el-form-item>
|
||||
<el-form-item label="登录信息:">{{ form.operName }} / {{ form.operIp }} / {{ form.operLocation }}</el-form-item>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="登录信息:">{{ form.operName }} / {{form.deptName}} / {{ form.operIp }} / {{ form.operLocation }}</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="请求地址:">{{ form.operUrl }}</el-form-item>
|
||||
<el-form-item label="请求方式:">{{ form.requestMethod }}</el-form-item>
|
||||
<el-form-item label="请求信息:">{{ form.requestMethod }} {{ form.operUrl }}</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="操作模块:">{{ form.title }} / {{ typeFormat(form) }}</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="操作方法:">{{ form.method }}</el-form-item>
|
||||
@@ -215,6 +220,7 @@ const data = reactive<PageData<OperLogForm, OperLogQuery>>({
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
operIp: '',
|
||||
title: '',
|
||||
operName: '',
|
||||
businessType: '',
|
||||
|
||||
@@ -105,12 +105,12 @@ const registerRules: ElFormRules = {
|
||||
const codeUrl = ref("");
|
||||
const loading = ref(false);
|
||||
const captchaEnabled = ref(true);
|
||||
const registerRef = ref(ElForm);
|
||||
const registerRef = ref<ElFormInstance>();
|
||||
// 租户列表
|
||||
const tenantList = ref<TenantVO[]>([]);
|
||||
|
||||
const handleRegister = () => {
|
||||
registerRef.value.validate(async (valid: boolean) => {
|
||||
registerRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
loading.value = true;
|
||||
const [err] = await to(register(registerForm.value));
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
<div class="search" v-show="showSearch">
|
||||
<el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="100px">
|
||||
<el-form-item label="客户端key" prop="clientKey">
|
||||
<el-input v-model="queryParams.clientKey" placeholder="请输入客户端key" clearable @keyup.enter="handleQuery" />
|
||||
<el-input v-model="queryParams.clientKey" placeholder="请输入客户端key" clearable style="width: 240px" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="客户端秘钥" prop="clientSecret">
|
||||
<el-input v-model="queryParams.clientSecret" placeholder="请输入客户端秘钥" clearable @keyup.enter="handleQuery" />
|
||||
<el-input v-model="queryParams.clientSecret" placeholder="请输入客户端秘钥" clearable style="width: 240px" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="状态" clearable>
|
||||
@@ -53,11 +53,7 @@
|
||||
<el-table-column label="客户端秘钥" align="center" prop="clientSecret" />
|
||||
<el-table-column label="授权类型" align="center">
|
||||
<template #default="scope">
|
||||
<div>
|
||||
<template v-for="(type, index) in scope.row.grantTypeList" :key="index">
|
||||
<dict-tag class="el-check-tag" :options="sys_grant_type" :value="type" />
|
||||
</template>
|
||||
</div>
|
||||
<dict-tag :options="sys_grant_type" :value="scope.row.grantTypeList" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="设备类型" align="center">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
|
||||
<el-form-item label="部门名称" prop="deptName">
|
||||
<el-input v-model="queryParams.deptName" placeholder="请输入部门名称" clearable @keyup.enter="handleQuery" />
|
||||
<el-input v-model="queryParams.deptName" placeholder="请输入部门名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="部门状态" clearable>
|
||||
@@ -70,7 +70,7 @@
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" destroy-on-close append-to-bod width="600px">
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" destroy-on-close append-to-body width="600px">
|
||||
<el-form ref="deptFormRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-row>
|
||||
<el-col :span="24" v-if="form.parentId !== 0">
|
||||
|
||||
@@ -49,9 +49,8 @@
|
||||
<el-table-column label="字典编码" align="center" prop="dictCode" v-if="false" />
|
||||
<el-table-column label="字典标签" align="center" prop="dictLabel">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.listClass === '' || scope.row.listClass === 'default'">{{ scope.row.dictLabel }}</span>
|
||||
<el-tag v-else :type="scope.row.listClass === 'primary' ? '' : scope.row.listClass">{{ scope.row.dictLabel
|
||||
}}</el-tag>
|
||||
<span v-if="(scope.row.listClass === '' || scope.row.listClass === 'default') && (scope.row.cssClass === '' || scope.row.cssClass == null)">{{ scope.row.dictLabel }}</span>
|
||||
<el-tag v-else :type="(scope.row.listClass === 'primary' || scope.row.listClass === 'default') ? '' : scope.row.listClass" :class="scope.row.cssClass">{{ scope.row.dictLabel }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="字典键值" align="center" prop="dictValue" />
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
|
||||
<el-form-item label="菜单名称" prop="menuName">
|
||||
<el-input v-model="queryParams.menuName" placeholder="请输入菜单名称" clearable @keyup.enter="handleQuery" />
|
||||
<el-input v-model="queryParams.menuName" placeholder="请输入菜单名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="菜单状态" clearable>
|
||||
@@ -169,7 +169,7 @@
|
||||
<el-input v-model="form.perms" placeholder="请输入权限标识" maxlength="100" />
|
||||
<template #label>
|
||||
<span>
|
||||
<el-tooltip content="控制器中定义的权限字符,如:@PreAuthorize(`@ss.hasPermi('system:user:list')`)" placement="top">
|
||||
<el-tooltip content="控制器中定义的权限字符,如:@SaCheckPermission('system:user:list')" placement="top">
|
||||
<el-icon>
|
||||
<question-filled />
|
||||
</el-icon>
|
||||
@@ -229,7 +229,7 @@
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" v-if="form.menuType !== 'F'">
|
||||
<el-col :span="12">
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<span>
|
||||
|
||||
@@ -29,13 +29,13 @@
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:oss:add']">新增</el-button>
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:ossConfig:add']">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:oss:edit']">修改</el-button>
|
||||
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:ossConfig:edit']">修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:oss:remove']">
|
||||
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:ossConfig:remove']">
|
||||
删除
|
||||
</el-button>
|
||||
</el-col>
|
||||
@@ -67,10 +67,10 @@
|
||||
<el-table-column label="操作" fixed="right" align="center" width="150" class-name="small-padding">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:oss:edit']"></el-button>
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:ossConfig:edit']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:oss:remove']"></el-button>
|
||||
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:ossConfig:remove']"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
@@ -194,7 +194,7 @@ const { queryParams, form, rules } = toRefs(data);
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
const res = await proxy?.getConfigKey("sys.oss.previewListResource");
|
||||
previewListResource.value = res?.msg === undefined ? true : res.msg === 'true';
|
||||
previewListResource.value = res?.data === undefined ? true : res.data === 'true';
|
||||
const response = await listOss(proxy?.addDateRange(queryParams.value, dateRangeCreateTime.value, "CreateTime"));
|
||||
ossList.value = response.rows;
|
||||
total.value = response.total;
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
<el-form-item prop="roleKey">
|
||||
<template #label>
|
||||
<span>
|
||||
<el-tooltip content="控制器中定义的权限字符,如:@PreAuthorize(`@ss.hasRole('admin')`)" placement="top">
|
||||
<el-tooltip content="控制器中定义的权限字符,如:@SaCheckRole('admin')" placement="top">
|
||||
<el-icon><question-filled /></el-icon>
|
||||
</el-tooltip>
|
||||
权限字符
|
||||
|
||||
@@ -320,7 +320,7 @@ const total = ref(0);
|
||||
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
|
||||
const deptName = ref('');
|
||||
const deptOptions = ref<DeptVO[]>([]);
|
||||
const initPassword = ref('123456');
|
||||
const initPassword = ref<String>('');
|
||||
const postOptions = ref<PostVO[]>([]);
|
||||
const roleOptions = ref<RoleVO[]>([]);
|
||||
/*** 用户导入参数 */
|
||||
@@ -340,13 +340,13 @@ const upload = reactive<ImportOption>({
|
||||
})
|
||||
// 列显隐信息
|
||||
const columns = ref<FieldOption[]>([
|
||||
{ key: 0, label: `用户编号`, visible: false },
|
||||
{ key: 1, label: `用户名称`, visible: true },
|
||||
{ key: 2, label: `用户昵称`, visible: true },
|
||||
{ key: 3, label: `部门`, visible: true },
|
||||
{ key: 4, label: `手机号码`, visible: true },
|
||||
{ key: 5, label: `状态`, visible: true },
|
||||
{ key: 6, label: `创建时间`, visible: true }
|
||||
{ key: 0, label: `用户编号`, visible: false,children: [] },
|
||||
{ key: 1, label: `用户名称`, visible: true,children: [] },
|
||||
{ key: 2, label: `用户昵称`, visible: true,children: [] },
|
||||
{ key: 3, label: `部门`, visible: true,children: [] },
|
||||
{ key: 4, label: `手机号码`, visible: true,children: [] },
|
||||
{ key: 5, label: `状态`, visible: true,children: [] },
|
||||
{ key: 6, label: `创建时间`, visible: true,children: [] }
|
||||
])
|
||||
|
||||
|
||||
@@ -561,7 +561,7 @@ const handleAdd = async () => {
|
||||
await initTreeData();
|
||||
postOptions.value = data.posts;
|
||||
roleOptions.value = data.roles;
|
||||
form.value.password = initPassword.value;
|
||||
form.value.password = initPassword.value.toString();
|
||||
}
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: UserForm) => {
|
||||
@@ -613,6 +613,9 @@ const resetForm = () => {
|
||||
onMounted(() => {
|
||||
getTreeSelect() // 初始化部门数据
|
||||
getList() // 初始化列表数据
|
||||
proxy?.getConfigKey("sys.user.initPassword").then(response => {
|
||||
initPassword.value = response.data;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<div id="git-user-binding">
|
||||
<h4 class="provider-desc">你可以绑定以下第三方帐号</h4>
|
||||
<div id="authlist" class="user-bind">
|
||||
<a class="third-app" href="#" @click="authUrl('wechar');" title="使用 微信 账号授权登录">
|
||||
<a class="third-app" href="#" @click="authUrl('wechat');" title="使用 微信 账号授权登录">
|
||||
<div class="git-other-login-icon">
|
||||
<svg-icon icon-class="wechat" />
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="user-info-head" @click="editCropper()">
|
||||
<img :src="options.img as string" title="点击上传头像" class="img-circle img-lg" />
|
||||
<img :src="options.img" title="点击上传头像" class="img-circle img-lg" />
|
||||
<el-dialog :title="title" v-model="open" width="800px" append-to-body @opened="modalOpened" @close="closeDialog">
|
||||
<el-row>
|
||||
<el-col :xs="24" :md="12" :style="{ height: '350px' }">
|
||||
@@ -62,7 +62,7 @@ import { uploadAvatar } from "@/api/system/user";
|
||||
import useUserStore from "@/store/modules/user";
|
||||
|
||||
interface Options {
|
||||
img: string | ArrayBuffer | null; // 裁剪图片的地址
|
||||
img: string | any; // 裁剪图片的地址
|
||||
autoCrop: boolean; // 是否默认生成截图框
|
||||
autoCropWidth: number; // 默认生成截图框宽度
|
||||
autoCropHeight: number; // 默认生成截图框高度
|
||||
@@ -140,7 +140,7 @@ const uploadImg = async () => {
|
||||
const res = await uploadAvatar(formData);
|
||||
open.value = false;
|
||||
options.img = res.data.imgUrl;
|
||||
userStore.avatar = options.img as string
|
||||
userStore.setAvatar(options.img as string)
|
||||
proxy?.$modal.msgSuccess("修改成功");
|
||||
visible.value = false;
|
||||
});
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<template>
|
||||
<div>表单构建 <svg-icon icon-class="build" /></div>
|
||||
<div>表单构建(由于此功能的开源组件不支持 VUE3+TS 故暂时无法使用) <svg-icon icon-class="build" /></div>
|
||||
</template>
|
||||
|
||||
@@ -97,8 +97,8 @@
|
||||
<el-tabs v-model="preview.activeName">
|
||||
<el-tab-pane
|
||||
v-for="(value, key) in preview.data"
|
||||
:label="(key as any).substring((key as any).lastIndexOf('/') + 1, (key as any).indexOf('.vm'))"
|
||||
:name="(key as any).substring((key as any).lastIndexOf('/') + 1, (key as any).indexOf('.vm'))"
|
||||
:label="key.substring(key.lastIndexOf('/') + 1, key.indexOf('.vm'))"
|
||||
:name="key.substring(key.lastIndexOf('/') + 1, key.indexOf('.vm'))"
|
||||
:key="value"
|
||||
>
|
||||
<el-link :underline="false" icon="DocumentCopy" v-copyText="value" v-copyText:callback="copyTextSuccess" style="float:right">
|
||||
|
||||
@@ -28,6 +28,7 @@ export default defineConfig(({ mode, command }: ConfigEnv): UserConfig => {
|
||||
[env.VITE_APP_BASE_API]: {
|
||||
target: 'http://localhost:8080',
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
rewrite: (path) => path.replace(new RegExp('^' + env.VITE_APP_BASE_API), '')
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user