diff --git a/src/api/car/carRecord.ts b/src/api/car/carRecord.ts index 48d2f54..5f1eb66 100644 --- a/src/api/car/carRecord.ts +++ b/src/api/car/carRecord.ts @@ -1,5 +1,17 @@ import request from "/@/utils/request" +/* start 人闸统计 start */ + +export function totalPage(params: any) { + return request({ + url: '/admin/car/total/page', + method: 'get', + params, + }) +} + +/* end 人闸统计 end */ + export function fetchList(query?: Object) { return request({ url: '/admin/car/carRecord/page', diff --git a/src/api/device/deviceGroup.ts b/src/api/device/deviceGroup.ts index f029b0e..a6c4a8a 100644 --- a/src/api/device/deviceGroup.ts +++ b/src/api/device/deviceGroup.ts @@ -11,7 +11,6 @@ export function getDeviceGroupTree(params: any) { }) } - /** * 添加设施分组子项 */ diff --git a/src/api/door/doorRecord.ts b/src/api/door/doorRecord.ts index a65234a..4f27527 100644 --- a/src/api/door/doorRecord.ts +++ b/src/api/door/doorRecord.ts @@ -1,5 +1,18 @@ import request from "/@/utils/request" +/* start 人闸统计 start */ + +export function totalPage(params: any) { + return request({ + url: '/admin/door/total/page', + method: 'get', + params, + }) +} + + +/* end 人闸统计 end */ + /** * 加入考勤的人员在位数量分页 */ diff --git a/src/components/Select/SelectDeviceGroup.vue b/src/components/Select/SelectDeviceGroup.vue new file mode 100644 index 0000000..07cbdec --- /dev/null +++ b/src/components/Select/SelectDeviceGroup.vue @@ -0,0 +1,98 @@ + + + diff --git a/src/components/index.ts b/src/components/index.ts index 436510c..588a7ff 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -25,6 +25,7 @@ import TagUserStatus from "/@/components/Tag/TagUserStatus.vue" import SelectArea from "/@/components/Select/SelectArea.vue" import SelectDevice from "/@/components/Select/SelectDevice.vue" +import SelectDeviceGroup from "/@/components/Select/SelectDeviceGroup.vue" import SelectDuty from "/@/components/Select/SelectDuty.vue" import SelectDutyType from "/@/components/Select/SelectDutyType.vue" import SelectTreeChannel from "/@/components/Select/SelectTreeChannel.vue" @@ -84,6 +85,7 @@ export default { app.component('SelectArea', SelectArea); app.component('SelectDevice', SelectDevice); + app.component('SelectDeviceGroup', SelectDeviceGroup) app.component('SelectDuty', SelectDuty); app.component('SelectDutyType', SelectDutyType); app.component('SelectTreeChannel', SelectTreeChannel); diff --git a/src/utils/image.ts b/src/utils/image.ts new file mode 100644 index 0000000..8474f4a --- /dev/null +++ b/src/utils/image.ts @@ -0,0 +1,8 @@ +export const paseSingleImage = (urlStr: any) => { + if (!urlStr) return '' + const urlList = urlStr.split(',') + if (urlList.length > 0) { + return urlList[0] + } + return '' +} diff --git a/src/utils/poll.ts b/src/utils/poll.ts new file mode 100644 index 0000000..6a6315c --- /dev/null +++ b/src/utils/poll.ts @@ -0,0 +1,325 @@ +/** + * 轮询回调函数类型 + */ +type PollCallback = () => Promise | void; + +/** + * 错误处理回调函数类型 + */ +type ErrorCallback = (error: Error, retryCount: number, executionCount: number) => Promise | void; + +/** + * 轮询配置选项接口 + */ +interface PollOptions { + callback?: PollCallback; + interval?: number; + immediate?: boolean; + maxRetries?: number; + onError?: ErrorCallback; +} + +/** + * 轮询状态接口 + */ +interface PollStatus { + isRunning: boolean; + isPaused: boolean; + interval: number; + executionCount: number; + retryCount: number; +} + +/** + * 轮询类 - 用于管理定时轮询任务 + */ +class Poll { + private callback: PollCallback; + private interval: number; + private immediate: boolean; + private maxRetries: number; + private onError: ErrorCallback; + + // 内部状态 + private timerId: ReturnType | null; + private isRunning: boolean; + private isPaused: boolean; + private retryCount: number; + private executionCount: number; + + /** + * 构造函数 + * @param options 配置选项 + */ + constructor(options: PollOptions = {}) { + this.callback = options.callback || (() => { }); + this.interval = options.interval || 1000; + this.immediate = options.immediate ?? true; + this.maxRetries = options.maxRetries || 3; + this.onError = options.onError || ((error: Error) => console.error("轮询错误:", error)); + + // 内部状态 + this.timerId = null; + this.isRunning = false; + this.isPaused = false; + this.retryCount = 0; + this.executionCount = 0; + + // 绑定this上下文 + this._execute = this._execute.bind(this); + } + + /** + * 初始化轮询配置 + * @param callback 轮询回调函数 + * @param interval 轮询间隔时间(毫秒) + * @param options 其他配置选项 + */ + init(callback?: PollCallback, interval?: number, options: Partial = {}): Poll { + // 如果正在运行,先停止 + if (this.isRunning) { + this.stop(); + } + + this.callback = callback || this.callback; + this.interval = interval || this.interval; + this.immediate = options.immediate !== undefined ? options.immediate : this.immediate; + this.maxRetries = options.maxRetries !== undefined ? options.maxRetries : this.maxRetries; + this.onError = options.onError || this.onError; + + // 重置状态 + this.retryCount = 0; + this.executionCount = 0; + + console.log(`轮询已初始化,间隔: ${this.interval}ms`); + return this; + } + + /** + * 开始轮询 + */ + start(): Poll { + if (this.isRunning) { + console.warn("轮询已在运行中"); + return this; + } + + if (!this.callback || typeof this.callback !== "function") { + throw new Error("轮询回调函数未定义或不是函数"); + } + + this.isRunning = true; + this.isPaused = false; + + console.log("开始轮询..."); + + // 如果设置了立即执行,先执行一次 + if (this.immediate) { + this._execute(); + } else { + this._scheduleNext(); + } + + return this; + } + + /** + * 暂停轮询 + */ + pause(): Poll { + if (!this.isRunning) { + console.warn("轮询未在运行中"); + return this; + } + + this.isPaused = true; + this._clearTimer(); + console.log("轮询已暂停"); + return this; + } + + /** + * 恢复轮询 + */ + resume(): Poll { + if (!this.isRunning) { + console.warn("轮询未在运行中"); + return this; + } + + if (!this.isPaused) { + console.warn("轮询未暂停"); + return this; + } + + this.isPaused = false; + this._scheduleNext(); + console.log("轮询已恢复"); + return this; + } + + /** + * 停止轮询 + */ + stop(): Poll { + if (!this.isRunning) { + console.warn("轮询未在运行中"); + return this; + } + + this.isRunning = false; + this.isPaused = false; + this._clearTimer(); + console.log("轮询已停止"); + return this; + } + + /** + * 销毁轮询实例 + */ + destroy(): void { + this.stop(); + this.callback = (() => { }) as PollCallback; + this.onError = ((error: Error) => console.error("轮询错误:", error)) as ErrorCallback; + this.retryCount = 0; + this.executionCount = 0; + console.log("轮询实例已销毁"); + } + + /** + * 立即执行一次轮询(不影响定时器) + */ + executeOnce(): Poll { + if (typeof this.callback === "function") { + this._execute(false); // false表示不调度下一次 + } + return this; + } + + /** + * 更新轮询间隔 + * @param newInterval 新的间隔时间(毫秒) + */ + updateInterval(newInterval: number): Poll { + if (typeof newInterval !== "number" || newInterval <= 0) { + throw new Error("间隔时间必须是正数"); + } + + this.interval = newInterval; + + // 如果正在运行且未暂停,重新调度 + if (this.isRunning && !this.isPaused) { + this._clearTimer(); + this._scheduleNext(); + } + + console.log(`轮询间隔已更新为: ${this.interval}ms`); + return this; + } + + /** + * 获取轮询状态 + */ + getStatus(): PollStatus { + return { + isRunning: this.isRunning, + isPaused: this.isPaused, + interval: this.interval, + executionCount: this.executionCount, + retryCount: this.retryCount, + }; + } + + /** + * 内部方法:执行轮询回调 + * @param scheduleNext 是否调度下一次执行 + */ + private async _execute(scheduleNext = true): Promise { + if (!this.isRunning || this.isPaused) { + return; + } + + try { + this.executionCount++; + + // 执行回调函数 + await this.callback(); + + // 重置重试计数 + this.retryCount = 0; + + // 调度下一次执行 + if (scheduleNext && this.isRunning && !this.isPaused) { + this._scheduleNext(); + } + } catch (error) { + this.retryCount++; + console.error(`轮询执行失败 (第${this.retryCount}次重试):`, error); + + // 调用错误处理回调 + if (typeof this.onError === "function") { + try { + await this.onError(error as Error, this.retryCount, this.executionCount); + } catch (errorHandlerError) { + console.error("错误处理回调执行失败:", errorHandlerError); + } + } + + // 检查是否需要重试 + if (this.retryCount < this.maxRetries) { + console.log(`将在 ${this.interval}ms 后重试...`); + if (scheduleNext && this.isRunning && !this.isPaused) { + this._scheduleNext(); + } + } else { + console.error(`达到最大重试次数 (${this.maxRetries}),停止轮询`); + this.stop(); + } + } + } + + /** + * 内部方法:调度下一次执行 + */ + private _scheduleNext(): void { + this._clearTimer(); + this.timerId = setTimeout(this._execute, this.interval); + } + + /** + * 内部方法:清除定时器 + */ + private _clearTimer(): void { + if (this.timerId) { + clearTimeout(this.timerId); + this.timerId = null; + } + } +} + +export default Poll; + +/** + * 工厂函数,方便快速创建轮询实例 + * @param callback 轮询回调函数 + * @param interval 轮询间隔时间(毫秒) + * @param options 其他配置选项 + */ +export function createPoll(callback: PollCallback, interval?: number, options: Partial = {}): Poll { + return new Poll({ + callback, + interval, + ...options, + }); +} + +/** + * 简化的轮询函数,用于简单场景 + * @param callback 轮询回调函数 + * @param interval 轮询间隔时间(毫秒),默认1000ms + */ +export function simplePoll(callback: PollCallback, interval: number = 1000): Poll { + const poll = new Poll({ callback, interval }); + poll.start(); + return poll; +} diff --git a/src/views/duty/dutyPlan/components/car-list.vue b/src/views/duty/dutyPlan/components/car-list.vue index e8cdb74..bb621f3 100644 --- a/src/views/duty/dutyPlan/components/car-list.vue +++ b/src/views/duty/dutyPlan/components/car-list.vue @@ -1,32 +1,94 @@ \ No newline at end of file diff --git a/src/views/duty/dutyPlan/index.vue b/src/views/duty/dutyPlan/index.vue index b650f00..852ddf2 100644 --- a/src/views/duty/dutyPlan/index.vue +++ b/src/views/duty/dutyPlan/index.vue @@ -31,9 +31,15 @@ const VideoPay = defineAsyncComponent(() => import('./components/video-pay.vue')