websocket、字典、配置

This commit is contained in:
陈红丽 2024-12-18 13:25:38 +08:00
parent 62c0b86313
commit 6d16d72e3e
6 changed files with 368 additions and 127 deletions

View File

@ -5,24 +5,30 @@
<transition v-if="isLock && $route.name !== 'login'" name="slide-up">
<LockScreen />
</transition>
<global-websocket :uri="'/api/websocket/'+userInfo.id" @rollback="rollback" />
</template>
<script lang="ts" setup>
import { computed, onMounted, onUnmounted } from 'vue';
import { ConfigProvider } from 'ant-design-vue';
import { computed, onMounted, onUnmounted,defineAsyncComponent ,h } from 'vue';
import { ConfigProvider,notification } from 'ant-design-vue';
import { LockScreen } from '@/components/Lockscreen';
import { useLockscreenStore } from '@/store/modules/lockscreen';
import { useRoute } from 'vue-router';
import { useUserStore } from '@/store/modules/user';
import zhCN from 'ant-design-vue/es/locale/zh_CN';
import dayjs from 'dayjs';
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
import Watermark from '@/utils/wartermark';
import { initWebSocket,sendWebSocket } from '@/components/Websocket/index';
const GlobalWebsocket = defineAsyncComponent(() => import('@/components/Websocket/index.vue'));
import 'dayjs/locale/zh-cn';
dayjs.locale('zh-cn');
const route = useRoute();
const locale = zhCN;
const useLockscreen = useLockscreenStore();
const userStore = useUserStore();
const userInfo: object = userStore.getUserInfo || {};
const isLock = computed(() => useLockscreen.isLock);
const lockTime = computed(() => useLockscreen.lockTime);
const {getIsWaterMark} = useProjectSetting();
@ -45,7 +51,13 @@
}
}, 1000);
};
const rollback = (msg)=>{
notification.info({
message: '通知',
description: () =>
h('div', msg),
});
}
onMounted(() => {
if(getIsWaterMark.value) {
const waterText = import.meta.env.VITE_GLOB_APP_TITLE;

View File

@ -0,0 +1,155 @@
<template>
<div></div>
</template>
<script setup lang="ts" name="global-websocket">
import { reactive, ref, computed,onMounted,onUnmounted } from 'vue';
import { useUserStore } from '@/store/modules/user';
const emit = defineEmits(['rollback']);
const props = defineProps({
uri: {
type: String,
},
});
const state = reactive({
webSocket: ref(), // webSocket
lockReconnect: false, //
maxReconnect: 6, // -1
reconnectTime: 0, //
heartbeat: {
interval: 30 * 1000, //
timeout: 10 * 1000, //
pingTimeoutObj: ref(), //
pongTimeoutObj: ref(), //
pingMessage: JSON.stringify({ type: 'ping' }), //
},
});
const token = computed(() => {
return useUserStore().getToken;
});
const tenant = computed(() => {
return Session.getTenant();
});
onMounted(() => {
initWebSocket();
});
onUnmounted(() => {
state.webSocket.close();
clearTimeoutObj(state.heartbeat);
});
const initWebSocket = () => {
// ws
let host = window.location.host;
let wsUri =`${location.protocol === 'https:' ? 'wss' : 'ws'}://${host}${props.uri}`;
//
state.webSocket = new WebSocket(wsUri);
//
state.webSocket.onopen = onOpen;
//
state.webSocket.onerror = onError;
//
state.webSocket.onmessage = onMessage;
//
state.webSocket.onclose = onClose;
};
const reconnect = () => {
if (!token) {
return;
}
if (state.lockReconnect || (state.maxReconnect !== -1 && state.reconnectTime > state.maxReconnect)) {
return;
}
state.lockReconnect = true;
setTimeout(() => {
state.reconnectTime++;
//
initWebSocket();
state.lockReconnect = false;
}, 5000);
};
/**
* 清空定时器
*/
const clearTimeoutObj = (heartbeat: any) => {
heartbeat.pingTimeoutObj && clearTimeout(heartbeat.pingTimeoutObj);
heartbeat.pongTimeoutObj && clearTimeout(heartbeat.pongTimeoutObj);
};
/**
* 开启心跳
*/
const startHeartbeat = () => {
const webSocket = state.webSocket;
const heartbeat = state.heartbeat;
//
clearTimeoutObj(heartbeat);
//
heartbeat.pingTimeoutObj = setTimeout(() => {
//
if (webSocket.readyState === 1) {
//
webSocket.send(heartbeat.pingMessage);
//
heartbeat.pongTimeoutObj = setTimeout(() => {
webSocket.close();
}, heartbeat.timeout);
} else {
//
reconnect();
}
}, heartbeat.interval);
};
/**
* 连接成功事件
*/
const onOpen = () => {
//
startHeartbeat();
state.reconnectTime = 0;
};
/**
* 连接失败事件
* @param e
*/
const onError = () => {
//
reconnect();
};
/**
* 连接关闭事件
* @param e
*/
const onClose = () => {
//
reconnect();
};
/**
* 接收服务器推送的信息
* @param msgEvent
*/
const onMessage = (msgEvent: any) => {
//
startHeartbeat();
// if (msgEvent.data.indexOf('pong') > 0) {
// return;
// }
// let text =''
// try{
// text = JSON.parse(msgEvent.data);
// }catch(e){
// return
// }
emit('rollback', msgEvent.data);
};
</script>

View File

@ -1,10 +1,6 @@
import { h } from 'vue';
export const columns = [
{
title: 'ID',
dataIndex: 'id',
},
{
title: '配置名称',
dataIndex: 'name',
@ -17,4 +13,11 @@ export const columns = [
title: '排序',
dataIndex: 'sort',
},
{
title: '操作',
fixed: 'right',
dataIndex: 'action',
key: 'action',
width: 200,
},
];

View File

@ -1,9 +1,8 @@
<template>
<PageWrapper>
<a-row :gutter="10" class="mt-3">
<a-col :xs="24" :sm="24" :md="7" :lg="7" :xl="7">
<a-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
<a-card shadow="hover" class="border-0">
<template #title>
<a-space>
<a-input
type="text"
@ -13,10 +12,7 @@
/>
<a-button
type="primary"
@click="
pager.page = 1;
loadDataTable();
"
@click="reloadTable"
>
<template #icon> <SearchOutlined /> </template>查询
</a-button>
@ -29,37 +25,40 @@
</template>
新建
</a-button>
<a-button type="warning" @click="handleEdit" v-perm="['sys:config:edit']">
<template #icon> <EditOutlined /> </template>编辑
</a-button>
<a-button type="danger" @click="handleDelete()" v-perm="['sys:config:delete']">
<a-button type="danger" @click="handleDelete()" v-perm="['sys:config:delete']" :disabled="!selectionData.length">
<template #icon> <DeleteOutlined /> </template>删除
</a-button>
</a-space>
</div>
<BasicTable
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
:showTableSetting="false"
ref="tableRef"
:row-selection="{ onChange: onSelectionChange }"
:customRow="rowClick"
:row-class-name="setRowClassName"
:pagination="{showQuickJumper:false,showSizeChanger:false}"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<a-space>
<a-button type="primary" @click="handleEdit(record.id)" v-perm="['sys:dict:update']">
<template #icon><EditOutlined /></template>
编辑
</a-button>
<a-button type="primary" danger @click="handleDelete(record.id)" v-perm="['sys:dict:delete']">
<template #icon><DeleteOutlined /></template>
删除
</a-button>
</a-space>
</template>
</template>
<div :style="{ height: fwbHeight + 'px' }" class="dict-list-box">
<div
v-for="(item, index) in configDataList"
:key="index"
@click="onCheckedRow(item)"
class="dict-item"
:class="item.id == configId ? 'active' : ''"
>
<span class="t1"
>{{ item.name }}<span class="t2">({{ item.code }})</span></span
>
</div>
</div>
<pagination
style="justify-content: flex-end"
class="mt-10 flex"
@change="loadDataTable"
v-model="pager"
/>
</BasicTable>
</a-card>
</a-col>
<a-col :xs="24" :sm="24" :md="17" :lg="17" :xl="17">
<a-col :xs="24" :sm="24" :md="16" :lg="16" :xl="16">
<a-card shadow="hover" class="mb-4 border-0 proCard">
<configItem :configId="configId" v-if="configItemShow" />
</a-card>
@ -80,6 +79,7 @@
import { getConfigList, configDelete } from '@/api/data/config';
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons-vue';
import configItem from './configItem.vue';
import { columns } from './columns';
import { Modal, message } from 'ant-design-vue';
/**
@ -92,7 +92,9 @@
*/
const configId = ref(0);
const configItemShow = ref(false);
const tableRef = ref();
const editVisible = ref(false);
const selectionData = ref([]);
/**
* 定义查询参数
@ -100,17 +102,7 @@
const params = ref({
name: '',
});
const configDataList = ref([]);
/**
* 定义分页参数
*/
const pager = ref({
page: 1,
size: 10,
count: configDataList.value.length,
});
const fwbHeight = document.body.clientHeight - 370;
/**
* 执行添加
@ -124,7 +116,9 @@
/**
* 执行编辑
*/
const handleEdit = () => {
const handleEdit = async(id) => {
configId.value = id;
await nextTick();
editVisible.value = true;
};
@ -136,36 +130,61 @@
configId.value = row.id;
}
/**
* 刷新字典项值列表
*/
function reloadTable() {
tableRef.value.reload({ pageNo: 1 });
}
/**
* 加载数据列表
*/
const loadDataTable = async () => {
const result = await getConfigList({
...params.value,
pageNo: pager.value.page,
pageSize: pager.value.size,
});
configId.value = result?.records[0]?.id;
configItemShow.value = true;
configDataList.value = result.records;
pager.value.count = result.total;
const loadDataTable = async (res) => {
const result = await getConfigList({ ...params.value, ...res });
configId.value = result?.records[0]?.id
configItemShow.value = true
return result;
};
/**
* 执行删除
*/
async function handleDelete() {
async function handleDelete(id) {
Modal.confirm({
title: '提示',
content: '确定要删除?',
onOk: async () => {
configDelete(configId.value);
id ? await configDelete(id) : await configBatchDelete(selectionData.value);
message.success('删除成功');
pager.value.page = 1;
loadDataTable();
reloadTable();
},
});
}
/**
* 选项发生变化
* @param value 参数
*/
function onSelectionChange(value) {
selectionData.value = value;
}
/**
* 数据行点击事件
* @param record 参数
*/
const rowClick = (record) => {
return {
onClick: () => {
configId.value = record.id;
},
};
};
/**
* 设置选项行类名
* @param record 参数
*/
const setRowClassName = (record) => {
return record.id === configId.value ? 'clickRowStyle' : '';
};
/**
* 钩子函数
@ -220,3 +239,18 @@
}
}
</style>
<style lang="less">
.ant-table-tbody {
.ant-table-row.clickRowStyle,
.ant-table-cell-row.clickRowStyle {
td {
background-color: #e7eeff !important;
}
&:hover {
td {
background-color: #e7eeff !important;
}
}
}
}
</style>

View File

@ -1,10 +1,6 @@
import { h } from 'vue';
export const columns = [
{
title: 'ID',
dataIndex: 'id',
},
{
title: '字典名称',
dataIndex: 'name',
@ -13,4 +9,11 @@ export const columns = [
title: '字典编码',
dataIndex: 'code',
},
{
title: '操作',
fixed: 'right',
dataIndex: 'action',
key: 'action',
width: 200,
},
];

View File

@ -1,9 +1,8 @@
<template>
<PageWrapper>
<a-row :gutter="10" class="mt-3">
<a-col :xs="24" :sm="24" :md="7" :lg="7" :xl="7">
<a-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
<a-card shadow="hover" class="border-0">
<template #title>
<a-space>
<a-input
type="text"
@ -14,13 +13,14 @@
<a-button
type="primary"
@click="
pager.page = 1;
loadDataTable();
"
@click="reloadTable"
>
<template #icon> <SearchOutlined /> </template>查询
</a-button>
</a-space>
<div style="margin-top: 15px">
<a-space>
<a-button
type="primary"
@click="dictRefresh"
@ -28,46 +28,46 @@
>
<template #icon> <RedoOutlined /> </template>刷新缓存</a-button
>
</a-space>
<div style="margin-top: 15px">
<a-space>
<a-button type="primary" @click="handleAdd" v-perm="['sys:dict:add']">
<template #icon>
<PlusOutlined />
</template>
新建
</a-button>
<a-button type="warning" @click="handleEdit" v-perm="['sys:dict:edit']">
<template #icon> <EditOutlined /> </template>编辑
</a-button>
<a-button type="danger" @click="handleDelete()" v-perm="['sys:dict:delete']">
<a-button type="danger" @click="handleDelete()" v-perm="['sys:dict:delete']" :disabled="!selectionData.length">
<template #icon> <DeleteOutlined /> </template>删除
</a-button>
</a-space>
</div>
<BasicTable
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
:showTableSetting="false"
ref="tableRef"
:row-selection="{ onChange: onSelectionChange }"
:customRow="rowClick"
:row-class-name="setRowClassName"
:pagination="{showQuickJumper:false,showSizeChanger:false}"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<a-space>
<a-button type="primary" @click="handleEdit(record.id)" v-perm="['sys:dict:update']">
<template #icon><EditOutlined /></template>
编辑
</a-button>
<a-button type="primary" danger @click="handleDelete(record.id)" v-perm="['sys:dict:delete']">
<template #icon><DeleteOutlined /></template>
删除
</a-button>
</a-space>
</template>
</template>
<div :style="{ height: fwbHeight + 'px' }" class="dict-list-box">
<div
v-for="(item, index) in dictDataList"
:key="index"
@click="onCheckedRow(item)"
class="dict-item"
:class="item.id == dictId ? 'active' : ''"
>
<span class="t1"
>{{ item.name }}<span class="t2">({{ item.code }})</span></span
>
</div>
</div>
<pagination
style="justify-content: flex-end"
class="mt-10 flex"
@change="loadDataTable"
v-model="pager"
/>
</BasicTable>
</a-card>
</a-col>
<a-col :xs="24" :sm="24" :md="17" :lg="17" :xl="17">
<a-col :xs="24" :sm="24" :md="16" :lg="16" :xl="16">
<a-card shadow="hover" class="mb-4 border-0 proCard">
<dictItem :dictId="dictId" v-if="dictItemShow" />
</a-card>
@ -88,6 +88,7 @@
import { getDictList, refreshCache, dictDelete } from '@/api/data/dictionary';
import { PlusOutlined, EditOutlined, DeleteOutlined, RedoOutlined } from '@ant-design/icons-vue';
import dictItem from './dictItem.vue';
import { columns } from './columns';
import { Modal, message } from 'ant-design-vue';
/**
@ -100,7 +101,9 @@
*/
const dictId = ref(0);
const dictItemShow = ref(false);
const tableRef = ref();
const editVisible = ref(false);
const selectionData = ref([]);
/**
* 定义查询参数
@ -108,17 +111,6 @@
const params = ref({
name: '',
});
const dictDataList = ref([]);
/**
* 定义分页参数
*/
const pager = ref({
page: 1,
size: 20,
count: dictDataList.value.length,
});
const fwbHeight = document.body.clientHeight - 370;
/**
* 添加字典
@ -139,7 +131,9 @@
/**
* 执行编辑
*/
const handleEdit = () => {
const handleEdit = async (id) => {
dictId.value = id;
await nextTick();
editVisible.value = true;
};
@ -151,37 +145,62 @@
dictId.value = row.id;
}
/**
* 刷新字典项值列表
*/
function reloadTable() {
tableRef.value.reload({ pageNo: 1 });
}
/**
* 加载数据列表
*/
const loadDataTable = async () => {
const result = await getDictList({
...params.value,
pageNo: pager.value.page,
pageSize: pager.value.size,
});
dictId.value = result?.records[0]?.id;
dictItemShow.value = true;
dictDataList.value = result.records;
pager.value.count = result.total;
const loadDataTable = async (res: any) => {
const result = await getDictList({ ...params.value, ...res });
dictId.value = result?.records[0]?.id
dictItemShow.value = true
return result;
};
/**
/**
* 执行删除
*/
async function handleDelete() {
async function handleDelete(id) {
Modal.confirm({
title: '提示',
content: '确定要删除?',
onOk: async () => {
dictDelete(dictId.value);
id ? await dictDelete(id) : await dictBatchDelete(selectionData.value);
message.success('删除成功');
pager.value.page = 1;
loadDataTable();
reloadTable();
},
});
}
/**
* 选项发生变化
* @param value 参数
*/
function onSelectionChange(value) {
selectionData.value = value;
}
/**
* 数据行点击事件
* @param record 参数
*/
const rowClick = (record) => {
return {
onClick: () => {
dictId.value = record.id;
},
};
};
/**
* 设置选项行类名
* @param record 参数
*/
const setRowClassName = (record) => {
return record.id === dictId.value ? 'clickRowStyle' : '';
};
/**
* 钩子函数
*/
@ -235,3 +254,18 @@
}
}
</style>
<style lang="less">
.ant-table-tbody {
.ant-table-row.clickRowStyle,
.ant-table-cell-row.clickRowStyle {
td {
background-color: #e7eeff !important;
}
&:hover {
td {
background-color: #e7eeff !important;
}
}
}
}
</style>