feat:军校bug修改

This commit is contained in:
dzc committed 2026-06-02 17:56:45 +08:00
1 parent 55c7d77c11
commit cdb7496ade
35 files changed
+4043 -202

No files matched your search

+2 -1
View File
@@ -14,4 +14,5 @@ VITE_ADMIN_PROXY_PATH = http://127.0.0.1:36588
# VITE_ADMIN_PROXY_PATH = http://172.20.10.2:36588
# 图片访问基础地址
VITE_IMAGE_BASE_PATH = http://123.157.250.162:36588/api
# VITE_IMAGE_BASE_PATH = http://123.157.250.162:36588/api
VITE_IMAGE_BASE_PATH = http://127.0.0.1:36588/api
+2410 -116
View File
File diff suppressed because it is too large. Load diff
+5 -1
View File
@@ -9,6 +9,7 @@
"build": "cross-env NODE_OPTIONS=--max-old-space-size=4096 vite build",
"build:prod": "cross-env NODE_OPTIONS=--max-old-space-size=4096 vite build --mode prod",
"build:docker": "cross-env NODE_OPTIONS=--max-old-space-size=4096 vite build --outDir ./docker/dist/",
"test:unit": "vitest run",
"lint:eslint": "eslint --fix --ext .js,.ts,.vue ./src",
"prettier": "prettier --write ."
},
@@ -75,11 +76,13 @@
"@typescript-eslint/parser": "^5.53.0",
"@vitejs/plugin-vue": "^4.0.0",
"@vue/compiler-sfc": "^3.2.47",
"@vue/test-utils": "^2.4.10",
"consola": "^2.15.3",
"cross-env": "7.0.3",
"daisyui": "4.11.1",
"eslint": "^8.34.0",
"eslint-plugin-vue": "^9.9.0",
"jsdom": "^24.1.3",
"pinia-plugin-persist": "^1.0.0",
"prettier": "2.8.4",
"sass": "1.58.3",
@@ -91,6 +94,7 @@
"vite-plugin-style-import": "^2.0.0",
"vite-plugin-top-level-await": "^1.3.0",
"vite-plugin-vue-setup-extend": "^0.4.0",
"vitest": "^1.6.1",
"vue-eslint-parser": "^9.1.0"
},
"browserslist": [
@@ -116,4 +120,4 @@
"type": "git",
"url": "https://gitee.com/log4j/pig"
}
}
}
+16
View File
@@ -31,6 +31,22 @@ export function delObjs(ids?: Object) {
})
}
export function delPlayback(ids?: Object) {
return request({
url: '/admin/car/carRecord/del/playback',
method: 'delete',
data: ids
})
}
export function recoverPlayback(ids?: Object) {
return request({
url: '/admin/car/carRecord/recover/playback',
method: 'post',
data: ids
})
}
export function putObj(obj?: Object) {
return request({
url: '/admin/car/carAccess',
+9
View File
@@ -0,0 +1,9 @@
import request from "/@/utils/request"
export function notClockPage(query?: Object) {
return request({
url: '/admin//clock-record/not-clock-page',
method: 'get',
params: query
})
}
+24
View File
@@ -105,6 +105,22 @@ export function putObj(obj?: Object) {
})
}
export function delPlayback(ids?: Object) {
return request({
url: '/admin/door/doorRecord/del/playback',
method: 'delete',
data: ids
})
}
export function recoverPlayback(ids?: Object) {
return request({
url: '/admin/door/doorRecord/recover/playback',
method: 'post',
data: ids
})
}
/* ************************************* start 人员出入次数统计 start ************************************* */
export function doorTimesPage(obj: any) {
@@ -115,6 +131,14 @@ export function doorTimesPage(obj: any) {
})
}
export function jglTimesPage(obj: any) {
return request({
url: '/admin/door/doorTimes/jglPage',
method: 'get',
params: obj
})
}
export function doorTimesExport(obj: any) {
return request({
url: '/admin/door/doorTimes/export',
+41
View File
@@ -0,0 +1,41 @@
import request from "/@/utils/request"
export function fetchList(query?: Object) {
return request({
url: '/admin/routing-manage/page',
method: 'get',
params: query
})
}
export function addObj(obj?: Object) {
return request({
url: '/admin/routing-manage',
method: 'post',
data: obj
})
}
export function getObj(id?: string) {
return request({
url: '/admin/routing-manage/' + id,
method: 'get'
})
}
export function delObjs(ids?: Object) {
return request({
url: '/admin/routing-manage',
method: 'delete',
data: ids
})
}
export function putObj(obj?: Object) {
return request({
url: '/admin/routing-manage',
method: 'put',
data: obj
})
}
+56
View File
@@ -0,0 +1,56 @@
import request from "/@/utils/request"
export function fetchList(query?: Object) {
return request({
url: '/admin/service-manage/page',
method: 'get',
params: query
})
}
export function addObj(obj?: Object) {
return request({
url: '/admin/service-manage',
method: 'post',
data: obj
})
}
export function getObj(id?: string) {
return request({
url: '/admin/service-manage/' + id,
method: 'get'
})
}
export function delObjs(ids?: Object) {
return request({
url: '/admin/service-manage',
method: 'delete',
data: ids
})
}
export function putObj(obj?: Object) {
return request({
url: '/admin/service-manage',
method: 'put',
data: obj
})
}
export function start(query?: Object) {
return request({
url: '/admin/service-manage/start',
method: 'get',
params: query
})
}
export function done(query?: Object) {
return request({
url: '/admin/service-manage/done',
method: 'get',
params: query
})
}
+63 -19
View File
@@ -1,11 +1,16 @@
<template>
<div>
<el-button :style="jsonViewStyle" type="text" link @click="clickJsonView">
<!-- <div :style="jsonViewStyle" class="json-view">{{ jsonView }}</div> -->
<div :style="jsonViewStyle" class="json-view">查看json</div>
<div class="json-view-container">
<div class="json-view">{{ jsonView }}</div>
<div class="json-view-btn">查看json</div>
</div>
</el-button>
<el-dialog append-to-body v-model="jsonViewVisible">
<vue-json-pretty :data="jsonData" showLength showLineNumber showIcon :collapsedOnClickBrackets="false" />
<div v-if="displayData.type === 'html'" v-html="displayData.content" class="html-preview"></div>
<div v-else-if="displayData.type === 'text'" class="text-preview">{{ displayData.content }}</div>
<vue-json-pretty v-else :data="displayData.content" showLength showLineNumber showIcon
:collapsedOnClickBrackets="false" />
<template #footer>
<el-button @click="jsonViewVisible = false">关闭</el-button>
</template>
@@ -24,7 +29,7 @@ type Props = {
}
const state = reactive({
jsonData: {},
jsonData: null as any,
})
const props = withDefaults(defineProps<Props>(), {
@@ -34,12 +39,25 @@ const props = withDefaults(defineProps<Props>(), {
const jsonViewVisible = ref<boolean>(false)
const jsonData = computed(() => {
// 先用lazyJson
if (state.jsonData) return state.jsonData
if (!props.json) return ""
if (typeof props.json == "string") return JSON.parse(props.json)
return props.json
const displayData = computed(() => {
// 优先使用 lazyJson 返回的数据,如果没有则使用 props.json
const raw = state.jsonData || props.json;
if (!raw) return { type: 'empty', content: '' };
let content = raw;
if (typeof raw === 'string') {
try {
content = JSON.parse(raw);
return { type: 'json', content };
} catch (e) {
// 解析 JSON 失败,判断是否包含 HTML 标签
if (/<\/?[a-z][\s\S]*>/i.test(raw)) {
return { type: 'html', content: raw };
}
return { type: 'text', content: raw };
}
}
return { type: 'json', content: raw };
})
const jsonView = computed(() => {
@@ -56,23 +74,49 @@ const jsonViewStyle = computed(() => {
// 点击事件
const clickJsonView = async () => {
jsonViewVisible.value = true
console.log("props:", props);
if (props.lazyJson) {
const string = await props.lazyJson()
if (string) {
if (typeof string == "string") {
state.jsonData = JSON.parse(string)
} else {
try {
const string = await props.lazyJson()
if (string) {
state.jsonData = string
}
} catch (error) {
console.error("lazyJson error:", error)
}
}
}
</script>
<style lang="scss" scoped>
.json-view {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
.json-view-container {
width: 100%;
display: flex;
align-items: center;
justify-items: start;
.json-view {
max-width: 150px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.json-view-btn {
color: var(--el-color-primary);
}
}
.html-preview {
max-height: 60vh;
overflow: auto;
}
.text-preview {
max-height: 60vh;
overflow: auto;
white-space: pre-wrap;
word-break: break-all;
}
</style>
+48 -5
View File
@@ -21,6 +21,7 @@
</template>
<script setup lang="ts" name="select-tree-channel">
import { onMounted, reactive, watch } from 'vue'
import * as DeviceManageApi from '/@/api/device/deviceManage'
const emit = defineEmits(["change"])
@@ -51,20 +52,34 @@ const queryForm = reactive<any>({
})
const state = reactive<any>({
loading: true,
loading: false,
selectId: selectId,
options: [],
type: [],
loaded: false,
})
let loadOptionsTask: Promise<void> | null = null
const hasSelectedValue = (value: unknown) => {
if (Array.isArray(value)) {
return value.some((item) => item !== null && item !== undefined && `${item}`.trim() !== '')
}
return value !== null && value !== undefined && `${value}`.trim() !== ''
}
const shouldLoadOptionsOnInit = (value: unknown) => props.autoFirst || hasSelectedValue(value)
watch(
selectId,
(newVal, oldValue) => {
if (newVal == oldValue) return
state.selectId = newVal
// getOptions()
if (shouldLoadOptionsOnInit(newVal)) {
void ensureOptionsLoaded()
}
},
{ immediate: true },
{ immediate: true, deep: true },
)
const filterNode = (value: string, data: any, node: any) => {
@@ -79,7 +94,34 @@ const filterNode = (value: string, data: any, node: any) => {
return false
}
async function getOptions() {
watch(
() => props.apiParams,
() => {
if (shouldLoadOptionsOnInit(selectId.value) || state.loaded) {
state.loaded = false
void ensureOptionsLoaded(true)
}
},
{ deep: true },
)
onMounted(() => {
if (shouldLoadOptionsOnInit(selectId.value)) {
void ensureOptionsLoaded()
}
})
async function ensureOptionsLoaded(force = false) {
if (state.loaded && !force) return
if (loadOptionsTask) return loadOptionsTask
loadOptionsTask = getOptions(force).finally(() => {
loadOptionsTask = null
})
return loadOptionsTask
}
async function getOptions(force = false) {
if (state.loaded && !force) return
try {
state.loading = true
const _pa = {
@@ -88,12 +130,13 @@ async function getOptions() {
}
const { data } = await DeviceManageApi.channelTree(_pa)
state.options = data ?? []
state.loaded = true
if (props.autoFirst && state.options.length != 0) {
state.selectId = state.options[0].id
emit("change", state.selectId)
}
} finally {
setTimeout(() => (state.loading = false), 500)
state.loading = false
}
}
</script>
@@ -7,7 +7,7 @@
<template #label>
<div class="flex items-center gap-1">
<span>过车间隔</span>
<el-tooltip class="box-item" :content="`每前后${form.cleaningData.carGap}分钟同一过车数据算一次`" placement="top">
<el-tooltip class="box-item" :content="`每前后${form.cleaningData.carGap}秒同一过车数据算一次`" placement="top">
<el-icon :size="16">
<InfoFilled />
</el-icon>
@@ -15,14 +15,14 @@
</div>
</template>
<el-input v-model="form.cleaningData.carGap" placeholder="请输入过车间隔">
<template #append>分钟</template>
<template #append>秒</template>
</el-input>
</el-form-item>
<el-form-item prop="cleaningData.personGap">
<template #label>
<div class="flex items-center gap-1">
<span>过人间隔</span>
<el-tooltip class="box-item" :content="`每前后${form.cleaningData.personGap}分钟同一过人数据算一次`"
<el-tooltip class="box-item" :content="`每前后${form.cleaningData.personGap}秒同一过人数据算一次`"
placement="top">
<el-icon :size="16">
<InfoFilled />
@@ -31,7 +31,7 @@
</div>
</template>
<el-input v-model="form.cleaningData.personGap" placeholder="请输入过人间隔">
<template #append>分钟</template>
<template #append>秒</template>
</el-input>
</el-form-item>
<!-- **************************** end 同步间隔配置 end **************************** -->
+11 -11
View File
@@ -64,25 +64,25 @@
</el-form-item>
<!-- **************************** end 夜晚出入办公楼 end **************************** -->
<!-- **************************** start 打卡 start **************************** -->
<el-divider class="my-12">打卡配置</el-divider>
<!-- **************************** start 出勤 start **************************** -->
<el-divider class="my-12">出勤配置</el-divider>
<el-form-item prop="personDk.forenoon">
<template #label>
<div class="flex items-center gap-1">
<span>打卡时间(上午)</span>
<span>出勤时间(上午)</span>
</div>
</template>
<el-time-picker format="HH:mm" value-format="HH:mm" v-model="form.personDk.forenoon" is-range
placeholder="请选择打卡时间(上午)" />
placeholder="请选择出勤时间(上午)" />
</el-form-item>
<el-form-item prop="personDk.afternoon">
<template #label>
<div class="flex items-center gap-1">
<span>打卡时间(下午)</span>
<span>出勤时间(下午)</span>
</div>
</template>
<el-time-picker format="HH:mm" value-format="HH:mm" v-model="form.personDk.afternoon" is-range
placeholder="请选择打卡时间(下午)" />
placeholder="请选择出勤时间(下午)" />
</el-form-item>
<el-form-item prop="personDk.cameraChannels">
<template #label>
@@ -108,7 +108,7 @@
</template>
<select-tree-dept v-model="form.personDk.deptCodes" placeholder="请选择部门" />
</el-form-item>
<!-- **************************** end 打卡 end **************************** -->
<!-- **************************** end 出勤 end **************************** -->
<!-- **************************** start 考勤 start **************************** -->
@@ -180,7 +180,7 @@ const form = reactive<any>({
alarmTime: [],
cameraChannels: [],
},
// 人员打卡配置
// 人员出勤配置
personDk: {
forenoon: [],
afternoon: [],
@@ -201,9 +201,9 @@ const dataRules = ref({
"personNightCamp.alarmTime": [{ required: true, message: '夜晚出入时间不能为空', trigger: 'blur' }],
// 人员晚上出入办公楼
"personNightOffice.alarmTime": [{ required: true, message: '夜晚出入时间不能为空', trigger: 'blur' }],
// 人员打卡配置
"personDk.forenoon": [{ required: true, message: '打卡时间(上午)不能为空', trigger: 'blur' }],
"personDk.afternoon": [{ required: true, message: '打卡时间(下午)不能为空', trigger: 'blur' }],
// 人员出勤配置
"personDk.forenoon": [{ required: true, message: '出勤时间(上午)不能为空', trigger: 'blur' }],
"personDk.afternoon": [{ required: true, message: '出勤时间(下午)不能为空', trigger: 'blur' }],
// 人员考勤配置
"personKq.forenoon": [{ required: true, message: '考勤时间(上午)不能为空', trigger: 'blur' }],
"personKq.afternoon": [{ required: true, message: '考勤时间(下午)不能为空', trigger: 'blur' }],
+4 -2
View File
@@ -89,6 +89,7 @@ import { fetchList, delObjs } from "/@/api/area/areaHouse";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { useDict } from '/@/hooks/dict';
import { usePanel } from "/@/stores/panel"
import dayjs from "dayjs";
const storesPanel = usePanel()
@@ -119,7 +120,7 @@ const {
currentChangeHandle,
sizeChangeHandle,
sortChangeHandle,
downBlobFile,
postDownBlobFile,
tableStyle
} = useTable(state)
@@ -152,7 +153,8 @@ onBeforeUnmount(() => {
// 导出excel
const exportExcel = () => {
downBlobFile('/area/areaHouse/export', Object.assign(state.queryForm, { ids: selectObjs }), 'areaHouse.xlsx')
const timeSuffix = dayjs().format('YYYY-MM-DD_HH-mm-ss')
postDownBlobFile('/admin/areaHouse/export', Object.assign(state.queryForm, { ids: selectObjs }), `房屋管理_${timeSuffix}.xlsx`)
}
// 多选事件
+10 -2
View File
@@ -26,6 +26,9 @@
v-auth="'admin_areaManage_add'">
新 增
</el-button>
<el-button plain icon="upload-filled" class="ml10" @click="uploadExcelRef.show()">
导 入
</el-button>
<el-button plain :disabled="multiple" icon="Delete" type="primary" v-auth="'admin_areaManage_del'"
@click="handleDelete(selectObjs)">
删除
@@ -77,6 +80,8 @@
<form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
<area-house-table-dialog ref="areaHouseTableDialogRef" />
<area-playback-device-table-dialog ref="areaPlaybackDeviceTableDialogRef" />
<upload-excel ref="uploadExcelRef" title="空间导入" url="/admin/areaManage/read-excel"
temp-url="/admin/sys-file/local/file/area_import_temp.xlsx" @refreshDataList="getDataList(false)" />
</div>
</template>
@@ -85,6 +90,7 @@ import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObjs } from "/@/api/area/areaManage";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { useDict } from '/@/hooks/dict';
import dayjs from "dayjs";
// 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
@@ -99,6 +105,7 @@ const areaPlaybackDeviceTableDialogRef = ref()
const bindCameraDialogRef = ref()
// 搜索变量
const uploadExcelRef = ref()
const queryRef = ref()
const showSearch = ref(true)
// 多选变量
@@ -116,7 +123,7 @@ const {
currentChangeHandle,
sizeChangeHandle,
sortChangeHandle,
downBlobFile,
postDownBlobFile,
tableStyle
} = useTable(state)
@@ -131,7 +138,8 @@ const resetQuery = () => {
// 导出excel
const exportExcel = () => {
downBlobFile('/admin/areaManage/export', Object.assign(state.queryForm, { ids: selectObjs }), 'areaManage.xlsx')
const timeSuffix = dayjs().format('YYYY-MM-DD_HH-mm-ss')
postDownBlobFile('/admin/areaManage/export', Object.assign(state.queryForm, { ids: selectObjs }), `空间管理_${timeSuffix}.xlsx`)
}
// 多选事件
+42 -3
View File
@@ -140,12 +140,20 @@
</template>
</el-table-column>
</el-table-column>
<el-table-column label="操作" width="100" fixed="right">
<el-table-column label="操作" width="180" fixed="right">
<template #default="{ row }">
<el-button v-if="row.devChnId" icon="VideoCamera" text type="primary"
@click="openReplayDialog(row)">
<el-button v-if="row.devChnId && row.delPlaybackVideoFlag == '0'" icon="VideoCamera" text
type="primary" @click="openReplayDialog(row)">
回放
</el-button>
<el-button v-if="row.delPlaybackVideoFlag == '0'" v-auth="'car_carRecord_del_playback'"
icon="delete" text type="primary" @click="delPlaybackVideo(row)">
删除回放
</el-button>
<el-button v-if="row.delPlaybackVideoFlag == '1'" v-auth="'car_carRecord_recover_playback'"
icon="delete" text type="primary" @click="recoverPlaybackVideo(row)">
恢复回放
</el-button>
</template>
</el-table-column>
</el-table>
@@ -164,6 +172,7 @@ import { useDict } from '/@/hooks/dict';
import { parseTime } from "/@/utils/formatTime"
import { usePanel } from "/@/stores/panel"
import { getDefaultAlarmDateList } from "/@/utils/other"
import { useMessage, useMessageBox } from "/@/hooks/message";
const storesPanel = usePanel()
@@ -261,6 +270,36 @@ function openReplayDialog(row: any) {
replayVideoDialogRef.value.openDialog('playbackGroup', row.devChnId, { startTime, endTime })
}
async function delPlaybackVideo(row: any) {
try {
await useMessageBox().confirm('此操作将删除回放功能');
} catch {
return;
}
try {
await CarAccessApi.delPlayback([row.id]);
getDataList();
useMessage().success('删除成功');
} catch (err: any) {
useMessage().error(err.msg);
}
}
async function recoverPlaybackVideo(row: any) {
try {
await useMessageBox().confirm('此操作将恢复回放功能');
} catch {
return;
}
try {
await CarAccessApi.recoverPlayback([row.id]);
getDataList();
useMessage().success('恢复成功');
} catch (err: any) {
useMessage().error(err.msg);
}
}
</script>
<style lang="scss" scoped>
+1 -1
View File
@@ -221,7 +221,7 @@ async function dataCleaning() {
background: 'rgba(0, 0, 0, 0.7)',
})
try {
await carRecordApi.dataCleaning()
await carRecordApi.dataCleaning(state.queryForm)
} catch (error) {
console.error("|清洗数据|意外的错误:", error);
} finally {
+2 -2
View File
@@ -8,12 +8,12 @@
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="设备编码" prop="deviceCode">
<el-form-item label="设备编码">
<el-input v-model="form.deviceCode" clearable maxlength="100" placeholder="请输入设备编码" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="设备唯一标识码" prop="deviceSn">
<el-form-item label="设备唯一标识码">
<el-input v-model="form.deviceSn" clearable maxlength="100" placeholder="请输入设备唯一标识码" />
</el-form-item>
</el-col>
+8
View File
@@ -30,6 +30,9 @@
v-auth="'admin_deviceManage_add'">
新 增
</el-button>
<el-button plain icon="upload-filled" class="ml10" @click="uploadExcelRef.show()">
导 入
</el-button>
<el-button plain :disabled="multiple" icon="Delete" type="primary" v-auth="'admin_deviceManage_del'"
@click="handleDelete(selectObjs)">
删除
@@ -45,6 +48,7 @@
<el-table-column type="selection" width="40" align="center" />
<el-table-column type="index" label="#" width="80" />
<el-table-column prop="deviceCode" align="left" min-width="100" label="设备编码" />
<el-table-column prop="deviceSn" align="left" min-width="150" label="设备唯一标识码" />
<el-table-column prop="deviceName" align="left" min-width="150" label="设备名称" />
<el-table-column prop="deviceIp" align="left" min-width="150" label="ip" />
<el-table-column prop="devicePort" align="left" min-width="100" label="端口" />
@@ -92,6 +96,8 @@
</draggable-org-Tree>
</div>
<upload-excel ref="uploadExcelRef" title="设备导入" url="/admin/deviceManage/read-excel"
temp-url="/admin/sys-file/local/file/device_import_temp.xlsx" @refreshDataList="getDataList(false)" />
<!-- 编辑、新增 -->
<form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
</div>
@@ -107,6 +113,8 @@ import dayjs from "dayjs";
const storesPanel = usePanel()
const uploadExcelRef = ref()
// 引入组件
const FormDialog = defineAsyncComponent(() => import('../components/deviceForm.vue'));
// 定义查询字典
+42 -3
View File
@@ -153,12 +153,20 @@
</template>
</el-table-column>
<el-table-column prop="remark" label="开门失败原因" min-width="200" show-overflow-tooltip />
<el-table-column label="操作" min-width="100" fixed="right">
<el-table-column label="操作" min-width="180" fixed="right">
<template #default="{ row }">
<el-button v-if="row.channelCode" icon="VideoCamera" text type="primary"
@click="openReplayDialog(row)">
<el-button v-if="row.channelCode && row.delPlaybackVideoFlag == '0'" icon="VideoCamera" text
type="primary" @click="openReplayDialog(row)">
回放
</el-button>
<el-button v-if="row.delPlaybackVideoFlag == '0'" v-auth="'doorRecord_del_play'" icon="delete"
text type="primary" @click="delPlaybackVideo(row)">
删除回放
</el-button>
<el-button v-if="row.delPlaybackVideoFlag == '1'" v-auth="'doorRecord_recover_playback'"
icon="delete" text type="primary" @click="recoverPlaybackVideo(row)">
恢复回放
</el-button>
</template>
</el-table-column>
</el-table>
@@ -177,6 +185,7 @@ import { useDict } from '/@/hooks/dict';
import { parseTime } from "/@/utils/formatTime"
import { usePanel } from "/@/stores/panel"
import { getDefaultAlarmDateList } from "/@/utils/other"
import { useMessage, useMessageBox } from "/@/hooks/message";
const storesPanel = usePanel()
@@ -277,6 +286,36 @@ function openReplayDialog(row: any) {
replayVideoDialogRef.value.openDialog('playbackGroup', row.channelCode, { startTime, endTime })
}
async function delPlaybackVideo(row: any) {
try {
await useMessageBox().confirm('此操作将删除回放功能');
} catch {
return;
}
try {
await DoorRecordApi.delPlayback([row.id]);
getDataList();
useMessage().success('删除成功');
} catch (err: any) {
useMessage().error(err.msg);
}
}
async function recoverPlaybackVideo(row: any) {
try {
await useMessageBox().confirm('此操作将恢复回放功能');
} catch {
return;
}
try {
await DoorRecordApi.recoverPlayback([row.id]);
getDataList();
useMessage().success('恢复成功');
} catch (err: any) {
useMessage().error(err.msg);
}
}
</script>
<style lang="scss" scoped>
+133
View File
@@ -0,0 +1,133 @@
<!-- 未出勤人数 -->
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<el-row shadow="hover" v-show="showSearch" class="ml10">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item style="width: 250px;" label="姓名或手机号" prop="nameOrPhone">
<el-input v-model="state.queryForm.nameOrPhone" placeholder="请输入姓名或手机号" />
</el-form-item>
<el-form-item>
<el-button icon="search" type="primary" @click="getDataList">
{{ $t('common.queryBtn') }}
</el-button>
<el-button icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button>
</el-form-item>
</el-form>
</el-row>
<el-table :data="state.dataList" v-loading="state.loading" border :cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle" @selection-change="selectionChangHandle"
@sort-change="sortChangeHandle">
<el-table-column type="index" label="#" width="80" />
<el-table-column prop="code" min-width="100" label="未打卡时间">
<template #default="{ row }">
<span class="color-danger">{{ state.queryForm.clockDate }}</span>
</template>
</el-table-column>
<el-table-column prop="code" min-width="100" label="人员编号">
<template #default="{ row }">
<div v-if="row.code">
<span v-if="row.code">{{ row.code }}</span>
<span v-else class="color-danger">未知</span>
</div>
<div v-else>
<span class="color-danger">访客</span>
</div>
</template>
</el-table-column>
<el-table-column prop="personName" min-width="100" label="姓名">
<template #default="{ row }">
<div v-if="row.name">
<span v-if="row.name">{{ row.name }}</span>
<span v-else class="color-danger">未知</span>
</div>
<div v-else>
<span class="color-danger">访客</span>
</div>
</template>
</el-table-column>
<el-table-column prop="paperNumber" min-width="100" label="证件号码" show-overflow-tooltip>
<template #default="{ row }">
<div v-if="row.code">
<span v-if="row.paperNumber">{{ row.paperNumber }}</span>
<span v-else class="color-danger">未知</span>
</div>
<div v-else>
<span class="color-danger">访客</span>
</div>
</template>
</el-table-column>
<el-table-column prop="deptName" min-width="100" label="部门名称">
<template #default="{ row }">
<div v-if="row.code">
<span v-if="row.deptName">{{ row.deptName }}</span>
<span v-else class="color-danger">未知</span>
</div>
<div v-else>
<span class="color-danger">访客</span>
</div>
</template>
</el-table-column>
</el-table>
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
</div>
</template>
<script setup lang="ts" name="doorNotReignDialog">
import { BasicTableProps, useTable } from "/@/hooks/table";
import * as clockRecordApi from "/@/api/clock/clock-record";
const route = useRoute();
// 搜索变量
const queryRef = ref()
// 定义变量内容
const showSearch = ref(true)
// 多选变量
const selectObjs = ref([]) as any
const multiple = ref(true)
const state: BasicTableProps = reactive<BasicTableProps>({
createdIsNeed: false,
pagination: {
size: 10,
},
queryForm: {
},
pageList: clockRecordApi.notClockPage
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
sortChangeHandle,
tableStyle
} = useTable(state)
// 清空搜索条件
const resetQuery = () => {
// 清空搜索条件
queryRef.value?.resetFields()
getDataList()
}
// 多选事件
const selectionChangHandle = (objs: { id: string }[]) => {
selectObjs.value = objs.map(({ id }) => id);
multiple.value = !objs.length;
};
onMounted(() => {
const time = route?.query?.time
if (time) {
state.queryForm.clockDate = time
getDataList()
}
})
</script>
+7 -1
View File
@@ -210,6 +210,12 @@ onBeforeMount(() => {
}
storesPanel.openFromPanel()
}
if (query && query.action == 'admin') {
if (query.params) {
Object.assign(state.queryForm, JSON.parse(query.params))
getDataList()
}
}
})
onBeforeUnmount(() => {
@@ -238,7 +244,7 @@ async function dataCleaning() {
background: 'rgba(0, 0, 0, 0.7)',
})
try {
await doorRecordApi.dataCleaning({})
await doorRecordApi.dataCleaning(state.queryForm)
} catch (error) {
console.error("|清洗数据|意外的错误", error);
} finally {
+57 -7
View File
@@ -32,15 +32,44 @@
<span class="color-primary">{{ row.weekStr }}</span>
</template>
</el-table-column>
<el-table-column min-width="100" align="center" label="打卡人数">
<el-table-column min-width="100" align="center" label="出勤人数">
<el-table-column prop="reignCount" min-width="100" align="center" label="人闸识别">
<template #default="{ row }">
<div class="color-success">
<span> {{ row.reignCount ?? 0 }}</span>
<span>次</span>
</div>
</template>
</el-table-column>
<el-table-column prop="carReignCount" min-width="100" align="center" label="道闸识别">
<template #default="{ row }">
<div class="color-success">
<span> {{ row.carReignCount ?? 0 }}</span>
<span>次</span>
</div>
</template>
</el-table-column>
<el-table-column prop="totalReignCount" min-width="100" align="center" label="总识别数">
<template #default="{ row }">
<div class="color-success">
<span> {{ row.totalReignCount ?? 0 }}</span>
<span>次</span>
</div>
</template>
</el-table-column>
</el-table-column>
<el-table-column prop="notReignCount" min-width="100" align="center" label="未出勤人数">
<template #default="{ row }">
<div class="color-success">
<span> {{ row.reignCount }}</span>
<div class="not-reign-count color-danger" @click="doNotReignCount(row)">
<span> {{ row.notReignCount ?? 0 }}</span>
<span>次</span>
<span v-if="row.notReignCount" style="font-size: 12px; margin-left: 4px; opacity: 0.8;">
(点击查看)
</span>
</div>
</template>
</el-table-column>
<el-table-column min-width="100" align="center" label="异常进入人数">
<el-table-column prop="lateCount" min-width="100" align="center" label="异常进入人数">
<template #default="{ row }">
<div class="color-danger">
<span> {{ row.lateCount }}</span>
@@ -48,7 +77,7 @@
</div>
</template>
</el-table-column>
<el-table-column min-width="100" align="center" label="异常外出人数">
<el-table-column prop="earlyCount" min-width="100" align="center" label="异常外出人数">
<template #default="{ row }">
<div class="color-danger">
<span> {{ row.earlyCount }}</span>
@@ -123,6 +152,13 @@ const exportExcel = () => {
downBlobFile('/door/doorRecord/export', Object.assign(state.queryForm, { ids: selectObjs }), 'doorRecord.xlsx')
}
const doNotReignCount = (row: any) => {
push({
path: "/door/doorNotReign/index",
query: { time: row.time }
})
}
// 多选事件
const selectionChangHandle = (objs: { id: string }[]) => {
selectObjs.value = objs.map(({ id }) => id);
@@ -131,7 +167,7 @@ const selectionChangHandle = (objs: { id: string }[]) => {
const getSummaries = (param: any) => {
const { columns, data } = param
const totalColumns = ["preCount"]
const totalColumns = ["reignCount", "carReignCount", "totalReignCount", "notReignCount", "lateCount", "earlyCount"]
const totalRow = Array.from({ length: columns.length })
totalRow[0] = "合计"
columns.forEach((i: any, index: number) => {
@@ -158,4 +194,18 @@ function addDays(dateStr: string, days: number) {
date.setDate(date.getDate() + days);
return date.toISOString().split('T')[0];
};
</script>
</script>
<style lang="scss" scoped>
.not-reign-count {
cursor: pointer;
display: inline-block;
transition: transform 0.2s ease-in-out;
&:hover {
text-decoration: underline;
text-decoration-color: var(--el-color-danger);
transform: scale(1.1);
}
}
</style>
+1 -1
View File
@@ -51,7 +51,7 @@
</el-table-column>
</el-table-column>
<el-table-column min-width="100" label="统计">
<el-table-column min-width="100" label="打卡次数">
<el-table-column min-width="100" label="出勤次数">
<template #default="{ row }">
<div class="color-danger">
<span> {{ row.reignCount ?? 0 }}</span>
+6
View File
@@ -15,6 +15,12 @@
<dict-select v-model="state.queryForm.openType" :options="dh_door_open_type"
placeholder="请选择进开门类型" />
</el-form-item>
<el-form-item style="width: 250px;" label="人员类型" prop="personType">
<select-person-car-type areaType="person" v-model="state.queryForm.personType" />
</el-form-item>
<el-form-item style="width: 250px;" label="区域" prop="areaType">
<select-area-type areaType="person" v-model="state.queryForm.areaType" />
</el-form-item>
<el-form-item>
<el-button icon="search" type="primary" @click="getDataList">
{{ $t('common.queryBtn') }}
+195
View File
@@ -0,0 +1,195 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<el-row shadow="hover" v-show="showSearch" class="ml10">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item style="width: 400px;" label="日期" prop="swingTimeList">
<el-date-picker v-model="state.queryForm.swingTimeList" value-format="YYYY-MM-DD HH:mm:ss"
type="daterange" />
</el-form-item>
<el-form-item>
<el-button icon="search" type="primary" @click="getDataList">
{{ $t('common.queryBtn') }}
</el-button>
<el-button icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button>
</el-form-item>
</el-form>
</el-row>
<el-table :data="state.dataList" v-loading="state.loading" border :cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle" @selection-change="selectionChangHandle"
@sort-change="sortChangeHandle" show-summary :summary-method="getSummaries">
<el-table-column min-width="140" label="日期">
<template #default="{ row }">
<span>{{ row.time }}</span>
<span> - </span>
<span class="color-primary">{{ row.weekStr }}</span>
</template>
</el-table-column>
<el-table-column prop="enterCount" align="center" min-width="200" label="进门总次数">
<template #default="{ row }">
<div class="color-success enter-count" @click="doEnterPage(row)">
<span class="color-success">{{ row.enterCount }}</span>
<span>次</span>
<span v-if="row.enterCount" style="font-size: 12px; margin-left: 4px; opacity: 0.8;">
(点击查看)
</span>
</div>
</template>
</el-table-column>
<el-table-column prop="exitCount" align="center" min-width="200" label="出门总次数">
<template #default="{ row }">
<div class="color-danger exit-count" @click="doExitPage(row)">
<span class="color-danger">{{ row.exitCount }}</span>
<span>次</span>
<span v-if="row.exitCount" style="font-size: 12px; margin-left: 4px; opacity: 0.8;">
(点击查看)
</span>
</div>
</template>
</el-table-column>
<el-table-column prop="exitOrExitCount" align="center" min-width="200" label="进/出门总次数">
<template #default="{ row }">
<span class="color-primary">{{ row.exitOrExitCount }}次</span>
</template>
</el-table-column>
<el-table-column prop="insideCount" align="center" min-width="200" label="预计在楼总人数">
<template #default="{ row }">
<span class="color-primary">{{ row.insideCount }}人</span>
</template>
</el-table-column>
</el-table>
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
</div>
</template>
<script setup lang="ts" name="jglTotal">
import { BasicTableProps, useTable } from "/@/hooks/table";
import { useDict } from '/@/hooks/dict';
import dayjs from "dayjs"
const { push } = useRouter()
import * as DoorRecordApi from "/@/api/door/doorRecord";
// 搜索变量
const queryRef = ref()
const showSearch = ref(true)
// 多选变量
const selectObjs = ref([]) as any
const multiple = ref(true)
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {},
pageList: DoorRecordApi.jglTimesPage
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
sortChangeHandle,
downBlobFile,
tableStyle
} = useTable(state)
// 清空搜索条件
const resetQuery = () => {
// 清空搜索条件
queryRef.value?.resetFields()
// 清空多选
selectObjs.value = []
getDataList()
}
// 多选事件
const selectionChangHandle = (objs: { id: string }[]) => {
selectObjs.value = objs.map(({ id }) => id);
multiple.value = !objs.length;
};
// 去查询进入机关楼人员信息
const doEnterPage = (row: any) => {
push({
path: "/door/doorRecord/index",
query: {
action: "admin",
params: JSON.stringify({
personType: '1',
areaType: 'BGL',
openType: '61',
swingTimeList: [
dayjs(row.time).startOf('day').format("YYYY-MM-DD HH:mm:ss"),
dayjs(row.time).endOf('day').format("YYYY-MM-DD HH:mm:ss"),
],
})
}
})
}
// 去查询出门机关楼人员信息
const doExitPage = (row: any) => {
push({
path: "/door/doorRecord/index",
query: {
action: "admin",
params: JSON.stringify({
// personType: '1',
areaType: 'BGL',
openType: '57',
swingTimeList: [
dayjs(row.time).startOf('day').format("YYYY-MM-DD HH:mm:ss"),
dayjs(row.time).endOf('day').format("YYYY-MM-DD HH:mm:ss"),
],
}),
}
})
}
const getSummaries = (param: any) => {
const { columns, data } = param
const totalColumns = ["enterCount", "exitCount", "exitOrExitCount"]
const totalRow = Array.from({ length: columns.length })
totalRow[0] = "合计"
columns.forEach((i: any, index: number) => {
if (totalColumns.includes(i.property)) {
const total = data
.filter((v: any) => v[i.property] != null)
.map((v: any) => Number(v[i.property]))
.reduce((pren: number, next: number) => pren + next, 0)
totalRow[index] = `${total}次`
}
})
return totalRow
}
</script>
<style lang="scss" scoped>
.enter-count {
cursor: pointer;
display: inline-block;
transition: transform 0.2s ease-in-out;
&:hover {
text-decoration: underline;
text-decoration-color: var(--el-color-success);
transform: scale(1.1);
}
}
.exit-count {
cursor: pointer;
display: inline-block;
transition: transform 0.2s ease-in-out;
&:hover {
text-decoration: underline;
text-decoration-color: var(--el-color-danger);
transform: scale(1.1);
}
}
</style>
+11 -14
View File
@@ -7,17 +7,12 @@
<!-- Steps Navigation -->
<div class="flex-2 text-center max-w-[600px]">
<span
v-for="(step, index) in steps"
:key="index"
class="inline-block px-5 py-2.5 cursor-pointer"
<span v-for="(step, index) in steps" :key="index" class="inline-block px-5 py-2.5 cursor-pointer"
:class="{ 'border-b-2 border-primary text-primary': activeStep === index }"
@click="activeStep = index"
>
@click="activeStep = index">
<span
class="mr-1.5 inline-block w-6 h-6 text-base font-normal text-center leading-[22px] border rounded-full"
:class="[activeStep === index ? 'bg-primary text-white' : 'border-current']"
>
:class="[activeStep === index ? 'bg-primary text-white' : 'border-current']">
{{ index + 1 }}
</span>
<span class="text-lg font-medium">{{ $t(step.title) }}</span>
@@ -47,18 +42,18 @@
<div class="text-center">
<!-- Success Result -->
<el-result v-if="validateFlowStep === 3" icon="success" :title="$t('flow.checkSuccess')" :sub-title="$t('flow.checkSubSuccess')">
<el-result v-if="validateFlowStep === 3" icon="success" :title="$t('flow.checkSuccess')"
:sub-title="$t('flow.checkSubSuccess')">
<template #extra>
<el-button type="primary" :loading="isSubmitting" @click="submitFlow">{{ $t('flow.submit') }}</el-button>
<el-button type="primary" :loading="isSubmitting" @click="submitFlow">{{ $t('flow.submit')
}}</el-button>
</template>
</el-result>
<!-- Loading Result -->
<el-result
v-if="validateErrMsg.length === 0 && validateDialogShow && validatingShow && validateFlowStep < 3"
:title="$t('flow.checkIng')"
:sub-title="$t('flow.checkSubIng')"
>
:title="$t('flow.checkIng')" :sub-title="$t('flow.checkSubIng')">
<template #icon>
<span class="inline-block w-25 h-25" v-loading="true"></span>
</template>
@@ -68,7 +63,9 @@
<el-result v-if="validateErrMsg.length > 0" icon="error" title="检查失败">
<template #sub-title>
<div v-for="item in validateErrMsg" :key="item" class="text-red-500">
<el-icon><WarnTriangleFilled /></el-icon>
<el-icon>
<WarnTriangleFilled />
</el-icon>
{{ item }}
</div>
</template>
+1 -1
View File
@@ -117,7 +117,7 @@ const getThemeConfig = computed(() => {
*/
const jumpAdminPanel = () => {
const isDev = import.meta.env.MODE == "development"
const baseAdminUrl = isDev ? 'http://localhost:5173/device' : 'http://192.168.50.10:7001/device';
const baseAdminUrl = isDev ? 'http://localhost:5173/device' : 'http://123.157.250.162:7001/device';
const roleCodes = useUserInfoStore.userInfos?.roleCodes ?? []
if (!roleCodes || !roleCodes.length) {
openPlanUrl(baseAdminUrl, true)
+16 -2
View File
@@ -51,13 +51,13 @@
<el-table-column prop="req" label="请求参数" width="250">
<template #default="{ row }">
<preview-json :json="row.req" />
<preview-json :lazyJson="() => getJson(row, 'req')" :json="row.req" />
</template>
</el-table-column>
<el-table-column prop="res" label="请求响应" width="250">
<template #default="{ row }">
<preview-json :json="{ html: row.res }" />
<preview-json :lazyJson="() => getJson(row, 'res')" :json="{ html: row.res }" />
</template>
</el-table-column>
@@ -123,6 +123,20 @@ const exportExcel = () => {
downBlobFile('/third/thirdLog/export', Object.assign(state.queryForm, { ids: selectObjs }), 'thirdLog.xlsx')
}
// 获取日志详情
const getJson = async (row: any, key: string) => {
try {
const { data, code } = await ThirdLogApi.getObj(row.id)
if (code != 0) {
throw new Error("获取日志详情失败")
}
return data[key]
} catch (error) {
console.log("获取日志详情,意外的错误error:", error);
return ""
}
}
// 多选事件
const selectionChangHandle = (objs: { id: string }[]) => {
selectObjs.value = objs.map(({ id }) => id);
+123
View File
@@ -0,0 +1,123 @@
<template>
<el-dialog :title="form.id ? '编辑' : '新增'" v-model="visible" :close-on-click-modal="false" draggable>
<el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px" v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="标题" prop="title">
<el-input v-model="form.title" placeholder="请输入巡检标题" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="巡检人" prop="assigneeId">
<select-person v-model="form.assigneeId" placeholder="请选择巡检人" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="巡检时间" prop="routingDate">
<el-date-picker v-model="form.routingDate" value-format="YYYY-MM-DD HH:mm:ss" type="datetime"
placeholder="请选择巡检时间" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="描述" prop="description">
<el-input type="textarea" v-model="form.description" :autosize="{ minRows: 6 }" placeholder="请输入巡检描述" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading">确认</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="RoutingManageDialog">
import { dayjs } from 'element-plus';
import { useDict } from '/@/hooks/dict';
import { useMessage } from "/@/hooks/message";
import * as routingManageApi from "/@/api/todo/routingManage";
import { rule } from '/@/utils/validate';
const emit = defineEmits(['refresh']);
// 定义变量内容
const dataFormRef = ref();
const visible = ref(false)
const loading = ref(false)
// 定义字典
// 提交表单数据
const form = reactive({
id: '',
title: '',
description: '',
assigneeId: '',
assigneeName: '',
routingDate: '',
});
const getDefaultRoutingDate = () => dayjs().format('YYYY-MM-DD HH:mm:ss');
// 定义校验规则
const dataRules = ref({
title: [{ required: true, message: '标题不能为空', trigger: 'blur' }],
routingDate: [{ required: true, message: '巡检时间不能为空', trigger: 'change' }],
})
// 打开弹窗
const openDialog = (id: string) => {
visible.value = true
form.id = ''
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields();
if (!id) {
form.routingDate = getDefaultRoutingDate();
}
});
// 获取routingManage信息
if (id) {
form.id = id
getroutingManageData(id)
}
};
// 提交
const onSubmit = async () => {
const valid = await dataFormRef.value.validate().catch(() => { });
if (!valid) return false;
try {
loading.value = true;
form.id ? await routingManageApi.putObj(form) : await routingManageApi.addObj(form);
useMessage().success(form.id ? '修改成功' : '添加成功');
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
};
// 初始化表单数据
const getroutingManageData = (id: string) => {
// 获取数据
loading.value = true
routingManageApi.getObj(id).then((res: any) => {
Object.assign(form, res.data)
}).finally(() => {
loading.value = false
})
};
// 暴露变量
defineExpose({
openDialog
});
</script>
+137
View File
@@ -0,0 +1,137 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<el-row>
<div class="mb8" style="width: 100%">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item style="width: 250px;" label="标题" prop="title">
<el-input v-model="state.queryForm.title" placeholder="请输入标题" />
</el-form-item>
<el-form-item style="width: 480px;" label="巡检创建时间" prop="createTimeList">
<el-date-picker v-model="state.queryForm.createTimeList" value-format="YYYY-MM-DD HH:mm:ss"
type="datetimerange" />
</el-form-item>
<el-form-item style="width: 480px;" label="巡检时间" prop="routingDateList">
<el-date-picker v-model="state.queryForm.routingDateList" value-format="YYYY-MM-DD HH:mm:ss"
type="datetimerange" />
</el-form-item>
<el-form-item>
<el-button icon="search" type="primary" @click="getDataList">
{{ $t('common.queryBtn') }}
</el-button>
<el-button icon="Refresh" @click="resetQuery">
{{ $t('common.resetBtn') }}
</el-button>
</el-form-item>
</el-form>
<el-button icon="folder-add" type="primary" class="ml10" @click="formDialogRef.openDialog()"
v-auth="'admin_routingManage_add'">
新 增
</el-button>
<el-button plain :disabled="multiple" icon="Delete" type="primary" v-auth="'admin_routingManage_del'"
@click="handleDelete(selectObjs)">
删除
</el-button>
<!-- :export="'admin_routingManage_export'" -->
<right-toolbar v-model:showSearch="showSearch" @exportExcel="exportExcel" class="ml10 mr20"
style="float: right;" @queryTable="getDataList"></right-toolbar>
</div>
</el-row>
<el-table :data="state.dataList" v-loading="state.loading" border :cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle" @selection-change="selectionChangHandle"
@sort-change="sortChangeHandle">
<el-table-column type="selection" width="40" align="center" />
<el-table-column type="index" label="#" width="40" />
<el-table-column prop="title" label="标题" show-overflow-tooltip />
<el-table-column prop="assigneeName" label="巡检人" show-overflow-tooltip />
<el-table-column prop="routingDate" label="巡检时间" width="180" />
<el-table-column prop="description" label="备注" show-overflow-tooltip />
<el-table-column label="操作" width="150">
<template #default="scope">
<el-button icon="edit-pen" text type="primary" v-auth="'admin_routingManage_edit'"
@click="formDialogRef.openDialog(scope.row.id)">编辑</el-button>
<el-button icon="delete" text type="primary" v-auth="'admin_routingManage_del'"
@click="handleDelete([scope.row.id])">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
</div>
<!-- 编辑、新增 -->
<form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
</div>
</template>
<script setup lang="ts" name="systemRoutingManage">
import { BasicTableProps, useTable } from "/@/hooks/table";
import * as routingManageApi from "/@/api/todo/routingManage";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { useDict } from '/@/hooks/dict';
// 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
// 定义查询字典
// 定义变量内容
const formDialogRef = ref()
// 搜索变量
const queryRef = ref()
const showSearch = ref(true)
// 多选变量
const selectObjs = ref([]) as any
const multiple = ref(true)
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {},
pageList: routingManageApi.fetchList
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
sortChangeHandle,
downBlobFile,
tableStyle
} = useTable(state)
// 清空搜索条件
const resetQuery = () => {
// 清空搜索条件
queryRef.value?.resetFields()
// 清空多选
selectObjs.value = []
getDataList()
}
// 导出excel
const exportExcel = () => {
downBlobFile('/admin/routingManage/export', Object.assign(state.queryForm, { ids: selectObjs }), 'routingManage.xlsx')
}
// 多选事件
const selectionChangHandle = (objs: { id: string }[]) => {
selectObjs.value = objs.map(({ id }) => id);
multiple.value = !objs.length;
};
// 删除操作
const handleDelete = async (ids: string[]) => {
try {
await useMessageBox().confirm('此操作将永久删除');
} catch {
return;
}
try {
await routingManageApi.delObjs(ids);
getDataList();
useMessage().success('删除成功');
} catch (err: any) {
useMessage().error(err.msg);
}
};
</script>
+144
View File
@@ -0,0 +1,144 @@
<template>
<el-dialog :title="'详情'" width="70%" v-model="visible" :close-on-click-modal="false" draggable>
<div class="header">
<div class="title">基本信息</div>
<div class="info" v-loading="loading">
<div class="info-item">
<span>标题:</span>
<span>{{ form.title }}</span>
</div>
<div class="info-item">
<span>状态:</span>
<span>
<dict-tag :options="todo_status" :value="form.status" />
</span>
</div>
<div class="info-item">
<span>优先级:</span>
<span>
<dict-tag :options="todo_priority" :value="form.priority" />
</span>
</div>
<div class="info-item">
<span>处理人:</span>
<span>{{ form.assigneeName }}</span>
</div>
<div class="info-item">
<span>预计完成时间:</span>
<span v-if="form.startDate && form.dueDate">
{{ form.startDate }} 至 {{ form.dueDate }}
</span>
<span class="color-danger" v-else>未设置</span>
</div>
<div class="info-item">
<span>创建时间:</span>
<span>{{ form.createTime }}</span>
</div>
<div class="info-item">
<span>完成时间:</span>
<span v-if="form.completedDate">{{ form.completedDate }}</span>
<span class="color-danger" v-else>未完成</span>
</div>
<div class="info-item" style="grid-column: 1 / -1;">
<span>描述:</span>
<span v-html="form.description"></span>
</div>
</div>
</div>
</el-dialog>
</template>
<script setup lang="ts" name="ServiceManageDetailDialog">
import { useDict } from '/@/hooks/dict';
import * as serviceManageApi from "/@/api/todo/serviceManage"
const visible = ref(false)
const loading = ref(false)
const { todo_priority, todo_status } = useDict("todo_priority", "todo_status")
const form = reactive<any>({
id: '',
title: '',
description: '',
status: '',
priority: '',
assigneeId: '',
assigneeName: '',
orgName: '',
startDate: '',
dueDate: '',
completedDate: '',
reminderTime: '',
remindChannel: '',
tags: '',
createTime: ''
})
const openDialog = (id: string) => {
visible.value = true
loading.value = true
serviceManageApi.getObj(id).then((res: any) => {
Object.assign(form, res.data)
}).finally(() => {
loading.value = false
})
};
defineExpose({
openDialog
});
</script>
<style lang="scss" scoped>
.info {
padding: 10px 20px;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
&-item {
display: flex;
align-items: center;
width: 100%;
>span:first-child {
width: 40%;
color: #333;
font-size: 13px;
font-weight: 500;
}
>span:last-child {
flex: 1;
color: #666;
font-size: 13px;
}
}
}
.title {
display: flex;
align-items: center;
padding: 10px 20px;
font-size: 14px;
font-weight: 500;
&::before {
content: "";
display: block;
width: 4px;
height: 15px;
margin-right: 4px;
border-radius: 2px;
background: var(--el-color-primary);
}
}
.header {
width: 100%;
background: var(--el-color-white);
border: 1px solid var(--el-border-color-light, #ebeef5);
border-radius: 4px;
}
</style>
+168
View File
@@ -0,0 +1,168 @@
<template>
<el-dialog :title="form.id ? '编辑' : '新增'" v-model="visible" :close-on-click-modal="false" draggable>
<el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="120px" v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="标题" prop="title">
<el-input v-model="form.title" placeholder="请输入标题" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="描述" prop="description">
<editor v-if="visible" v-model:get-html="form.description" placeholder="请输入描述" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="维修设备" prop="devices">
<select-tree-channel v-model="form.devices" placeholder="请选择维修设备" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="处理人" prop="assigneeId">
<select-person v-model="form.assigneeId" placeholder="请选择处理人" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="优先级" prop="priority">
<dict-select v-model="form.priority" defaultValue="1" :options="todo_priority" placeholder="请选择优先级" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="预计完成时间" prop="date">
<el-date-picker v-model="form.date" value-format="YYYY-MM-DD HH:mm:ss" type="datetimerange" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading">确认</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="ServiceManageDialog">
import { dayjs } from 'element-plus';
import { useDict } from '/@/hooks/dict';
import { useMessage } from "/@/hooks/message";
import * as serviceManageApi from '/@/api/todo/serviceManage'
import { rule } from '/@/utils/validate';
const emit = defineEmits(['refresh']);
// 定义变量内容
const dataFormRef = ref();
const visible = ref(false)
const loading = ref(false)
// 定义字典
const { todo_priority } = useDict("todo_priority")
// 提交表单数据
const defaultForm = {
id: '',
title: '',
devices: [],
description: '',
status: '',
priority: '',
assigneeId: '',
assigneeName: '',
orgName: '',
startDate: '',
dueDate: '',
completedDate: '',
date: [],
};
const form = reactive<any>({ ...defaultForm });
const getDefaultDateRange = () => {
const today = dayjs().format('YYYY-MM-DD');
return [`${today} 00:00:00`, `${today} 23:23:23`];
}
// 定义校验规则
const dataRules = ref({
title: [{ required: true, message: '标题不能为空', trigger: 'blur' }],
priority: [{ required: true, message: '优先级不能为空', trigger: 'change' }],
})
// 打开弹窗
const openDialog = (id: string) => {
Object.assign(form, {
...defaultForm,
date: id ? [] : getDefaultDateRange(),
});
visible.value = true
nextTick(() => {
dataFormRef.value?.clearValidate();
});
// 获取serviceManage信息
if (id) {
form.id = id
getserviceManageData(id)
}
};
// 提交
const onSubmit = async () => {
const valid = await dataFormRef.value.validate().catch(() => { });
if (!valid) return false;
try {
loading.value = true;
const _params = { ...form }
if (_params.date && _params.date.length == 2) {
_params.startDate = _params.date[0]
_params.dueDate = _params.date[1]
}
// 处理设备列表
_params.devices = _params.devices?.join(',') || ''
form.id ? await serviceManageApi.putObj(_params) : await serviceManageApi.addObj(_params);
useMessage().success(form.id ? '修改成功' : '添加成功');
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
};
// 初始化表单数据
const getserviceManageData = async (id: string) => {
// 获取数据
loading.value = true
try {
const { data } = await serviceManageApi.getObj(id)
Object.assign(form, {
...data,
title: data.title ?? '',
description: data.description ?? '',
status: data.status ?? '',
priority: data.priority ?? '',
assigneeId: data.assigneeId ?? '',
assigneeName: data.assigneeName ?? '',
orgName: data.orgName ?? '',
startDate: data.startDate ?? '',
dueDate: data.dueDate ?? '',
completedDate: data.completedDate ?? '',
})
form.devices = data.devices ? data.devices.split(',') : []
form.date = data.startDate && data.dueDate ? [data.startDate, data.dueDate] : []
} catch (error) {
console.log("初始化表单数据意外的错误error:", error);
} finally {
loading.value = false
}
};
// 暴露变量
defineExpose({
openDialog
});
</script>
+218
View File
@@ -0,0 +1,218 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<el-row>
<div class="mb8" style="width: 100%">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item style="width: 250px;" label="标题" prop="title">
<el-input v-model="state.queryForm.title" placeholder="请输入标题" />
</el-form-item>
<el-form-item style="width: 250px;" label="状态" prop="status">
<dict-select v-model="state.queryForm.status" dict-type="todo_status" />
</el-form-item>
<el-form-item style="width: 250px;" label="优先级" prop="priority">
<dict-select v-model="state.queryForm.priority" dict-type="todo_priority" />
</el-form-item>
<el-form-item style="width: 480px;" label="维修创建时间" prop="createTimeList">
<el-date-picker v-model="state.queryForm.createTimeList" value-format="YYYY-MM-DD HH:mm:ss"
type="datetimerange" />
</el-form-item>
<el-form-item style="width: 480px;" label="维修完成时间" prop="completedDateList">
<el-date-picker v-model="state.queryForm.completedDateList" value-format="YYYY-MM-DD HH:mm:ss"
type="datetimerange" />
</el-form-item>
<el-form-item>
<el-button icon="search" type="primary" @click="getDataList">
{{ $t('common.queryBtn') }}
</el-button>
<el-button icon="Refresh" @click="resetQuery">
{{ $t('common.resetBtn') }}
</el-button>
</el-form-item>
</el-form>
<el-button icon="folder-add" type="primary" class="ml10" @click="formDialogRef.openDialog()"
v-auth="'admin_serviceManage_add'">
新 增
</el-button>
<el-button plain :disabled="multiple" icon="Delete" type="primary" v-auth="'admin_serviceManage_del'"
@click="handleDelete(selectObjs)">
删除
</el-button>
<!-- :export="'admin_serviceManage_export'" -->
<right-toolbar v-model:showSearch="showSearch" @exportExcel="exportExcel" class="ml10 mr20"
style="float: right;" @queryTable="getDataList"></right-toolbar>
</div>
</el-row>
<el-table :data="state.dataList" v-loading="state.loading" border :cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle" @selection-change="selectionChangHandle"
@sort-change="sortChangeHandle">
<el-table-column type="selection" width="40" align="center" />
<el-table-column type="index" label="#" width="40" />
<el-table-column prop="title" label="标题" show-overflow-tooltip />
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<dict-tag :options="todo_status" :value="row.status" />
</template>
</el-table-column>
<el-table-column prop="priority" label="优先级" width="100">
<template #default="{ row }">
<dict-tag :options="todo_priority" :value="row.priority" />
</template>
</el-table-column>
<el-table-column prop="assigneeName" label="处理人" show-overflow-tooltip />
<el-table-column prop="startDate" label="预计完成时间" width="320">
<template #default="{ row }">
<div v-if="row.startDate && row.dueDate">
<span>{{ row.startDate }}</span>
<span> 至 </span>
<span>{{ row.dueDate }}</span>
</div>
<div class="color-danger" v-else>未设置</div>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="180" />
<el-table-column prop="completedDate" label="完成时间" width="180">
<template #default="{ row }">
<div v-if="row.completedDate">{{ row.completedDate }}</div>
<div class="color-danger" v-else>未完成</div>
</template>
</el-table-column>
<el-table-column fixed="right" label="操作" width="220">
<template #default="{ row }">
<el-button v-if="['1'].includes(row.status)" icon="VideoPlay" text type="primary"
v-auth="'admin_serviceManage_start'" @click="doStart(row)">
开始
</el-button>
<el-button v-if="['2', '4'].includes(row.status)" icon="CircleCheckFilled" text type="primary"
v-auth="'admin_serviceManage_done'" @click="doDone(row)">
完成
</el-button>
<el-button v-if="['3', '4'].includes(row.status)" icon="edit-pen" text type="primary"
v-auth="'admin_serviceManage_edit'" @click="formDialogRef.openDialog(row.id)">
编辑
</el-button>
<el-button v-if="!['3', '4'].includes(row.status)" icon="InfoFilled" text type="primary"
v-auth="'admin_todoRecord_edit'" @click="detailDialogRef.openDialog(row.id)">
详情
</el-button>
<el-button icon="delete" text type="primary" v-auth="'admin_serviceManage_del'"
@click="handleDelete([row.id])">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
</div>
<!-- 编辑、新增 -->
<form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
<!-- 详情 -->
<detail-dialog ref="detailDialogRef" />
</div>
</template>
<script setup lang="ts" name="systemServiceManage">
import { BasicTableProps, useTable } from "/@/hooks/table";
import * as serviceManageApi from '/@/api/todo/serviceManage'
import { useMessage, useMessageBox } from "/@/hooks/message";
import { useDict } from '/@/hooks/dict';
// 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
const DetailDialog = defineAsyncComponent(() => import('./detail.vue'));
// 定义查询字典
const { todo_priority, todo_status } = useDict("todo_priority", "todo_status")
// 定义变量内容
const formDialogRef = ref()
const detailDialogRef = ref()
// 搜索变量
const queryRef = ref()
const showSearch = ref(true)
// 多选变量
const selectObjs = ref([]) as any
const multiple = ref(true)
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {},
pageList: serviceManageApi.fetchList
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
sortChangeHandle,
downBlobFile,
tableStyle
} = useTable(state)
// 清空搜索条件
const resetQuery = () => {
// 清空搜索条件
queryRef.value?.resetFields()
// 清空多选
selectObjs.value = []
getDataList()
}
// 导出excel
const exportExcel = () => {
downBlobFile('/admin/serviceManage/export', Object.assign(state.queryForm, { ids: selectObjs }), 'serviceManage.xlsx')
}
// 多选事件
const selectionChangHandle = (objs: { id: string }[]) => {
selectObjs.value = objs.map(({ id }) => id);
multiple.value = !objs.length;
};
// 删除操作
const handleDelete = async (ids: string[]) => {
try {
await useMessageBox().confirm('此操作将永久删除');
} catch {
return;
}
try {
await serviceManageApi.delObjs(ids);
getDataList();
useMessage().success('删除成功');
} catch (err: any) {
useMessage().error(err.msg);
}
};
const doStart = async (row: any) => {
try {
await useMessageBox().confirm(`是否开始${row.title}?`);
} catch {
return;
}
try {
await serviceManageApi.start({ id: row.id })
getDataList();
useMessage().success('操作成功');
} catch (err: any) {
useMessage().error(err.msg);
}
}
const doDone = async (row: any) => {
try {
await useMessageBox().confirm(`是否完成${row.title}?`);
} catch {
return;
}
try {
await serviceManageApi.done({ id: row.id })
getDataList();
useMessage().success('操作成功');
} catch (err: any) {
useMessage().error(err.msg);
}
}
</script>
+3 -3
View File
@@ -1,6 +1,6 @@
<template>
<el-dialog :title="form.id ? '编辑' : '新增'" v-model="visible" :close-on-click-modal="false" draggable>
<el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px" v-loading="loading">
<el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="120px" v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="标题" prop="title">
@@ -9,7 +9,7 @@
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="描述" prop="description">
<editor v-model:get-html="form.description" placeholder="请输入描述" />
<el-input type="textarea" v-model="form.description" :autosize="{ minRows: 6 }" placeholder="请输入描述" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
@@ -23,7 +23,7 @@
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="完成时间" prop="date">
<el-form-item label="预计完成时间" prop="date">
<el-date-picker v-model="form.date" value-format="YYYY-MM-DD HH:mm:ss" type="datetimerange" />
</el-form-item>
</el-col>
+25 -3
View File
@@ -38,8 +38,9 @@
@click="handleDelete(selectObjs)">
删除
</el-button>
<right-toolbar v-model:showSearch="showSearch" :export="'admin_todoRecord_export'" @exportExcel="exportExcel"
class="ml10 mr20" style="float: right;" @queryTable="getDataList"></right-toolbar>
<!-- :export="'admin_todoRecord_export'" -->
<right-toolbar v-model:showSearch="showSearch" @exportExcel="exportExcel" class="ml10 mr20"
style="float: right;" @queryTable="getDataList"></right-toolbar>
</div>
</el-row>
<el-table :data="state.dataList" v-loading="state.loading" border :cell-style="tableStyle.cellStyle"
@@ -76,7 +77,7 @@
<div class="color-danger" v-else>未完成</div>
</template>
</el-table-column>
<el-table-column label="操作" width="280">
<el-table-column fixed="right" label="操作" width="280">
<template #default="{ row }">
<el-button v-if="['1'].includes(row.status)" icon="VideoPlay" text type="primary"
v-auth="'admin_todoRecord_start'" @click="doStart(row)">
@@ -116,6 +117,7 @@ import { BasicTableProps, useTable } from "/@/hooks/table";
import * as todoRecordApi from "/@/api/todo/todoRecord"
import { useMessage, useMessageBox } from "/@/hooks/message";
import { useDict } from '/@/hooks/dict';
import { usePanel } from "/@/stores/panel"
// 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
@@ -132,6 +134,8 @@ const showSearch = ref(true)
const selectObjs = ref([]) as any
const multiple = ref(true)
const storesPanel = usePanel()
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {},
pageList: todoRecordApi.fetchList
@@ -213,4 +217,22 @@ const doDone = async (row: any) => {
useMessage().error(err.msg);
}
}
onBeforeMount(() => {
const query: any = useRoute().query
if (query && query.action == 'panel') {
if (query.params) {
Object.assign(state.queryForm, JSON.parse(query.params))
getDataList()
}
storesPanel.openFromPanel()
}
})
onBeforeUnmount(() => {
const query = useRoute().query
if (query && query.action == 'panel') {
storesPanel.closeFromPanel()
}
})
</script>