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

1 year ago
<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,
1 year ago
fixed: 'right',
key: '',
render: row => {
// let images: Array<string> = [];
// if (row.breakdownImage) {
// images = row.breakdownImage.split(',');
// }
return (
<n-space>
{/* <n-button
1 year ago
size={'small'}
type="info"
onClick={() => {
const url = `https://huaerda-jimu.24yt.com/jmreport/view/1017315497049653248?id=${row.id}`;
window.open(url, '_blank');
}}
>
打印单据
</n-button> */}
1 year ago
<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);
1 year ago
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>