Compare commits

..
Author SHA1 Message Date
疯狂的狮子Li 9e89ab5bae !57 发布 vue 版本 5.1.1 与 cloud 版本2.1.1
Merge pull request !57 from 疯狂的狮子Li/dev
2023-11-14 03:11:07 +00:00
疯狂的狮子Li 6e6062e824 😴发布 vue 版本 5.1.1 与 cloud 版本2.1.1 2023-11-14 10:51:12 +08:00
疯狂的狮子Li 35bebf403c update 适配 websocket在https下的连接方式 2023-11-13 13:02:09 +08:00
疯狂的狮子Li 0a8c33c3cb fix 修复 五级路由缓存无效问题 2023-11-10 15:59:09 +08:00
疯狂的狮子Li 8a7de17b58 fix 修复 内链iframe没有传递参数问题 2023-11-10 11:35:40 +08:00
疯狂的狮子Li f65eb2aee5 !55 绑定第三方帐号参数“wechar”更正为“wechat”
Merge pull request !55 from scmiot/dev
2023-11-07 01:30:47 +00:00
scmiot efb53d4bcb !1 绑定第三方帐号参数“wechar”更正为“wechat”
Merge pull request !1 from scmiot/N/A
2023-11-04 22:51:31 +00:00
scmiot ad2134f07d 绑定第三方帐号参数“wechar”更正为“wechat”
Signed-off-by: scmiot <10322731+scmiot@user.noreply.gitee.com>
2023-11-04 08:46:06 +00:00
疯狂的狮子Li caa9d0fcdc fix 修复 环境变量书写错误 2023-11-03 22:40:18 +08:00
疯狂的狮子Li b829ba4aa0 fix 修复 websocket 初始化无法连接进程死掉问题(vue代理问题) 2023-11-03 18:36:11 +08:00
疯狂的狮子Li a232437abc update 优化 !pr50 bug与代码实现 2023-11-02 14:00:48 +08:00
疯狂的狮子Li f2372d3243 update 优化 !pr50 bug与代码实现 2023-11-02 13:53:44 +08:00
三个三 a8a334b3c3 !50 add 新增 前端接入websocket接收消息
*  add 新增 前端接入websocket接收消息
2023-11-02 04:36:31 +00:00
疯狂的狮子Li 1e3f18ce22 !54 update 优化表格列的显示与隐藏小组件
Merge pull request !54 from bestrevens/dev
2023-11-01 04:09:35 +00:00
疯狂的狮子Li 631f338be2 fix 修复 用户注册缺失 clientid 问题 2023-11-01 10:46:42 +08:00
bestrevens bf977fe00a update 优化表格列的显示与隐藏小组件
优化表格列的显示与隐藏小组件,解决小组件之间距离间隔不一样的问题

Signed-off-by: bestrevens <201840026@qq.com>
2023-10-31 08:54:22 +00:00
疯狂的狮子Li 5bc7044032 update 优化 补全操作日志部门数据 2023-10-27 22:44:05 +08:00
疯狂的狮子Li 6ad0ed5316 update 优化 去掉多余的参数 2023-10-26 17:02:12 +08:00
疯狂的狮子Li ccb2ecc5ee update 更新 表单构建不能使用说明 2023-10-26 17:01:38 +08:00
疯狂的狮子Li ec0cb472df update 优化 去掉多余的参数 2023-10-25 23:05:32 +08:00
疯狂的狮子Li cd0a459c2b update 更新 表单构建不能使用说明 2023-10-25 16:21:21 +08:00
疯狂的狮子Li 18fea86a2c !53 更新登录日志以及在线用户展示信息
Merge pull request !53 from MichelleChung/dev
2023-10-23 12:46:48 +00:00
Michelle.Chung 66bfff450c update 更新 logininfor/index, online/index 字段显示 (客户端, 设备类型) ; 2023-10-23 20:32:38 +08:00
疯狂的狮子Li ec34c759ec !52 fix 优化表格列的显示与隐藏小组件不显示tip和浏览器告警
Merge pull request !52 from bestrevens/ts
2023-10-16 07:36:23 +00:00
bestrevens c1c5ea5e9d fix 优化表格列的显示与隐藏小组件不显示字段和告警
fix 优化表格列的显示与隐藏小组件不显示字段和告警

