feat : 巡檢任務切版 | 運維管理切版
This commit is contained in:
parent
4237875453
commit
211d42cd86
5
src/components.d.ts
vendored
5
src/components.d.ts
vendored
@ -26,12 +26,15 @@ declare module 'vue' {
|
|||||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||||
|
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
||||||
ElForm: typeof import('element-plus/es')['ElForm']
|
ElForm: typeof import('element-plus/es')['ElForm']
|
||||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||||
ElHeader: typeof import('element-plus/es')['ElHeader']
|
ElHeader: typeof import('element-plus/es')['ElHeader']
|
||||||
ElIcon: typeof import('element-plus/es')['ElIcon']
|
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||||
|
ElImage: typeof import('element-plus/es')['ElImage']
|
||||||
ElInput: typeof import('element-plus/es')['ElInput']
|
ElInput: typeof import('element-plus/es')['ElInput']
|
||||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
||||||
|
ElLink: typeof import('element-plus/es')['ElLink']
|
||||||
ElMain: typeof import('element-plus/es')['ElMain']
|
ElMain: typeof import('element-plus/es')['ElMain']
|
||||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||||
@ -48,6 +51,8 @@ declare module 'vue' {
|
|||||||
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
||||||
ElTabs: typeof import('element-plus/es')['ElTabs']
|
ElTabs: typeof import('element-plus/es')['ElTabs']
|
||||||
ElTag: typeof import('element-plus/es')['ElTag']
|
ElTag: typeof import('element-plus/es')['ElTag']
|
||||||
|
ElText: typeof import('element-plus/es')['ElText']
|
||||||
|
ElUpload: typeof import('element-plus/es')['ElUpload']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
}
|
}
|
||||||
|
|||||||
238
src/components/PatrolMission/CompletedTasks.vue
Normal file
238
src/components/PatrolMission/CompletedTasks.vue
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 已完成任務列表 -->
|
||||||
|
<div class="filter-section">
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :xs="24" :md="6">
|
||||||
|
<el-form-item label="電廠:">
|
||||||
|
<el-select
|
||||||
|
v-model="filterFactory"
|
||||||
|
placeholder="選擇電廠"
|
||||||
|
clearable
|
||||||
|
>
|
||||||
|
<el-option label="四磺子坪" value="四磺子坪" />
|
||||||
|
<el-option label="宜蘭大清水" value="宜蘭大清水" />
|
||||||
|
<el-option label="宜蘭小清水" value="宜蘭小清水" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :xs="24" :md="6">
|
||||||
|
<el-form-item label="類別:">
|
||||||
|
<el-select
|
||||||
|
v-model="filterCategory"
|
||||||
|
placeholder="選擇類別"
|
||||||
|
clearable
|
||||||
|
>
|
||||||
|
<el-option label="巡檢表" value="巡檢表" />
|
||||||
|
<el-option label="點檢表" value="點檢表" />
|
||||||
|
<el-option
|
||||||
|
label="高壓特每日自動檢查表"
|
||||||
|
value="高壓特每日自動檢查表"
|
||||||
|
/>
|
||||||
|
<el-option
|
||||||
|
label="高壓特每月自動檢查表"
|
||||||
|
value="高壓特每月自動檢查表"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :xs="24" :md="10">
|
||||||
|
<el-form-item label="任務日期:">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="dateRange"
|
||||||
|
type="daterange"
|
||||||
|
:shortcuts="shortcuts"
|
||||||
|
range-separator="至"
|
||||||
|
start-placeholder="開始日期"
|
||||||
|
end-placeholder="結束日期"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :xs="24" :md="2">
|
||||||
|
<el-button type="primary" @click="handleSearch" :icon="Search"
|
||||||
|
>查詢</el-button
|
||||||
|
>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<PaginatedTable
|
||||||
|
v-model:current-page="currentPage"
|
||||||
|
v-model:page-size="pageSize"
|
||||||
|
:data="filteredData"
|
||||||
|
:page-sizes="[10, 20, 50]"
|
||||||
|
row-key="index"
|
||||||
|
:table-props="{ border: true }"
|
||||||
|
>
|
||||||
|
<el-table-column prop="index" label="項次" width="80" />
|
||||||
|
<el-table-column prop="factory" label="電廠" width="120" />
|
||||||
|
<el-table-column prop="category" label="類別" width="200" />
|
||||||
|
<el-table-column prop="taskName" label="任務名稱" min-width="300" />
|
||||||
|
<el-table-column prop="dispatchDate" label="派工日期" />
|
||||||
|
<el-table-column label="功能" width="100" fixed="right">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button type="success" size="small" :icon="Download">下載</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</PaginatedTable>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from "vue";
|
||||||
|
import { Search, Download } from "@element-plus/icons-vue";
|
||||||
|
import PaginatedTable from "../Common/PaginatedTable.vue";
|
||||||
|
|
||||||
|
const filterFactory = ref("四磺子坪");
|
||||||
|
const filterCategory = ref("");
|
||||||
|
const dateRange = ref("");
|
||||||
|
const currentPage = ref(1);
|
||||||
|
const pageSize = ref(10);
|
||||||
|
const shortcuts = [
|
||||||
|
{
|
||||||
|
text: "近7天",
|
||||||
|
value: () => {
|
||||||
|
const end = new Date();
|
||||||
|
const start = new Date();
|
||||||
|
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
|
||||||
|
return [start, end];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "近30天",
|
||||||
|
value: () => {
|
||||||
|
const end = new Date();
|
||||||
|
const start = new Date();
|
||||||
|
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
|
||||||
|
return [start, end];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "近3個月",
|
||||||
|
value: () => {
|
||||||
|
const end = new Date();
|
||||||
|
const start = new Date();
|
||||||
|
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
|
||||||
|
return [start, end];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const tableData = ref([
|
||||||
|
{
|
||||||
|
index: 1,
|
||||||
|
factory: "四磺子坪",
|
||||||
|
category: "巡檢表",
|
||||||
|
taskName: "四磺子坪1.2MW地熱能ORC發電機",
|
||||||
|
dispatchDate: "2025-11-11",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 2,
|
||||||
|
factory: "四磺子坪",
|
||||||
|
category: "高壓特每日自動檢查表",
|
||||||
|
taskName: "四磺子坪高壓特每日檢點臥式蒸發器",
|
||||||
|
dispatchDate: "2025-11-11",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 3,
|
||||||
|
factory: "四磺子坪",
|
||||||
|
category: "巡檢表",
|
||||||
|
taskName: "四磺子坪1.2MW地熱能ORC發電機",
|
||||||
|
dispatchDate: "2025-11-10",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 4,
|
||||||
|
factory: "四磺子坪",
|
||||||
|
category: "高壓特每日自動檢查表",
|
||||||
|
taskName: "四磺子坪高壓特每日檢點臥式蒸發器",
|
||||||
|
dispatchDate: "2025-11-10",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 5,
|
||||||
|
factory: "四磺子坪",
|
||||||
|
category: "巡檢表",
|
||||||
|
taskName: "四磺子坪1.2MW地熱能ORC發電機",
|
||||||
|
dispatchDate: "2025-11-09",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 6,
|
||||||
|
factory: "四磺子坪",
|
||||||
|
category: "高壓特每日自動檢查表",
|
||||||
|
taskName: "四磺子坪高壓特每日檢點臥式蒸發器",
|
||||||
|
dispatchDate: "2025-11-09",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 7,
|
||||||
|
factory: "四磺子坪",
|
||||||
|
category: "巡檢表",
|
||||||
|
taskName: "四磺子坪1.2MW地熱能ORC發電機",
|
||||||
|
dispatchDate: "2025-11-08",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 8,
|
||||||
|
factory: "四磺子坪",
|
||||||
|
category: "高壓特每日自動檢查表",
|
||||||
|
taskName: "四磺子坪高壓特每日檢點臥式蒸發器",
|
||||||
|
dispatchDate: "2025-11-08",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 9,
|
||||||
|
factory: "四磺子坪",
|
||||||
|
category: "點檢表",
|
||||||
|
taskName: "四磺子坪1.2MW先導地熱能ORC發電機",
|
||||||
|
dispatchDate: "2025-11-08",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 10,
|
||||||
|
factory: "四磺子坪",
|
||||||
|
category: "高壓特每月自動檢查表",
|
||||||
|
taskName: "四磺子坪高壓特每月檢點臥式蒸發器",
|
||||||
|
dispatchDate: "2025-11-08",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const filteredData = computed(() => {
|
||||||
|
let result = tableData.value;
|
||||||
|
|
||||||
|
// 過濾電廠
|
||||||
|
if (filterFactory.value) {
|
||||||
|
result = result.filter((item) => item.factory === filterFactory.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 過濾類別
|
||||||
|
if (filterCategory.value) {
|
||||||
|
result = result.filter((item) => item.category === filterCategory.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 過濾日期範圍
|
||||||
|
if (dateRange.value && dateRange.value.length === 2) {
|
||||||
|
const [startDate, endDate] = dateRange.value;
|
||||||
|
result = result.filter((item) => {
|
||||||
|
return item.dispatchDate >= startDate && item.dispatchDate <= endDate;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
console.log("搜尋條件:", {
|
||||||
|
factory: filterFactory.value,
|
||||||
|
category: filterCategory.value,
|
||||||
|
dateRange: dateRange.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.filter-section {
|
||||||
|
background: #f5f7fa;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-section .el-form-item {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
350
src/components/PatrolMission/FillTaskDialog.vue
Normal file
350
src/components/PatrolMission/FillTaskDialog.vue
Normal file
@ -0,0 +1,350 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:model-value="visible"
|
||||||
|
:title="dialogTitle"
|
||||||
|
modal-penetrable
|
||||||
|
style="max-width: 800px; width: 90%"
|
||||||
|
@close="onClose"
|
||||||
|
>
|
||||||
|
<el-form :model="form" label-width="120px">
|
||||||
|
<!-- 動態渲染樣板內容欄位 -->
|
||||||
|
<template v-if="activeSchema">
|
||||||
|
<template v-for="section in activeSchema.sections" :key="section.title">
|
||||||
|
<el-divider content-position="left">{{ section.title }}</el-divider>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col
|
||||||
|
v-for="field in section.fields"
|
||||||
|
:key="field.key"
|
||||||
|
:xs="24"
|
||||||
|
:sm="field.span"
|
||||||
|
>
|
||||||
|
<el-form-item :label="field.label">
|
||||||
|
<!-- 下拉選單 -->
|
||||||
|
<el-select
|
||||||
|
v-if="field.type === 'select'"
|
||||||
|
v-model="dynamicFields[field.key]"
|
||||||
|
:placeholder="`請選擇${field.label}`"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="option in field.options"
|
||||||
|
:key="option.value"
|
||||||
|
:label="option.label"
|
||||||
|
:value="option.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<!-- 多行文本 -->
|
||||||
|
<el-input
|
||||||
|
v-else-if="field.type === 'textarea'"
|
||||||
|
v-model="dynamicFields[field.key]"
|
||||||
|
type="textarea"
|
||||||
|
:rows="3"
|
||||||
|
:placeholder="`請輸入${field.label}`"
|
||||||
|
/>
|
||||||
|
<!-- 一般文本輸入 -->
|
||||||
|
<el-input
|
||||||
|
v-else
|
||||||
|
v-model="dynamicFields[field.key]"
|
||||||
|
:placeholder="`請輸入${field.label}`"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 動態渲染表格 -->
|
||||||
|
<template v-for="table in activeSchema.tables" :key="table.key">
|
||||||
|
<el-divider content-position="left">{{ table.title }}</el-divider>
|
||||||
|
<el-row :align="'middle'" justify="space-between" style="margin-bottom: 10px;">
|
||||||
|
<h3>{{ table.title }}</h3>
|
||||||
|
<el-button
|
||||||
|
v-if="table.allowAdd !== false"
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
@click="openDialog(table.dialogType)"
|
||||||
|
>
|
||||||
|
{{ table.addButtonText }}
|
||||||
|
</el-button>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<!-- 使用分頁表格或一般表格 -->
|
||||||
|
<PaginatedTable
|
||||||
|
v-if="table.usePagination"
|
||||||
|
:data="dynamicTableData[table.key] || []"
|
||||||
|
:page-sizes="[10, 20]"
|
||||||
|
row-key="index"
|
||||||
|
>
|
||||||
|
<el-table-column
|
||||||
|
v-for="column in table.columns"
|
||||||
|
:key="column.prop"
|
||||||
|
:prop="column.prop"
|
||||||
|
:label="column.label"
|
||||||
|
:width="column.width"
|
||||||
|
>
|
||||||
|
<template #default="scope">
|
||||||
|
<!-- 如果是巡檢表的數值欄位,提供輸入框 -->
|
||||||
|
<el-input
|
||||||
|
v-if="table.key === 'inspection' && isValueColumn(column.prop)"
|
||||||
|
v-model="scope.row[column.prop]"
|
||||||
|
size="small"
|
||||||
|
placeholder="請輸入數值"
|
||||||
|
/>
|
||||||
|
<span v-else>{{ scope.row[column.prop] }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
v-if="table.allowEdit !== false"
|
||||||
|
label="功能"
|
||||||
|
width="140"
|
||||||
|
fixed="right"
|
||||||
|
>
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button size="small" type="primary" plain>修改</el-button>
|
||||||
|
<el-button
|
||||||
|
size="small"
|
||||||
|
type="danger"
|
||||||
|
plain
|
||||||
|
@click="deleteItem(table.key, scope.$index)"
|
||||||
|
>
|
||||||
|
刪除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</PaginatedTable>
|
||||||
|
|
||||||
|
<el-table
|
||||||
|
v-else
|
||||||
|
:data="dynamicTableData[table.key] || []"
|
||||||
|
:border="true"
|
||||||
|
>
|
||||||
|
<el-table-column
|
||||||
|
v-for="column in table.columns"
|
||||||
|
:key="column.prop"
|
||||||
|
:prop="column.prop"
|
||||||
|
:label="column.label"
|
||||||
|
:width="column.width"
|
||||||
|
>
|
||||||
|
<template #default="scope">
|
||||||
|
<span>{{ scope.row[column.prop] }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
v-if="table.allowEdit !== false"
|
||||||
|
label="功能"
|
||||||
|
min-width="80"
|
||||||
|
width="140"
|
||||||
|
fixed="right"
|
||||||
|
>
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button size="small" type="primary" plain>修改</el-button>
|
||||||
|
<el-button
|
||||||
|
size="small"
|
||||||
|
type="danger"
|
||||||
|
plain
|
||||||
|
@click="deleteItem(table.key, scope.$index)"
|
||||||
|
>
|
||||||
|
刪除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 動態渲染驗證區塊 -->
|
||||||
|
<template v-if="activeSchema.verification?.enabled">
|
||||||
|
<el-divider content-position="left">樣板查證</el-divider>
|
||||||
|
<el-row :align="'middle'" justify="space-between" style="margin-bottom: 10px;">
|
||||||
|
<h3>{{ activeSchema.verification.title }}</h3>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-table :data="dynamicTableData.verification || []" :border="true">
|
||||||
|
<el-table-column
|
||||||
|
v-for="column in activeSchema.verification.columns"
|
||||||
|
:key="column.prop"
|
||||||
|
:prop="column.prop"
|
||||||
|
:label="column.label"
|
||||||
|
:width="column.width"
|
||||||
|
/>
|
||||||
|
<el-table-column label="查證值" min-width="150">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-input
|
||||||
|
v-model="scope.row.verificationValue"
|
||||||
|
size="small"
|
||||||
|
placeholder="請輸入查證值"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="備註" min-width="150">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-input
|
||||||
|
v-model="scope.row.remark"
|
||||||
|
size="small"
|
||||||
|
placeholder="請輸入備註"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="onClose">取消</el-button>
|
||||||
|
<el-button type="primary" @click="onSave">保存</el-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 子對話框 -->
|
||||||
|
<DutyLogItemDialog
|
||||||
|
v-model:visible="dialogVisibility.dutyLog"
|
||||||
|
@add="(item) => addItem('dutyLog', item)"
|
||||||
|
/>
|
||||||
|
<HandoverItemDialog
|
||||||
|
v-model:visible="dialogVisibility.handover"
|
||||||
|
@add="(item) => addItem('handover', item)"
|
||||||
|
/>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, watch, reactive } from "vue";
|
||||||
|
import PaginatedTable from "../Common/PaginatedTable.vue";
|
||||||
|
import DutyLogItemDialog from "./DutyLogItemDialog.vue";
|
||||||
|
import HandoverItemDialog from "./HandoverItemDialog.vue";
|
||||||
|
import {
|
||||||
|
getTemplateSchema,
|
||||||
|
initializeTableData,
|
||||||
|
initializeFieldData,
|
||||||
|
} from "../../constants/templateSchemas.js";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
visible: { type: Boolean, default: false },
|
||||||
|
taskData: { type: Object, default: null }, // 任務資料,包含樣板資訊
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(["update:visible", "save", "close"]);
|
||||||
|
|
||||||
|
// 表單資料
|
||||||
|
const form = reactive({});
|
||||||
|
|
||||||
|
// 動態欄位資料
|
||||||
|
const dynamicFields = reactive({});
|
||||||
|
|
||||||
|
// 動態表格資料
|
||||||
|
const dynamicTableData = reactive({});
|
||||||
|
|
||||||
|
// 對話框顯示狀態
|
||||||
|
const dialogVisibility = reactive({
|
||||||
|
dutyLog: false,
|
||||||
|
handover: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 當前模板的 schema
|
||||||
|
const activeSchema = computed(() => {
|
||||||
|
if (!props.taskData || !props.taskData.templateType) return null;
|
||||||
|
const schema = getTemplateSchema(props.taskData.templateType);
|
||||||
|
if (!schema) return null;
|
||||||
|
|
||||||
|
// 過濾只顯示於填寫的內容
|
||||||
|
return {
|
||||||
|
...schema,
|
||||||
|
sections: schema.sections?.filter(s => s.showInFill !== false) || [],
|
||||||
|
tables: schema.tables?.filter(t => t.showInFill !== false) || [],
|
||||||
|
verification: schema.verification, // 查證始終顯示
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 對話框標題
|
||||||
|
const dialogTitle = computed(() => {
|
||||||
|
return props.taskData?.taskName || "填寫任務";
|
||||||
|
});
|
||||||
|
|
||||||
|
// 判斷是否為可輸入的數值欄位 (非系統、設備、項目、單位欄位)
|
||||||
|
const isValueColumn = (prop) => {
|
||||||
|
const infoColumns = ['index', 'system1', 'system2', 'deviceId', 'item', 'unit'];
|
||||||
|
return !infoColumns.includes(prop);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 監聽任務資料變化,載入樣板配置
|
||||||
|
watch(
|
||||||
|
() => props.taskData,
|
||||||
|
(newTaskData) => {
|
||||||
|
if (!newTaskData || !newTaskData.templateType) return;
|
||||||
|
|
||||||
|
const schema = getTemplateSchema(newTaskData.templateType);
|
||||||
|
if (!schema) return;
|
||||||
|
|
||||||
|
// 重置動態欄位
|
||||||
|
Object.keys(dynamicFields).forEach((key) => delete dynamicFields[key]);
|
||||||
|
Object.assign(dynamicFields, initializeFieldData(schema));
|
||||||
|
|
||||||
|
// 重置動態表格
|
||||||
|
Object.keys(dynamicTableData).forEach((key) => delete dynamicTableData[key]);
|
||||||
|
|
||||||
|
// 如果任務已有填寫資料,載入已填寫的資料
|
||||||
|
if (newTaskData.fillData) {
|
||||||
|
Object.assign(dynamicFields, newTaskData.fillData.fields || {});
|
||||||
|
Object.assign(dynamicTableData, newTaskData.fillData.tables || initializeTableData(schema));
|
||||||
|
} else {
|
||||||
|
// 否則初始化空白表格
|
||||||
|
Object.assign(dynamicTableData, initializeTableData(schema));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 打開子對話框
|
||||||
|
function openDialog(dialogType) {
|
||||||
|
if (dialogType === "dutyLog") {
|
||||||
|
dialogVisibility.dutyLog = true;
|
||||||
|
} else if (dialogType === "handover") {
|
||||||
|
dialogVisibility.handover = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用新增項目方法
|
||||||
|
function addItem(tableKey, item) {
|
||||||
|
if (!dynamicTableData[tableKey]) {
|
||||||
|
dynamicTableData[tableKey] = [];
|
||||||
|
}
|
||||||
|
const arr = dynamicTableData[tableKey];
|
||||||
|
arr.push({ index: arr.length + 1, ...item });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刪除項目
|
||||||
|
function deleteItem(tableKey, index) {
|
||||||
|
if (dynamicTableData[tableKey]) {
|
||||||
|
dynamicTableData[tableKey].splice(index, 1);
|
||||||
|
// 重新計算項次
|
||||||
|
dynamicTableData[tableKey].forEach((item, idx) => {
|
||||||
|
item.index = idx + 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClose() {
|
||||||
|
emit("update:visible", false);
|
||||||
|
emit("close");
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSave() {
|
||||||
|
// 整合填寫的資料
|
||||||
|
const fillData = {
|
||||||
|
fields: { ...dynamicFields },
|
||||||
|
tables: { ...dynamicTableData },
|
||||||
|
fillTime: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
emit("save", {
|
||||||
|
taskData: props.taskData,
|
||||||
|
fillData,
|
||||||
|
});
|
||||||
|
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.el-divider--horizontal {
|
||||||
|
margin-top: 30px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
253
src/components/PatrolMission/PendingTasks.vue
Normal file
253
src/components/PatrolMission/PendingTasks.vue
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 待完成內容 -->
|
||||||
|
<div class="filter-section">
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :xs="24" :md="6">
|
||||||
|
<el-form-item label="電廠:">
|
||||||
|
<el-select v-model="filterFactory" placeholder="選擇電廠">
|
||||||
|
<el-option label="四磺子坪" value="四磺子坪" />
|
||||||
|
<el-option label="宜蘭大清水" value="宜蘭大清水" />
|
||||||
|
<el-option label="宜蘭小清水" value="宜蘭小清水" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :xs="24" :md="6">
|
||||||
|
<el-form-item label="類別:">
|
||||||
|
<el-select v-model="filterCategory" placeholder="選擇類別">
|
||||||
|
<el-option label="全部顯示" value="" />
|
||||||
|
<el-option label="巡檢表" value="巡檢表" />
|
||||||
|
<el-option label="點檢表" value="點檢表" />
|
||||||
|
<el-option
|
||||||
|
label="高壓特每日自動檢查表"
|
||||||
|
value="高壓特每日自動檢查表"
|
||||||
|
/>
|
||||||
|
<el-option
|
||||||
|
label="高壓特每月自動檢查表"
|
||||||
|
value="高壓特每月自動檢查表"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :xs="24" :md="12">
|
||||||
|
<el-button type="primary" @click="handleSearch" :icon="Search"
|
||||||
|
>查詢</el-button
|
||||||
|
>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<PaginatedTable
|
||||||
|
v-model:current-page="currentPage"
|
||||||
|
v-model:page-size="pageSize"
|
||||||
|
:data="tableData"
|
||||||
|
:page-sizes="[5, 10]"
|
||||||
|
row-key="index"
|
||||||
|
>
|
||||||
|
<el-table-column prop="index" label="項次" width="60" />
|
||||||
|
<el-table-column prop="factory" label="電廠" width="100" />
|
||||||
|
<el-table-column prop="template" label="模板" width="100" />
|
||||||
|
<el-table-column prop="taskName" label="任務名稱" />
|
||||||
|
<el-table-column prop="dispatchDate" label="派工日期" />
|
||||||
|
<el-table-column label="功能" width="100" fixed="right">
|
||||||
|
<template #default>
|
||||||
|
<el-button size="small" type="info" :icon="View">查看</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="填表" width="100" fixed="right">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
:icon="Edit"
|
||||||
|
@click="handleFillTask(scope.row)"
|
||||||
|
>
|
||||||
|
填寫
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="1關審查" width="200" fixed="right">
|
||||||
|
<template #default>
|
||||||
|
<el-radio-group v-model="radio" fill="#409eff">
|
||||||
|
<el-radio-button label="未確認" value="" />
|
||||||
|
<el-radio-button label="已確認" value="" />
|
||||||
|
</el-radio-group>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="2關審查" width="200" fixed="right">
|
||||||
|
<template #default>
|
||||||
|
<el-radio-group v-model="radio" fill="#409eff">
|
||||||
|
<el-radio-button label="未確認" value="" />
|
||||||
|
<el-radio-button label="已確認" value="" />
|
||||||
|
</el-radio-group>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</PaginatedTable>
|
||||||
|
<!-- 填寫任務對話框 -->
|
||||||
|
<FillTaskDialog
|
||||||
|
v-model:visible="fillDialogVisible"
|
||||||
|
:task-data="currentTask"
|
||||||
|
@save="handleSaveFillData"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<h3 style="margin-top: 20px">簽名板</h3>
|
||||||
|
<el-button-group>
|
||||||
|
<el-button :icon="TakeawayBox" @click="save('image/png')"
|
||||||
|
>保存PNG</el-button
|
||||||
|
>
|
||||||
|
<el-button :icon="TakeawayBox" @click="save('image/jpeg')"
|
||||||
|
>保存JPEG</el-button
|
||||||
|
>
|
||||||
|
<el-button :icon="Loading" @click="clear">清除</el-button>
|
||||||
|
<el-button :icon="RefreshLeft" @click="undo">返回</el-button>
|
||||||
|
</el-button-group>
|
||||||
|
<Vue3Signature
|
||||||
|
ref="signature"
|
||||||
|
:sigOption="options"
|
||||||
|
:w="'300px'"
|
||||||
|
:h="'150px'"
|
||||||
|
class="signature-pad"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, reactive } from "vue";
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Edit,
|
||||||
|
TakeawayBox,
|
||||||
|
Loading,
|
||||||
|
RefreshLeft,
|
||||||
|
Search,
|
||||||
|
} from "@element-plus/icons-vue";
|
||||||
|
import PaginatedTable from "../Common/PaginatedTable.vue";
|
||||||
|
import FillTaskDialog from "./FillTaskDialog.vue";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
// 分頁狀態
|
||||||
|
const currentPage = ref(1);
|
||||||
|
const pageSize = ref(10);
|
||||||
|
// 篩選條件
|
||||||
|
const filterFactory = ref("");
|
||||||
|
const filterCategory = ref("");
|
||||||
|
// table 資料
|
||||||
|
const allTableData = ref([
|
||||||
|
{
|
||||||
|
index: 1,
|
||||||
|
factory: "四磺子坪",
|
||||||
|
template: "巡檢表",
|
||||||
|
templateType: "1", // 對應 templateSchemas 的 key
|
||||||
|
taskName: "四磺子坪1.2MW地熱能ORC發電機",
|
||||||
|
dispatchDate: "2025-11-01",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 2,
|
||||||
|
factory: "四磺子坪",
|
||||||
|
template: "巡檢表",
|
||||||
|
templateType: "1",
|
||||||
|
taskName: "四磺子坪1.2MW先導地熱能ORC發電機",
|
||||||
|
dispatchDate: "2025-11-01",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 3,
|
||||||
|
factory: "四磺子坪",
|
||||||
|
template: "點檢表",
|
||||||
|
templateType: "2",
|
||||||
|
taskName: "廠房01維修工程",
|
||||||
|
dispatchDate: "2025-11-02",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 篩選後的資料
|
||||||
|
const tableData = computed(() => {
|
||||||
|
let result = allTableData.value;
|
||||||
|
|
||||||
|
// 過濾電廠
|
||||||
|
if (filterFactory.value) {
|
||||||
|
result = result.filter((item) => item.factory === filterFactory.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 過濾類別
|
||||||
|
if (filterCategory.value) {
|
||||||
|
result = result.filter((item) => item.template === filterCategory.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 查詢按鈕處理
|
||||||
|
const handleSearch = () => {
|
||||||
|
console.log("搜尋條件:", {
|
||||||
|
factory: filterFactory.value,
|
||||||
|
category: filterCategory.value,
|
||||||
|
});
|
||||||
|
// 篩選邏輯已經在 computed tableData 中自動執行
|
||||||
|
};
|
||||||
|
|
||||||
|
// 填寫對話框
|
||||||
|
const fillDialogVisible = ref(false);
|
||||||
|
const currentTask = ref(null);
|
||||||
|
|
||||||
|
// 打開填寫對話框
|
||||||
|
const handleFillTask = (task) => {
|
||||||
|
currentTask.value = task;
|
||||||
|
fillDialogVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 保存填寫的資料
|
||||||
|
const handleSaveFillData = (data) => {
|
||||||
|
console.log("保存填寫資料:", data);
|
||||||
|
// 這裡可以調用 API 保存資料
|
||||||
|
// 更新任務狀態
|
||||||
|
const taskIndex = allTableData.value.findIndex(
|
||||||
|
(t) => t.index === data.taskData.index
|
||||||
|
);
|
||||||
|
if (taskIndex !== -1) {
|
||||||
|
allTableData.value[taskIndex].fillData = data.fillData;
|
||||||
|
allTableData.value[taskIndex].status = "已填寫";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const signature = ref(null);
|
||||||
|
|
||||||
|
// 簽名板選項
|
||||||
|
const options = reactive({
|
||||||
|
penColor: "rgb(0, 0, 0)",
|
||||||
|
backgroundColor: "rgb(255, 255, 255)",
|
||||||
|
});
|
||||||
|
// 保存簽名圖片
|
||||||
|
const save = (format) => {
|
||||||
|
if (signature.value.isEmpty()) {
|
||||||
|
alert("請先簽名");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const dataUrl = signature.value.save(format);
|
||||||
|
const link = document.createElement("a");
|
||||||
|
const extension = format === "image/jpeg" ? "jpg" : "png";
|
||||||
|
const nowStr = dayjs().format("YYYYMMDD_HHmmss");
|
||||||
|
link.download = `signature_${nowStr}.${extension}`;
|
||||||
|
link.href = dataUrl;
|
||||||
|
link.click();
|
||||||
|
};
|
||||||
|
// 清除簽名板
|
||||||
|
const clear = () => {
|
||||||
|
signature.value.clear();
|
||||||
|
};
|
||||||
|
// 簽名板返回上一步
|
||||||
|
const undo = () => {
|
||||||
|
signature.value.undo();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.filter-section {
|
||||||
|
background: #f5f7fa;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.filter-section .el-form-item {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.signature-pad {
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -6,19 +6,20 @@
|
|||||||
style="max-width: 800px; width: 90%"
|
style="max-width: 800px; width: 90%"
|
||||||
@close="onClose"
|
@close="onClose"
|
||||||
>
|
>
|
||||||
<DutyLogItemDialog v-model:visible="showDutyLogDialog" @add="addDutyLog" />
|
<!-- 通用對話框 -->
|
||||||
<HandoverItemDialog
|
|
||||||
v-model:visible="showHandoverDialog"
|
|
||||||
@add="addHandover"
|
|
||||||
/>
|
|
||||||
<VerificationSettingDialog
|
<VerificationSettingDialog
|
||||||
v-model:visible="showVerificationDialog"
|
v-model:visible="dialogVisibility.verification"
|
||||||
@add="addVerification"
|
@add="(item) => addItem('verification', item)"
|
||||||
/>
|
/>
|
||||||
<InspectionItemDialog
|
<InspectionItemDialog
|
||||||
v-model:visible="showInspectionDialog"
|
v-model:visible="dialogVisibility.inspection"
|
||||||
@add="addInspection"
|
@add="(item) => addItem('inspection', item)"
|
||||||
/>
|
/>
|
||||||
|
<CheckItemsDialog
|
||||||
|
v-model:visible="dialogVisibility.checkItems"
|
||||||
|
@add="(item) => addItem('checkItems', item)"
|
||||||
|
/>
|
||||||
|
|
||||||
<el-form :model="form">
|
<el-form :model="form">
|
||||||
<el-divider content-position="left">樣板資訊</el-divider>
|
<el-divider content-position="left">樣板資訊</el-divider>
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
@ -35,28 +36,13 @@
|
|||||||
<el-col :xs="24" :sm="12">
|
<el-col :xs="24" :sm="12">
|
||||||
<el-form-item label="模板">
|
<el-form-item label="模板">
|
||||||
<el-select v-model="form.template" placeholder="請選擇模板類型">
|
<el-select v-model="form.template" placeholder="請選擇模板類型">
|
||||||
<el-option label="巡檢表" value="巡檢表" />
|
<el-option label="巡檢表" value="1" />
|
||||||
<el-option label="點檢表" value="點檢表" />
|
<el-option label="點檢表" value="2" />
|
||||||
<el-option
|
<el-option label="高壓特每日自動檢查表" value="3" />
|
||||||
label="高壓特每日自動檢查表"
|
<el-option label="高壓特每月自動檢查表" value="4" />
|
||||||
value="高壓特每日自動檢查表"
|
<el-option label="一班作業安全許可申請表" value="5" />
|
||||||
/>
|
<el-option label="侷限作業安全許可申請表" value="6" />
|
||||||
<el-option
|
<el-option label="動火作業安全許可申請表" value="7" />
|
||||||
label="高壓特每月自動檢查表"
|
|
||||||
value="高壓特每月自動檢查表"
|
|
||||||
/>
|
|
||||||
<el-option
|
|
||||||
label="一班作業安全許可申請表"
|
|
||||||
value="一班作業安全許可申請表"
|
|
||||||
/>
|
|
||||||
<el-option
|
|
||||||
label="侷限作業安全許可申請表"
|
|
||||||
value="侷限作業安全許可申請表"
|
|
||||||
/>
|
|
||||||
<el-option
|
|
||||||
label="動火作業安全許可申請表"
|
|
||||||
value="動火作業安全許可申請表"
|
|
||||||
/>
|
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
@ -76,145 +62,179 @@
|
|||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<el-divider content-position="left">樣板內容</el-divider>
|
|
||||||
<el-row :gutter="20">
|
|
||||||
<el-col :xs="24" :sm="24">
|
<el-col :xs="24" :sm="24">
|
||||||
<el-form-item label="案場名稱">
|
<el-form-item label="備註">
|
||||||
<el-input />
|
<el-input
|
||||||
</el-form-item>
|
v-model="form.remark"
|
||||||
</el-col>
|
placeholder="請輸入備註"
|
||||||
<el-col :xs="24" :sm="12">
|
type="textarea"
|
||||||
<el-form-item label="值班人員">
|
/>
|
||||||
<el-input />
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :xs="24" :sm="12">
|
|
||||||
<el-form-item label="班別">
|
|
||||||
<el-input />
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :xs="24" :sm="12">
|
|
||||||
<el-form-item label="天氣">
|
|
||||||
<el-input />
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :xs="24" :sm="12">
|
|
||||||
<el-form-item label="環境溫度/濕度">
|
|
||||||
<el-input />
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-row :align="'middle'" justify="space-between">
|
<!-- 動態渲染樣板內容欄位 -->
|
||||||
<h3>值班日誌</h3>
|
<template v-if="activeSchema">
|
||||||
<el-button type="primary" size="small" @click="showDutyLogDialog = true"
|
<template v-for="section in activeSchema.sections" :key="section.title">
|
||||||
>新增日誌項目</el-button
|
<el-divider content-position="left">{{ section.title }}</el-divider>
|
||||||
>
|
<el-row :gutter="20">
|
||||||
<el-table :data="dutyLogTableData" :border="true">
|
<el-col
|
||||||
<el-table-column prop="index" label="項次" width="60" />
|
v-for="field in section.fields"
|
||||||
<el-table-column prop="change" label="變更內容" />
|
:key="field.key"
|
||||||
<el-table-column prop="continue" label="持續" />
|
:xs="24"
|
||||||
<el-table-column prop="result" label="結案" />
|
:sm="field.span"
|
||||||
<el-table-column prop="remark" label="備註" />
|
>
|
||||||
<el-table-column
|
<el-form-item :label="field.label">
|
||||||
label="功能"
|
<!-- 下拉選單 -->
|
||||||
min-width="80"
|
<el-select
|
||||||
width="140"
|
v-if="field.type === 'select'"
|
||||||
fixed="right"
|
v-model="form[field.key]"
|
||||||
|
:placeholder="`請選擇${field.label}`"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="option in field.options"
|
||||||
|
:key="option.value"
|
||||||
|
:label="option.label"
|
||||||
|
:value="option.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<!-- 多行文本 -->
|
||||||
|
<el-input
|
||||||
|
v-else-if="field.type === 'textarea'"
|
||||||
|
v-model="form[field.key]"
|
||||||
|
type="textarea"
|
||||||
|
:rows="3"
|
||||||
|
:placeholder="`請輸入${field.label}`"
|
||||||
|
/>
|
||||||
|
<!-- 一般文本輸入 -->
|
||||||
|
<el-input
|
||||||
|
v-else
|
||||||
|
v-model="dynamicFields[field.key]"
|
||||||
|
:placeholder="`請輸入${field.label}`"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 動態渲染表格 -->
|
||||||
|
<template v-for="table in activeSchema.tables" :key="table.key">
|
||||||
|
<el-row :align="'middle'" justify="space-between">
|
||||||
|
<h3>{{ table.title }}</h3>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
@click="openDialog(table.dialogType)"
|
||||||
|
>
|
||||||
|
{{ table.addButtonText }}
|
||||||
|
</el-button>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<!-- 使用分頁表格或一般表格 -->
|
||||||
|
<PaginatedTable
|
||||||
|
v-if="table.usePagination"
|
||||||
|
:data="dynamicTableData[table.key] || []"
|
||||||
|
:page-sizes="[5, 10]"
|
||||||
|
row-key="index"
|
||||||
>
|
>
|
||||||
<template #default>
|
<el-table-column
|
||||||
<el-button size="small" type="primary" plain>修改</el-button>
|
v-for="column in table.columns"
|
||||||
<el-button size="small" type="danger" plain>刪除</el-button>
|
:key="column.prop"
|
||||||
</template>
|
:prop="column.prop"
|
||||||
</el-table-column>
|
:label="column.label"
|
||||||
</el-table>
|
:width="column.width"
|
||||||
</el-row>
|
/>
|
||||||
|
<el-table-column label="功能" width="140" fixed="right">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button size="small" type="primary" plain>修改</el-button>
|
||||||
|
<el-button
|
||||||
|
size="small"
|
||||||
|
type="danger"
|
||||||
|
plain
|
||||||
|
@click="deleteItem(table.key, scope.$index)"
|
||||||
|
>
|
||||||
|
刪除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</PaginatedTable>
|
||||||
|
|
||||||
<el-row :align="'middle'" justify="space-between">
|
<el-table
|
||||||
<h3>交接事項</h3>
|
v-else
|
||||||
<el-button
|
:data="dynamicTableData[table.key] || []"
|
||||||
type="primary"
|
:border="true"
|
||||||
size="small"
|
|
||||||
@click="showHandoverDialog = true"
|
|
||||||
>新增交接事項</el-button
|
|
||||||
>
|
|
||||||
<el-table :data="handoverTableData" :border="true">
|
|
||||||
<el-table-column prop="index" label="項次" width="60" />
|
|
||||||
<el-table-column prop="content" label="工作內容" />
|
|
||||||
<el-table-column prop="continue" label="持續" />
|
|
||||||
<el-table-column prop="result" label="結案" />
|
|
||||||
<el-table-column prop="remark" label="備註" />
|
|
||||||
<el-table-column
|
|
||||||
label="功能"
|
|
||||||
min-width="80"
|
|
||||||
width="140"
|
|
||||||
fixed="right"
|
|
||||||
>
|
>
|
||||||
<template #default>
|
<el-table-column
|
||||||
<el-button size="small" type="primary" plain>修改</el-button>
|
v-for="column in table.columns"
|
||||||
<el-button size="small" type="danger" plain>刪除</el-button>
|
:key="column.prop"
|
||||||
</template>
|
:prop="column.prop"
|
||||||
</el-table-column>
|
:label="column.label"
|
||||||
</el-table>
|
:width="column.width"
|
||||||
</el-row>
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="功能"
|
||||||
|
min-width="80"
|
||||||
|
width="140"
|
||||||
|
fixed="right"
|
||||||
|
>
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button size="small" type="primary" plain>修改</el-button>
|
||||||
|
<el-button
|
||||||
|
size="small"
|
||||||
|
type="danger"
|
||||||
|
plain
|
||||||
|
@click="deleteItem(table.key, scope.$index)"
|
||||||
|
>
|
||||||
|
刪除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</template>
|
||||||
|
|
||||||
<el-row :align="'middle'" justify="space-between">
|
<!-- 動態渲染驗證區塊 -->
|
||||||
<h3>巡檢表</h3>
|
<template v-if="activeSchema.verification?.enabled">
|
||||||
<el-button
|
<el-divider content-position="left">樣板查證</el-divider>
|
||||||
type="primary"
|
<el-row :align="'middle'" justify="space-between">
|
||||||
size="small"
|
<h3>{{ activeSchema.verification.title }}</h3>
|
||||||
@click="showInspectionDialog = true"
|
<el-button
|
||||||
>新增巡檢表</el-button
|
type="primary"
|
||||||
>
|
size="small"
|
||||||
</el-row>
|
@click="openDialog(activeSchema.verification.dialogType)"
|
||||||
|
>
|
||||||
|
{{ activeSchema.verification.addButtonText }}
|
||||||
|
</el-button>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
<PaginatedTable
|
<el-table :data="dynamicTableData.verification || []" :border="true">
|
||||||
:data="inspectionTableData"
|
<el-table-column
|
||||||
:page-sizes="[5, 10]"
|
v-for="column in activeSchema.verification.columns"
|
||||||
row-key="index"
|
:key="column.prop"
|
||||||
>
|
:prop="column.prop"
|
||||||
<el-table-column prop="index" label="項次" width="60" />
|
:label="column.label"
|
||||||
<el-table-column prop="system1" label="系統1" />
|
:width="column.width"
|
||||||
<el-table-column prop="system2" label="系統2" />
|
/>
|
||||||
<el-table-column prop="deviceId" label="設備編號" />
|
<el-table-column
|
||||||
<el-table-column prop="item" label="項目" />
|
label="功能"
|
||||||
<el-table-column prop="unit" label="單位" />
|
min-width="80"
|
||||||
<el-table-column label="功能" width="140" fixed="right">
|
width="140"
|
||||||
<template #default>
|
fixed="right"
|
||||||
<el-button size="small" type="primary" plain>修改</el-button>
|
>
|
||||||
<el-button size="small" type="danger" plain>刪除</el-button>
|
<template #default="scope">
|
||||||
</template>
|
<el-button size="small" type="primary" plain>修改</el-button>
|
||||||
</el-table-column>
|
<el-button
|
||||||
</PaginatedTable>
|
size="small"
|
||||||
|
type="danger"
|
||||||
<el-divider content-position="left">樣板查證</el-divider>
|
plain
|
||||||
<el-row :align="'middle'" justify="space-between">
|
@click="deleteItem('verification', scope.$index)"
|
||||||
<h3>查證</h3>
|
>
|
||||||
<el-button
|
刪除
|
||||||
type="primary"
|
</el-button>
|
||||||
size="small"
|
</template>
|
||||||
@click="showVerificationDialog = true"
|
</el-table-column>
|
||||||
>新增查證設定</el-button
|
</el-table>
|
||||||
>
|
</template>
|
||||||
<el-table :data="verificationTableData" :border="true">
|
</template>
|
||||||
<el-table-column prop="index" label="項次" width="60" />
|
|
||||||
<el-table-column prop="fieldName" label="欄位名稱" />
|
|
||||||
<el-table-column prop="type" label="類型" />
|
|
||||||
<el-table-column
|
|
||||||
label="功能"
|
|
||||||
min-width="80"
|
|
||||||
width="140"
|
|
||||||
fixed="right"
|
|
||||||
>
|
|
||||||
<template #default>
|
|
||||||
<el-button size="small" type="primary" plain>修改</el-button>
|
|
||||||
<el-button size="small" type="danger" plain>刪除</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</el-row>
|
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="onClose">取消</el-button>
|
<el-button @click="onClose">取消</el-button>
|
||||||
@ -224,55 +244,123 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { ref, computed, watch, reactive } from "vue";
|
||||||
import PaginatedTable from "../Common/PaginatedTable.vue";
|
import PaginatedTable from "../Common/PaginatedTable.vue";
|
||||||
import DutyLogItemDialog from "./DutyLogItemDialog.vue";
|
|
||||||
import HandoverItemDialog from "./HandoverItemDialog.vue";
|
|
||||||
import VerificationSettingDialog from "./VerificationSettingDialog.vue";
|
import VerificationSettingDialog from "./VerificationSettingDialog.vue";
|
||||||
import InspectionItemDialog from "./InspectionItemDialog.vue";
|
import InspectionItemDialog from "./InspectionItemDialog.vue";
|
||||||
|
import CheckItemsDialog from "./CheckItemsDialog.vue";
|
||||||
|
import {
|
||||||
|
getTemplateSchema,
|
||||||
|
initializeTableData,
|
||||||
|
initializeFieldData,
|
||||||
|
} from "../../constants/templateSchemas.js";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
visible: { type: Boolean, default: false },
|
visible: { type: Boolean, default: false },
|
||||||
form: { type: Object, required: true },
|
form: { type: Object, required: true },
|
||||||
dutyLogTableData: { type: Array, default: () => [] },
|
|
||||||
handoverTableData: { type: Array, default: () => [] },
|
|
||||||
inspectionTableData: { type: Array, default: () => [] },
|
|
||||||
verificationTableData: { type: Array, default: () => [] },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(["update:visible", "confirm", "close"]);
|
const emit = defineEmits(["update:visible", "confirm", "close"]);
|
||||||
|
|
||||||
|
// 當前模板的 schema
|
||||||
|
const activeSchema = computed(() => {
|
||||||
|
const schema = getTemplateSchema(props.form.template);
|
||||||
|
if (!schema) return null;
|
||||||
|
|
||||||
|
// 過濾只顯示於樣板的內容
|
||||||
|
return {
|
||||||
|
...schema,
|
||||||
|
sections: schema.sections?.filter(s => s.showInTemplate !== false) || [],
|
||||||
|
tables: schema.tables?.filter(t => t.showInTemplate !== false) || [],
|
||||||
|
verification: schema.verification, // 查證始終顯示
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 動態欄位資料
|
||||||
|
const dynamicFields = reactive({});
|
||||||
|
|
||||||
|
// 動態表格資料
|
||||||
|
const dynamicTableData = reactive({});
|
||||||
|
|
||||||
|
// 對話框顯示狀態
|
||||||
|
const dialogVisibility = reactive({
|
||||||
|
dutyLog: false,
|
||||||
|
handover: false,
|
||||||
|
inspection: false,
|
||||||
|
verification: false,
|
||||||
|
checkItems: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 監聽模板類型變化,重新初始化資料
|
||||||
|
watch(
|
||||||
|
() => props.form.template,
|
||||||
|
(newTemplate) => {
|
||||||
|
if (!newTemplate) return;
|
||||||
|
|
||||||
|
const schema = getTemplateSchema(newTemplate);
|
||||||
|
if (!schema) return;
|
||||||
|
|
||||||
|
// 重置動態欄位
|
||||||
|
Object.keys(dynamicFields).forEach((key) => delete dynamicFields[key]);
|
||||||
|
Object.assign(dynamicFields, initializeFieldData(schema));
|
||||||
|
|
||||||
|
// 重置動態表格
|
||||||
|
Object.keys(dynamicTableData).forEach(
|
||||||
|
(key) => delete dynamicTableData[key]
|
||||||
|
);
|
||||||
|
Object.assign(dynamicTableData, initializeTableData(schema));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 打開對話框
|
||||||
|
function openDialog(dialogType) {
|
||||||
|
if (dialogType === "dutyLog") {
|
||||||
|
dialogVisibility.dutyLog = true;
|
||||||
|
} else if (dialogType === "handover") {
|
||||||
|
dialogVisibility.handover = true;
|
||||||
|
} else if (dialogType === "inspection") {
|
||||||
|
dialogVisibility.inspection = true;
|
||||||
|
} else if (dialogType === "verification") {
|
||||||
|
dialogVisibility.verification = true;
|
||||||
|
} else if (dialogType === "checkItems") {
|
||||||
|
dialogVisibility.checkItems = true;
|
||||||
|
}
|
||||||
|
// 其他 dialogType 可以根據需要擴展
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用新增項目方法
|
||||||
|
function addItem(tableKey, item) {
|
||||||
|
if (!dynamicTableData[tableKey]) {
|
||||||
|
dynamicTableData[tableKey] = [];
|
||||||
|
}
|
||||||
|
const arr = dynamicTableData[tableKey];
|
||||||
|
arr.push({ index: arr.length + 1, ...item });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刪除項目
|
||||||
|
function deleteItem(tableKey, index) {
|
||||||
|
if (dynamicTableData[tableKey]) {
|
||||||
|
dynamicTableData[tableKey].splice(index, 1);
|
||||||
|
// 重新計算項次
|
||||||
|
dynamicTableData[tableKey].forEach((item, idx) => {
|
||||||
|
item.index = idx + 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function onClose() {
|
function onClose() {
|
||||||
emit("update:visible", false);
|
emit("update:visible", false);
|
||||||
emit("close");
|
emit("close");
|
||||||
}
|
}
|
||||||
|
|
||||||
function onConfirm() {
|
function onConfirm() {
|
||||||
emit("confirm");
|
// 可以在這裡整合 dynamicFields 和 dynamicTableData 到 form
|
||||||
}
|
const templateData = {
|
||||||
|
...props.form,
|
||||||
// 子項目 Dialog 顯示狀態
|
fields: dynamicFields,
|
||||||
import { ref } from "vue";
|
tables: dynamicTableData,
|
||||||
const showDutyLogDialog = ref(false);
|
};
|
||||||
const showHandoverDialog = ref(false);
|
emit("confirm", templateData);
|
||||||
const showVerificationDialog = ref(false);
|
|
||||||
const showInspectionDialog = ref(false);
|
|
||||||
|
|
||||||
// 取得父層提供的陣列並推入新項目
|
|
||||||
function addDutyLog(item) {
|
|
||||||
const arr = props.dutyLogTableData;
|
|
||||||
arr.push({ index: arr.length + 1, ...item });
|
|
||||||
}
|
|
||||||
function addHandover(item) {
|
|
||||||
const arr = props.handoverTableData;
|
|
||||||
arr.push({ index: arr.length + 1, ...item });
|
|
||||||
}
|
|
||||||
function addVerification(item) {
|
|
||||||
const arr = props.verificationTableData;
|
|
||||||
arr.push({ index: arr.length + 1, ...item });
|
|
||||||
}
|
|
||||||
function addInspection(item) {
|
|
||||||
const arr = props.inspectionTableData;
|
|
||||||
arr.push({ index: arr.length + 1, ...item });
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
110
src/components/PatrolSetting/CheckItemsDialog.vue
Normal file
110
src/components/PatrolSetting/CheckItemsDialog.vue
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:model-value="visible"
|
||||||
|
title="新增點檢項目"
|
||||||
|
width="600px"
|
||||||
|
@close="onClose"
|
||||||
|
>
|
||||||
|
<el-form :model="form" label-width="120px">
|
||||||
|
<el-form-item label="設備">
|
||||||
|
<el-input v-model="form.index" placeholder="請輸入設備名稱" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="項目">
|
||||||
|
<el-input v-model="form.item" placeholder="請輸入項目" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="內容">
|
||||||
|
<el-input
|
||||||
|
v-model="form.content"
|
||||||
|
type="textarea"
|
||||||
|
:rows="3"
|
||||||
|
placeholder="請輸入內容"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="檢查結果 (早)">
|
||||||
|
<el-input v-model="form.resultMorning" placeholder="請輸入檢查結果" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="檢查結果 (中)">
|
||||||
|
<el-input v-model="form.resultNoon" placeholder="請輸入檢查結果" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="檢查結果 (下)">
|
||||||
|
<el-input v-model="form.resultAfternoon" placeholder="請輸入檢查結果" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="檢查結果 (晚)">
|
||||||
|
<el-input v-model="form.resultEvening" placeholder="請輸入檢查結果" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="備註">
|
||||||
|
<el-input
|
||||||
|
v-model="form.remark"
|
||||||
|
type="textarea"
|
||||||
|
:rows="2"
|
||||||
|
placeholder="請輸入備註"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="onClose">取消</el-button>
|
||||||
|
<el-button type="primary" @click="onConfirm">確定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch } from "vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
visible: { type: Boolean, default: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(["update:visible", "add"]);
|
||||||
|
|
||||||
|
const form = ref({
|
||||||
|
index: "",
|
||||||
|
item: "",
|
||||||
|
content: "",
|
||||||
|
resultMorning: "",
|
||||||
|
resultNoon: "",
|
||||||
|
resultAfternoon: "",
|
||||||
|
resultEvening: "",
|
||||||
|
remark: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
// 當對話框關閉時重置表單
|
||||||
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
(newVal) => {
|
||||||
|
if (!newVal) {
|
||||||
|
resetForm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
form.value = {
|
||||||
|
index: "",
|
||||||
|
item: "",
|
||||||
|
content: "",
|
||||||
|
resultMorning: "",
|
||||||
|
resultNoon: "",
|
||||||
|
resultAfternoon: "",
|
||||||
|
resultEvening: "",
|
||||||
|
remark: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClose() {
|
||||||
|
emit("update:visible", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onConfirm() {
|
||||||
|
// 驗證必填欄位
|
||||||
|
if (!form.value.index || !form.value.item) {
|
||||||
|
// 可以添加提示訊息
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit("add", { ...form.value });
|
||||||
|
emit("update:visible", false);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
@ -1,5 +1,42 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
<div class="filter-section">
|
||||||
|
<el-row :gutter="20" >
|
||||||
|
<el-col :xs="24" :md="6">
|
||||||
|
<el-form-item label="電廠:">
|
||||||
|
<el-select
|
||||||
|
v-model="filterFactory"
|
||||||
|
placeholder="選擇電廠"
|
||||||
|
clearable
|
||||||
|
>
|
||||||
|
<el-option label="四磺子坪" value="四磺子坪" />
|
||||||
|
<el-option label="宜蘭大清水" value="宜蘭大清水" />
|
||||||
|
<el-option label="宜蘭小清水" value="宜蘭小清水" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :xs="24" :md="6">
|
||||||
|
<el-form-item label="模板:" >
|
||||||
|
<el-select
|
||||||
|
v-model="filterCategory"
|
||||||
|
placeholder="選擇模板"
|
||||||
|
clearable
|
||||||
|
>
|
||||||
|
<el-option label="巡檢表" value="巡檢表" />
|
||||||
|
<el-option label="點檢表" value="點檢表" />
|
||||||
|
<el-option label="交接表" value="交接表" />
|
||||||
|
<el-option label="值班日誌" value="值班日誌" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :xs="24" :md="6">
|
||||||
|
<el-button type="primary" :icon="Search" @click="handleSearch">
|
||||||
|
查詢
|
||||||
|
</el-button>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
|
||||||
<el-row justify="end">
|
<el-row justify="end">
|
||||||
<el-button type="primary" :icon="Plus" @click="showDialog = true"
|
<el-button type="primary" :icon="Plus" @click="showDialog = true"
|
||||||
>新增任務</el-button
|
>新增任務</el-button
|
||||||
@ -18,7 +55,7 @@
|
|||||||
<PaginatedTable
|
<PaginatedTable
|
||||||
v-model:current-page="currentPage"
|
v-model:current-page="currentPage"
|
||||||
v-model:page-size="pageSize"
|
v-model:page-size="pageSize"
|
||||||
:data="tableData"
|
:data="filteredData"
|
||||||
:page-sizes="[5, 10]"
|
:page-sizes="[5, 10]"
|
||||||
row-key="index"
|
row-key="index"
|
||||||
>
|
>
|
||||||
@ -26,16 +63,10 @@
|
|||||||
<el-table-column
|
<el-table-column
|
||||||
prop="factory"
|
prop="factory"
|
||||||
label="電廠"
|
label="電廠"
|
||||||
:filters="factoryFilters"
|
|
||||||
:filter-method="filterFactory"
|
|
||||||
filter-placement="bottom-end"
|
|
||||||
/>
|
/>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
prop="category"
|
prop="category"
|
||||||
label="類別"
|
label="模板"
|
||||||
:filters="categoryFilters"
|
|
||||||
:filter-method="filterCategory"
|
|
||||||
filter-placement="bottom-end"
|
|
||||||
/>
|
/>
|
||||||
<el-table-column prop="taskName" label="任務名稱" />
|
<el-table-column prop="taskName" label="任務名稱" />
|
||||||
<el-table-column prop="frequency" label="巡檢頻率" />
|
<el-table-column prop="frequency" label="巡檢頻率" />
|
||||||
@ -57,11 +88,15 @@ import { ref, computed } from "vue";
|
|||||||
import { templateList } from "../../constants/templateList.js";
|
import { templateList } from "../../constants/templateList.js";
|
||||||
import PaginatedTable from "../Common/PaginatedTable.vue";
|
import PaginatedTable from "../Common/PaginatedTable.vue";
|
||||||
import AddTaskDialog from "./AddTaskDialog.vue";
|
import AddTaskDialog from "./AddTaskDialog.vue";
|
||||||
import { Plus, Delete, Edit } from "@element-plus/icons-vue";
|
import { Plus, Delete, Edit, Search } from "@element-plus/icons-vue";
|
||||||
|
|
||||||
const currentPage = ref(1);
|
const currentPage = ref(1);
|
||||||
const pageSize = ref(10);
|
const pageSize = ref(10);
|
||||||
const tableData = [
|
// 篩選條件
|
||||||
|
const filterFactory = ref("");
|
||||||
|
const filterCategory = ref("");
|
||||||
|
|
||||||
|
const tableData = ref([
|
||||||
{
|
{
|
||||||
index: 1,
|
index: 1,
|
||||||
factory: "四磺子坪",
|
factory: "四磺子坪",
|
||||||
@ -172,24 +207,23 @@ const tableData = [
|
|||||||
creator: "管理員03",
|
creator: "管理員03",
|
||||||
createdAt: "2025-11-13 17:14:05",
|
createdAt: "2025-11-13 17:14:05",
|
||||||
},
|
},
|
||||||
];
|
]);
|
||||||
// 篩選選項
|
|
||||||
const factoryFilters = computed(() =>
|
const filteredData = computed(() => {
|
||||||
Array.from(new Set(tableData.map((item) => item.factory))).map((factory) => ({
|
let result = tableData.value;
|
||||||
text: factory,
|
|
||||||
value: factory,
|
// 過濾電廠
|
||||||
}))
|
if (filterFactory.value) {
|
||||||
);
|
result = result.filter((item) => item.factory === filterFactory.value);
|
||||||
const categoryFilters = computed(() =>
|
}
|
||||||
Array.from(new Set(tableData.map((item) => item.category))).map(
|
|
||||||
(category) => ({
|
// 過濾類別
|
||||||
text: category,
|
if (filterCategory.value) {
|
||||||
value: category,
|
result = result.filter((item) => item.category === filterCategory.value);
|
||||||
})
|
}
|
||||||
)
|
|
||||||
);
|
return result;
|
||||||
const filterFactory = (value, row) => row.factory === value;
|
});
|
||||||
const filterCategory = (value, row) => row.category === value;
|
|
||||||
|
|
||||||
// dialog 狀態與表單
|
// dialog 狀態與表單
|
||||||
const showDialog = ref(false);
|
const showDialog = ref(false);
|
||||||
@ -215,15 +249,23 @@ function resetTaskForm() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleAddTask() {
|
function handleAddTask() {
|
||||||
|
// 根據 templateName 判斷 category
|
||||||
|
let category = "";
|
||||||
|
if (taskForm.value.templateName) {
|
||||||
|
if (taskForm.value.templateName.includes("點檢表")) {
|
||||||
|
category = "點檢表";
|
||||||
|
} else if (taskForm.value.templateName.includes("巡檢表")) {
|
||||||
|
category = "巡檢表";
|
||||||
|
} else {
|
||||||
|
category = taskForm.value.templateName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 新增到 tableData
|
// 新增到 tableData
|
||||||
tableData.push({
|
tableData.value.push({
|
||||||
index: tableData.length + 1,
|
index: tableData.value.length + 1,
|
||||||
factory: taskForm.value.factory,
|
factory: taskForm.value.factory,
|
||||||
category: taskForm.value.templateName
|
category: category,
|
||||||
? taskForm.value.templateName.split("每日").length > 1
|
|
||||||
? "點檢表"
|
|
||||||
: "巡檢表"
|
|
||||||
: "",
|
|
||||||
taskName: taskForm.value.taskName,
|
taskName: taskForm.value.taskName,
|
||||||
frequency: taskForm.value.frequency,
|
frequency: taskForm.value.frequency,
|
||||||
count: taskForm.value.count,
|
count: taskForm.value.count,
|
||||||
@ -234,6 +276,24 @@ function handleAddTask() {
|
|||||||
resetTaskForm();
|
resetTaskForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 樣板選擇由 AddTaskDialog 內部處理
|
// 查詢按鈕處理
|
||||||
|
const handleSearch = () => {
|
||||||
|
console.log("搜尋條件:", {
|
||||||
|
factory: filterFactory.value,
|
||||||
|
template: filterCategory.value,
|
||||||
|
});
|
||||||
|
// 篩選邏輯已經在 computed filteredData 中自動執行
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
<style scoped></style>
|
<style scoped>
|
||||||
|
.filter-section {
|
||||||
|
background: #f5f7fa;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-section .el-form-item {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -1,19 +1,53 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<!-- 樣板管理內容 -->
|
<!-- 樣板管理內容 -->
|
||||||
<el-row justify="end">
|
<div class="filter-section">
|
||||||
|
<el-row :gutter="20" >
|
||||||
|
<el-col :xs="24" :md="6">
|
||||||
|
<el-form-item label="電廠:">
|
||||||
|
<el-select
|
||||||
|
v-model="filterFactory"
|
||||||
|
placeholder="選擇電廠"
|
||||||
|
clearable
|
||||||
|
>
|
||||||
|
<el-option label="四磺子坪" value="四磺子坪" />
|
||||||
|
<el-option label="宜蘭大清水" value="宜蘭大清水" />
|
||||||
|
<el-option label="宜蘭小清水" value="宜蘭小清水" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :xs="24" :md="6">
|
||||||
|
<el-form-item label="模板:" >
|
||||||
|
<el-select
|
||||||
|
v-model="filterTemplate"
|
||||||
|
placeholder="選擇模板"
|
||||||
|
clearable
|
||||||
|
>
|
||||||
|
<el-option label="巡檢表" value="巡檢表" />
|
||||||
|
<el-option label="點檢表" value="點檢表" />
|
||||||
|
<el-option label="交接表" value="交接表" />
|
||||||
|
<el-option label="值班日誌" value="值班日誌" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :xs="24" :md="6">
|
||||||
|
<el-button type="primary" :icon="Search" @click="handleSearch">
|
||||||
|
查詢
|
||||||
|
</el-button>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-row justify="end" >
|
||||||
<el-button type="primary" :icon="Plus" @click="showDialog = true"
|
<el-button type="primary" :icon="Plus" @click="showDialog = true"
|
||||||
>新增樣板</el-button
|
>新增樣板</el-button
|
||||||
>
|
>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
|
<!-- 新增樣板Dialog -->
|
||||||
<AddTemplateDialog
|
<AddTemplateDialog
|
||||||
v-model:visible="showDialog"
|
v-model:visible="showDialog"
|
||||||
:form="addTemplateForm"
|
:form="addTemplateForm"
|
||||||
:duty-log-table-data="dutyLogTableData"
|
|
||||||
:handover-table-data="handoverTableData"
|
|
||||||
:inspection-table-data="inspectionTableData"
|
|
||||||
:verification-table-data="verificationTableData"
|
|
||||||
@confirm="handleAddTemplate"
|
@confirm="handleAddTemplate"
|
||||||
@close="resetAddTemplateForm"
|
@close="resetAddTemplateForm"
|
||||||
/>
|
/>
|
||||||
@ -31,16 +65,10 @@
|
|||||||
prop="factory"
|
prop="factory"
|
||||||
label="電廠"
|
label="電廠"
|
||||||
width="200"
|
width="200"
|
||||||
:filters="factoryFilters"
|
|
||||||
:filter-method="filterFactory"
|
|
||||||
filter-placement="bottom-end"
|
|
||||||
/>
|
/>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
prop="template"
|
prop="template"
|
||||||
label="模板"
|
label="模板"
|
||||||
:filters="templateFilters"
|
|
||||||
:filter-method="filterTemplate"
|
|
||||||
filter-placement="bottom-end"
|
|
||||||
/>
|
/>
|
||||||
<el-table-column prop="templateName" label="樣板名稱" />
|
<el-table-column prop="templateName" label="樣板名稱" />
|
||||||
<el-table-column prop="isParent" label="母版" width="100">
|
<el-table-column prop="isParent" label="母版" width="100">
|
||||||
@ -75,11 +103,16 @@ import {
|
|||||||
Edit,
|
Edit,
|
||||||
CopyDocument,
|
CopyDocument,
|
||||||
View,
|
View,
|
||||||
|
Search,
|
||||||
} from "@element-plus/icons-vue";
|
} from "@element-plus/icons-vue";
|
||||||
import PaginatedTable from "../Common/PaginatedTable.vue";
|
import PaginatedTable from "../Common/PaginatedTable.vue";
|
||||||
import AddTemplateDialog from "./AddTemplateDialog.vue";
|
import AddTemplateDialog from "./AddTemplateDialog.vue";
|
||||||
import { templateList } from "../../constants/templateList.js";
|
import { templateList } from "../../constants/templateList.js";
|
||||||
|
|
||||||
|
// 篩選條件
|
||||||
|
const filterFactory = ref("");
|
||||||
|
const filterTemplate = ref("");
|
||||||
|
|
||||||
// 對話框顯示
|
// 對話框顯示
|
||||||
const showDialog = ref(false);
|
const showDialog = ref(false);
|
||||||
|
|
||||||
@ -89,6 +122,7 @@ const addTemplateForm = ref({
|
|||||||
template: "",
|
template: "",
|
||||||
templateName: "",
|
templateName: "",
|
||||||
isParent: "",
|
isParent: "",
|
||||||
|
remark: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
function resetAddTemplateForm() {
|
function resetAddTemplateForm() {
|
||||||
@ -97,139 +131,61 @@ function resetAddTemplateForm() {
|
|||||||
template: "",
|
template: "",
|
||||||
templateName: "",
|
templateName: "",
|
||||||
isParent: "",
|
isParent: "",
|
||||||
|
remark: "",
|
||||||
};
|
};
|
||||||
showDialog.value = false;
|
showDialog.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleAddTemplate() {
|
function handleAddTemplate(templateData) {
|
||||||
// 可依需求推入 tableData 或呼叫 API
|
// 可依需求推入 tableData 或呼叫 API
|
||||||
|
console.log("新增樣板資料:", templateData);
|
||||||
|
// TODO: 呼叫 API 儲存樣板
|
||||||
resetAddTemplateForm();
|
resetAddTemplateForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 假資料:巡檢表
|
|
||||||
const inspectionTableData = ref([
|
|
||||||
{
|
|
||||||
index: 1,
|
|
||||||
system1: "熱源系統",
|
|
||||||
system2: "#2生產井",
|
|
||||||
deviceId: "TE_1004",
|
|
||||||
item: "#2地熱井溫度",
|
|
||||||
unit: "°C",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
index: 2,
|
|
||||||
system1: "熱源系統",
|
|
||||||
system2: "#2生產井",
|
|
||||||
deviceId: "TG_1004",
|
|
||||||
item: "#2地熱井溫度表",
|
|
||||||
unit: "°C",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
index: 3,
|
|
||||||
system1: "熱源系統",
|
|
||||||
system2: "#2生產井",
|
|
||||||
deviceId: "PT_1004",
|
|
||||||
item: "#2地熱井壓力",
|
|
||||||
unit: "bar",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
index: 4,
|
|
||||||
system1: "熱源系統",
|
|
||||||
system2: "#2生產井",
|
|
||||||
deviceId: "PG_1004",
|
|
||||||
item: "#2地熱井壓力表",
|
|
||||||
unit: "kg/cm2",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
index: 5,
|
|
||||||
system1: "熱源系統",
|
|
||||||
system2: "#2生產井",
|
|
||||||
deviceId: "",
|
|
||||||
item: "H2S洩漏偵測",
|
|
||||||
unit: "ppm",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
index: 6,
|
|
||||||
system1: "熱源系統",
|
|
||||||
system2: "#3生產井",
|
|
||||||
deviceId: "TE_1005",
|
|
||||||
item: "#3地熱井溫度",
|
|
||||||
unit: "°C",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
index: 7,
|
|
||||||
system1: "熱源系統",
|
|
||||||
system2: "#3生產井",
|
|
||||||
deviceId: "TG_1005",
|
|
||||||
item: "#3地熱井溫度表",
|
|
||||||
unit: "°C",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
index: 8,
|
|
||||||
system1: "熱源系統",
|
|
||||||
system2: "#3生產井",
|
|
||||||
deviceId: "PT_1005",
|
|
||||||
item: "#3地熱井壓力",
|
|
||||||
unit: "bar",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
index: 9,
|
|
||||||
system1: "熱源系統",
|
|
||||||
system2: "#3生產井",
|
|
||||||
deviceId: "PG_1005",
|
|
||||||
item: "#3地熱井壓力表",
|
|
||||||
unit: "kg/cm2",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
index: 10,
|
|
||||||
system1: "熱源系統",
|
|
||||||
system2: "#3生產井",
|
|
||||||
deviceId: "",
|
|
||||||
item: "H2S洩漏偵測",
|
|
||||||
unit: "ppm",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 值班日誌 table 資料
|
|
||||||
const dutyLogTableData = ref([
|
|
||||||
{ index: 1, change: "", continue: "", result: "", remark: "" },
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 交接事項 table 資料
|
|
||||||
const handoverTableData = ref([
|
|
||||||
{ index: 1, content: "", continue: "", result: "", remark: "" },
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 查證 table 資料
|
|
||||||
const verificationTableData = ref([
|
|
||||||
{ index: 1, fieldName: "現場", type: "數值輸入" },
|
|
||||||
{ index: 2, fieldName: "比對結果", type: "單一選擇" },
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 分頁狀態
|
// 分頁狀態
|
||||||
const currentPage = ref(1);
|
const currentPage = ref(1);
|
||||||
const pageSize = ref(10);
|
const pageSize = ref(10);
|
||||||
|
|
||||||
// 樣板列表資料(共用)
|
// 樣板列表資料(共用)
|
||||||
const tableData = templateList;
|
const allTableData = templateList;
|
||||||
|
|
||||||
// 篩選選項
|
// 篩選後的資料
|
||||||
const factoryFilters = computed(() =>
|
const tableData = computed(() => {
|
||||||
Array.from(new Set(tableData.map((item) => item.factory))).map((factory) => ({
|
let result = allTableData;
|
||||||
text: factory,
|
|
||||||
value: factory,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
const templateFilters = computed(() =>
|
// 過濾電廠
|
||||||
Array.from(new Set(tableData.map((item) => item.template))).map(
|
if (filterFactory.value) {
|
||||||
(template) => ({ text: template, value: template })
|
result = result.filter((item) => item.factory === filterFactory.value);
|
||||||
)
|
}
|
||||||
);
|
|
||||||
|
|
||||||
// 篩選方法
|
// 過濾模板
|
||||||
const filterFactory = (value, row) => row.factory === value;
|
if (filterTemplate.value) {
|
||||||
const filterTemplate = (value, row) => row.template === value;
|
result = result.filter((item) => item.template === filterTemplate.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 查詢按鈕處理
|
||||||
|
const handleSearch = () => {
|
||||||
|
console.log("搜尋條件:", {
|
||||||
|
factory: filterFactory.value,
|
||||||
|
template: filterTemplate.value,
|
||||||
|
});
|
||||||
|
// 篩選邏輯已經在 computed tableData 中自動執行
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped>
|
||||||
|
.filter-section {
|
||||||
|
background: #f5f7fa;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-section .el-form-item {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
172
src/components/PlantInfo/OperationFormDialog.vue
Normal file
172
src/components/PlantInfo/OperationFormDialog.vue
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
v-model="dialogVisible"
|
||||||
|
title="填寫運維表單"
|
||||||
|
width="800px"
|
||||||
|
:before-close="handleClose"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="form"
|
||||||
|
:rules="rules"
|
||||||
|
label-width="120px"
|
||||||
|
label-position="right"
|
||||||
|
>
|
||||||
|
<el-form-item label="電廠" prop="factory">
|
||||||
|
<el-input v-model="form.factory" disabled />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="項目" prop="item">
|
||||||
|
<el-input v-model="form.item" disabled />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="類型" prop="type">
|
||||||
|
<el-input v-model="form.type" disabled />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="處理人員" prop="operator">
|
||||||
|
<el-input v-model="form.operator" placeholder="請輸入處理人員姓名" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="作業日期" prop="workDate">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="form.workDate"
|
||||||
|
type="date"
|
||||||
|
placeholder="選擇作業日期"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="作業內容" prop="content">
|
||||||
|
<el-input
|
||||||
|
v-model="form.content"
|
||||||
|
type="textarea"
|
||||||
|
:rows="4"
|
||||||
|
placeholder="請描述作業內容"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="上傳照片" prop="photos">
|
||||||
|
<el-upload
|
||||||
|
v-model:file-list="form.photoList"
|
||||||
|
action="#"
|
||||||
|
list-type="picture-card"
|
||||||
|
:auto-upload="false"
|
||||||
|
:limit="5"
|
||||||
|
accept="image/*"
|
||||||
|
>
|
||||||
|
<el-icon><Plus /></el-icon>
|
||||||
|
</el-upload>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="備註" prop="remark">
|
||||||
|
<el-input
|
||||||
|
v-model="form.remark"
|
||||||
|
type="textarea"
|
||||||
|
:rows="3"
|
||||||
|
placeholder="其他備註事項"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="handleClose">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleSubmit">確認送出</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch } from "vue";
|
||||||
|
import { Plus } from "@element-plus/icons-vue";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
rowData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(["update:visible", "submit"]);
|
||||||
|
|
||||||
|
const dialogVisible = ref(props.visible);
|
||||||
|
const formRef = ref();
|
||||||
|
|
||||||
|
const form = ref({
|
||||||
|
factory: "",
|
||||||
|
item: "",
|
||||||
|
type: "",
|
||||||
|
operator: "",
|
||||||
|
workDate: "",
|
||||||
|
content: "",
|
||||||
|
photoList: [],
|
||||||
|
remark: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const rules = {
|
||||||
|
operator: [{ required: true, message: "請輸入處理人員", trigger: "blur" }],
|
||||||
|
workDate: [{ required: true, message: "請選擇作業日期", trigger: "change" }],
|
||||||
|
content: [{ required: true, message: "請描述作業內容", trigger: "blur" }],
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
(val) => {
|
||||||
|
dialogVisible.value = val;
|
||||||
|
if (val && props.rowData) {
|
||||||
|
// 初始化表單資料
|
||||||
|
form.value.factory = props.rowData.factory || "";
|
||||||
|
form.value.item = props.rowData.item || "";
|
||||||
|
form.value.type = props.rowData.type || "";
|
||||||
|
form.value.operator = props.rowData.operator || "";
|
||||||
|
form.value.workDate = "";
|
||||||
|
form.value.content = "";
|
||||||
|
form.value.photoList = [];
|
||||||
|
form.value.remark = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(dialogVisible, (val) => {
|
||||||
|
emit("update:visible", val);
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
dialogVisible.value = false;
|
||||||
|
formRef.value?.resetFields();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!formRef.value) return;
|
||||||
|
|
||||||
|
await formRef.value.validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
// 組裝提交資料
|
||||||
|
const submitData = {
|
||||||
|
...props.rowData,
|
||||||
|
...form.value,
|
||||||
|
photos: form.value.photoList.map((file) => file.url || file.raw),
|
||||||
|
};
|
||||||
|
|
||||||
|
emit("submit", submitData);
|
||||||
|
ElMessage.success("表單提交成功");
|
||||||
|
handleClose();
|
||||||
|
} else {
|
||||||
|
ElMessage.error("請填寫必填欄位");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep(.el-upload--picture-card) {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
:deep(.el-upload-list--picture-card .el-upload-list__item) {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
242
src/components/PlantInfo/OperationRecordTable.vue
Normal file
242
src/components/PlantInfo/OperationRecordTable.vue
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="header-row">
|
||||||
|
<el-radio-group v-model="tabType" size="large">
|
||||||
|
<el-radio-button label="全部" value="all" />
|
||||||
|
<el-radio-button label="維修" value="repair" />
|
||||||
|
<el-radio-button label="保養" value="maintenance" />
|
||||||
|
</el-radio-group>
|
||||||
|
<div>
|
||||||
|
<el-date-picker
|
||||||
|
v-model="dateValue"
|
||||||
|
type="daterange"
|
||||||
|
size="large"
|
||||||
|
:shortcuts="shortcuts"
|
||||||
|
range-separator="至"
|
||||||
|
start-placeholder="開始日期"
|
||||||
|
end-placeholder="結束日期"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
date-format="YYYY/MM/DD"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="button-row">
|
||||||
|
<el-button type="primary" size="large" :icon="Search">查詢</el-button>
|
||||||
|
<el-button type="success" size="large" :icon="Printer">匯出</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<PaginatedTable
|
||||||
|
v-model:current-page="currentPage"
|
||||||
|
v-model:page-size="pageSize"
|
||||||
|
:data="filteredData"
|
||||||
|
:page-sizes="[5, 10, 20]"
|
||||||
|
row-key="index"
|
||||||
|
>
|
||||||
|
<el-table-column prop="factory" label="電廠" width="100" />
|
||||||
|
<el-table-column prop="item" label="項目" width="100" />
|
||||||
|
<el-table-column prop="type" label="類型" />
|
||||||
|
<el-table-column prop="status" label="狀態" width="140">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tag :type="getTagType(scope.row.status)" disable-transitions>
|
||||||
|
{{ scope.row.status }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="operator" label="處理人員" width="100" />
|
||||||
|
<el-table-column prop="plan" label="本次作業預計">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-text type="danger" v-if="scope.row.status === '未完成-過期'">{{
|
||||||
|
scope.row.plan
|
||||||
|
}}</el-text>
|
||||||
|
<span v-else>{{ scope.row.plan }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="photos" label="照片">
|
||||||
|
<template #default="scope">
|
||||||
|
<div v-if="!scope.row.photos || scope.row.photos.length === 0">
|
||||||
|
無照片
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<el-link
|
||||||
|
v-for="(img, idx) in scope.row.photos"
|
||||||
|
:key="img || `${scope.row.index}-${idx}`"
|
||||||
|
:href="img"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
style="display: inline-block"
|
||||||
|
>
|
||||||
|
<el-image
|
||||||
|
:src="img"
|
||||||
|
style="width: 40px; height: 40px; margin-right: 4px"
|
||||||
|
fit="cover"
|
||||||
|
/>
|
||||||
|
</el-link>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="finishDate" label="完成時間" />
|
||||||
|
<el-table-column prop="formNo" label="表單號" width="160" fixed="right">
|
||||||
|
<template #default="scope">
|
||||||
|
<template v-if="scope.row.formNo">
|
||||||
|
<el-link
|
||||||
|
:href="scope.row.formNo"
|
||||||
|
type="primary"
|
||||||
|
target="_blank"
|
||||||
|
underline="always"
|
||||||
|
>{{ scope.row.formNo }}</el-link
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
:icon="Edit"
|
||||||
|
@click="openFormDialog(scope.row)"
|
||||||
|
>
|
||||||
|
填寫表單
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</PaginatedTable>
|
||||||
|
|
||||||
|
<OperationFormDialog
|
||||||
|
v-model:visible="dialogVisible"
|
||||||
|
:row-data="selectedRow"
|
||||||
|
@submit="handleFormSubmit"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from "vue";
|
||||||
|
import { Search, Printer, Edit } from "@element-plus/icons-vue";
|
||||||
|
import PaginatedTable from "../Common/PaginatedTable.vue";
|
||||||
|
import OperationFormDialog from "./OperationFormDialog.vue";
|
||||||
|
const tabType = ref("all");
|
||||||
|
const currentPage = ref(1);
|
||||||
|
const pageSize = ref(10);
|
||||||
|
const dateValue = ref("");
|
||||||
|
const dialogVisible = ref(false);
|
||||||
|
const selectedRow = ref({});
|
||||||
|
const shortcuts = [
|
||||||
|
{
|
||||||
|
text: "近7天",
|
||||||
|
value: () => {
|
||||||
|
const end = new Date();
|
||||||
|
const start = new Date();
|
||||||
|
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
|
||||||
|
return [start, end];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "近30天",
|
||||||
|
value: () => {
|
||||||
|
const end = new Date();
|
||||||
|
const start = new Date();
|
||||||
|
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
|
||||||
|
return [start, end];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "近3個月",
|
||||||
|
value: () => {
|
||||||
|
const end = new Date();
|
||||||
|
const start = new Date();
|
||||||
|
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
|
||||||
|
return [start, end];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const tableData = ref([
|
||||||
|
{
|
||||||
|
index: 1,
|
||||||
|
factory: "四磺子坪",
|
||||||
|
formNo: "op20210630001",
|
||||||
|
item: "維修",
|
||||||
|
type: "逆變器異常",
|
||||||
|
status: "完成",
|
||||||
|
checked: true,
|
||||||
|
operator: "王小明",
|
||||||
|
plan: "2025/03/01 ~ 2025/03/15",
|
||||||
|
photos: [
|
||||||
|
"https://images.unsplash.com/photo-1506744038136-46273834b3fb?w=300",
|
||||||
|
"https://images.unsplash.com/photo-1464983953574-0892a716854b?w=300",
|
||||||
|
],
|
||||||
|
finishDate: "2025-03-5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 2,
|
||||||
|
factory: "四磺子坪",
|
||||||
|
formNo: null,
|
||||||
|
item: "保養",
|
||||||
|
type: "巡檢",
|
||||||
|
status: "未完成-過期",
|
||||||
|
checked: false,
|
||||||
|
operator: "",
|
||||||
|
plan: "2025/05/01 ~ 2025/05/15",
|
||||||
|
photos: [],
|
||||||
|
finishDate: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 3,
|
||||||
|
factory: "四磺子坪",
|
||||||
|
formNo: null,
|
||||||
|
item: "保養",
|
||||||
|
type: "清洗",
|
||||||
|
status: "未完成",
|
||||||
|
checked: false,
|
||||||
|
operator: "",
|
||||||
|
plan: "2025/06/01 ~ 2025/06/15",
|
||||||
|
photos: [],
|
||||||
|
finishDate: "",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const filteredData = computed(() => {
|
||||||
|
if (tabType.value === "all") return tableData.value;
|
||||||
|
if (tabType.value === "repair")
|
||||||
|
return tableData.value.filter((row) => row.item === "維修");
|
||||||
|
if (tabType.value === "maintenance")
|
||||||
|
return tableData.value.filter((row) => row.item === "保養");
|
||||||
|
return tableData.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
function getTagType(status) {
|
||||||
|
if (status === "完成") return "success";
|
||||||
|
if (status === "未完成") return "warning";
|
||||||
|
if (status === "未完成-過期") return "danger";
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function openFormDialog(row) {
|
||||||
|
selectedRow.value = row;
|
||||||
|
dialogVisible.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFormSubmit(data) {
|
||||||
|
console.log("表單提交資料:", data);
|
||||||
|
// TODO: 呼叫 API 儲存表單資料
|
||||||
|
// 更新表格中的資料
|
||||||
|
const index = tableData.value.findIndex((item) => item.index === data.index);
|
||||||
|
if (index !== -1) {
|
||||||
|
tableData.value[index] = {
|
||||||
|
...tableData.value[index],
|
||||||
|
operator: data.operator,
|
||||||
|
finishDate: data.workDate,
|
||||||
|
status: "完成",
|
||||||
|
formNo: `op${Date.now()}`, // 生成表單號
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.header-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -61,7 +61,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<div class="button-row">
|
<div class="button-row">
|
||||||
<el-button type="primary" size="large" :icon="Search">查詢</el-button>
|
<el-button type="primary" size="large" :icon="Search">查詢</el-button>
|
||||||
<el-button type="success" size="large" :icon="Printer">查詢</el-button>
|
<el-button type="success" size="large" :icon="Printer">匯出</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|||||||
620
src/constants/templateSchemas.js
Normal file
620
src/constants/templateSchemas.js
Normal file
@ -0,0 +1,620 @@
|
|||||||
|
/**
|
||||||
|
* 模板配置文件
|
||||||
|
* 定義每個模板類型的欄位結構和表格配置
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const templateSchemas = {
|
||||||
|
1: {
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
title: "表單內容",
|
||||||
|
showInTemplate: false, // 新增樣板時不顯示
|
||||||
|
showInFill: true, // 填寫任務時顯示
|
||||||
|
fields: [
|
||||||
|
{ key: "siteName", label: "案場名稱", span: 24 },
|
||||||
|
{ key: "dutyPerson", label: "值班人員", span: 12 },
|
||||||
|
{ key: "shift", label: "班別", span: 12 },
|
||||||
|
{ key: "weather", label: "天氣", span: 12 },
|
||||||
|
{ key: "temperature", label: "環境溫度/濕度", span: 12 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tables: [
|
||||||
|
{
|
||||||
|
key: "dutyLog",
|
||||||
|
title: "值班日誌",
|
||||||
|
addButtonText: "新增日誌項目",
|
||||||
|
dialogType: "dutyLog",
|
||||||
|
showInTemplate: false, // 新增樣板時不顯示
|
||||||
|
showInFill: true, // 填寫任務時顯示
|
||||||
|
columns: [
|
||||||
|
{ prop: "index", label: "項次", width: "60" },
|
||||||
|
{ prop: "change", label: "變更內容" },
|
||||||
|
{ prop: "continue", label: "持續" },
|
||||||
|
{ prop: "result", label: "結案" },
|
||||||
|
{ prop: "remark", label: "備註" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "handover",
|
||||||
|
title: "交接事項",
|
||||||
|
addButtonText: "新增交接事項",
|
||||||
|
dialogType: "handover",
|
||||||
|
showInTemplate: false, // 新增樣板時不顯示
|
||||||
|
showInFill: true, // 填寫任務時顯示
|
||||||
|
columns: [
|
||||||
|
{ prop: "index", label: "項次", width: "60" },
|
||||||
|
{ prop: "content", label: "工作內容" },
|
||||||
|
{ prop: "continue", label: "持續" },
|
||||||
|
{ prop: "result", label: "結案" },
|
||||||
|
{ prop: "remark", label: "備註" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "inspection",
|
||||||
|
title: "巡檢表",
|
||||||
|
addButtonText: "新增巡檢項目",
|
||||||
|
dialogType: "inspection",
|
||||||
|
usePagination: true,
|
||||||
|
showInTemplate: true, // 新增樣板時顯示
|
||||||
|
showInFill: true, // 填寫任務時顯示
|
||||||
|
columns: [
|
||||||
|
{ prop: "index", label: "項次", width: "60" },
|
||||||
|
{ prop: "system1", label: "系統1" },
|
||||||
|
{ prop: "system2", label: "系統2" },
|
||||||
|
{ prop: "deviceId", label: "設備編號" },
|
||||||
|
{ prop: "item", label: "項目" },
|
||||||
|
{ prop: "unit", label: "單位" },
|
||||||
|
],
|
||||||
|
defaultData: [
|
||||||
|
{
|
||||||
|
index: 1,
|
||||||
|
system1: "熱源系統",
|
||||||
|
system2: "#2生產井",
|
||||||
|
deviceId: "TE_1004",
|
||||||
|
item: "#2地熱井溫度",
|
||||||
|
unit: "°C",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 2,
|
||||||
|
system1: "熱源系統",
|
||||||
|
system2: "#2生產井",
|
||||||
|
deviceId: "TG_1004",
|
||||||
|
item: "#2地熱井溫度表",
|
||||||
|
unit: "°C",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 3,
|
||||||
|
system1: "熱源系統",
|
||||||
|
system2: "#2生產井",
|
||||||
|
deviceId: "PT_1004",
|
||||||
|
item: "#2地熱井壓力",
|
||||||
|
unit: "bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 4,
|
||||||
|
system1: "熱源系統",
|
||||||
|
system2: "#2生產井",
|
||||||
|
deviceId: "PG_1004",
|
||||||
|
item: "#2地熱井壓力表",
|
||||||
|
unit: "kg/cm2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 5,
|
||||||
|
system1: "熱源系統",
|
||||||
|
system2: "#2生產井",
|
||||||
|
deviceId: "",
|
||||||
|
item: "H2S洩漏偵測",
|
||||||
|
unit: "ppm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 6,
|
||||||
|
system1: "熱源系統",
|
||||||
|
system2: "#3生產井",
|
||||||
|
deviceId: "TE_1005",
|
||||||
|
item: "#3地熱井溫度",
|
||||||
|
unit: "°C",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 7,
|
||||||
|
system1: "熱源系統",
|
||||||
|
system2: "#3生產井",
|
||||||
|
deviceId: "TG_1005",
|
||||||
|
item: "#3地熱井溫度表",
|
||||||
|
unit: "°C",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 8,
|
||||||
|
system1: "熱源系統",
|
||||||
|
system2: "#3生產井",
|
||||||
|
deviceId: "PT_1005",
|
||||||
|
item: "#3地熱井壓力",
|
||||||
|
unit: "bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 9,
|
||||||
|
system1: "熱源系統",
|
||||||
|
system2: "#3生產井",
|
||||||
|
deviceId: "PG_1005",
|
||||||
|
item: "#3地熱井壓力表",
|
||||||
|
unit: "kg/cm2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 10,
|
||||||
|
system1: "熱源系統",
|
||||||
|
system2: "#3生產井",
|
||||||
|
deviceId: "",
|
||||||
|
item: "H2S洩漏偵測",
|
||||||
|
unit: "ppm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 11,
|
||||||
|
system1: "熱源系統",
|
||||||
|
system2: "#4生產井",
|
||||||
|
deviceId: "TE_1006",
|
||||||
|
item: "#4地熱井溫度",
|
||||||
|
unit: "°C",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 12,
|
||||||
|
system1: "熱源系統",
|
||||||
|
system2: "#4生產井",
|
||||||
|
deviceId: "TG_1006",
|
||||||
|
item: "#4地熱井溫度表",
|
||||||
|
unit: "°C",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 13,
|
||||||
|
system1: "熱源系統",
|
||||||
|
system2: "#4生產井",
|
||||||
|
deviceId: "PT_1006",
|
||||||
|
item: "#4地熱井壓力",
|
||||||
|
unit: "bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 14,
|
||||||
|
system1: "熱源系統",
|
||||||
|
system2: "#4生產井",
|
||||||
|
deviceId: "PG_1006",
|
||||||
|
item: "#4地熱井壓力表",
|
||||||
|
unit: "kg/cm2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 15,
|
||||||
|
system1: "熱源系統",
|
||||||
|
system2: "#4生產井",
|
||||||
|
deviceId: "",
|
||||||
|
item: "H2S洩漏偵測",
|
||||||
|
unit: "ppm",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
verification: {
|
||||||
|
enabled: true,
|
||||||
|
showInTemplate: true, // 新增樣板時顯示
|
||||||
|
showInFill: false, // 填寫任務時不顯示
|
||||||
|
title: "查證",
|
||||||
|
addButtonText: "新增查證設定",
|
||||||
|
dialogType: "verification",
|
||||||
|
columns: [
|
||||||
|
{ prop: "index", label: "項次", width: "60" },
|
||||||
|
{ prop: "fieldName", label: "欄位名稱" },
|
||||||
|
{ prop: "type", label: "類型" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
2: {
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
title: "樣板內容",
|
||||||
|
showInTemplate: false, // 新增樣板時不顯示
|
||||||
|
showInFill: true, // 填寫任務時顯示
|
||||||
|
fields: [
|
||||||
|
{ key: "siteName", label: "案場名稱", span: 12 },
|
||||||
|
{ key: "inspectionDate", label: "點檢日期", span: 12 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tables: [
|
||||||
|
{
|
||||||
|
key: "checkItems",
|
||||||
|
title: "點檢項目",
|
||||||
|
addButtonText: "新增點檢項目",
|
||||||
|
dialogType: "checkItems",
|
||||||
|
usePagination: true,
|
||||||
|
showInTemplate: true, // 新增樣板時顯示
|
||||||
|
showInFill: true, // 填寫任務時顯示
|
||||||
|
columns: [
|
||||||
|
{ prop: "index", label: "設備"},
|
||||||
|
{ prop: "item", label: "項目" },
|
||||||
|
{ prop: "content", label: "內容" },
|
||||||
|
{ prop: "resultMorning", label: "檢查結果 (早)" },
|
||||||
|
{ prop: "resultNoon", label: "檢查結果 (中)" },
|
||||||
|
{ prop: "resultAfternoon", label: "檢查結果 (下)" },
|
||||||
|
{ prop: "resultEvening", label: "檢查結果 (晚)" },
|
||||||
|
{ prop: "remark", label: "備註" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
verification: {
|
||||||
|
enabled: true,
|
||||||
|
title: "查證",
|
||||||
|
addButtonText: "新增查證設定",
|
||||||
|
dialogType: "verification",
|
||||||
|
columns: [
|
||||||
|
{ prop: "index", label: "項次", width: "60" },
|
||||||
|
{ prop: "fieldName", label: "欄位名稱" },
|
||||||
|
{ prop: "type", label: "類型" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
3: {
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
title: "樣板內容",
|
||||||
|
showInTemplate: false,
|
||||||
|
showInFill: true,
|
||||||
|
fields: [
|
||||||
|
{ key: "siteName", label: "案場名稱", span: 24 },
|
||||||
|
{ key: "inspector", label: "檢查人員", span: 12 },
|
||||||
|
{ key: "checkDate", label: "檢查日期", span: 12 },
|
||||||
|
{ key: "weather", label: "天氣狀況", span: 12 },
|
||||||
|
{ key: "temperature", label: "環境溫度", span: 12 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tables: [
|
||||||
|
{
|
||||||
|
key: "dailyCheck",
|
||||||
|
title: "每日檢查項目",
|
||||||
|
addButtonText: "新增檢查項目",
|
||||||
|
dialogType: "dailyCheck",
|
||||||
|
usePagination: true,
|
||||||
|
showInTemplate: true,
|
||||||
|
showInFill: true,
|
||||||
|
columns: [
|
||||||
|
{ prop: "index", label: "項次", width: "60" },
|
||||||
|
{ prop: "equipment", label: "設備名稱" },
|
||||||
|
{ prop: "checkItem", label: "檢查項目" },
|
||||||
|
{ prop: "standard", label: "判定基準" },
|
||||||
|
{ prop: "result", label: "檢查結果" },
|
||||||
|
{ prop: "remark", label: "備註" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "abnormal",
|
||||||
|
title: "異常記錄",
|
||||||
|
addButtonText: "新增異常記錄",
|
||||||
|
dialogType: "abnormal",
|
||||||
|
showInTemplate: false,
|
||||||
|
showInFill: true,
|
||||||
|
columns: [
|
||||||
|
{ prop: "index", label: "項次", width: "60" },
|
||||||
|
{ prop: "time", label: "發現時間" },
|
||||||
|
{ prop: "equipment", label: "設備名稱" },
|
||||||
|
{ prop: "description", label: "異常描述" },
|
||||||
|
{ prop: "action", label: "處理措施" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
verification: {
|
||||||
|
enabled: true,
|
||||||
|
title: "查證",
|
||||||
|
addButtonText: "新增查證設定",
|
||||||
|
dialogType: "verification",
|
||||||
|
columns: [
|
||||||
|
{ prop: "index", label: "項次", width: "60" },
|
||||||
|
{ prop: "fieldName", label: "欄位名稱" },
|
||||||
|
{ prop: "type", label: "類型" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
4: {
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
title: "樣板內容",
|
||||||
|
showInTemplate: false,
|
||||||
|
showInFill: true,
|
||||||
|
fields: [
|
||||||
|
{ key: "siteName", label: "案場名稱", span: 24 },
|
||||||
|
{ key: "inspector", label: "檢查人員", span: 12 },
|
||||||
|
{ key: "checkMonth", label: "檢查月份", span: 12 },
|
||||||
|
{ key: "supervisor", label: "督導人員", span: 12 },
|
||||||
|
{ key: "approver", label: "核准人員", span: 12 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tables: [
|
||||||
|
{
|
||||||
|
key: "monthlyCheck",
|
||||||
|
title: "每月檢查項目",
|
||||||
|
addButtonText: "新增檢查項目",
|
||||||
|
dialogType: "monthlyCheck",
|
||||||
|
usePagination: true,
|
||||||
|
showInTemplate: true,
|
||||||
|
showInFill: true,
|
||||||
|
columns: [
|
||||||
|
{ prop: "index", label: "項次", width: "60" },
|
||||||
|
{ prop: "category", label: "檢查類別" },
|
||||||
|
{ prop: "equipment", label: "設備名稱" },
|
||||||
|
{ prop: "checkItem", label: "檢查項目" },
|
||||||
|
{ prop: "standard", label: "判定基準" },
|
||||||
|
{ prop: "result", label: "檢查結果" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
verification: {
|
||||||
|
enabled: true,
|
||||||
|
title: "查證",
|
||||||
|
addButtonText: "新增查證設定",
|
||||||
|
dialogType: "verification",
|
||||||
|
columns: [
|
||||||
|
{ prop: "index", label: "項次", width: "60" },
|
||||||
|
{ prop: "fieldName", label: "欄位名稱" },
|
||||||
|
{ prop: "type", label: "類型" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
5: {
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
title: "申請資訊",
|
||||||
|
showInTemplate: false,
|
||||||
|
showInFill: true,
|
||||||
|
fields: [
|
||||||
|
{ key: "applicant", label: "申請人", span: 12 },
|
||||||
|
{ key: "applyDate", label: "申請日期", span: 12 },
|
||||||
|
{ key: "workLocation", label: "作業地點", span: 24 },
|
||||||
|
{ key: "workContent", label: "作業內容", span: 24, type: "textarea" },
|
||||||
|
{ key: "startTime", label: "開始時間", span: 12 },
|
||||||
|
{ key: "endTime", label: "結束時間", span: 12 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tables: [
|
||||||
|
{
|
||||||
|
key: "safetyMeasures",
|
||||||
|
title: "安全措施",
|
||||||
|
addButtonText: "新增安全措施",
|
||||||
|
dialogType: "safetyMeasures",
|
||||||
|
showInTemplate: true,
|
||||||
|
showInFill: true,
|
||||||
|
columns: [
|
||||||
|
{ prop: "index", label: "項次", width: "60" },
|
||||||
|
{ prop: "measure", label: "措施項目" },
|
||||||
|
{ prop: "responsible", label: "負責人" },
|
||||||
|
{ prop: "checkResult", label: "確認結果" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "personnel",
|
||||||
|
title: "作業人員",
|
||||||
|
addButtonText: "新增人員",
|
||||||
|
dialogType: "personnel",
|
||||||
|
showInTemplate: true,
|
||||||
|
showInFill: true,
|
||||||
|
columns: [
|
||||||
|
{ prop: "index", label: "項次", width: "60" },
|
||||||
|
{ prop: "name", label: "姓名" },
|
||||||
|
{ prop: "role", label: "職務" },
|
||||||
|
{ prop: "certNumber", label: "證照編號" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
verification: {
|
||||||
|
enabled: true,
|
||||||
|
title: "查證",
|
||||||
|
addButtonText: "新增查證設定",
|
||||||
|
dialogType: "verification",
|
||||||
|
columns: [
|
||||||
|
{ prop: "index", label: "項次", width: "60" },
|
||||||
|
{ prop: "fieldName", label: "欄位名稱" },
|
||||||
|
{ prop: "type", label: "類型" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
6: {
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
title: "申請資訊",
|
||||||
|
showInTemplate: false,
|
||||||
|
showInFill: true,
|
||||||
|
fields: [
|
||||||
|
{ key: "applicant", label: "申請人", span: 12 },
|
||||||
|
{ key: "applyDate", label: "申請日期", span: 12 },
|
||||||
|
{ key: "confinedSpace", label: "俺限空間名稱", span: 24 },
|
||||||
|
{ key: "workContent", label: "作業內容", span: 24, type: "textarea" },
|
||||||
|
{ key: "startTime", label: "開始時間", span: 12 },
|
||||||
|
{ key: "endTime", label: "結束時間", span: 12 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "環境檢測",
|
||||||
|
showInTemplate: false,
|
||||||
|
showInFill: true,
|
||||||
|
fields: [
|
||||||
|
{ key: "oxygenLevel", label: "氧氣濃度 (%)", span: 12 },
|
||||||
|
{ key: "toxicGas", label: "有毒氣體 (ppm)", span: 12 },
|
||||||
|
{ key: "combustibleGas", label: "可燃性氣體 (%LEL)", span: 12 },
|
||||||
|
{ key: "testTime", label: "檢測時間", span: 12 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tables: [
|
||||||
|
{
|
||||||
|
key: "safetyEquipment",
|
||||||
|
title: "安全設備",
|
||||||
|
addButtonText: "新增設備",
|
||||||
|
dialogType: "safetyEquipment",
|
||||||
|
showInTemplate: true,
|
||||||
|
showInFill: true,
|
||||||
|
columns: [
|
||||||
|
{ prop: "index", label: "項次", width: "60" },
|
||||||
|
{ prop: "equipment", label: "設備名稱" },
|
||||||
|
{ prop: "quantity", label: "數量" },
|
||||||
|
{ prop: "checkResult", label: "檢查結果" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "personnel",
|
||||||
|
title: "作業人員與監視人",
|
||||||
|
addButtonText: "新增人員",
|
||||||
|
dialogType: "personnel",
|
||||||
|
showInTemplate: true,
|
||||||
|
showInFill: true,
|
||||||
|
columns: [
|
||||||
|
{ prop: "index", label: "項次", width: "60" },
|
||||||
|
{ prop: "name", label: "姓名" },
|
||||||
|
{ prop: "role", label: "角色" },
|
||||||
|
{ prop: "training", label: "教育訓練" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
verification: {
|
||||||
|
enabled: true,
|
||||||
|
title: "查證",
|
||||||
|
addButtonText: "新增查證設定",
|
||||||
|
dialogType: "verification",
|
||||||
|
columns: [
|
||||||
|
{ prop: "index", label: "項次", width: "60" },
|
||||||
|
{ prop: "fieldName", label: "欄位名稱" },
|
||||||
|
{ prop: "type", label: "類型" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
7: {
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
title: "申請資訊",
|
||||||
|
showInTemplate: false,
|
||||||
|
showInFill: true,
|
||||||
|
fields: [
|
||||||
|
{ key: "applicant", label: "申請人", span: 12 },
|
||||||
|
{ key: "applyDate", label: "申請日期", span: 12 },
|
||||||
|
{ key: "fireLocation", label: "動火地點", span: 24 },
|
||||||
|
{ key: "fireType", label: "動火類型", span: 12 },
|
||||||
|
{ key: "workContent", label: "作業內容", span: 24, type: "textarea" },
|
||||||
|
{ key: "startTime", label: "開始時間", span: 12 },
|
||||||
|
{ key: "endTime", label: "結束時間", span: 12 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tables: [
|
||||||
|
{
|
||||||
|
key: "fireSafety",
|
||||||
|
title: "消防安全措施",
|
||||||
|
addButtonText: "新增措施",
|
||||||
|
dialogType: "fireSafety",
|
||||||
|
showInTemplate: true,
|
||||||
|
showInFill: true,
|
||||||
|
columns: [
|
||||||
|
{ prop: "index", label: "項次", width: "60" },
|
||||||
|
{ prop: "measure", label: "措施項目" },
|
||||||
|
{ prop: "checkPoint", label: "檢查重點" },
|
||||||
|
{ prop: "checkResult", label: "確認結果" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "fireEquipment",
|
||||||
|
title: "消防器材",
|
||||||
|
addButtonText: "新增器材",
|
||||||
|
dialogType: "fireEquipment",
|
||||||
|
showInTemplate: true,
|
||||||
|
showInFill: true,
|
||||||
|
columns: [
|
||||||
|
{ prop: "index", label: "項次", width: "60" },
|
||||||
|
{ prop: "equipment", label: "器材名稱" },
|
||||||
|
{ prop: "quantity", label: "數量" },
|
||||||
|
{ prop: "location", label: "放置位置" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "firePersonnel",
|
||||||
|
title: "動火作業人員",
|
||||||
|
addButtonText: "新增人員",
|
||||||
|
dialogType: "personnel",
|
||||||
|
showInTemplate: true,
|
||||||
|
showInFill: true,
|
||||||
|
columns: [
|
||||||
|
{ prop: "index", label: "項次", width: "60" },
|
||||||
|
{ prop: "name", label: "姓名" },
|
||||||
|
{ prop: "role", label: "職務" },
|
||||||
|
{ prop: "certNumber", label: "證照編號" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
verification: {
|
||||||
|
enabled: true,
|
||||||
|
title: "查證",
|
||||||
|
addButtonText: "新增查證設定",
|
||||||
|
dialogType: "verification",
|
||||||
|
columns: [
|
||||||
|
{ prop: "index", label: "項次", width: "60" },
|
||||||
|
{ prop: "fieldName", label: "欄位名稱" },
|
||||||
|
{ prop: "type", label: "類型" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根據模板類型獲取對應的 schema
|
||||||
|
* @param {string} templateType - 模板類型
|
||||||
|
* @returns {object|null} 對應的 schema 或 null
|
||||||
|
*/
|
||||||
|
export function getTemplateSchema(templateType) {
|
||||||
|
const schema = templateSchemas[templateType];
|
||||||
|
if (!schema) return null;
|
||||||
|
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化模板表格資料
|
||||||
|
* @param {object} schema - 模板 schema
|
||||||
|
* @returns {object} 包含所有表格初始資料的物件
|
||||||
|
*/
|
||||||
|
export function initializeTableData(schema) {
|
||||||
|
if (!schema) return {};
|
||||||
|
|
||||||
|
const tableData = {};
|
||||||
|
|
||||||
|
// 初始化一般表格
|
||||||
|
if (schema.tables) {
|
||||||
|
schema.tables.forEach((table) => {
|
||||||
|
// 如果有預設資料,使用深拷貝避免引用問題
|
||||||
|
tableData[table.key] = table.defaultData
|
||||||
|
? JSON.parse(JSON.stringify(table.defaultData))
|
||||||
|
: [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化驗證表格
|
||||||
|
if (schema.verification?.enabled) {
|
||||||
|
tableData.verification = schema.verification.defaultData
|
||||||
|
? JSON.parse(JSON.stringify(schema.verification.defaultData))
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return tableData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化模板欄位資料
|
||||||
|
* @param {object} schema - 模板 schema
|
||||||
|
* @returns {object} 包含所有欄位初始值的物件
|
||||||
|
*/
|
||||||
|
export function initializeFieldData(schema) {
|
||||||
|
if (!schema || !schema.sections) return {};
|
||||||
|
|
||||||
|
const fieldData = {};
|
||||||
|
|
||||||
|
schema.sections.forEach((section) => {
|
||||||
|
section.fields.forEach((field) => {
|
||||||
|
fieldData[field.key] = "";
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return fieldData;
|
||||||
|
}
|
||||||
@ -9,28 +9,10 @@
|
|||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</template>
|
</template>
|
||||||
<div v-if="radio === 'pending'">
|
<div v-if="radio === 'pending'">
|
||||||
<!-- 待完成內容 -->
|
<PendingTasks />
|
||||||
<el-button-group>
|
|
||||||
<el-button :icon="TakeawayBox" @click="save('image/png')"
|
|
||||||
>保存PNG</el-button
|
|
||||||
>
|
|
||||||
<el-button :icon="TakeawayBox" @click="save('image/jpeg')"
|
|
||||||
>保存JPEG</el-button
|
|
||||||
>
|
|
||||||
<el-button :icon="Loading" @click="clear">清除</el-button>
|
|
||||||
<el-button :icon="RefreshLeft" @click="undo">返回</el-button>
|
|
||||||
</el-button-group>
|
|
||||||
<Vue3Signature
|
|
||||||
ref="signature"
|
|
||||||
:sigOption="options"
|
|
||||||
:w="'300px'"
|
|
||||||
:h="'150px'"
|
|
||||||
class="signature-pad"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-if="radio === 'completed'">
|
<div v-if="radio === 'completed'">
|
||||||
<!-- 已完成內容 -->
|
<CompletedTasks />
|
||||||
已完成任務列表
|
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
@ -38,43 +20,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, reactive } from "vue";
|
import { ref } from "vue";
|
||||||
import { TakeawayBox, Loading, RefreshLeft } from "@element-plus/icons-vue";
|
import PendingTasks from "../components/PatrolMission/PendingTasks.vue";
|
||||||
import dayjs from "dayjs";
|
import CompletedTasks from "../components/PatrolMission/CompletedTasks.vue";
|
||||||
|
|
||||||
const radio = ref("pending");
|
const radio = ref("pending");
|
||||||
const signature = ref(null);
|
|
||||||
|
|
||||||
const options = reactive({
|
|
||||||
penColor: "rgb(0, 0, 0)",
|
|
||||||
backgroundColor: "rgb(255, 255, 255)",
|
|
||||||
});
|
|
||||||
|
|
||||||
const save = (format) => {
|
|
||||||
if (signature.value.isEmpty()) {
|
|
||||||
alert("Please provide a signature first.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dataUrl = signature.value.save(format);
|
|
||||||
|
|
||||||
// Download the image
|
|
||||||
const link = document.createElement("a");
|
|
||||||
const extension = format === "image/jpeg" ? "jpg" : "png";
|
|
||||||
// 取得當下時間字串
|
|
||||||
const nowStr = dayjs().format("YYYYMMDD_HHmmss");
|
|
||||||
link.download = `signature_${nowStr}.${extension}`;
|
|
||||||
link.href = dataUrl;
|
|
||||||
link.click();
|
|
||||||
};
|
|
||||||
|
|
||||||
const clear = () => {
|
|
||||||
signature.value.clear();
|
|
||||||
};
|
|
||||||
|
|
||||||
const undo = () => {
|
|
||||||
signature.value.undo();
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@ -31,6 +31,9 @@
|
|||||||
<template v-else-if="tab.name === 'abnormal-record'">
|
<template v-else-if="tab.name === 'abnormal-record'">
|
||||||
異常紀錄
|
異常紀錄
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="tab.name === 'operation-record'">
|
||||||
|
<OperationRecordTable />
|
||||||
|
</template>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</el-card>
|
</el-card>
|
||||||
@ -42,6 +45,7 @@
|
|||||||
import { ref, computed } from "vue";
|
import { ref, computed } from "vue";
|
||||||
import { useRouter, useRoute } from "vue-router";
|
import { useRouter, useRoute } from "vue-router";
|
||||||
import CustomIframe from "../components/Common/CustomIframe.vue";
|
import CustomIframe from "../components/Common/CustomIframe.vue";
|
||||||
|
import OperationRecordTable from "../components/PlantInfo/OperationRecordTable.vue";
|
||||||
import {
|
import {
|
||||||
Odometer,
|
Odometer,
|
||||||
Postcard,
|
Postcard,
|
||||||
@ -109,6 +113,12 @@ const tabList = computed(() => [
|
|||||||
icon: Warning,
|
icon: Warning,
|
||||||
src: null,
|
src: null,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "operation-record",
|
||||||
|
label: "運維紀錄",
|
||||||
|
icon: Operation,
|
||||||
|
src: null,
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user