You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

745 lines
20 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div>
<my-card title="搜索条件" search>
<n-form inline>
<n-form-item label="设备编码">
<n-input v-model:value="searchForm.equipmentCode" placeholder="请输入设备编码"></n-input>
</n-form-item>
<n-form-item label="故障类型">
<n-input v-model:value="searchForm.breakdownName" placeholder="请输入故障类型"></n-input>
</n-form-item>
<n-form-item label="故障描述">
<n-input v-model:value="searchForm.breakdownDescription" placeholder="请输入故障描述"></n-input>
</n-form-item>
<n-form-item label="维修方法">
<n-input v-model:value="searchForm.repairMethod" placeholder="请输入维修方法"></n-input>
</n-form-item>
<n-form-item label="报修时间">
<n-date-picker v-model:value="range" type="datetimerange" clearable />
</n-form-item>
<n-form-item label="状态">
<n-select
v-model:value="searchForm.status"
class="w-180px"
:options="[
{ label: '全部', value: '' },
{ label: '维修中', value: '0' },
{ label: '审批中', value: '1' },
{ label: '已完成', value: '2' }
]"
></n-select>
</n-form-item>
<n-form-item>
<component
:is="
useSearchBtn(
() => {
handleSearch();
},
() => {
resetThen();
}
)
"
></component>
</n-form-item>
</n-form>
</my-card>
<my-card title="报修列表">
<template #right>
<div>
<n-button size="small" type="info" style="margin-right: 10px" @click="openAddRepairs">
<icon-mdi-add />
新增报修
</n-button>
<CxColumns v-model:columns="columns" />
</div>
</template>
<n-data-table
:loading="loading"
:columns="columns"
:data="data"
:max-height="dataTableConfig.maxHeight"
:scroll-x="dataTableConfig.scrollWidth(columns)"
></n-data-table>
<my-pagination v-model:search-form="searchForm" @init="init"></my-pagination>
</my-card>
<my-dialog
title="新增报修单"
width="800px"
:show="showDialog"
@cancel="() => (showDialog = false)"
@submit="handleSubmit"
>
<template #content>
<div>
<n-form
ref="addFormRef"
:rules="rules"
style="width: 700"
:model="addForm"
label-placement="left"
label-width="auto"
>
<n-grid :cols="1" :x-gap="20">
<n-form-item-grid-item label="报修设备" path="equipmentId">
<n-select
v-model:value="addForm.equipmentId"
class="w-560px"
:options="equipmentList"
placeholder="请选择报修设备"
filterable
:disabled="!completeRequest"
@update:value="changeEquipment"
/>
</n-form-item-grid-item>
<n-form-item-grid-item label="故障类别" path="breakdownId">
<n-select
v-model:value="addForm.breakdownId"
class="w-560px"
:options="faultCategoryList"
placeholder="请选择故障类别"
filterable
@update:value="changeBreakdown"
/>
</n-form-item-grid-item>
<n-form-item-grid-item label="故障描述" path="breakdownDescription">
<n-input
v-model:value="addForm.breakdownDescription"
placeholder="请输入故障描述"
class="w-560px"
type="textarea"
size="small"
:autosize="{
minRows: 3,
maxRows: 5
}"
/>
</n-form-item-grid-item>
<n-form-item-grid-item label="上传图片" name="breakdownImage" required>
<n-upload
class="w-560px"
:show-file-list="true"
:default-file-list="fileList"
:headers="{ Authorization: 'Bearer ' + useAuthStore().token }"
:default-upload="true"
:max="3"
multiple
:action="baseurl + '/file/upload'"
list-type="image-card"
:on-error="errorFile"
@finish="finish"
@remove="removeFile"
></n-upload>
</n-form-item-grid-item>
</n-grid>
</n-form>
</div>
</template>
</my-dialog>
<div v-if="imageList.length > 0" class="image-box" @click="closeLookImages($event)">
<n-image-group :theme-overrides="imageGroupThemeOverrides">
<n-space>
<n-image v-for="(item, index) in imageList" :key="index" width="100" :src="item" />
</n-space>
</n-image-group>
</div>
</div>
</template>
<script setup lang="tsx">
import type { Ref } from 'vue';
import { ref, onMounted, computed } from 'vue';
import type { DataTableColumns, GlobalThemeOverrides, FormInst, UploadFileInfo } from 'naive-ui';
import { useMessage, useThemeVars, useDialog } from 'naive-ui';
import { ArrowRedo } from '@vicons/ionicons5';
import { createRequiredFormRule } from '@/utils';
import {
getRepairReportList,
deleteRepairReport,
addKnowledgeRepairReport,
addRepairReport
} from '@/service/api/device/repairForm';
import { getBreakdownTypeList } from '@/service/api/device/facilityFaultCategory';
import { dataTableConfig } from '@/config/dataTableConfig';
import { useSearchBtn } from '~/src/hooks/common/useBtn';
import { useLoading } from '~/src/hooks/index';
import { formatDate } from '~/src/utils/form/rule';
import { useResetSearch } from '~/src/utils/common/searchReset';
import { getEquipmentAll } from '~/src/service/api/device/equipmentLedger';
import { serviceEnv } from '~/.env-config';
import { useAuthStore } from '~/src/store/modules/auth';
const { loading, startLoading, endLoading } = useLoading();
const message = useMessage();
const { searchForm, reset } = useResetSearch({
pageNum: 1,
pageSize: 10,
status: '',
equipmentCode: '',
total: 0,
breakdownName: '',
breakdownDescription: '',
repairMethod: '',
params: {
beginTime: null,
endTime: null
}
});
const imageList = ref<Array<string>>([]);
const addForm = ref<{
equipmentId: null | number;
equipmentName: string;
equipmentCode: string;
breakdownId: null;
breakdownName: string;
breakdownDescription: string;
breakdownImage: string;
status: string;
}>({
equipmentId: null,
equipmentName: '',
equipmentCode: '',
breakdownId: null,
breakdownName: '',
breakdownDescription: '',
breakdownImage: '',
status: '0'
});
const rules = {
equipmentId: createRequiredFormRule('请选择报修机台'),
breakdownId: createRequiredFormRule('请选择报修机台'),
breakdownDescription: createRequiredFormRule('请输入故障描述'),
breakdownImage: createRequiredFormRule('请选择故障图片')
};
type selectList = {
value: number;
label: string;
};
const data = ref<DataType[]>([]);
const completeRequest = ref<boolean>(true);
const baseurl = ref<string>(serviceEnv[import.meta.env.VITE_SERVICE_ENV as string].url);
const range = ref<[number, number] | null>(null);
const fileList = ref<UploadFileInfo[]>([]);
const showDialog = ref<boolean>(false);
const submitImages = ref<Array<selectList>>([]);
const equipmentList = ref<Array<selectList>>([]);
const faultCategoryList = ref<Array<selectList>>([]);
const addFormRef = ref<FormInst | null>(null);
const dialog = useDialog();
const statusMap = {
0: {
key: 'default',
label: '确认到货'
},
1: {
key: 'warning',
label: '确认完成'
},
2: {
key: 'success',
label: '已到货'
}
};
function handleInfo(row) {
addKnowledgeRepairReport(row).then(res => {
if (res.code === 200) {
message.success('写入知识库成功');
init();
}
});
}
type DataType = {
id?: number;
equipmentCode: string;
breakdownName: string;
breakdownDescription: string;
breakdownImage: string;
applyTime: Date;
applyBy: string;
repairTime: Date;
repairBy: string;
repairMethod: string;
status: string;
isKnowledge: number;
approveTime: Date;
maintainTime: string;
};
// const rowKey = (row: any) => row.id;
const columns: Ref<DataTableColumns<DataType>> = ref([
{
type: 'selection'
},
{
title: '序号',
key: 'index',
width: 100,
align: 'center',
render: (_row, index) => (searchForm.value.pageNum - 1) * searchForm.value.pageSize + index + 1
},
{
title: '设备编码',
align: 'center',
width: 100,
key: 'equipmentCode'
},
{
title: '故障类型',
align: 'center',
width: 100,
key: 'breakdownName'
},
{
title: '故障描述',
align: 'center',
width: 180,
key: 'breakdownDescription',
ellipsis: {
tooltip: true
}
},
{
title: '故障图片',
align: 'center',
width: 180,
key: '',
ellipsis: {
tooltip: true
},
render: row => {
let images: Array<string> = [];
if (row.breakdownImage) {
images = row.breakdownImage.split(',');
}
if (images.length > 1) {
images = images.slice(0, 1);
}
return (
<n-image-group>
<n-space>
{images.map((item, index) => {
return (
<n-image
width="100"
src={item}
key={index}
preview-disabled={true}
onClick={() => {
previewImages(row.breakdownImage);
}}
/>
);
})}
</n-space>
</n-image-group>
);
}
},
{
title: '报修时间',
align: 'center',
width: 180,
key: 'applyTime'
},
{
title: '报修人',
align: 'center',
width: 100,
key: 'applyBy'
},
{
title: '维修时间',
align: 'center',
width: 180,
key: 'repairTime'
},
{
title: '维修人',
align: 'center',
width: 150,
key: 'repairBy'
},
{
title: '维修方法',
align: 'center',
width: 180,
key: 'repairMethod',
ellipsis: {
tooltip: true
}
},
{
title: '维修时长',
align: 'center',
width: 180,
key: 'maintainTime',
ellipsis: {
tooltip: true
}
},
{
title: '审批人',
align: 'center',
width: 100,
key: 'approveBy'
},
{
title: '审批时间',
align: 'center',
width: 180,
key: 'approveTime',
render: row => (row.approveTime ? formatDate(new Date(row.approveTime), 'yyyy-MM-dd hh:mm:ss') : '')
},
{
title: '审批结论',
align: 'center',
width: 180,
key: 'attr1'
},
{
title: '状态',
align: 'center',
width: 100,
key: 'status',
render(row) {
return <n-tag type={statusMap[row.status].key}>{getStatus(row.status)}</n-tag>;
}
},
{
title: '操作',
align: 'center',
width: 200,
fixed: 'right',
key: '',
render: row => {
// let images: Array<string> = [];
// if (row.breakdownImage) {
// images = row.breakdownImage.split(',');
// }
return (
<n-space>
{/* <n-button
size={'small'}
type="info"
onClick={() => {
const url = `https://huaerda-jimu.24yt.com/jmreport/view/1017315497049653248?id=${row.id}`;
window.open(url, '_blank');
}}
>
打印单据
</n-button> */}
<n-button
size={'small'}
type="error"
onClick={() => {
deleteOne(row);
}}
>
删除
</n-button>
<n-button
color="#85E05B"
size="small"
v-show={row.status === '2' && row.isKnowledge === 0}
onClick={() => handleInfo(row)}
>
<ArrowRedo class="mr-1px text-15px w-13px" />
写入知识库
</n-button>
{/* <n-button
attr-type="button"
size={'small'}
type="info"
v-show={row.breakdownImage && !row.breakdownImage.includes('undefined') && images.length > 1}
onClick={() => {
previewImages(row.breakdownImage);
}}
>
<icon-mdi-search />
预览全部图片
</n-button> */}
</n-space>
);
}
}
]);
function getStatus(status: string) {
switch (status) {
case '0':
return '维修中';
case '1':
return '审批中';
case '2':
return '已完成';
default:
return '';
}
}
function errorFile({ file, event }) {
console.log(file, event);
const response = JSON.parse(event.currentTarget.response);
if (response.code !== 200) {
message.error(response.msg);
}
}
function finish({ file, event }) {
const type = file?.type.split('/');
if (type[0] === 'image') {
const response = JSON.parse(event.currentTarget.response);
console.log('response ==>', response);
if (response.code === 200) {
submitImages.value.push({
label: response.data.url,
value: file.id
});
} else {
message.error(response.msg);
}
} else {
message.warning('请选择图片进行上传');
}
}
function removeFile(e) {
submitImages.value = submitImages.value.filter(item => item.value !== e.file.id);
}
function changeEquipment(value: number, item: any) {
console.log('value ==>', value);
addForm.value.equipmentCode = item.label;
completeRequest.value = false;
faultCategoryList.value = [];
getBreakdownTypeList({ pageSize: 999999, typeName: item.workbenchType }).then(res => {
completeRequest.value = true;
if (res.code === 200) {
const resultData = res.rows || [];
resultData.forEach((val: any) => {
faultCategoryList.value.push({
label: val.breakdownName,
value: val.id
});
});
}
});
}
const changeBreakdown = (value: number, item: selectList) => {
console.log(value);
addForm.value.breakdownName = item.label;
};
function openAddRepairs() {
showDialog.value = true;
addForm.value = {
equipmentId: null,
equipmentName: '',
equipmentCode: '',
breakdownId: null,
breakdownName: '',
breakdownDescription: '',
breakdownImage: '',
status: '0'
};
submitImages.value = [];
}
function handleSearch() {
searchForm.value.pageNum = 1;
if (range.value !== null) {
searchForm.value.params.beginTime = formatDate(new Date(range.value[0]), 'yyyy-MM-dd');
searchForm.value.params.endTime = formatDate(new Date(range.value[1]), 'yyyy-MM-dd');
} else {
searchForm.value.params.beginTime = null;
searchForm.value.params.endTime = null;
}
init();
}
function resetThen() {
reset();
init();
}
function deleteOne(row: DataType) {
const d = dialog.warning({
title: '删除',
content: '确认要删除吗?',
positiveText: '确认',
negativeText: '取消',
onPositiveClick: () => {
d.loading = true;
deleteRepairReport(row).then(res => {
if (res.code === 200) {
window.$message?.success('删除成功');
init();
}
return false;
});
}
});
}
const imageGroupThemeOverrides = computed(() => {
const { popoverColor, boxShadow2, textColor2, borderRadius } = useThemeVars().value;
const themeOverrides: NonNullable<GlobalThemeOverrides['Image']> = {
toolbarColor: popoverColor,
toolbarBoxShadow: boxShadow2,
toolbarIconColor: textColor2,
toolbarBorderRadius: borderRadius
};
return themeOverrides;
});
function previewImages(imageUrl: string) {
imageList.value = imageUrl.split(/,/);
}
function closeLookImages(event: any) {
if (event.target.tagName.toLowerCase() === 'div') {
imageList.value = [];
}
}
function handleSubmit() {
if (submitImages.value.length === 0) {
message.warning('请选择图片进行上传');
return;
}
addFormRef.value?.validate(errors => {
if (!errors) {
submitImages.value.forEach(item => {
addForm.value.breakdownImage += `${item.label},`;
});
addForm.value.breakdownImage = addForm.value.breakdownImage.slice(0, -1);
addRepairReport(addForm.value).then(res => {
if (res.code === 200) {
showDialog.value = false;
init();
message.success('添加成功');
}
});
}
});
}
function formatTimeDifference(timestamp) {
// 获取毫秒差
const diffInMs = Math.abs(timestamp);
// 计算年差
const years = Math.floor(diffInMs / (1000 * 60 * 60 * 24 * 365));
// 计算月份差(按实际天数)
let remainingMsAfterYears = diffInMs % (1000 * 60 * 60 * 24 * 365); // 剩余毫秒数
const startDate = new Date(0); // 从 Unix 纪元开始
startDate.setFullYear(startDate.getFullYear() + years);
const months = Math.floor(remainingMsAfterYears / (1000 * 60 * 60 * 24 * 30.436875)); // 平均每月30.436875天,计算月份
remainingMsAfterYears -= months * (1000 * 60 * 60 * 24 * 30.436875); // 更新剩余毫秒数
// 计算天数差
const days = Math.floor(remainingMsAfterYears / (1000 * 60 * 60 * 24));
remainingMsAfterYears -= days * (1000 * 60 * 60 * 24); // 更新剩余毫秒数
// 计算小时差
const hours = Math.floor(remainingMsAfterYears / (1000 * 60 * 60));
remainingMsAfterYears -= hours * (1000 * 60 * 60); // 更新剩余毫秒数
// 计算分钟差
const minutes = Math.floor(remainingMsAfterYears / (1000 * 60));
remainingMsAfterYears -= minutes * (1000 * 60); // 更新剩余毫秒数
// 计算秒差
const seconds = Math.floor(remainingMsAfterYears / 1000);
// 将时间数据按单位存储
const timeData = [
{ time: years, test: '年' },
{ time: months, test: '月' },
{ time: days, test: '日' },
{ time: hours, test: '时' },
{ time: minutes, test: '分' },
{ time: seconds, test: '秒' }
];
// 构建结果字符串
let result = '';
timeData.forEach(item => {
if (item.time > 0) {
result += item.time + item.test;
}
});
return result || '0秒'; // 如果时间差为0则返回'0秒'
}
function getList() {
getEquipmentAll().then(res => {
if (res.code === 200) {
const resultData = res.data || [];
resultData.forEach((item: any) => {
equipmentList.value.push({
label: item.equipmentCode,
value: item.id,
...item
});
});
}
});
}
function init() {
startLoading();
getRepairReportList(searchForm.value).then(res => {
const resultData = res.rows || [];
data.value = resultData.map(item => {
if (item.applyTime && item.repairTime) {
const timestamp = new Date(item.repairTime).getTime() - new Date(item.applyTime).getTime();
item.maintainTime = formatTimeDifference(timestamp);
}
return item;
});
searchForm.value.total = res.total;
endLoading();
});
}
onMounted(() => {
init();
getList();
});
</script>
<style lang="scss" scoped>
.image-box {
width: 100%;
height: 100vh;
position: absolute;
left: 0;
top: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 99;
display: flex;
align-items: center;
justify-content: center;
}
:deep(.n-space) {
justify-content: flex-start !important;
}
</style>