Merge branch 'refs/heads/main' into bonus-ai

This commit is contained in:
jiang 2024-11-20 15:11:12 +08:00
commit 98edaf421a
16 changed files with 413 additions and 44 deletions

View File

@ -60,7 +60,8 @@
"vue-meta": "2.4.0", "vue-meta": "2.4.0",
"vue-router": "3.4.9", "vue-router": "3.4.9",
"vuedraggable": "2.24.3", "vuedraggable": "2.24.3",
"vuex": "3.6.0" "vuex": "3.6.0",
"webstomp-client": "^1.2.6"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "4.4.6", "@vue/cli-plugin-babel": "4.4.6",

View File

@ -9,6 +9,7 @@
import ThemePicker from "@/components/ThemePicker"; import ThemePicker from "@/components/ThemePicker";
import { mapActions } from 'vuex' import { mapActions } from 'vuex'
import { get } from '@/utils/config' import { get } from '@/utils/config'
// import AlertNotification from "@/views/warning/AlertNotification.vue";
export default { export default {
name: "App", name: "App",

View File

@ -30,7 +30,7 @@ export function getSysLogs(data) {
//备份系统日志列表 //备份系统日志列表
export function downloadSysLogs(data) { export function downloadSysLogs(data) {
return request({ return request({
url: '/sys/sysLog/downloadSysLogs', url: '/system/sys/sysLog/downloadSysLogs',
method: 'get', method: 'get',
params: data, params: data,
responseType: 'blob' responseType: 'blob'
@ -92,6 +92,12 @@ export function getLogSize(data) {
}) })
} }
export function handleNoWarningLog(data) {
return request({
url: '/system/sys/sysLog/logWarn',
method: 'get'
})
}

View File

@ -25,10 +25,6 @@
<el-form-item label="确认密码" prop="confirmPassword"> <el-form-item label="确认密码" prop="confirmPassword">
<el-input v-model="user.confirmPassword" placeholder="请确认新密码" type="password" show-password/> <el-input v-model="user.confirmPassword" placeholder="请确认新密码" type="password" show-password/>
</el-form-item> </el-form-item>
<!-- <el-form-item>
<el-button type="primary" size="mini" @click="submit">保存</el-button>
<el-button type="danger" size="mini" @click="close">关闭</el-button>
</el-form-item>-->
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submit"> </el-button> <el-button type="primary" @click="submit"> </el-button>
@ -46,6 +42,8 @@ import { mapState } from 'vuex'
import variables from '@/assets/styles/variables.scss' import variables from '@/assets/styles/variables.scss'
import { validateNewPassword } from '@/utils/validate' import { validateNewPassword } from '@/utils/validate'
import { updateUserPwd, checkPasswordStatus } from '@/api/system/user' import { updateUserPwd, checkPasswordStatus } from '@/api/system/user'
import { handleNoWarningLog } from '@/api/system/log'
import {MessageBox} from "element-ui";
export default { export default {
name: 'Layout', name: 'Layout',
@ -78,7 +76,12 @@ export default {
{ required: true, message: '确认密码不能为空', trigger: 'blur' }, { required: true, message: '确认密码不能为空', trigger: 'blur' },
{ required: true, validator: equalToPassword, trigger: 'blur' } { required: true, validator: equalToPassword, trigger: 'blur' }
] ]
} },
socket: null,
wsUrl: JSON.parse(localStorage.getItem('systemConfig')).webSocketurl,//'ws://localhost:18082/ws', // WebSocket
isConnected: false, //
reconnectInterval: 5000 //
} }
}, },
components: { components: {
@ -97,7 +100,8 @@ export default {
sidebar: state => state.app.sidebar, sidebar: state => state.app.sidebar,
device: state => state.app.device, device: state => state.app.device,
needTagsView: state => state.settings.tagsView, needTagsView: state => state.settings.tagsView,
fixedHeader: state => state.settings.fixedHeader fixedHeader: state => state.settings.fixedHeader,
roles: state => state.user.roles,
}), }),
classObj() { classObj() {
return { return {
@ -113,6 +117,10 @@ export default {
}, },
created() { created() {
this.checkPasswordStatus() this.checkPasswordStatus()
if (this.roles.includes("audit") || this.roles.includes("systemAdmin")) {
this.connectWebSocket();
}
this.handleNoWarningLog()
}, },
methods: { methods: {
checkPasswordStatus() { checkPasswordStatus() {
@ -123,6 +131,13 @@ export default {
} }
}) })
}, },
handleNoWarningLog(){
handleNoWarningLog().then(response => {
})
},
handleClickOutside() { handleClickOutside() {
this.$store.dispatch('app/closeSideBar', { withoutAnimation: false }) this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
}, },
@ -140,7 +155,112 @@ export default {
this.$store.dispatch('LogOut').then(() => { this.$store.dispatch('LogOut').then(() => {
location.href = '/index'; location.href = '/index';
}) })
},
// WebSocket
connectWebSocket() {
if (this.socket) {
console.log("WebSocket 已连接");
return;
}
console.log("WebSocket URL:{}",this.wsUrl)
this.socket = new WebSocket(this.wsUrl);
// WebSocket
this.socket.onopen = () => {
console.log("WebSocket 连接成功");
this.isConnected = true;
};
//
this.socket.onmessage = (event) => {
console.log("收到消息:", event.data);
const warning = JSON.parse(event.data);
this.handleWarning(warning);
};
//
this.socket.onclose = () => {
console.log("WebSocket 连接已关闭");
this.isConnected = false;
this.socket = null;
//
this.reconnectWebSocket();
};
//
this.socket.onerror = (error) => {
console.error("WebSocket 错误:", error);
this.isConnected = false;
this.socket = null;
//
this.reconnectWebSocket();
};
},
// WebSocket
reconnectWebSocket() {
console.log("尝试重新连接 WebSocket...");
setTimeout(() => {
this.connectWebSocket();
}, this.reconnectInterval);
},
//
handleWarning(warning) {
console.log(warning)
let warningContent = '';
if (warning.operaUserName) {
warningContent += `<p><strong>操作人:</strong>${warning.operaUserName}</p>`;
}
if (warning.warningEvent) {
warningContent += `<p><strong>事件:</strong>${warning.warningEvent}</p>`;
}
if (warning.warningIp) {
warningContent += `<p><strong>IP</strong>${warning.warningIp}</p>`;
}
if (warning.operaTime) {
warningContent += `<p><strong>时间:</strong>${warning.operaTime}</p>`;
}
if (warningContent) {
MessageBox.alert(
warningContent,
'告警通知',
{
dangerouslyUseHTMLString: true,
confirmButtonText: '确认',
customClass: 'custom-message-box',
callback: () => {
this.notifyBackend(warning.warningId);
}
}
);
}
},
//
notifyBackend(warningId) {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
const message = {
warningId,
};
this.socket.send(warningId);
console.log(`已通知后端处理告警: ${warningId}`);
}
} }
},
beforeDestroy() {
// WebSocket
if (this.socket) {
this.socket.close();
}
} }
} }
</script> </script>

