merge
This commit is contained in:
@@ -192,7 +192,7 @@ interface LogoutParams {
|
||||
token: string;
|
||||
redirect_uri: string;
|
||||
}
|
||||
export const logout = (params: LogoutParams) => {
|
||||
export const logout = (params?: LogoutParams) => {
|
||||
return request({
|
||||
// url: '/auth/token/logout',
|
||||
url: '/auth-service/v1/logout',
|
||||
|
||||
@@ -97,12 +97,16 @@ const list = computed(() => {
|
||||
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(() => {
|
||||
return list.value.filter((item) => !disabledIdList.includes(item?.id)).every((item) => item.checked);
|
||||
return cacheList.value.every((item) => item.checked);
|
||||
});
|
||||
const indeterminate = computed(() => {
|
||||
const checkedCount = list.value.filter((item) => !disabledIdList.includes(item?.id) && item.checked).length;
|
||||
return checkedCount > 0 && checkedCount < list.value.filter((item) => !disabledIdList.includes(item?.id)).length;
|
||||
const checkedCount = cacheList.value.filter((item) => item.checked).length;
|
||||
return checkedCount > 0 && checkedCount < cacheList.value.length;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -258,6 +258,7 @@ export default {
|
||||
simulationProcessDetail: 'Simulation Process Detail',
|
||||
spdmSystemApplication: 'Application Center',
|
||||
spdmCompetenceCenterCondition: 'Condition Map Library',
|
||||
spdmCompetenceCenterConditionDetail: 'Condition Map Library Detail',
|
||||
spdmCompetenceCenterStandardScene: 'Standard Scene Library',
|
||||
spdmCompetenceCenterKnowledge: 'Knowledge Library',
|
||||
spdmCompetenceCenterParameter: 'Parameter Library',
|
||||
|
||||
@@ -155,6 +155,7 @@ export default {
|
||||
simulationProcessDetail: '仿真流程详情',
|
||||
spdmSystemApplication: '应用中心',
|
||||
spdmCompetenceCenterCondition: '仿真地图库',
|
||||
spdmCompetenceCenterConditionDetail: '仿真地图库详情',
|
||||
spdmCompetenceCenterStandardScene: '标准场景库',
|
||||
spdmCompetenceCenterKnowledge: '仿真标准库',
|
||||
spdmCompetenceCenterParameter: '仿真参数库',
|
||||
|
||||
@@ -11,8 +11,33 @@ import mittBus from '/@/utils/mitt';
|
||||
import {useTenantInfo} from '/@/stores/tenant';
|
||||
import {useRoutesList} from '/@/stores/routesList';
|
||||
import LayoutDefault from '/@/layout/main/defaults.vue';
|
||||
import {useUserInfo} from '/@/stores/userInfo';
|
||||
import {logout, useUserInfo} from '/@/stores/userInfo';
|
||||
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();
|
||||
|
||||
@@ -143,6 +143,12 @@ export default [
|
||||
component: () => import('/@/spdm/views/index.vue'),
|
||||
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',
|
||||
name: 'moduleRoutes.spdmCompetenceCenterStandardScene',
|
||||
|
||||
@@ -377,6 +377,16 @@ export const appList = [
|
||||
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',
|
||||
},
|
||||
},
|
||||
spdmCompetenceCenterConditionDetail: {
|
||||
path: '/spdm/competenceCenter/condition/detail',
|
||||
name: 'moduleRoutes.spdmCompetenceCenterConditionDetail',
|
||||
meta: {
|
||||
icon: 'fa fa-map',
|
||||
code: 'spdm_CompetenceCenterCondition_view',
|
||||
},
|
||||
},
|
||||
spdmCompetenceCenterStandardScene: {
|
||||
path: '/spdm/competenceCenter/standardScene',
|
||||
name: 'moduleRoutes.spdmCompetenceCenterStandardScene',
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
import Cookies from 'js-cookie';
|
||||
import { loginHeartbeat } from '/@/spdm/utils/index';
|
||||
import { defineStore } from 'pinia';
|
||||
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 { avatarFormat } from '/@/utils/commonFunction';
|
||||
import { ITenant } from '/@/api/admin/tenant';
|
||||
import Cookies from 'js-cookie';
|
||||
import { loginHeartbeat } from '/@/spdm/utils/index';
|
||||
import { slidingTimeout } from '/@/utils/slidingTimeout';
|
||||
|
||||
export const logout = async () => {
|
||||
await logoutApi();
|
||||
// 清除缓存/token等
|
||||
Session.clear();
|
||||
// 使用 reload 时,不需要调用 resetRoute() 重置路由
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
/**
|
||||
* @function useUserInfo
|
||||
@@ -58,11 +67,12 @@ export const useUserInfo = defineStore('userInfo', {
|
||||
login(data)
|
||||
.then(({ data: res }) => {
|
||||
// SPDM CODE
|
||||
Cookies.set('cid_user_id', res.user_id)
|
||||
Cookies.set('cid_user_id', res.user_id);
|
||||
Session.setTenant(res.tenant_id);
|
||||
// 存储token 信息
|
||||
Token.set(res.access_token);
|
||||
Session.set(REFRESH_TOKEN_KEY, res.refresh_token);
|
||||
slidingTimeout.updateTimeout();
|
||||
resolve(res);
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -85,7 +95,7 @@ export const useUserInfo = defineStore('userInfo', {
|
||||
login({
|
||||
grant_type: 'remote',
|
||||
scope: 'server',
|
||||
...resp.data
|
||||
...resp.data,
|
||||
})
|
||||
.then(({ data: res }) => {
|
||||
Session.setTenant(res.tenant_id);
|
||||
@@ -103,7 +113,7 @@ export const useUserInfo = defineStore('userInfo', {
|
||||
const codeMsg: Record<string, string> = {
|
||||
'TENANT-0057': '校验同步用户账号密码失败(员工编号或密码不存在)!',
|
||||
'TENANT-0058': '校验同步用户账号密码失败(员工停用,无法登录)!"',
|
||||
}
|
||||
};
|
||||
useMessage().error(codeMsg[err?.errorCode] || '系统异常请联系管理员');
|
||||
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) {
|
||||
let json = <string>window.localStorage.getItem(Local.setKey(key));
|
||||
try {
|
||||
return JSON.parse(json);
|
||||
} catch (error) {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
// 移除永久缓存
|
||||
remove(key: string) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Echarts class="h-80 w-full" :option="option"/>
|
||||
<Echarts class="h-full w-full" :option="option"/>
|
||||
</template>
|
||||
<script setup lang="ts" name="log-line-chart">
|
||||
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>
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<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 class="deptTreeContainer">
|
||||
<el-tree
|
||||
class="mt20"
|
||||
:default-expand-all="false"
|
||||
:data="tree"
|
||||
:props="{ children: 'children', label: 'deptName', value: 'deptId' }"
|
||||
:expand-on-click-node="false"
|
||||
ref="deptTreeRef"
|
||||
node-key="id"
|
||||
node-key="deptId"
|
||||
:loading="loading"
|
||||
:current-node-key="deptId || DEFAULT_ROOT_ID"
|
||||
highlight-current
|
||||
default-expand-all
|
||||
@node-click="handleNodeClick"
|
||||
>
|
||||
<template #default="{ node, data }" v-if="$slots.default">
|
||||
@@ -20,12 +33,18 @@
|
||||
</template>
|
||||
</el-tree>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DEFAULT_ROOT_ID } from './constant';
|
||||
import { getDeptList, IDeptIntroTree } from '/@/api/admin/deptManage';
|
||||
import { useTenantInfo } from '/@/stores/tenant';
|
||||
import { Search } from '@element-plus/icons-vue';
|
||||
|
||||
const props = defineProps<{
|
||||
deptId?: string;
|
||||
}>();
|
||||
const { t } = useI18n();
|
||||
const emit = defineEmits(['nodeClick']);
|
||||
const tenantStore = useTenantInfo();
|
||||
@@ -51,7 +70,7 @@ const tree = computed(() => {
|
||||
const { companyName } = currentTenant.value;
|
||||
|
||||
const root: IDeptIntroTree = {
|
||||
deptId: '0',
|
||||
deptId: DEFAULT_ROOT_ID,
|
||||
deptName: companyName,
|
||||
children: [],
|
||||
};
|
||||
@@ -59,7 +78,7 @@ const tree = computed(() => {
|
||||
return [root];
|
||||
});
|
||||
const handleNodeClick = (item: IDeptIntroTree) => {
|
||||
if (item.deptId === '0') {
|
||||
if (item.deptId === DEFAULT_ROOT_ID) {
|
||||
emit('nodeClick');
|
||||
} else {
|
||||
emit('nodeClick', item.deptId);
|
||||
@@ -70,3 +89,14 @@ onMounted(() => {
|
||||
getDeptData();
|
||||
});
|
||||
</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>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import { DEFAULT_ROOT_ID } from './constant';
|
||||
import { getDeptList, IDeptIntroTree } from '/@/api/admin/deptManage';
|
||||
import { getStaffDetail, updateStaff } from '/@/api/admin/staffManage';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
@@ -96,7 +97,7 @@ const staffForm = ref<{
|
||||
email: '',
|
||||
posts: [],
|
||||
roles: [],
|
||||
dept: '',
|
||||
dept: DEFAULT_ROOT_ID,
|
||||
});
|
||||
|
||||
const dataRules = ref({
|
||||
@@ -142,7 +143,7 @@ const getDetail = async (id: string) => {
|
||||
email,
|
||||
posts,
|
||||
roles,
|
||||
dept: depts[0],
|
||||
dept: depts[0] || DEFAULT_ROOT_ID,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@@ -156,7 +157,7 @@ const getDeptTree = async () => {
|
||||
}
|
||||
const {companyName} = currentTenant.value;
|
||||
const root: IDeptIntroTree = {
|
||||
deptId: '0',
|
||||
deptId: DEFAULT_ROOT_ID,
|
||||
deptName: companyName,
|
||||
children: [],
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="layout-padding">
|
||||
<div class="flex">
|
||||
<div class="tree">
|
||||
<DeptTree @node-click="handleNodeClick" />
|
||||
<DeptTree :dept-id="deptId" @node-click="handleNodeClick" />
|
||||
</div>
|
||||
<div class="table ml-2">
|
||||
<StaffTable :dept-id="deptId" :posts="posts" :roles="roles" />
|
||||
|
||||
@@ -81,6 +81,7 @@
|
||||
</el-button>
|
||||
</div>
|
||||
<TableRender
|
||||
v-loading="loading"
|
||||
class="min-h-[440px]"
|
||||
:data="tableData"
|
||||
:columns="tableColumns"
|
||||
@@ -104,6 +105,10 @@ import {ElButton} from 'element-plus';
|
||||
import EditForm from './editForm.vue';
|
||||
import InviteForm from './inviteForm.vue';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { onlyResolvesLast } from '/@/utils/onlyResolvesLast';
|
||||
|
||||
const getStaffListApi = onlyResolvesLast(getStaffList);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const editFormRef = ref<InstanceType<typeof EditForm>>();
|
||||
@@ -132,6 +137,15 @@ type QueryForm = {
|
||||
roles: string[];
|
||||
posts: string[];
|
||||
};
|
||||
|
||||
const loading = ref(false);
|
||||
const setTrue = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
const setFalse = () => {
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const initialQueryForm = (): QueryForm => {
|
||||
return {
|
||||
phone: '',
|
||||
@@ -277,8 +291,9 @@ const queryStaffs = () => {
|
||||
};
|
||||
|
||||
const getDataList = async () => {
|
||||
setTrue();
|
||||
try {
|
||||
const {data} = await getStaffList(queryParams.value);
|
||||
const { data } = await getStaffListApi({ ...queryParams.value, deptId: props.deptId });
|
||||
pagination.size = data.size;
|
||||
pagination.current = data.current;
|
||||
pagination.total = data.total;
|
||||
@@ -303,6 +318,8 @@ const getDataList = async () => {
|
||||
console.log(toRaw(tableData.value));
|
||||
} catch (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">
|
||||
import {getTaskCountApi} from '/@/api/admin/tenant';
|
||||
import {getTenantAssignmentsListByUsername} from '/@/api/admin/user';
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
<script lang="ts">
|
||||
import {i18n} from "/@/i18n"
|
||||
|
||||
export default {
|
||||
title: "components.audit-log.0910665-0", // 审计日志
|
||||
title: 'components.audit-log.0910665-0', // 审计日志
|
||||
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>
|
||||
<template>
|
||||
<el-card class="box-card h-[400px]">
|
||||
<el-card class="box-card h-full w-full">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ $t('home.auditLogsTip') }}</span>
|
||||
|
||||
@@ -3,11 +3,17 @@ export default {
|
||||
title: 'components.calendar.0910666-0', // 日程管理
|
||||
icon: 'Calendar',
|
||||
description: 'components.calendar.0910666-1', // 日历组件展示
|
||||
w: 6,
|
||||
h: 14,
|
||||
minW: 3,
|
||||
minH: 5,
|
||||
maxW: 12,
|
||||
maxH: 16,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-card class="h-[400px] box-card">
|
||||
<el-card class="h-full w-full box-card">
|
||||
<Calendar
|
||||
ref="calendar"
|
||||
view="weekly"
|
||||
@@ -23,7 +29,7 @@ export default {
|
||||
/>
|
||||
<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)">
|
||||
<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">
|
||||
<div class="flex items-center space-x-4">
|
||||
<!--Icon-->
|
||||
|
||||
@@ -3,6 +3,12 @@ export default {
|
||||
title: 'components.favorite-flow.0910773-0', // '常用流程'
|
||||
icon: 'Star',
|
||||
description: 'components.favorite-flow.0910773-1', // '常用流程收藏'
|
||||
w: 6,
|
||||
h: 9,
|
||||
minW: 3,
|
||||
minH: 5,
|
||||
maxW: 12,
|
||||
maxH: 16,
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
|
||||
@@ -3,6 +3,12 @@ export default {
|
||||
title: 'components.favorite-menu.0910774-0', // '常用功能'
|
||||
icon: 'Star',
|
||||
description: 'components.favorite-menu.0910774-1', // '常用功能收藏'
|
||||
w: 6,
|
||||
h: 5,
|
||||
minW: 3,
|
||||
minH: 5,
|
||||
maxW: 12,
|
||||
maxH: 16,
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
|
||||
@@ -3,10 +3,16 @@ export default {
|
||||
title: 'components.news.0910777-0', // '系统公告'
|
||||
icon: 'MuteNotification',
|
||||
description: 'components.news.0910777-1', // '系统公告展示'
|
||||
w: 6,
|
||||
h: 10,
|
||||
minW: 3,
|
||||
minH: 5,
|
||||
maxW: 12,
|
||||
maxH: 16,
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<el-card class="h-[400px]">
|
||||
<el-card class="h-full w-full">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ $t('home.newsletterTip') }}</span>
|
||||
@@ -15,7 +21,7 @@ export default {
|
||||
</template>
|
||||
<el-timeline v-if="newsList.length > 0">
|
||||
<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">
|
||||
{{ item.readFlag === '1' ? $t('msg.readed') : $t('msg.unread') }}
|
||||
</button>
|
||||
|
||||
@@ -3,6 +3,12 @@ export default {
|
||||
title: 'components.sys-log-line.0910980-0', // '请求折线图'
|
||||
icon: 'DocumentCopy',
|
||||
description: 'components.sys-log-line.0910980-1', // '折线图示例'
|
||||
w: 6,
|
||||
h: 14,
|
||||
minW: 3,
|
||||
minH: 5,
|
||||
maxW: 12,
|
||||
maxH: 16,
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
|
||||
@@ -3,6 +3,12 @@ export default {
|
||||
title: 'components.sys-log.0910779-0', // '系统日志'
|
||||
icon: 'Monitor',
|
||||
description: 'components.sys-log.0910779-1', // '系统日志列表'
|
||||
w: 6,
|
||||
h: 10,
|
||||
minW: 3,
|
||||
minH: 5,
|
||||
maxW: 12,
|
||||
maxH: 16,
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
|
||||
Reference in New Issue
Block a user