| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471 |
- <template>
- <div class="pwork-list-page">
- <!-- 搜索栏 -->
- <el-card shadow="never" class="search-card">
- <el-form :model="queryParams" inline class="search-form" @submit.prevent>
- <el-form-item label="关键字">
- <el-input
- v-model="queryParams.keyword"
- placeholder="工单编号 / 设备编码 / 故障描述"
- clearable
- style="width: 240px"
- @keyup.enter="handleQuery"
- />
- </el-form-item>
- <el-form-item label="优先级">
- <el-select v-model="queryParams.orderLevel" placeholder="全部" clearable style="width: 120px">
- <el-option label="紧急" :value="WO_LEVEL.EMERGENCY" />
- <el-option label="一般" :value="WO_LEVEL.NORMAL" />
- <el-option label="低" :value="WO_LEVEL.LOW" />
- </el-select>
- </el-form-item>
- <el-form-item label="工单状态">
- <el-select v-model="queryParams.orderStatus" placeholder="全部" clearable style="width: 150px">
- <el-option
- v-for="(item, key) in ORDER_STATUS_MAP"
- :key="key"
- :label="item.text"
- :value="Number(key)"
- />
- </el-select>
- </el-form-item>
- <el-form-item label="故障时间">
- <el-date-picker
- v-model="faultTimeRange"
- type="datetimerange"
- value-format="YYYY-MM-DD HH:mm:ss"
- start-placeholder="开始时间"
- end-placeholder="结束时间"
- style="width: 360px"
- />
- </el-form-item>
- <el-form-item>
- <el-button type="primary" icon="Search" @click="handleQuery">查询</el-button>
- <el-button icon="Refresh" @click="resetQuery">重置</el-button>
- <el-button
- v-hasPermi="['ops:workOrder:add']"
- type="success"
- icon="Plus"
- @click="openAdd"
- >新增工单</el-button>
- </el-form-item>
- </el-form>
- </el-card>
- <el-card shadow="never" class="table-card">
- <el-table v-loading="loading" :data="list" border stripe>
- <el-table-column label="工单编号" prop="orderNo" min-width="180" show-overflow-tooltip />
- <el-table-column label="故障描述" min-width="240">
- <template #default="{ row }">
- <el-tooltip
- :content="row.orderDesc"
- placement="top"
- popper-class="order-desc-tooltip"
- :show-after="200"
- :disabled="!row.orderDesc"
- >
- <span class="desc-cell">{{ row.orderDesc || '-' }}</span>
- </el-tooltip>
- </template>
- </el-table-column>
- <el-table-column label="优先级" width="100" align="center">
- <template #default="{ row }">
- <el-tag :type="orderLevelType(row.orderLevel)">{{ orderLevelText(row.orderLevel) }}</el-tag>
- </template>
- </el-table-column>
- <el-table-column label="设备编码" prop="deviceCode" min-width="150" show-overflow-tooltip>
- <template #default="{ row }">{{ row.deviceCode || '-' }}</template>
- </el-table-column>
- <el-table-column label="责任人" prop="receiveUserName" min-width="100" show-overflow-tooltip>
- <template #default="{ row }">{{ row.receiveUserName || '-' }}</template>
- </el-table-column>
- <el-table-column label="故障时间" prop="faultTime" width="180" align="center">
- <template #default="{ row }">{{ timeText(row.faultTime) }}</template>
- </el-table-column>
- <el-table-column label="计划完成时间" prop="planFinishTime" width="180" align="center">
- <template #default="{ row }">{{ timeText(row.planFinishTime) }}</template>
- </el-table-column>
- <el-table-column label="状态" width="100" align="center">
- <template #default="{ row }">
- <el-tag :type="orderStatusType(row.orderStatus)">{{ orderStatusText(row.orderStatus) }}</el-tag>
- </template>
- </el-table-column>
- </el-table>
- <pagination
- v-show="total > 0"
- v-model:page="queryParams.pageNum"
- v-model:limit="queryParams.pageSize"
- :total="total"
- @pagination="getList"
- />
- </el-card>
- <!-- 新增工单弹窗 -->
- <el-dialog v-model="addVisible" title="新增工单" width="680px" append-to-body destroy-on-close>
- <el-form ref="addFormRef" :model="addForm" :rules="addRules" label-width="100px">
- <el-form-item label="关联设备">
- <el-select
- v-model="addForm.deviceId"
- filterable
- remote
- clearable
- placeholder="输入设备编码/名称搜索"
- :remote-method="searchDevice"
- :loading="deviceLoading"
- style="width: 100%"
- @change="onDeviceChange"
- >
- <el-option
- v-for="dev in deviceOptions"
- :key="dev.equipmentId"
- :label="`${dev.equipmentName || dev.equipmentCode} (${dev.equipmentCode})`"
- :value="dev.equipmentId"
- />
- </el-select>
- </el-form-item>
- <el-form-item v-if="deviceInfo" label="设备信息">
- <el-descriptions :column="2" border size="small">
- <el-descriptions-item label="设备类型">{{ deviceInfo.equipmentTypeName || '-' }}</el-descriptions-item>
- <el-descriptions-item label="设备编码">{{ deviceInfo.equipmentCode || '-' }}</el-descriptions-item>
- <el-descriptions-item label="设备位置">{{ deviceInfo.equipmentLocation || deviceInfo.pointAddress || '-' }}</el-descriptions-item>
- <el-descriptions-item label="设备厂家">{{ deviceInfo.manufacturer || '-' }}</el-descriptions-item>
- <el-descriptions-item label="维护人员">{{ deviceInfo.maintainer || '-' }}</el-descriptions-item>
- <el-descriptions-item label="联系方式">{{ deviceInfo.maintainerPhone || '-' }}</el-descriptions-item>
- </el-descriptions>
- </el-form-item>
- <el-form-item label="优先级" prop="orderLevel">
- <el-radio-group v-model="addForm.orderLevel">
- <el-radio-button :value="WO_LEVEL.EMERGENCY">紧急</el-radio-button>
- <el-radio-button :value="WO_LEVEL.NORMAL">一般</el-radio-button>
- <el-radio-button :value="WO_LEVEL.LOW">低</el-radio-button>
- </el-radio-group>
- </el-form-item>
- <el-form-item label="故障时间" prop="faultTime">
- <el-date-picker
- v-model="addForm.faultTime"
- type="datetime"
- placeholder="故障时间"
- value-format="YYYY-MM-DD HH:mm:ss"
- style="width: 100%"
- />
- </el-form-item>
- <el-form-item label="计划完成时间" prop="planFinishTime">
- <el-date-picker
- v-model="addForm.planFinishTime"
- type="datetime"
- placeholder="请选择计划完成时间"
- value-format="YYYY-MM-DD HH:mm:ss"
- style="width: 100%"
- />
- </el-form-item>
- <el-form-item label="故障描述" prop="orderDesc">
- <el-input
- v-model="addForm.orderDesc"
- type="textarea"
- :rows="4"
- placeholder="请描述故障信息"
- maxlength="500"
- show-word-limit
- />
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button @click="addVisible = false">取消</el-button>
- <el-button type="primary" :loading="submitting" @click="submitAdd">提交工单</el-button>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup>
- import { ref, reactive, computed, onMounted, watch } from 'vue'
- import { useRoute } from 'vue-router'
- import { addWorkOrder, listWorkOrder } from '@/api/pipeNetwork/ops/workOrder.js'
- import { listEquipment } from '@/api/pipeNetwork/pdeviceInform'
- import {
- ORDER_STATUS_MAP,
- WO_LEVEL,
- orderLevelText,
- orderLevelType,
- orderStatusText,
- orderStatusType,
- timeText
- } from './workOrderDict.js'
- import { ElMessage } from 'element-plus'
- defineOptions({ name: 'PworkList' })
- const route = useRoute()
- /**
- * 本组件被多个菜单复用,不同菜单按业务口径查询不同范围的工单:
- * - 运维工单列表菜单(默认):查询全部工单
- * - 设备维修记录菜单:仅查 parentTypeId=1 的设备所产生的工单
- *
- * 判定方式:同一组件由后台 sys_menu.component 指向本文件时,菜单的 path 会体现在路由 path 中,
- * 因此按路由 path 关键字区分来源;匹配不上时走默认(全量),保证新菜单接入不会误过滤。
- * 若后续调整菜单 path,同步维护 MENU_DEVICE_TYPE_SCOPE.pathKey 即可。
- */
- const MENU_DEVICE_TYPE_SCOPE = [
- { pathKey: 'record', parentTypeId: '1', name: '设备维修记录' }
- ]
- const menuScope = computed(() => {
- const curPath = String(route.path || '')
- return MENU_DEVICE_TYPE_SCOPE.find((m) => curPath.includes(m.pathKey))
- })
- /** 统一构造查询入参:命中业务菜单时附加设备父类别过滤,且不随查询重置丢失 */
- function buildQueryParams() {
- return {
- ...queryParams,
- ...(menuScope.value ? { deviceTypeParentId: menuScope.value.parentTypeId } : {})
- }
- }
- const loading = ref(false)
- const submitting = ref(false)
- const list = ref([])
- const total = ref(0)
- const faultTimeRange = ref([])
- const queryParams = reactive({
- pageNum: 1,
- pageSize: 10,
- keyword: '',
- orderLevel: undefined,
- orderStatus: undefined,
- faultTimeStart: '',
- faultTimeEnd: ''
- })
- /* 列表查询 */
- async function getList() {
- loading.value = true
- try {
- const res = await listWorkOrder(buildQueryParams())
- const data = res.data || {}
- list.value = data.records || []
- total.value = data.total || 0
- } finally {
- loading.value = false
- }
- }
- function handleQuery() {
- queryParams.pageNum = 1
- if (faultTimeRange.value && faultTimeRange.value.length === 2) {
- queryParams.faultTimeStart = faultTimeRange.value[0]
- queryParams.faultTimeEnd = faultTimeRange.value[1]
- } else {
- queryParams.faultTimeStart = ''
- queryParams.faultTimeEnd = ''
- }
- getList()
- }
- function resetQuery() {
- queryParams.keyword = ''
- queryParams.orderLevel = undefined
- queryParams.orderStatus = undefined
- faultTimeRange.value = []
- queryParams.faultTimeStart = ''
- queryParams.faultTimeEnd = ''
- handleQuery()
- }
- /* 新增工单 */
- const addVisible = ref(false)
- const addFormRef = ref(null)
- const deviceOptions = ref([])
- const deviceLoading = ref(false)
- const deviceInfo = ref(null)
- const addForm = reactive({
- deviceId: '',
- orderLevel: WO_LEVEL.NORMAL,
- faultTime: '',
- planFinishTime: '',
- orderDesc: ''
- })
- /** 当前时间文本,格式与 el-date-picker 的 value-format 保持一致('YYYY-MM-DD HH:mm:ss') */
- function nowText() {
- const d = new Date()
- const pad = (n) => String(n).padStart(2, '0')
- return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} `
- + `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
- }
- /** 故障时间不能大于系统当前时间(定长字符串字典序即时间顺序) */
- function validateFaultTime(rule, value, callback) {
- if (!value) {
- callback()
- return
- }
- if (String(value) > nowText()) {
- callback(new Error('故障时间不能大于当前时间'))
- return
- }
- callback()
- }
- /**
- * 计划完成时间不能早于故障时间。
- * 两者均为 el-date-picker 的 value-format 输出('YYYY-MM-DD HH:mm:ss' 定长字符串),
- * 字典序与时间顺序一致,直接比较即可,无需解析为 Date 对象。
- */
- function validatePlanFinishTime(rule, value, callback) {
- if (!value || !addForm.faultTime) {
- callback()
- return
- }
- if (String(value) < String(addForm.faultTime)) {
- callback(new Error('计划完成时间不能早于故障时间'))
- return
- }
- callback()
- }
- const addRules = {
- orderDesc: [{ required: true, message: '请填写故障描述', trigger: 'blur' }],
- faultTime: [
- { required: true, message: '请选择故障发生时间', trigger: 'change' },
- { validator: validateFaultTime, trigger: 'change' }
- ],
- planFinishTime: [{ validator: validatePlanFinishTime, trigger: 'change' }]
- }
- function openAdd() {
- addForm.deviceId = ''
- addForm.orderLevel = WO_LEVEL.NORMAL
- addForm.faultTime = ''
- addForm.planFinishTime = ''
- addForm.orderDesc = ''
- deviceOptions.value = []
- deviceInfo.value = null
- addVisible.value = true
- }
- async function searchDevice(query) {
- if (!query) {
- deviceOptions.value = []
- return
- }
- deviceLoading.value = true
- try {
- const res = await listEquipment({ keyword: query, pageNum: 1, pageSize: 20 })
- if (res.records) deviceOptions.value = res.records || []
- } catch (e) {
- console.warn('设备搜索失败', e)
- } finally {
- deviceLoading.value = false
- }
- }
- function onDeviceChange(id) {
- deviceInfo.value = null
- if (!id) {
- return
- }
- const dev = deviceOptions.value.find((d) => String(d.equipmentId) === String(id))
- if (dev) {
- deviceInfo.value = dev
- }
- }
- async function submitAdd() {
- await addFormRef.value.validate().catch(() => {
- throw new Error('校验失败')
- })
- submitting.value = true
- try {
- const data = {
- orderLevel: addForm.orderLevel,
- orderDesc: addForm.orderDesc
- }
- if (addForm.deviceId) data.deviceId = addForm.deviceId
- if (addForm.faultTime) data.faultTime = addForm.faultTime
- if (addForm.planFinishTime) data.planFinishTime = addForm.planFinishTime
- await addWorkOrder(data)
- ElMessage.success('工单创建成功,状态为待派单')
- addVisible.value = false
- getList()
- } finally {
- submitting.value = false
- }
- }
- // 故障时间变化后需重新校验计划完成时间:先填计划完成时间、再改故障时间时,
- // 原有校验结论会失效(比如故障时间改晚后,原合法的计划时间可能变得非法)
- watch(
- () => addForm.faultTime,
- () => {
- if (!addVisible.value || !addForm.planFinishTime) return
- addFormRef.value?.validateField('planFinishTime').catch(() => {})
- }
- )
- // 同一组件被多个菜单复用(运维工单列表/设备维修记录),
- // 路由切换时组件实例被复用、onMounted 不会重新执行,
- // 必须监听路由变化按新菜单的业务口径重新拉取数据
- watch(
- () => route.path,
- (nv, ov) => {
- if (nv === ov) return
- queryParams.pageNum = 1
- getList()
- }
- )
- onMounted(() => {
- getList()
- })
- </script>
- <style scoped lang="scss">
- .pwork-list-page {
- padding: 16px;
- background: #f5f7fa;
- min-height: calc(100vh - 84px);
- }
- .search-card {
- margin-bottom: 12px;
- }
- /* 查询栏:改用 flex + row-gap 控制换行时的行间距。
- el-form-item 默认 margin-bottom: 18px 会让表单底部留白明显大于顶部,
- 这里清零后由卡片自身的上下 padding 决定留白,保证上下一致 */
- .search-form {
- display: flex;
- flex-wrap: wrap;
- row-gap: 4px;
- :deep(.el-form-item) {
- margin-bottom: 0;
- }
- }
- /* 单元格内省略:交给自定义 tooltip 展示全文 */
- .desc-cell {
- display: inline-block;
- max-width: 100%;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- vertical-align: bottom;
- }
- </style>
- <style lang="scss">
- /* 故障描述悬浮提示:popper 挂在 body 下不受 scoped 限制,单独用全局样式块。
- 限制最大宽度并允许换行,替代默认"一行横穿全屏"的效果 */
- .el-popper.order-desc-tooltip {
- max-width: 420px;
- line-height: 1.6;
- white-space: normal;
- word-break: break-all;
- }
- </style>
|