View File

@ -6,9 +6,8 @@ import errorCode from '@/utils/errorCode'
import { tansParams, blobValidate } from '@/utils/bonus' import { tansParams, blobValidate } from '@/utils/bonus'
import cache from '@/plugins/cache' import cache from '@/plugins/cache'
import { saveAs } from 'file-saver' import { saveAs } from 'file-saver'
import { encryptCBC, decryptCBC } from '@/utils/aescbc'
import { decryptWithSM4, encryptWithSM4, hashWithSM3AndSalt } from '@/utils/sm' import { decryptWithSM4, encryptWithSM4, hashWithSM3AndSalt } from '@/utils/sm'
const systemConfig = { const systemConfig = JSON.parse(localStorage.getItem('systemConfig')) || {
requestConfig: { encryptRequest: false, checkIntegrity: false, encryptResponse: false } requestConfig: { encryptRequest: false, checkIntegrity: false, encryptResponse: false }
}; };
@ -66,6 +65,8 @@ service.interceptors.request.use(config => {
if (contentType.includes('application/json') && typeof data !== 'undefined') { if (contentType.includes('application/json') && typeof data !== 'undefined') {
// 加密数据 // 加密数据
if (systemConfig.requestConfig.encryptRequest && encryptRequest) { if (systemConfig.requestConfig.encryptRequest && encryptRequest) {
console.log(data);
console.log(hashWithSM3AndSalt(data));
config.data = encryptWithSM4(data+"|"+hashWithSM3AndSalt(data)) config.data = encryptWithSM4(data+"|"+hashWithSM3AndSalt(data))
} }
} }

View File

@ -36,8 +36,7 @@ export function decryptWithSM2(encryptedText) {
* @returns {string} 加密后的密文Hex 编码格式 * @returns {string} 加密后的密文Hex 编码格式
*/ */
export function encryptWithSM4(plainText) { export function encryptWithSM4(plainText) {
const salt =SM_CONFIG.SM4_SALT return sm4.encrypt(plainText, SM_CONFIG.SM4_KEY,{ mode: 'cbc', padding: 'pkcs#5',iv:SM_CONFIG.SM4_SALT});
return sm4.encrypt(plainText, SM_CONFIG.SM4_KEY,{ mode: 'cbc', padding: 'pkcs#5',iv:salt})+salt;
} }
/** /**
@ -46,9 +45,6 @@ export function encryptWithSM4(plainText) {
* @returns {string} 解密后的明文 * @returns {string} 解密后的明文
*/ */
export function decryptWithSM4(cipherText){ export function decryptWithSM4(cipherText){
const length = cipherText.length; return SM4.decrypt(cipherText, SM_CONFIG.SM4_KEY,{ mode: 'cbc', padding: 'pkcs#5' ,iv:SM_CONFIG.SM4_SALT});
const salt = length > 32 ? cipherText.substring(length - 32) : cipherText;
const originalHex = length > 32 ? cipherText.substring(0, length - 32) : '';
return SM4.decrypt(originalHex, SM_CONFIG.SM4_KEY,{ mode: 'cbc', padding: 'pkcs#5' ,iv:salt});
} }

View File

@ -151,16 +151,16 @@
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body> <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px"> <el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="参数名称" prop="configName"> <el-form-item label="参数名称" prop="configName">
<el-input v-model="form.configName" placeholder="请输入参数名称" /> <el-input v-model="form.configName" placeholder="请输入参数名称" readonly />
</el-form-item> </el-form-item>
<el-form-item label="参数键名" prop="configKey"> <el-form-item label="参数键名" prop="configKey">
<el-input v-model="form.configKey" placeholder="请输入参数键名" /> <el-input v-model="form.configKey" placeholder="请输入参数键名" readonly />
</el-form-item> </el-form-item>
<el-form-item label="参数键值" prop="configValue"> <el-form-item label="参数键值" prop="configValue">
<el-input v-model="form.configValue" placeholder="请输入参数键值" /> <el-input v-model="form.configValue" placeholder="请输入参数键值" />
</el-form-item> </el-form-item>
<el-form-item label="系统内置" prop="configType"> <el-form-item label="系统内置" prop="configType">
<el-radio-group v-model="form.configType"> <el-radio-group v-model="form.configType" disabled>
<el-radio <el-radio
v-for="dict in dict.type.sys_yes_no" v-for="dict in dict.type.sys_yes_no"
:key="dict.value" :key="dict.value"
@ -300,6 +300,11 @@ export default {
submitForm: function() { submitForm: function() {
this.$refs["form"].validate(valid => { this.$refs["form"].validate(valid => {
if (valid) { if (valid) {
if (this.form.configKey === "sys.visit.tokentime" && (this.form.configValue <=0 || this.form.configValue > 30 ))
{
this.$modal.msgError("系统访问token有效期必须在0-30分钟之间");
return;
}
if (this.form.configId != undefined) { if (this.form.configId != undefined) {
updateConfig(this.form).then(response => { updateConfig(this.form).then(response => {
this.$modal.msgSuccess("修改成功"); this.$modal.msgSuccess("修改成功");

View File

@ -39,9 +39,9 @@
<el-button style="margin-left: 20px" class="filter-item" @click="resetFilter"> <el-button style="margin-left: 20px" class="filter-item" @click="resetFilter">
重置 重置
</el-button> </el-button>
<!-- <el-button @click="handleBackups" class="filter-item" style="margin-left: 20px" type="warning"> <el-button @click="handleBackups" class="filter-item" style="margin-left: 20px" type="warning">
备份 备份
</el-button> --> </el-button>
</div> </div>
<el-table <el-table

View File

@ -42,9 +42,9 @@
<el-button style="margin-left: 20px" class="filter-item" @click="resetFilter"> <el-button style="margin-left: 20px" class="filter-item" @click="resetFilter">
重置 重置
</el-button> </el-button>
<!-- <el-button @click="handleBackups" class="filter-item" style="margin-left: 20px" type="warning"> <el-button @click="handleBackups" class="filter-item" style="margin-left: 20px" type="warning">
备份 备份
</el-button> --> </el-button>
</div> </div>
<el-table <el-table

View File

@ -14,6 +14,7 @@
<el-radio-group v-model="listQuery.type" class="toptype"> <el-radio-group v-model="listQuery.type" class="toptype">
<el-radio label="1">日志类型</el-radio> <el-radio label="1">日志类型</el-radio>
<el-radio label="2">操作类型</el-radio> <el-radio label="2">操作类型</el-radio>
<el-radio label="3">操作人</el-radio>
</el-radio-group> </el-radio-group>
<el-button style="margin-left: 20px" class="filter-item" type="primary" @click="handleFilter"> <el-button style="margin-left: 20px" class="filter-item" type="primary" @click="handleFilter">
查询 查询

View File

@ -12,7 +12,7 @@
clearable clearable
style="width: 240px" style="width: 240px"
/> />
<span style="margin-left: 10px;">MB</span> <span style="margin-left: 10px;">GB</span>
</el-form-item> </el-form-item>

View File

@ -42,9 +42,9 @@
<el-button style="margin-left: 20px" class="filter-item" @click="resetFilter"> <el-button style="margin-left: 20px" class="filter-item" @click="resetFilter">
重置 重置
</el-button> </el-button>
<!-- <el-button @click="handleBackups" class="filter-item" style="margin-left: 20px" type="warning"> <el-button @click="handleBackups" class="filter-item" style="margin-left: 20px" type="warning">
备份 备份
</el-button> --> </el-button>
</div> </div>
<el-table <el-table
@ -125,7 +125,7 @@ export default {
listLoading: false, listLoading: false,
tableHeight: 650, tableHeight: 650,
operateList: operateList, operateList: operateList,
timeList:[{id:1,name:'时间'},{id:2,name:'操作人'},{id:3,name:'操作模块'},{id:4,name:'ip'},], timeList:[{id:1,name:'时间'},{id:2,name:'操作人'},{id:3,name:'操作模块'},{id:4,name:'ip'},{id:5,name:'操作类型'},],
sortList:[{id:1,name:'倒序'},{id:2,name:'升序'}], sortList:[{id:1,name:'倒序'},{id:2,name:'升序'}],
listQuery: { listQuery: {
pageNum: 1, pageNum: 1,

View File

@ -101,7 +101,7 @@
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row> </el-row>
<el-table v-loading="loading" :data="roleList" @selection-change="handleSelectionChange"> <el-table v-loading="loading" :data="roleList" @selection-change="handleSelectionChange" :selectable="checkSelectable" :row-class-name="getRowClassName">
<el-table-column type="selection" min-width="55" align="center"/> <el-table-column type="selection" min-width="55" align="center"/>
<el-table-column label="角色编号" prop="roleId" min-width="120" align="center"/> <el-table-column label="角色编号" prop="roleId" min-width="120" align="center"/>
<el-table-column label="角色名称" align="center" prop="roleName" :show-overflow-tooltip="true" min-width="150"/> <el-table-column label="角色名称" align="center" prop="roleName" :show-overflow-tooltip="true" min-width="150"/>
@ -123,7 +123,7 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope" v-if="scope.row.roleId !== 1"> <template slot-scope="scope" v-if="scope.row.roleId !== 1 && scope.row.isBuiltIn !== '0'">
<el-button <el-button
size="mini" size="mini"
type="text" type="text"
@ -476,10 +476,15 @@ export default {
}, },
// //
handleSelectionChange(selection) { handleSelectionChange(selection) {
this.ids = selection.map(item => item.roleId) //
this.single = selection.length != 1 const validSelection = selection.filter(row => this.checkSelectable(row));
this.multiple = !selection.length
}, // roleId
this.ids = validSelection.map(item => item.roleId);
//
this.single = validSelection.length !== 1;
this.multiple = !validSelection.length;
},
// //
handleCommand(command, row) { handleCommand(command, row) {
switch (command) { switch (command) {
@ -626,7 +631,33 @@ export default {
this.download('system/role/export', { this.download('system/role/export', {
...this.queryParams ...this.queryParams
}, `role_${new Date().getTime()}.xlsx`) }, `role_${new Date().getTime()}.xlsx`)
} },
//
checkSelectable(row) {
return !(row.roleId === 1 || row.isBuiltIn === '0');
},
getRowClassName(row) {
return !this.checkSelectable(row) ? 'disabled-row' : '';
},
} }
} }
</script> </script>
<style>
.disabled-row {
background-color: #f5f7fa !important;
color: #909399;
cursor: not-allowed;
}
.disabled-row .el-checkbox__input {
cursor: not-allowed !important;
}
.disabled-row:hover td {
background-color: #f5f7fa !important;
}
</style>

View File

@ -17,7 +17,7 @@
</el-form> </el-form>
<h4 class="form-header h4">角色信息</h4> <h4 class="form-header h4">角色信息</h4>
<el-table v-loading="loading" :row-key="getRowKey" @row-click="clickRow" ref="table" @selection-change="handleSelectionChange" :data="roles.slice((pageNum-1)*pageSize,pageNum*pageSize)"> <el-table v-loading="loading" :row-key="getRowKey" @row-click="clickRow" ref="table" @selection-change="handleSelectionChange" :data="filteredRoles.slice((pageNum - 1) * pageSize, pageNum * pageSize)">
<el-table-column label="序号" type="index" align="center"> <el-table-column label="序号" type="index" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<span>{{(pageNum - 1) * pageSize + scope.$index + 1}}</span> <span>{{(pageNum - 1) * pageSize + scope.$index + 1}}</span>
@ -113,5 +113,12 @@ export default {
this.$tab.closeOpenPage(obj); this.$tab.closeOpenPage(obj);
}, },
}, },
computed: {
filteredRoles() {
//
return this.roles.filter(role => role.roleId !== 1);
}
}
}; };
</script> </script>

View File

@ -82,7 +82,7 @@
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" :columns="columns"></right-toolbar> <right-toolbar :showSearch.sync="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
</el-row> </el-row>
<el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange"> <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange" :selectable="checkSelectable" :row-class-name="getRowClassName">
<el-table-column type="selection" width="50" align="center"/> <el-table-column type="selection" width="50" align="center"/>
<el-table-column label="用户编号" align="center" key="userId" prop="userId" v-if="columns[0].visible"/> <el-table-column label="用户编号" align="center" key="userId" prop="userId" v-if="columns[0].visible"/>
<el-table-column label="用户名称" align="center" key="userName" prop="userName" v-if="columns[1].visible" <el-table-column label="用户名称" align="center" key="userName" prop="userName" v-if="columns[1].visible"
@ -97,7 +97,11 @@
<el-table-column label="手机号码" align="center" key="phonenumber" prop="phonenumber" <el-table-column label="手机号码" align="center" key="phonenumber" prop="phonenumber"
v-if="columns[4].visible" v-if="columns[4].visible"
width="120" width="120"
/> >
<template slot-scope="scope">
<span>{{ hidePhone(scope.row.phonenumber) }}</span>
</template>
</el-table-column>
<el-table-column label="状态" align="center" key="status" v-if="columns[5].visible"> <el-table-column label="状态" align="center" key="status" v-if="columns[5].visible">
<template slot-scope="scope"> <template slot-scope="scope">
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" <el-switch v-model="scope.row.status" active-value="0" inactive-value="1"
@ -123,7 +127,7 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
<template slot-scope="scope" v-if="scope.row.userId !== 1"> <template slot-scope="scope" v-if="!hasSystemOrAuditrRole(scope.row.roles) && scope.row.userId !== 1 && scope.row.isBuiltIn !== '0'">
<el-button size="mini" type="text" icon="el-icon-edit" @click="confirmPassword(scope.row)" <el-button size="mini" type="text" icon="el-icon-edit" @click="confirmPassword(scope.row)"
v-hasPermi="['system:user:edit']" v-hasPermi="['system:user:edit']"
>修改 >修改
@ -244,7 +248,7 @@
:key="item.roleId" :key="item.roleId"
:label="item.roleName" :label="item.roleName"
:value="item.roleId" :value="item.roleId"
:disabled="item.status == 1" :disabled="item.status == 1 || item.roleId === 1"
></el-option> ></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
@ -470,6 +474,11 @@ export default {
}) })
}, },
methods: { methods: {
/* 手机号码脱敏 */
hidePhone(phone) {
if (!phone) return '';
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
},
/* 表单登录权限自定义校验 */ /* 表单登录权限自定义校验 */
validateLoginType(rule, value, callback) { validateLoginType(rule, value, callback) {
if (this.loginTypeArr.length > 0) { if (this.loginTypeArr.length > 0) {
@ -575,15 +584,20 @@ export default {
}, },
// //
handleSelectionChange(selection) { handleSelectionChange(selection) {
this.ids = selection.map(item => item.userId) //
this.single = selection.length != 1 const validSelection = selection.filter(row => this.checkSelectable(row));
this.multiple = !selection.length
// roleId
this.ids = validSelection.map(item => item.userId);
//
this.single = validSelection.length !== 1;
this.multiple = !validSelection.length;
}, },
// //
handleCommand(command, row) { handleCommand(command, row) {
switch (command) { switch (command) {
case 'handleResetPwd': case 'handleResetPwd':
this.handleResetPwd(row) this.confirmResetPwd(row)
break break
case 'handleAuthRole': case 'handleAuthRole':
this.handleAuthRole(row) this.handleAuthRole(row)
@ -653,6 +667,32 @@ export default {
}).catch(() => { }).catch(() => {
}) })
}, },
confirmResetPwd(row){
this.$prompt('请输入密码,鉴别用户', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
closeOnClickModal: false,
inputPattern: /^.{8,16}$/,
inputErrorMessage: '用户密码长度必须介于 8 和 16 之间',
inputValidator: (value) => {
// validateNewPassword
const errorMessage=function(error) {
if (error) {
return error.message;
} else {
console.log('验证通过');
}
};
validateNewPassword(null, value, errorMessage);
}
}).then(({ value }) => {
confirmPassword(value).then(response => {
this.$modal.msgSuccess('验证成功')
this.handleResetPwd(row)
})
}).catch(() => {
})
},
/** 重置密码按钮操作 */ /** 重置密码按钮操作 */
handleResetPwd(row) { handleResetPwd(row) {
this.$prompt('请输入"' + row.userName + '"的新密码', '提示', { this.$prompt('请输入"' + row.userName + '"的新密码', '提示', {
@ -747,7 +787,41 @@ export default {
// //
submitFileForm() { submitFileForm() {
this.$refs.upload.submit() this.$refs.upload.submit()
} },
hasSystemOrAuditrRole(roles) {
if (!roles || !Array.isArray(roles)) {
return false; // roles false
}
return roles.some(role => role.roleKey === 'systemAdmin' || role.roleKey === 'audit'); // roleKey admin
},
//
checkSelectable(row) {
return !(row.userId === 1 || row.isBuiltIn === '0' || this.hasSystemOrAuditrRole(row.roles));
},
getRowClassName(row) {
return !this.checkSelectable(row) ? 'disabled-row' : '';
},
} }
} }
</script> </script>
<style>
.disabled-row {
background-color: #f5f7fa !important;
color: #909399;
cursor: not-allowed;
}
.disabled-row .el-checkbox__input {
cursor: not-allowed !important;
}
.disabled-row:hover td {
background-color: #f5f7fa !important;
}
</style>

View File

@ -0,0 +1,126 @@
<template>
<div id="app">
<h2>告警系统</h2>
<p v-if="isConnected">WebSocket 已连接</p>
<p v-else>WebSocket 连接中...</p>
</div>
</template>
<script>
import { MessageBox } from 'element-ui';
export default {
data() {
return {
socket: null,
wsUrl: 'ws://localhost:18082/ws', // WebSocket
isConnected: false, //
reconnectInterval: 5000 //
};
},
created() {
// WebSocket
this.connectWebSocket();
},
methods: {
// WebSocket
connectWebSocket() {
if (this.socket) {
console.log("WebSocket 已连接");
return;
}
this.socket = new WebSocket(this.wsUrl);
// WebSocket
this.socket.onopen = () => {
console.log("WebSocket 连接成功");
this.isConnected = true;
};
//
this.socket.onmessage = (event) => {
console.log("收到消息:", event.data);
const warning = JSON.parse(event.data);
this.handleWarning(warning);
};
//
this.socket.onclose = () => {
console.log("WebSocket 连接已关闭");
this.isConnected = false;
this.socket = null;
//
this.reconnectWebSocket();
};
//
this.socket.onerror = (error) => {
console.error("WebSocket 错误:", error);
this.isConnected = false;
this.socket = null;
//
this.reconnectWebSocket();
};
},
// WebSocket
reconnectWebSocket() {
console.log("尝试重新连接 WebSocket...");
setTimeout(() => {
this.connectWebSocket();
}, this.reconnectInterval);
},
//
handleWarning(warning) {
const { warningEvent, warningContent, warningGrade, warningIp, warningTime } = warning;
const formattedTime = new Date(warningTime).toLocaleString();
//
MessageBox.alert(
`
<p><strong>事件</strong>${warningEvent}</p>
<p><strong>内容</strong>${warningContent}</p>
<p><strong>等级</strong>${warningGrade}</p>
<p><strong>IP</strong>${warningIp}</p>
<p><strong>时间</strong>${formattedTime}</p>
`,
'告警通知',
{
dangerouslyUseHTMLString: true,
confirmButtonText: '确认',
callback: () => {
this.notifyBackend(warning.warningId);
}
}
);
},
//
notifyBackend(warningId) {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
const message = {
warningId,
status: '1' // 1
};
this.socket.send(JSON.stringify(message));
console.log(`已通知后端处理告警: ${warningId}`);
}
}
},
beforeDestroy() {
// WebSocket
if (this.socket) {
this.socket.close();
}
}
};
</script>
<style scoped>
#app {
text-align: center;
margin-top: 50px;
}
</style>