merge
This commit is contained in:
@@ -192,7 +192,7 @@ interface LogoutParams {
|
|||||||
token: string;
|
token: string;
|
||||||
redirect_uri: string;
|
redirect_uri: string;
|
||||||
}
|
}
|
||||||
export const logout = (params: LogoutParams) => {
|
export const logout = (params?: LogoutParams) => {
|
||||||
return request({
|
return request({
|
||||||
// url: '/auth/token/logout',
|
// url: '/auth/token/logout',
|
||||||
url: '/auth-service/v1/logout',
|
url: '/auth-service/v1/logout',
|
||||||
|
|||||||
@@ -97,12 +97,16 @@ const list = computed(() => {
|
|||||||
return [...formatChildDepartments.value, ...formatUserList.value];
|
return [...formatChildDepartments.value, ...formatUserList.value];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const cacheList = computed(() => {
|
||||||
|
return list.value.filter((item) => (!disabledIdList.includes(item?.id) && item.type === SourcePickerTypeEnum.dept ? !props.deptDisabled : true));
|
||||||
|
});
|
||||||
|
|
||||||
const selectAllChecked = computed(() => {
|
const selectAllChecked = computed(() => {
|
||||||
return list.value.filter((item) => !disabledIdList.includes(item?.id)).every((item) => item.checked);
|
return cacheList.value.every((item) => item.checked);
|
||||||
});
|
});
|
||||||
const indeterminate = computed(() => {
|
const indeterminate = computed(() => {
|
||||||
const checkedCount = list.value.filter((item) => !disabledIdList.includes(item?.id) && item.checked).length;
|
const checkedCount = cacheList.value.filter((item) => item.checked).length;
|
||||||
return checkedCount > 0 && checkedCount < list.value.filter((item) => !disabledIdList.includes(item?.id)).length;
|
return checkedCount > 0 && checkedCount < cacheList.value.length;
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|||||||
@@ -258,6 +258,7 @@ export default {
|
|||||||
simulationProcessDetail: 'Simulation Process Detail',
|
simulationProcessDetail: 'Simulation Process Detail',
|
||||||
spdmSystemApplication: 'Application Center',
|
spdmSystemApplication: 'Application Center',
|
||||||
spdmCompetenceCenterCondition: 'Condition Map Library',
|
spdmCompetenceCenterCondition: 'Condition Map Library',
|
||||||
|
spdmCompetenceCenterConditionDetail: 'Condition Map Library Detail',
|
||||||
spdmCompetenceCenterStandardScene: 'Standard Scene Library',
|
spdmCompetenceCenterStandardScene: 'Standard Scene Library',
|
||||||
spdmCompetenceCenterKnowledge: 'Knowledge Library',
|
spdmCompetenceCenterKnowledge: 'Knowledge Library',
|
||||||
spdmCompetenceCenterParameter: 'Parameter Library',
|
spdmCompetenceCenterParameter: 'Parameter Library',
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ export default {
|
|||||||
simulationProcessDetail: '仿真流程详情',
|
simulationProcessDetail: '仿真流程详情',
|
||||||
spdmSystemApplication: '应用中心',
|
spdmSystemApplication: '应用中心',
|
||||||
spdmCompetenceCenterCondition: '仿真地图库',
|
spdmCompetenceCenterCondition: '仿真地图库',
|
||||||
|
spdmCompetenceCenterConditionDetail: '仿真地图库详情',
|
||||||
spdmCompetenceCenterStandardScene: '标准场景库',
|
spdmCompetenceCenterStandardScene: '标准场景库',
|
||||||
spdmCompetenceCenterKnowledge: '仿真标准库',
|
spdmCompetenceCenterKnowledge: '仿真标准库',
|
||||||
spdmCompetenceCenterParameter: '仿真参数库',
|
spdmCompetenceCenterParameter: '仿真参数库',
|
||||||
|
|||||||
@@ -11,8 +11,33 @@ import mittBus from '/@/utils/mitt';
|
|||||||
import {useTenantInfo} from '/@/stores/tenant';
|
import {useTenantInfo} from '/@/stores/tenant';
|
||||||
import {useRoutesList} from '/@/stores/routesList';
|
import {useRoutesList} from '/@/stores/routesList';
|
||||||
import LayoutDefault from '/@/layout/main/defaults.vue';
|
import LayoutDefault from '/@/layout/main/defaults.vue';
|
||||||
import {useUserInfo} from '/@/stores/userInfo';
|
import {logout, useUserInfo} from '/@/stores/userInfo';
|
||||||
import {useMsg} from '/@/stores/msg';
|
import {useMsg} from '/@/stores/msg';
|
||||||
|
import { slidingTimeout } from '/@/utils/slidingTimeout';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
beforeRouteEnter(_to, _from, next) {
|
||||||
|
/**
|
||||||
|
* 处理超时登出逻辑
|
||||||
|
*/
|
||||||
|
const handleTimeoutLogout = async () => {
|
||||||
|
console.log('超时登出');
|
||||||
|
try {
|
||||||
|
await logout();
|
||||||
|
slidingTimeout.reset();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('超时登出失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
slidingTimeout.start();
|
||||||
|
if (!slidingTimeout.checkInactiveTimeout()) {
|
||||||
|
next();
|
||||||
|
slidingTimeout.on('timeout', handleTimeoutLogout);
|
||||||
|
} else {
|
||||||
|
next('/login');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// 定义变量内容
|
// 定义变量内容
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|||||||
@@ -143,6 +143,12 @@ export default [
|
|||||||
component: () => import('/@/spdm/views/index.vue'),
|
component: () => import('/@/spdm/views/index.vue'),
|
||||||
meta: {"icon":"fa fa-map","code":"spdm_CompetenceCenterCondition_view"},
|
meta: {"icon":"fa fa-map","code":"spdm_CompetenceCenterCondition_view"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/spdm/competenceCenter/condition/detail',
|
||||||
|
name: 'moduleRoutes.spdmCompetenceCenterConditionDetail',
|
||||||
|
component: () => import('/@/spdm/views/index.vue'),
|
||||||
|
meta: {"icon":"fa fa-map","code":"spdm_CompetenceCenterCondition_view"},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/spdm/competenceCenter/standardScene',
|
path: '/spdm/competenceCenter/standardScene',
|
||||||
name: 'moduleRoutes.spdmCompetenceCenterStandardScene',
|
name: 'moduleRoutes.spdmCompetenceCenterStandardScene',
|
||||||
|
|||||||
@@ -377,6 +377,16 @@ export const appList = [
|
|||||||
icon: 'fa fa-map',
|
icon: 'fa fa-map',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'spdmCompetenceCenterConditionDetail',
|
||||||
|
zhCn: '仿真地图库详情',
|
||||||
|
en: 'Condition Map Library Detail',
|
||||||
|
path: '/spdm/competenceCenter/condition/detail',
|
||||||
|
component: () => import('/@/spdm/views/index.vue'),
|
||||||
|
meta: {
|
||||||
|
icon: 'fa fa-map',
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -223,6 +223,14 @@ export const pageMap = {
|
|||||||
code: 'spdmCompetenceCenter_view',
|
code: 'spdmCompetenceCenter_view',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
spdmCompetenceCenterConditionDetail: {
|
||||||
|
path: '/spdm/competenceCenter/condition/detail',
|
||||||
|
name: 'moduleRoutes.spdmCompetenceCenterConditionDetail',
|
||||||
|
meta: {
|
||||||
|
icon: 'fa fa-map',
|
||||||
|
code: 'spdm_CompetenceCenterCondition_view',
|
||||||
|
},
|
||||||
|
},
|
||||||
spdmCompetenceCenterStandardScene: {
|
spdmCompetenceCenterStandardScene: {
|
||||||
path: '/spdm/competenceCenter/standardScene',
|
path: '/spdm/competenceCenter/standardScene',
|
||||||
name: 'moduleRoutes.spdmCompetenceCenterStandardScene',
|
name: 'moduleRoutes.spdmCompetenceCenterStandardScene',
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
|
import Cookies from 'js-cookie';
|
||||||
|
import { loginHeartbeat } from '/@/spdm/utils/index';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { REFRESH_TOKEN_KEY, Session, Token } from '/@/utils/storage';
|
import { REFRESH_TOKEN_KEY, Session, Token } from '/@/utils/storage';
|
||||||
import {getUserInfo, login, loginByMobile, loginBySocial, refreshTokenApi, tenantAuthCheck, loginByAccount} from '/@/api/login/index';
|
import { getUserInfo, login, loginByMobile, loginBySocial, logout as logoutApi, refreshTokenApi, tenantAuthCheck } from '/@/api/login/index';
|
||||||
import { useMessage } from '/@/hooks/message';
|
import { useMessage } from '/@/hooks/message';
|
||||||
import { avatarFormat } from '/@/utils/commonFunction';
|
import { avatarFormat } from '/@/utils/commonFunction';
|
||||||
import { ITenant } from '/@/api/admin/tenant';
|
import { ITenant } from '/@/api/admin/tenant';
|
||||||
import Cookies from 'js-cookie';
|
import { slidingTimeout } from '/@/utils/slidingTimeout';
|
||||||
import { loginHeartbeat } from '/@/spdm/utils/index';
|
|
||||||
|
export const logout = async () => {
|
||||||
|
await logoutApi();
|
||||||
|
// 清除缓存/token等
|
||||||
|
Session.clear();
|
||||||
|
// 使用 reload 时,不需要调用 resetRoute() 重置路由
|
||||||
|
window.location.reload();
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @function useUserInfo
|
* @function useUserInfo
|
||||||
@@ -58,11 +67,12 @@ export const useUserInfo = defineStore('userInfo', {
|
|||||||
login(data)
|
login(data)
|
||||||
.then(({ data: res }) => {
|
.then(({ data: res }) => {
|
||||||
// SPDM CODE
|
// SPDM CODE
|
||||||
Cookies.set('cid_user_id', res.user_id)
|
Cookies.set('cid_user_id', res.user_id);
|
||||||
Session.setTenant(res.tenant_id);
|
Session.setTenant(res.tenant_id);
|
||||||
// 存储token 信息
|
// 存储token 信息
|
||||||
Token.set(res.access_token);
|
Token.set(res.access_token);
|
||||||
Session.set(REFRESH_TOKEN_KEY, res.refresh_token);
|
Session.set(REFRESH_TOKEN_KEY, res.refresh_token);
|
||||||
|
slidingTimeout.updateTimeout();
|
||||||
resolve(res);
|
resolve(res);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
@@ -85,7 +95,7 @@ export const useUserInfo = defineStore('userInfo', {
|
|||||||
login({
|
login({
|
||||||
grant_type: 'remote',
|
grant_type: 'remote',
|
||||||
scope: 'server',
|
scope: 'server',
|
||||||
...resp.data
|
...resp.data,
|
||||||
})
|
})
|
||||||
.then(({ data: res }) => {
|
.then(({ data: res }) => {
|
||||||
Session.setTenant(res.tenant_id);
|
Session.setTenant(res.tenant_id);
|
||||||
@@ -103,7 +113,7 @@ export const useUserInfo = defineStore('userInfo', {
|
|||||||
const codeMsg: Record<string, string> = {
|
const codeMsg: Record<string, string> = {
|
||||||
'TENANT-0057': '校验同步用户账号密码失败(员工编号或密码不存在)!',
|
'TENANT-0057': '校验同步用户账号密码失败(员工编号或密码不存在)!',
|
||||||
'TENANT-0058': '校验同步用户账号密码失败(员工停用,无法登录)!"',
|
'TENANT-0058': '校验同步用户账号密码失败(员工停用,无法登录)!"',
|
||||||
}
|
};
|
||||||
useMessage().error(codeMsg[err?.errorCode] || '系统异常请联系管理员');
|
useMessage().error(codeMsg[err?.errorCode] || '系统异常请联系管理员');
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
|
|||||||
34
src/utils/eventBinder.ts
Normal file
34
src/utils/eventBinder.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* 绑定操作事件
|
||||||
|
* @param events 监听的事件列表
|
||||||
|
* @param handler 事件处理函数
|
||||||
|
*/
|
||||||
|
export const bindEvents = (events: Array<keyof DocumentEventMap>, handler: () => void): void => {
|
||||||
|
events.forEach((event) => {
|
||||||
|
document.addEventListener(event, handler, { passive: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
// 页面可见性变化事件
|
||||||
|
document.addEventListener('visibilitychange', () => {
|
||||||
|
if (document.visibilityState === 'visible') {
|
||||||
|
handler();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解绑操作事件
|
||||||
|
* @param events 监听的事件列表
|
||||||
|
* @param handler 事件处理函数
|
||||||
|
*/
|
||||||
|
export const unbindEvents = (events: Array<keyof DocumentEventMap>, handler: () => void): void => {
|
||||||
|
events.forEach((event) => {
|
||||||
|
document.removeEventListener(event, handler);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.removeEventListener('visibilitychange', () => {
|
||||||
|
if (document.visibilityState === 'visible') {
|
||||||
|
handler();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
360
src/utils/slidingTimeout.ts
Normal file
360
src/utils/slidingTimeout.ts
Normal file
@@ -0,0 +1,360 @@
|
|||||||
|
import { bindEvents, unbindEvents } from '/@/utils/eventBinder';
|
||||||
|
import { Local } from '/@/utils/storage';
|
||||||
|
import { debounce, isFunction, isString } from 'lodash-es';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 滑动超时支持的事件类型
|
||||||
|
*/
|
||||||
|
type SlidingTimeoutEvent = 'timeout' | 'activity';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 滑动超时配置项(移除onTimeout/onActivity,改为事件监听)
|
||||||
|
* @interface SlidingTimeoutOptions
|
||||||
|
* @property {string} [storageKey=EXPIRY_TIME_KEY] - 存储最后活动时间的本地存储key
|
||||||
|
* @property {number} [timeout=1000 * 60 * 60 * 8] - 默认8小时 超时时间(毫秒)
|
||||||
|
* @property {Array<keyof DocumentEventMap>} [events=['mousemove', 'click', 'keydown', 'scroll', 'touchstart']] - 需要监听的用户操作事件列表
|
||||||
|
* @property {number} [debounceDelay=500] - 事件防抖延迟时间(毫秒),避免频繁触发检查
|
||||||
|
* @property {boolean} [checkOnStart=true] - 是否在调用start方法时立即检查超时状态
|
||||||
|
*/
|
||||||
|
interface SlidingTimeoutOptions {
|
||||||
|
storageKey?: string;
|
||||||
|
timeout?: number;
|
||||||
|
events?: Array<keyof DocumentEventMap>;
|
||||||
|
debounceDelay?: number;
|
||||||
|
checkOnStart?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 滑动超时监听类:监听用户操作事件,每次操作重置超时时间,超时后触发指定事件
|
||||||
|
* 核心逻辑:用户无操作达到指定时间后触发'timeout'事件,有操作则触发'activity'事件
|
||||||
|
* @example
|
||||||
|
* // 事件监听模式用法(核心修改点)
|
||||||
|
* const slidingTimeout = new SlidingTimeout({
|
||||||
|
* timeout: 8 * 60 * 60 * 1000,
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // 绑定超时事件
|
||||||
|
* slidingTimeout.on('timeout', () => {
|
||||||
|
* console.log('用户超时了');
|
||||||
|
* await logout();
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // 绑定用户活动事件
|
||||||
|
* slidingTimeout.on('activity', () => {
|
||||||
|
* console.log('用户操作了');
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // 启动监听
|
||||||
|
* slidingTimeout.start();
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // 高级用法:解绑事件/暂停恢复
|
||||||
|
* // 绑定事件并保存回调引用
|
||||||
|
* const activityHandler = () => console.log('用户有操作');
|
||||||
|
* slidingTimeout.on('activity', activityHandler);
|
||||||
|
*
|
||||||
|
* // 解绑指定事件
|
||||||
|
* slidingTimeout.off('activity', activityHandler);
|
||||||
|
*
|
||||||
|
* // 暂停监听
|
||||||
|
* slidingTimeout.pause();
|
||||||
|
* // 恢复监听
|
||||||
|
* slidingTimeout.resume();
|
||||||
|
*
|
||||||
|
* // 页面销毁时清理
|
||||||
|
* onUnmounted(() => slidingTimeout.destroy());
|
||||||
|
*/
|
||||||
|
class SlidingTimeout {
|
||||||
|
private static readonly DEFAULT_ACTIVITY_EVENTS: Array<keyof DocumentEventMap> = ['mousemove', 'click', 'keydown', 'scroll', 'touchstart'];
|
||||||
|
private static readonly DEFAULT_STORAGE_KEY = 'EXPIRY_TIME_KEY';
|
||||||
|
private static readonly EXPIRE_TIME = 8 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
// 配置项
|
||||||
|
private storageKey: string;
|
||||||
|
private timeout: number;
|
||||||
|
private events: Array<keyof DocumentEventMap>;
|
||||||
|
private debounceDelay: number;
|
||||||
|
private checkOnStart: boolean;
|
||||||
|
|
||||||
|
// 状态管理
|
||||||
|
private isListenerActive = false;
|
||||||
|
private isPaused = false;
|
||||||
|
private isTimeoutTriggered = false; // 防止重复触发timeout事件
|
||||||
|
|
||||||
|
// 事件注册表:存储不同事件的回调函数列表
|
||||||
|
private eventHandlers: Record<SlidingTimeoutEvent, Array<() => void | Promise<void>>> = {
|
||||||
|
timeout: [],
|
||||||
|
activity: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
private debouncedCheckTimeout: ReturnType<typeof debounce>;
|
||||||
|
|
||||||
|
constructor(options: SlidingTimeoutOptions) {
|
||||||
|
const {
|
||||||
|
storageKey = SlidingTimeout.DEFAULT_STORAGE_KEY,
|
||||||
|
timeout = SlidingTimeout.EXPIRE_TIME,
|
||||||
|
events = SlidingTimeout.DEFAULT_ACTIVITY_EVENTS,
|
||||||
|
debounceDelay = 500,
|
||||||
|
checkOnStart = true,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
// 初始化配置
|
||||||
|
this.storageKey = storageKey;
|
||||||
|
this.timeout = timeout;
|
||||||
|
this.events = events;
|
||||||
|
this.debounceDelay = debounceDelay;
|
||||||
|
this.checkOnStart = checkOnStart;
|
||||||
|
|
||||||
|
// 初始化防抖函数
|
||||||
|
this.debouncedCheckTimeout = debounce(this.executeTimeoutCheck.bind(this), this.debounceDelay, { leading: true, trailing: true, maxWait: 1000 });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绑定事件监听
|
||||||
|
* @param event 事件类型('timeout' | 'activity')
|
||||||
|
* @param handler 事件回调函数
|
||||||
|
*/
|
||||||
|
public on(event: SlidingTimeoutEvent, handler: () => void | Promise<void>): void {
|
||||||
|
if (!isString(event) || !['timeout', 'activity'].includes(event)) {
|
||||||
|
throw new TypeError(`事件类型必须是 'timeout' 或 'activity',当前传入:${event}`);
|
||||||
|
}
|
||||||
|
if (!isFunction(handler)) {
|
||||||
|
throw new TypeError('事件回调必须是一个函数');
|
||||||
|
}
|
||||||
|
console.log(`SlidingTimeout: 绑定 ${event} 事件`);
|
||||||
|
this.eventHandlers[event].push(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解绑事件监听
|
||||||
|
* @param event 事件类型('timeout' | 'activity')
|
||||||
|
* @param handler 要解绑的回调函数(不传则解绑该事件的所有回调)
|
||||||
|
*/
|
||||||
|
public off(event: SlidingTimeoutEvent, handler?: () => void | Promise<void>): void {
|
||||||
|
if (!isString(event) || !['timeout', 'activity'].includes(event)) {
|
||||||
|
throw new TypeError(`事件类型必须是 'timeout' 或 'activity',当前传入:${event}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handler) {
|
||||||
|
// 解绑指定回调
|
||||||
|
this.eventHandlers[event] = this.eventHandlers[event].filter((h) => h !== handler);
|
||||||
|
} else {
|
||||||
|
// 解绑该事件的所有回调
|
||||||
|
this.eventHandlers[event] = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触发指定事件(内部使用)
|
||||||
|
* @param event 事件类型
|
||||||
|
*/
|
||||||
|
private emit(event: SlidingTimeoutEvent): void {
|
||||||
|
const handlers = this.eventHandlers[event];
|
||||||
|
if (!handlers.length) return;
|
||||||
|
// 依次执行所有回调,捕获单个回调的错误(不影响其他回调)
|
||||||
|
for (const handler of handlers) {
|
||||||
|
try {
|
||||||
|
handler();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`SlidingTimeout: 执行 ${event} 事件回调失败`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置超时时间(清空本地存储的最后活动时间)
|
||||||
|
*/
|
||||||
|
public reset(): void {
|
||||||
|
try {
|
||||||
|
Local.remove(this.storageKey);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('滑动超时:清空最后操作时间失败', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁所有监听和计时器,重置状态
|
||||||
|
*/
|
||||||
|
public destroy(): void {
|
||||||
|
console.log('SlidingTimeout: 销毁所有监听和计时器');
|
||||||
|
// 取消防抖函数
|
||||||
|
this.debouncedCheckTimeout.cancel();
|
||||||
|
|
||||||
|
// 解绑用户操作事件
|
||||||
|
if (this.isListenerActive) {
|
||||||
|
try {
|
||||||
|
unbindEvents(this.events, this.debouncedCheckTimeout);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('滑动超时:解绑事件监听失败', error);
|
||||||
|
}
|
||||||
|
this.isListenerActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空事件回调注册表
|
||||||
|
this.eventHandlers = { timeout: [], activity: [] };
|
||||||
|
|
||||||
|
// 重置状态
|
||||||
|
this.isPaused = false;
|
||||||
|
this.isTimeoutTriggered = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动用户活动监听
|
||||||
|
* 1. 先销毁已有监听
|
||||||
|
* 2. 检查超时状态
|
||||||
|
* 3. 初始化事件监听
|
||||||
|
*/
|
||||||
|
public start(): void {
|
||||||
|
if (this.isListenerActive) {
|
||||||
|
this.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.checkOnStart) {
|
||||||
|
this.executeTimeoutCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initActivityListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 暂停监听
|
||||||
|
*/
|
||||||
|
public pause(): void {
|
||||||
|
if (this.isPaused || this.isTimeoutTriggered) return;
|
||||||
|
|
||||||
|
this.debouncedCheckTimeout.cancel();
|
||||||
|
if (this.isListenerActive) {
|
||||||
|
try {
|
||||||
|
unbindEvents(this.events, this.debouncedCheckTimeout);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('滑动超时:暂停解绑事件失败', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isPaused = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 恢复监听
|
||||||
|
*/
|
||||||
|
public resume(): void {
|
||||||
|
if (!this.isPaused || this.isTimeoutTriggered) return;
|
||||||
|
|
||||||
|
this.isPaused = false;
|
||||||
|
if (this.checkOnStart) {
|
||||||
|
this.executeTimeoutCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initActivityListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手动触发超时逻辑(立即执行timeout事件并销毁监听)
|
||||||
|
*/
|
||||||
|
public triggerTimeout(): void {
|
||||||
|
this.handleTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新最后活动时间为当前时间
|
||||||
|
*/
|
||||||
|
public updateTimeout(): void {
|
||||||
|
try {
|
||||||
|
Local.set(this.storageKey, Date.now());
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('滑动超时:更新最后操作时间失败', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private get lastActivityTime(): number | null {
|
||||||
|
try {
|
||||||
|
return Number(Local.get(this.storageKey));
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('滑动超时:获取最后操作时间失败', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否超时未操作
|
||||||
|
* @returns {boolean} 是否超时
|
||||||
|
*/
|
||||||
|
public checkInactiveTimeout(): boolean {
|
||||||
|
if (this.isPaused || this.isTimeoutTriggered) return false;
|
||||||
|
try {
|
||||||
|
if (!this.lastActivityTime) return true;
|
||||||
|
const currentTime = Date.now();
|
||||||
|
const timeElapsed = currentTime - this.lastActivityTime;
|
||||||
|
return timeElapsed > this.timeout;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('滑动超时:检查超时状态失败', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理用户活动逻辑
|
||||||
|
*/
|
||||||
|
private handleActivity(): void {
|
||||||
|
if (this.isPaused || this.isTimeoutTriggered) return;
|
||||||
|
|
||||||
|
this.updateTimeout();
|
||||||
|
this.emit('activity'); // 触发activity事件
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理超时逻辑(触发timeout事件)
|
||||||
|
*/
|
||||||
|
private handleTimeout(): void {
|
||||||
|
if (this.isTimeoutTriggered) return; // 防止重复触发
|
||||||
|
|
||||||
|
this.isTimeoutTriggered = true;
|
||||||
|
this.reset();
|
||||||
|
this.emit('timeout'); // 触发timeout事件
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行超时检查
|
||||||
|
* @returns {void} 是否超时
|
||||||
|
*/
|
||||||
|
private executeTimeoutCheck(): void {
|
||||||
|
const isTimeout = this.checkInactiveTimeout();
|
||||||
|
if (isTimeout) {
|
||||||
|
this.handleTimeout();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.handleActivity();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化事件监听
|
||||||
|
*/
|
||||||
|
private initActivityListener(): void {
|
||||||
|
if (this.isListenerActive || this.isPaused || this.isTimeoutTriggered) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
bindEvents(this.events, this.debouncedCheckTimeout);
|
||||||
|
this.isListenerActive = true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('滑动超时:绑定事件监听失败', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const EXPIRY_TIME_KEY = 'EXPIRY_TIME_KEY';
|
||||||
|
const DEFAULT_TIMEOUT = 8 * 60 * 60 * 1000;
|
||||||
|
export const slidingTimeout = new SlidingTimeout({
|
||||||
|
storageKey: EXPIRY_TIME_KEY,
|
||||||
|
timeout: DEFAULT_TIMEOUT,
|
||||||
|
checkOnStart: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// // 绑定timeout事件
|
||||||
|
// slidingTimeout.on('timeout', () => {
|
||||||
|
// console.log('用户超时了');
|
||||||
|
// });
|
||||||
|
|
||||||
|
// // 绑定activity事件
|
||||||
|
// slidingTimeout.on('activity', () => {
|
||||||
|
// console.log('用户操作了');
|
||||||
|
// });
|
||||||
@@ -25,7 +25,11 @@ export const Local = {
|
|||||||
// 获取永久缓存
|
// 获取永久缓存
|
||||||
get(key: string) {
|
get(key: string) {
|
||||||
let json = <string>window.localStorage.getItem(Local.setKey(key));
|
let json = <string>window.localStorage.getItem(Local.setKey(key));
|
||||||
|
try {
|
||||||
return JSON.parse(json);
|
return JSON.parse(json);
|
||||||
|
} catch (error) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
},
|
},
|
||||||
// 移除永久缓存
|
// 移除永久缓存
|
||||||
remove(key: string) {
|
remove(key: string) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<Echarts class="h-80 w-full" :option="option"/>
|
<Echarts class="h-full w-full" :option="option"/>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts" name="log-line-chart">
|
<script setup lang="ts" name="log-line-chart">
|
||||||
import Echarts from '/@/components/Echarts/index.vue';
|
import Echarts from '/@/components/Echarts/index.vue';
|
||||||
|
|||||||
2
src/views/admin/system/user/constant.ts
Normal file
2
src/views/admin/system/user/constant.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/** 根用户id */
|
||||||
|
export const DEFAULT_ROOT_ID = '0';
|
||||||
@@ -1,18 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="layout-padding-auto layout-padding-view">
|
<div class="layout-padding-auto layout-padding-view">
|
||||||
<div>
|
<div>
|
||||||
<el-input v-model="searchName" suffix-icon="search" :placeholder="$t('staffManage.searchDeptTip')" @change="getDeptData" />
|
<el-input
|
||||||
|
v-model="searchName"
|
||||||
|
suffix-icon="search"
|
||||||
|
:placeholder="t('staffManage.searchDeptTip')"
|
||||||
|
class="input-with-select"
|
||||||
|
clearable
|
||||||
|
@change="getDeptData"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<el-button :icon="Search" @click="getDeptData" />
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="deptTreeContainer">
|
||||||
<el-tree
|
<el-tree
|
||||||
class="mt20"
|
class="mt20"
|
||||||
|
:default-expand-all="false"
|
||||||
:data="tree"
|
:data="tree"
|
||||||
:props="{ children: 'children', label: 'deptName', value: 'deptId' }"
|
:props="{ children: 'children', label: 'deptName', value: 'deptId' }"
|
||||||
:expand-on-click-node="false"
|
:expand-on-click-node="false"
|
||||||
ref="deptTreeRef"
|
ref="deptTreeRef"
|
||||||
node-key="id"
|
node-key="deptId"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
|
:current-node-key="deptId || DEFAULT_ROOT_ID"
|
||||||
highlight-current
|
highlight-current
|
||||||
default-expand-all
|
|
||||||
@node-click="handleNodeClick"
|
@node-click="handleNodeClick"
|
||||||
>
|
>
|
||||||
<template #default="{ node, data }" v-if="$slots.default">
|
<template #default="{ node, data }" v-if="$slots.default">
|
||||||
@@ -20,12 +33,18 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-tree>
|
</el-tree>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { DEFAULT_ROOT_ID } from './constant';
|
||||||
import { getDeptList, IDeptIntroTree } from '/@/api/admin/deptManage';
|
import { getDeptList, IDeptIntroTree } from '/@/api/admin/deptManage';
|
||||||
import { useTenantInfo } from '/@/stores/tenant';
|
import { useTenantInfo } from '/@/stores/tenant';
|
||||||
|
import { Search } from '@element-plus/icons-vue';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
deptId?: string;
|
||||||
|
}>();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const emit = defineEmits(['nodeClick']);
|
const emit = defineEmits(['nodeClick']);
|
||||||
const tenantStore = useTenantInfo();
|
const tenantStore = useTenantInfo();
|
||||||
@@ -51,7 +70,7 @@ const tree = computed(() => {
|
|||||||
const { companyName } = currentTenant.value;
|
const { companyName } = currentTenant.value;
|
||||||
|
|
||||||
const root: IDeptIntroTree = {
|
const root: IDeptIntroTree = {
|
||||||
deptId: '0',
|
deptId: DEFAULT_ROOT_ID,
|
||||||
deptName: companyName,
|
deptName: companyName,
|
||||||
children: [],
|
children: [],
|
||||||
};
|
};
|
||||||
@@ -59,7 +78,7 @@ const tree = computed(() => {
|
|||||||
return [root];
|
return [root];
|
||||||
});
|
});
|
||||||
const handleNodeClick = (item: IDeptIntroTree) => {
|
const handleNodeClick = (item: IDeptIntroTree) => {
|
||||||
if (item.deptId === '0') {
|
if (item.deptId === DEFAULT_ROOT_ID) {
|
||||||
emit('nodeClick');
|
emit('nodeClick');
|
||||||
} else {
|
} else {
|
||||||
emit('nodeClick', item.deptId);
|
emit('nodeClick', item.deptId);
|
||||||
@@ -70,3 +89,14 @@ onMounted(() => {
|
|||||||
getDeptData();
|
getDeptData();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.deptTreeContainer {
|
||||||
|
height: calc(100vh - 190px);
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
:deep(.el-tree) {
|
||||||
|
--el-tree-node-content-height: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -52,6 +52,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
|
import { DEFAULT_ROOT_ID } from './constant';
|
||||||
import { getDeptList, IDeptIntroTree } from '/@/api/admin/deptManage';
|
import { getDeptList, IDeptIntroTree } from '/@/api/admin/deptManage';
|
||||||
import { getStaffDetail, updateStaff } from '/@/api/admin/staffManage';
|
import { getStaffDetail, updateStaff } from '/@/api/admin/staffManage';
|
||||||
import { useMessage } from '/@/hooks/message';
|
import { useMessage } from '/@/hooks/message';
|
||||||
@@ -96,7 +97,7 @@ const staffForm = ref<{
|
|||||||
email: '',
|
email: '',
|
||||||
posts: [],
|
posts: [],
|
||||||
roles: [],
|
roles: [],
|
||||||
dept: '',
|
dept: DEFAULT_ROOT_ID,
|
||||||
});
|
});
|
||||||
|
|
||||||
const dataRules = ref({
|
const dataRules = ref({
|
||||||
@@ -142,7 +143,7 @@ const getDetail = async (id: string) => {
|
|||||||
email,
|
email,
|
||||||
posts,
|
posts,
|
||||||
roles,
|
roles,
|
||||||
dept: depts[0],
|
dept: depts[0] || DEFAULT_ROOT_ID,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -156,7 +157,7 @@ const getDeptTree = async () => {
|
|||||||
}
|
}
|
||||||
const {companyName} = currentTenant.value;
|
const {companyName} = currentTenant.value;
|
||||||
const root: IDeptIntroTree = {
|
const root: IDeptIntroTree = {
|
||||||
deptId: '0',
|
deptId: DEFAULT_ROOT_ID,
|
||||||
deptName: companyName,
|
deptName: companyName,
|
||||||
children: [],
|
children: [],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="layout-padding">
|
<div class="layout-padding">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="tree">
|
<div class="tree">
|
||||||
<DeptTree @node-click="handleNodeClick" />
|
<DeptTree :dept-id="deptId" @node-click="handleNodeClick" />
|
||||||
</div>
|
</div>
|
||||||
<div class="table ml-2">
|
<div class="table ml-2">
|
||||||
<StaffTable :dept-id="deptId" :posts="posts" :roles="roles" />
|
<StaffTable :dept-id="deptId" :posts="posts" :roles="roles" />
|
||||||
|
|||||||
@@ -81,6 +81,7 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<TableRender
|
<TableRender
|
||||||
|
v-loading="loading"
|
||||||
class="min-h-[440px]"
|
class="min-h-[440px]"
|
||||||
:data="tableData"
|
:data="tableData"
|
||||||
:columns="tableColumns"
|
:columns="tableColumns"
|
||||||
@@ -104,6 +105,10 @@ import {ElButton} from 'element-plus';
|
|||||||
import EditForm from './editForm.vue';
|
import EditForm from './editForm.vue';
|
||||||
import InviteForm from './inviteForm.vue';
|
import InviteForm from './inviteForm.vue';
|
||||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||||
|
import { onlyResolvesLast } from '/@/utils/onlyResolvesLast';
|
||||||
|
|
||||||
|
const getStaffListApi = onlyResolvesLast(getStaffList);
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const editFormRef = ref<InstanceType<typeof EditForm>>();
|
const editFormRef = ref<InstanceType<typeof EditForm>>();
|
||||||
@@ -132,6 +137,15 @@ type QueryForm = {
|
|||||||
roles: string[];
|
roles: string[];
|
||||||
posts: string[];
|
posts: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const setTrue = () => {
|
||||||
|
loading.value = true;
|
||||||
|
};
|
||||||
|
const setFalse = () => {
|
||||||
|
loading.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
const initialQueryForm = (): QueryForm => {
|
const initialQueryForm = (): QueryForm => {
|
||||||
return {
|
return {
|
||||||
phone: '',
|
phone: '',
|
||||||
@@ -277,8 +291,9 @@ const queryStaffs = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getDataList = async () => {
|
const getDataList = async () => {
|
||||||
|
setTrue();
|
||||||
try {
|
try {
|
||||||
const {data} = await getStaffList(queryParams.value);
|
const { data } = await getStaffListApi({ ...queryParams.value, deptId: props.deptId });
|
||||||
pagination.size = data.size;
|
pagination.size = data.size;
|
||||||
pagination.current = data.current;
|
pagination.current = data.current;
|
||||||
pagination.total = data.total;
|
pagination.total = data.total;
|
||||||
@@ -303,6 +318,8 @@ const getDataList = async () => {
|
|||||||
console.log(toRaw(tableData.value));
|
console.log(toRaw(tableData.value));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
setFalse();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
w: 12,
|
||||||
|
h: 3,
|
||||||
|
static: true
|
||||||
|
};
|
||||||
|
</script>
|
||||||
<script lang="ts" setup name="TopNav">
|
<script lang="ts" setup name="TopNav">
|
||||||
import {getTaskCountApi} from '/@/api/admin/tenant';
|
import {getTaskCountApi} from '/@/api/admin/tenant';
|
||||||
import {getTenantAssignmentsListByUsername} from '/@/api/admin/user';
|
import {getTenantAssignmentsListByUsername} from '/@/api/admin/user';
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {i18n} from "/@/i18n"
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "components.audit-log.0910665-0", // 审计日志
|
title: 'components.audit-log.0910665-0', // 审计日志
|
||||||
icon: 'DocumentCopy',
|
icon: 'DocumentCopy',
|
||||||
description: "components.audit-log.0910665-1" // 审计日志列表
|
description: 'components.audit-log.0910665-1', // 审计日志列表
|
||||||
|
w: 6,
|
||||||
|
h: 10,
|
||||||
|
minW: 3,
|
||||||
|
minH: 5,
|
||||||
|
maxW: 12,
|
||||||
|
maxH: 16,
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<el-card class="box-card h-[400px]">
|
<el-card class="box-card h-full w-full">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span>{{ $t('home.auditLogsTip') }}</span>
|
<span>{{ $t('home.auditLogsTip') }}</span>
|
||||||
|
|||||||
@@ -3,11 +3,17 @@ export default {
|
|||||||
title: 'components.calendar.0910666-0', // 日程管理
|
title: 'components.calendar.0910666-0', // 日程管理
|
||||||
icon: 'Calendar',
|
icon: 'Calendar',
|
||||||
description: 'components.calendar.0910666-1', // 日历组件展示
|
description: 'components.calendar.0910666-1', // 日历组件展示
|
||||||
|
w: 6,
|
||||||
|
h: 14,
|
||||||
|
minW: 3,
|
||||||
|
minH: 5,
|
||||||
|
maxW: 12,
|
||||||
|
maxH: 16,
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-card class="h-[400px] box-card">
|
<el-card class="h-full w-full box-card">
|
||||||
<Calendar
|
<Calendar
|
||||||
ref="calendar"
|
ref="calendar"
|
||||||
view="weekly"
|
view="weekly"
|
||||||
@@ -23,7 +29,7 @@ export default {
|
|||||||
/>
|
/>
|
||||||
<div v-if="calendar" class="py-4 px-6 w-full h-[18rem] overflow-y-auto">
|
<div v-if="calendar" class="py-4 px-6 w-full h-[18rem] overflow-y-auto">
|
||||||
<template v-for="{ day, cells } in Object.values(dayCells)">
|
<template v-for="{ day, cells } in Object.values(dayCells)">
|
||||||
<ul v-if="cells.length > 0" :key="day" class="py-2 space-y-2">
|
<ul v-if="cells.length > 0" class="py-2 space-y-2" :key="day">
|
||||||
<li v-for="cell in cells" :key="cell">
|
<li v-for="cell in cells" :key="cell">
|
||||||
<div class="flex items-center space-x-4">
|
<div class="flex items-center space-x-4">
|
||||||
<!--Icon-->
|
<!--Icon-->
|
||||||
|
|||||||
@@ -3,6 +3,12 @@ export default {
|
|||||||
title: 'components.favorite-flow.0910773-0', // '常用流程'
|
title: 'components.favorite-flow.0910773-0', // '常用流程'
|
||||||
icon: 'Star',
|
icon: 'Star',
|
||||||
description: 'components.favorite-flow.0910773-1', // '常用流程收藏'
|
description: 'components.favorite-flow.0910773-1', // '常用流程收藏'
|
||||||
|
w: 6,
|
||||||
|
h: 9,
|
||||||
|
minW: 3,
|
||||||
|
minH: 5,
|
||||||
|
maxW: 12,
|
||||||
|
maxH: 16,
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -3,6 +3,12 @@ export default {
|
|||||||
title: 'components.favorite-menu.0910774-0', // '常用功能'
|
title: 'components.favorite-menu.0910774-0', // '常用功能'
|
||||||
icon: 'Star',
|
icon: 'Star',
|
||||||
description: 'components.favorite-menu.0910774-1', // '常用功能收藏'
|
description: 'components.favorite-menu.0910774-1', // '常用功能收藏'
|
||||||
|
w: 6,
|
||||||
|
h: 5,
|
||||||
|
minW: 3,
|
||||||
|
minH: 5,
|
||||||
|
maxW: 12,
|
||||||
|
maxH: 16,
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -3,10 +3,16 @@ export default {
|
|||||||
title: 'components.news.0910777-0', // '系统公告'
|
title: 'components.news.0910777-0', // '系统公告'
|
||||||
icon: 'MuteNotification',
|
icon: 'MuteNotification',
|
||||||
description: 'components.news.0910777-1', // '系统公告展示'
|
description: 'components.news.0910777-1', // '系统公告展示'
|
||||||
|
w: 6,
|
||||||
|
h: 10,
|
||||||
|
minW: 3,
|
||||||
|
minH: 5,
|
||||||
|
maxW: 12,
|
||||||
|
maxH: 16,
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<el-card class="h-[400px]">
|
<el-card class="h-full w-full">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span>{{ $t('home.newsletterTip') }}</span>
|
<span>{{ $t('home.newsletterTip') }}</span>
|
||||||
@@ -15,7 +21,7 @@ export default {
|
|||||||
</template>
|
</template>
|
||||||
<el-timeline v-if="newsList.length > 0">
|
<el-timeline v-if="newsList.length > 0">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<el-timeline-item v-for="(item, index) in newsList" :key="index" :timestamp="item.createTime" @click="contentRef.openDialog(item)">
|
<el-timeline-item v-for="(item, index) in newsList" :key="index" :timestamp="item.intervalTime">
|
||||||
<button class="absolute right-0 px-3 rotate-[10deg] -top-2 border bg-primary text-white font-bold">
|
<button class="absolute right-0 px-3 rotate-[10deg] -top-2 border bg-primary text-white font-bold">
|
||||||
{{ item.readFlag === '1' ? $t('msg.readed') : $t('msg.unread') }}
|
{{ item.readFlag === '1' ? $t('msg.readed') : $t('msg.unread') }}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -3,6 +3,12 @@ export default {
|
|||||||
title: 'components.sys-log-line.0910980-0', // '请求折线图'
|
title: 'components.sys-log-line.0910980-0', // '请求折线图'
|
||||||
icon: 'DocumentCopy',
|
icon: 'DocumentCopy',
|
||||||
description: 'components.sys-log-line.0910980-1', // '折线图示例'
|
description: 'components.sys-log-line.0910980-1', // '折线图示例'
|
||||||
|
w: 6,
|
||||||
|
h: 14,
|
||||||
|
minW: 3,
|
||||||
|
minH: 5,
|
||||||
|
maxW: 12,
|
||||||
|
maxH: 16,
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -3,6 +3,12 @@ export default {
|
|||||||
title: 'components.sys-log.0910779-0', // '系统日志'
|
title: 'components.sys-log.0910779-0', // '系统日志'
|
||||||
icon: 'Monitor',
|
icon: 'Monitor',
|
||||||
description: 'components.sys-log.0910779-1', // '系统日志列表'
|
description: 'components.sys-log.0910779-1', // '系统日志列表'
|
||||||
|
w: 6,
|
||||||
|
h: 10,
|
||||||
|
minW: 3,
|
||||||
|
minH: 5,
|
||||||
|
maxW: 12,
|
||||||
|
maxH: 16,
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
Reference in New Issue
Block a user