Signed-off-by: bestrevens <201840026@qq.com>
2023-10-16 07:30:14 +00:00
疯狂的狮子Li 6fe269efc7 fix 修复 !pr47 导致的打包异常 2023-10-16 15:13:27 +08:00
疯狂的狮子Li 920e079727 update 优化 富文本Editor组件检验图片格式 2023-10-09 12:00:15 +08:00
疯狂的狮子Li a2f13185e1 fix 修复 HeaderSearch组件跳转query参数丢失问题 2023-10-09 11:57:22 +08:00
疯狂的狮子Li 078bb50130 update vue-quill 1.1.0 => 1.2.0 2023-10-09 11:47:29 +08:00
疯狂的狮子Li 4854d8df00 update 优化 操作日志列表新增IP地址查询 2023-10-09 11:46:53 +08:00
疯狂的狮子Li ecf062605d update 优化 菜单管理类型为按钮状态可选 2023-10-09 11:41:53 +08:00
疯狂的狮子Li b989b07121 update 优化 用户初始密码从参数配置查询 2023-10-09 11:19:32 +08:00
疯狂的狮子Li bf957f06a0 !51 update 优化 通过参数键名获取键值接口的返回体
Merge pull request !51 from David Wei/update/config_response
2023-10-08 08:56:00 +00:00
David Wei aed8c89ded update 优化 通过参数键名获取键值接口的返回体 2023-10-08 16:05:58 +08:00
疯狂的狮子Li 4085811d24 !48 修复#I8550V
Merge pull request !48 from ahaos/ts
2023-09-28 16:51:40 +00:00
ahao fe384a3020 fix #I8550V https://gitee.com/dromara/RuoYi-Vue-Plus/issues/I8550V 2023-09-29 00:06:57 +08:00
ahaos c6f3cd068e !47 优化表格列的显示与隐藏小组件
Merge pull request !47 from 森爷/ts
2023-09-28 14:15:14 +00:00
DESKTOP-IGCH756\PC 99a6b56233 优化表格列的显示与隐藏小组件 2023-09-28 14:58:34 +08:00
疯狂的狮子Li 8ed32775cb !44 fix 个人信息修改密码接口隐藏新旧密码参数明文
Merge pull request !44 from Bleachtred/ts
2023-09-26 08:06:46 +00:00
bleachtred 395db813b5 fix 个人信息修改密码接口,隐藏新旧密码参数明文 2023-09-25 15:29:32 +08:00
疯狂的狮子Li ab1a7204fa update 优化 页面输入框样式 2023-09-17 15:53:25 +08:00
疯狂的狮子Li 92b2b489a4 fix 修复 自定义字典样式不生效的问题 2023-09-14 17:22:36 +08:00
疯狂的狮子Li 956d2ad495 update 优化 !pr40 代码结构 2023-09-09 11:44:56 +08:00
疯狂的狮子Li cb6cbc4f40 !40 优化字典标签支持数组和多标签
Merge pull request !40 from 抓蛙师/ts
2023-09-09 03:42:40 +00:00
抓蛙师 14538ce399 优化字典标签支持数组和多标签 2023-09-08 18:35:17 +08:00
疯狂的狮子Li 6c16d023f4 fix 修复 login 页面 loading 未关闭问题 2023-09-08 12:28:09 +08:00
40 changed files with 643 additions and 257 deletions
+3
View File
@@ -23,3 +23,6 @@ VITE_APP_RSA_PUBLIC_KEY = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbf
# 客户端id
VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e'
# websocket 开关
VITE_APP_WEBSOCKET = true
+3
View File
@@ -26,3 +26,6 @@ VITE_APP_RSA_PUBLIC_KEY = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbf
# 客户端id
VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e'
# websocket 开关
VITE_APP_WEBSOCKET = true
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "ruoyi-vue-plus",
"version": "5.1.0",
"version": "5.1.1",
"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
View File
@@ -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
View File
@@ -1,4 +1,5 @@
export interface OperLogQuery extends PageQuery {
operIp: string;
title: string;
operName: string;
businessType: string;
+1 -1
View File
@@ -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'
+1 -1
View File
@@ -136,7 +136,7 @@ export const updateUserPwd = (oldPassword: string, newPassword: string) => {
headers: {
isEncrypt: true
},
params: data
data: data
});
};
-19
View File
@@ -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;
}
}
}
+18 -30
View File
@@ -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
@@ -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 (!Array.isArray(props.value)) {
if (props.options.some((v) => v.value == props.value)) {
return false;
}
return true;
if (props.options?.length == 0 || props.value === '' || props.value === null || typeof props.value === "undefined") return false
// 传入值为非数组
values.value.forEach(item => {
if (!props.options.some(v => v.value === item)) {
return true // 如果有未匹配项,将标志设置为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)) {
itemUnmatchArray.push(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);
+16 -24
View File
@@ -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: "等宽字体";
+119 -107
View File
@@ -1,6 +1,6 @@
<template>
<div :class="{ 'show': show }" class="header-search">
<svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
<svg-icon class-name="search-icon" icon-class="search" @click.stop="click"/>
<el-select
ref="headerSearchSelectRef"
v-model="search"
@@ -12,21 +12,22 @@
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>
<script setup lang="ts" name="HeaderSearch">
import Fuse from 'fuse.js';
import { getNormalPath } from '@/utils/ruoyi';
import { isHttp } from '@/utils/validate';
import {getNormalPath} from '@/utils/ruoyi';
import {isHttp} from '@/utils/validate';
import usePermissionStore from '@/store/modules/permission';
import { RouteOption } from 'vue-router';
import {RouteOption} from 'vue-router';
type Router = Array<{
path: string;
title: string[];
path: string;
title: string[];
}>
const search = ref('');
@@ -39,88 +40,99 @@ const router = useRouter();
const routes = computed(() => usePermissionStore().routes);
const click = () => {
show.value = !show.value
if (show.value) {
headerSearchSelectRef.value && headerSearchSelectRef.value.focus()
}
show.value = !show.value
if (show.value) {
headerSearchSelectRef.value && headerSearchSelectRef.value.focus()
}
};
const close = () => {
headerSearchSelectRef.value && headerSearchSelectRef.value.blur()
options.value = []
show.value = false
headerSearchSelectRef.value && headerSearchSelectRef.value.blur()
options.value = []
show.value = false
}
const change = (val: any) => {
const path = val.path;
if (isHttp(path)) {
// http(s):// 路径新窗口打开
const pindex = path.indexOf("http");
window.open(path.substr(pindex, path.length), "_blank");
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)
router.push(path)
}
search.value = ''
options.value = []
nextTick(() => {
show.value = false
})
}
search.value = ''
options.value = []
nextTick(() => {
show.value = false
})
}
const initFuse = (list: Router) => {
fuse.value = new Fuse(list, {
shouldSort: true,
threshold: 0.4,
location: 0,
distance: 100,
minMatchCharLength: 1,
keys: [{
name: 'title',
weight: 0.7
}, {
name: 'path',
weight: 0.3
}]
})
fuse.value = new Fuse(list, {
shouldSort: true,
threshold: 0.4,
location: 0,
distance: 100,
minMatchCharLength: 1,
keys: [{
name: 'title',
weight: 0.7
}, {
name: 'path',
weight: 0.3
}]
})
}
// Filter out the routes that can be displayed in the sidebar
// And generate the internationalized title
const generateRoutes = (routes: RouteOption[], basePath = '', prefixTitle: string[] = []) => {
let res: Router = []
routes.forEach(r => {
// skip hidden router
if (!r.hidden) {
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]
}
if (r.meta && r.meta.title) {
data.title = [...data.title, r.meta.title];
if (r.redirect !== 'noRedirect') {
// only push the routes with title
// special case: need to exclude parent router without redirect
res.push(data);
}
}
// recursive child routes
if (r.children) {
const tempRoutes = generateRoutes(r.children, data.path, data.title);
if (tempRoutes.length >= 1) {
res = [...res, ...tempRoutes];
}
}
let res: Router = []
routes.forEach(r => {
// skip hidden router
if (!r.hidden) {
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],
query: ''
}
if (r.meta && r.meta.title) {
data.title = [...data.title, r.meta.title];
if (r.redirect !== 'noRedirect') {
// only push the routes with title
// special case: need to exclude parent router without redirect
res.push(data);
}
})
return res;
}
if (r.query) {
data.query = r.query
}
// recursive child routes
if (r.children) {
const tempRoutes = generateRoutes(r.children, data.path, data.title);
if (tempRoutes.length >= 1) {
res = [...res, ...tempRoutes];
}
}
}
})
return res;
}
const querySearch = (query: string) => {
if (query !== '') {
options.value = fuse.value.search(query)
} else {
options.value = []
}
if (query !== '') {
options.value = fuse.value.search(query)
} else {
options.value = []
}
}
onMounted(() => {
searchPool.value = generateRoutes(routes.value);
searchPool.value = generateRoutes(routes.value);
})
// watchEffect(() => {
@@ -128,54 +140,54 @@ onMounted(() => {
// })
watch(show, (value) => {
if (value) {
document.body.addEventListener('click', close)
} else {
document.body.removeEventListener('click', close)
}
if (value) {
document.body.addEventListener('click', close)
} else {
document.body.removeEventListener('click', close)
}
})
watch(searchPool, (list) => {
initFuse(list)
initFuse(list)
})
</script>
<style lang="scss" scoped>
.header-search {
font-size: 0 !important;
font-size: 0 !important;
.search-icon {
cursor: pointer;
font-size: 18px;
vertical-align: middle;
.search-icon {
cursor: pointer;
font-size: 18px;
vertical-align: middle;
}
.header-search-select {
font-size: 18px;
transition: width 0.2s;
width: 0;
overflow: hidden;
background: transparent;
border-radius: 0;
display: inline-block;
vertical-align: middle;
:deep(.el-input__inner) {
border-radius: 0;
border: 0;
padding-left: 0;
padding-right: 0;
box-shadow: none !important;
border-bottom: 1px solid #d9d9d9;
vertical-align: middle;
}
}
&.show {
.header-search-select {
font-size: 18px;
transition: width 0.2s;
width: 0;
overflow: hidden;
background: transparent;
border-radius: 0;
display: inline-block;
vertical-align: middle;
:deep(.el-input__inner) {
border-radius: 0;
border: 0;
padding-left: 0;
padding-right: 0;
box-shadow: none !important;
border-bottom: 1px solid #d9d9d9;
vertical-align: middle;
}
}
&.show {
.header-search-select {
width: 210px;
margin-left: 10px;
}
width: 210px;
margin-left: 10px;
}
}
}
</style>
+34 -24
View File
@@ -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[]) {
props.columns?.forEach((item) => {
item.visible = !data.includes(item.key);
})
}
// 打开显隐列dialog
const showColumn = () => {
open.value = true;
// 更改数据列的显示和隐藏
function columnChange(...args: any[]) {
props.columns?.forEach((item) => {
item.visible = args[1].checkedKeys.includes(item.key);
})
}
// 显隐列初始默认隐藏列
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>
+1
View File
@@ -18,6 +18,7 @@ export default {
language: 'Language',
dashboard: 'Dashboard',
document: 'Document',
message: 'Message',
layoutSize: 'Layout Size',
selectTenant: 'Select Tenant',
layoutSetting: 'Layout Setting',
+1
View File
@@ -17,6 +17,7 @@ export default {
language: '语言',
dashboard: '首页',
document: '项目文档',
message: '消息',
layoutSize: '布局大小',
selectTenant: '选择租户',
layoutSetting: '布局设置',
+10 -2
View File
@@ -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>
+28
View File
@@ -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"
v-for="(child) in item.children as RouteOption[]"
:key="child.path"
: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
};
+3 -2
View File
@@ -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);
+134
View File
@@ -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>
+42
View File
@@ -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;
+4
View File
@@ -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
View File
@@ -69,6 +69,7 @@ interface ImportMetaEnv {
VITE_APP_ENV: string;
VITE_APP_RSA_PUBLIC_KEY: string;
VITE_APP_CLIENT_ID: string;
VITE_APP_WEBSOCKET: boolean;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
+1
View File
@@ -13,6 +13,7 @@ declare global {
key: number;
label: string;
visible: boolean;
children?: Array<FieldOption>;
}
/**
+141
View File
@@ -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) {
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 -5
View File
@@ -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>
+1 -1
View File
@@ -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
View File
@@ -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.1</span></p>
<p>
<el-tag type="danger">&yen;免费开源</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.1</span></p>
<p>
<el-tag type="danger">&yen;免费开源</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')
+1
View File
@@ -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;
// 重新获取验证码
+7
View File
@@ -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[]>([]);
+7
View File
@@ -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" />
@@ -59,6 +65,7 @@ import { forceLogout, list as initData } from "@/api/monitor/online";
import { OnlineQuery, OnlineVO } from "@/api/monitor/online/types";
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_device_type } = toRefs<any>(proxy?.useDict("sys_device_type"));
const onlineList = ref<OnlineVO[]>([]);
const loading = ref(true);
+12 -6
View File
@@ -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: '',
+3 -7
View File
@@ -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">
+1 -1
View File
@@ -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>
+2 -3
View File
@@ -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" :class="scope.row.cssClass">{{ scope.row.dictLabel }}</el-tag>
</template>
</el-table-column>
<el-table-column label="字典键值" align="center" prop="dictValue" />
+2 -2
View File
@@ -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>
@@ -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>
+1 -1
View File
@@ -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;
+12 -9
View File
@@ -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>
+1 -1
View File
@@ -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 -1
View File
@@ -1,3 +1,3 @@
<template>
<div>表单构建 <svg-icon icon-class="build" /></div>
<div>表单构建(由于此功能的开源组件不支持 VUE3+TS 故暂时无法使用) <svg-icon icon-class="build" /></div>
</template>
+1
View File
@@ -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), '')
}
}