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']
|
||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
||||
ElForm: typeof import('element-plus/es')['ElForm']
|
||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||
ElHeader: typeof import('element-plus/es')['ElHeader']
|
||||
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||
ElImage: typeof import('element-plus/es')['ElImage']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
||||
ElLink: typeof import('element-plus/es')['ElLink']
|
||||
ElMain: typeof import('element-plus/es')['ElMain']
|
||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||
@ -48,6 +51,8 @@ declare module 'vue' {
|
||||
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
||||
ElTabs: typeof import('element-plus/es')['ElTabs']
|
||||
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']
|
||||
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%"
|
||||
@close="onClose"
|
||||
>
|
||||
<DutyLogItemDialog v-model:visible="showDutyLogDialog" @add="addDutyLog" />
|
||||
<HandoverItemDialog
|
||||
v-model:visible="showHandoverDialog"
|
||||
@add="addHandover"
|
||||
/>
|
||||
<!-- 通用對話框 -->
|
||||
<VerificationSettingDialog
|
||||
v-model:visible="showVerificationDialog"
|
||||
@add="addVerification"
|
||||
v-model:visible="dialogVisibility.verification"
|
||||
@add="(item) => addItem('verification', item)"
|
||||
/>
|
||||
<InspectionItemDialog
|
||||
v-model:visible="showInspectionDialog"
|
||||
@add="addInspection"
|
||||
v-model:visible="dialogVisibility.inspection"
|
||||
@add="(item) => addItem('inspection', item)"
|
||||
/>
|
||||
<CheckItemsDialog
|
||||
v-model:visible="dialogVisibility.checkItems"
|
||||
@add="(item) => addItem('checkItems', item)"
|
||||
/>
|
||||
|
||||
<el-form :model="form">
|
||||
<el-divider content-position="left">樣板資訊</el-divider>
|
||||
<el-row :gutter="20">
|
||||
@ -35,28 +36,13 @@
|
||||
<el-col :xs="24" :sm="12">
|
||||
<el-form-item label="模板">
|
||||
<el-select v-model="form.template" placeholder="請選擇模板類型">
|
||||
<el-option label="巡檢表" value="巡檢表" />
|
||||
<el-option label="點檢表" value="點檢表" />
|
||||
<el-option
|
||||
label="高壓特每日自動檢查表"
|
||||
value="高壓特每日自動檢查表"
|
||||
/>
|
||||
<el-option
|
||||
label="高壓特每月自動檢查表"
|
||||
value="高壓特每月自動檢查表"
|
||||
/>
|
||||
<el-option
|
||||
label="一班作業安全許可申請表"
|
||||
value="一班作業安全許可申請表"
|
||||
/>
|
||||
<el-option
|
||||
label="侷限作業安全許可申請表"
|
||||
value="侷限作業安全許可申請表"
|
||||
/>
|
||||
<el-option
|
||||
label="動火作業安全許可申請表"
|
||||
value="動火作業安全許可申請表"
|
||||
/>
|
||||
<el-option label="巡檢表" value="1" />
|
||||
<el-option label="點檢表" value="2" />
|
||||
<el-option label="高壓特每日自動檢查表" value="3" />
|
||||
<el-option label="高壓特每月自動檢查表" value="4" />
|
||||
<el-option label="一班作業安全許可申請表" value="5" />
|
||||
<el-option label="侷限作業安全許可申請表" value="6" />
|
||||
<el-option label="動火作業安全許可申請表" value="7" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@ -76,145 +62,179 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-divider content-position="left">樣板內容</el-divider>
|
||||
<el-row :gutter="20">
|
||||
<el-col :xs="24" :sm="24">
|
||||
<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-col>
|
||||
<el-col :xs="24" :sm="12">
|
||||
<el-form-item label="環境溫度/濕度">
|
||||
<el-input />
|
||||
<el-form-item label="備註">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
placeholder="請輸入備註"
|
||||
type="textarea"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :align="'middle'" justify="space-between">
|
||||
<h3>值班日誌</h3>
|
||||
<el-button type="primary" size="small" @click="showDutyLogDialog = true"
|
||||
>新增日誌項目</el-button
|
||||
>
|
||||
<el-table :data="dutyLogTableData" :border="true">
|
||||
<el-table-column prop="index" label="項次" width="60" />
|
||||
<el-table-column prop="change" 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 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="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-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-table-column
|
||||
v-for="column in table.columns"
|
||||
:key="column.prop"
|
||||
:prop="column.prop"
|
||||
:label="column.label"
|
||||
:width="column.width"
|
||||
/>
|
||||
<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">
|
||||
<h3>交接事項</h3>
|
||||
<el-button
|
||||
type="primary"
|
||||
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"
|
||||
<el-table
|
||||
v-else
|
||||
:data="dynamicTableData[table.key] || []"
|
||||
:border="true"
|
||||
>
|
||||
<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-table-column
|
||||
v-for="column in table.columns"
|
||||
:key="column.prop"
|
||||
:prop="column.prop"
|
||||
:label="column.label"
|
||||
:width="column.width"
|
||||
/>
|
||||
<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>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="showInspectionDialog = true"
|
||||
>新增巡檢表</el-button
|
||||
>
|
||||
</el-row>
|
||||
<!-- 動態渲染驗證區塊 -->
|
||||
<template v-if="activeSchema.verification?.enabled">
|
||||
<el-divider content-position="left">樣板查證</el-divider>
|
||||
<el-row :align="'middle'" justify="space-between">
|
||||
<h3>{{ activeSchema.verification.title }}</h3>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="openDialog(activeSchema.verification.dialogType)"
|
||||
>
|
||||
{{ activeSchema.verification.addButtonText }}
|
||||
</el-button>
|
||||
</el-row>
|
||||
|
||||
<PaginatedTable
|
||||
:data="inspectionTableData"
|
||||
:page-sizes="[5, 10]"
|
||||
row-key="index"
|
||||
>
|
||||
<el-table-column prop="index" label="項次" width="60" />
|
||||
<el-table-column prop="system1" label="系統1" />
|
||||
<el-table-column prop="system2" label="系統2" />
|
||||
<el-table-column prop="deviceId" label="設備編號" />
|
||||
<el-table-column prop="item" label="項目" />
|
||||
<el-table-column prop="unit" label="單位" />
|
||||
<el-table-column label="功能" 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>
|
||||
</PaginatedTable>
|
||||
|
||||
<el-divider content-position="left">樣板查證</el-divider>
|
||||
<el-row :align="'middle'" justify="space-between">
|
||||
<h3>查證</h3>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="showVerificationDialog = true"
|
||||
>新增查證設定</el-button
|
||||
>
|
||||
<el-table :data="verificationTableData" :border="true">
|
||||
<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-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="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('verification', scope.$index)"
|
||||
>
|
||||
刪除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
</template>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onClose">取消</el-button>
|
||||
@ -224,55 +244,123 @@
|
||||
</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 VerificationSettingDialog from "./VerificationSettingDialog.vue";
|
||||
import InspectionItemDialog from "./InspectionItemDialog.vue";
|
||||
import CheckItemsDialog from "./CheckItemsDialog.vue";
|
||||
import {
|
||||
getTemplateSchema,
|
||||
initializeTableData,
|
||||
initializeFieldData,
|
||||
} from "../../constants/templateSchemas.js";
|
||||
|
||||
const props = defineProps({
|
||||
visible: { type: Boolean, default: false },
|
||||
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"]);
|
||||
|
||||
// 當前模板的 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() {
|
||||
emit("update:visible", false);
|
||||
emit("close");
|
||||
}
|
||||
|
||||
function onConfirm() {
|
||||
emit("confirm");
|
||||
}
|
||||
|
||||
// 子項目 Dialog 顯示狀態
|
||||
import { ref } from "vue";
|
||||
const showDutyLogDialog = ref(false);
|
||||
const showHandoverDialog = ref(false);
|
||||
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 });
|
||||
// 可以在這裡整合 dynamicFields 和 dynamicTableData 到 form
|
||||
const templateData = {
|
||||
...props.form,
|
||||
fields: dynamicFields,
|
||||
tables: dynamicTableData,
|
||||
};
|
||||
emit("confirm", templateData);
|
||||
}
|
||||
</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>
|
||||
<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-button type="primary" :icon="Plus" @click="showDialog = true"
|
||||
>新增任務</el-button
|
||||
@ -18,7 +55,7 @@
|
||||
<PaginatedTable
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:data="tableData"
|
||||
:data="filteredData"
|
||||
:page-sizes="[5, 10]"
|
||||
row-key="index"
|
||||
>
|
||||
@ -26,16 +63,10 @@
|
||||
<el-table-column
|
||||
prop="factory"
|
||||
label="電廠"
|
||||
:filters="factoryFilters"
|
||||
:filter-method="filterFactory"
|
||||
filter-placement="bottom-end"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="category"
|
||||
label="類別"
|
||||
:filters="categoryFilters"
|
||||
:filter-method="filterCategory"
|
||||
filter-placement="bottom-end"
|
||||
label="模板"
|
||||
/>
|
||||
<el-table-column prop="taskName" label="任務名稱" />
|
||||
<el-table-column prop="frequency" label="巡檢頻率" />
|
||||
@ -57,11 +88,15 @@ import { ref, computed } from "vue";
|
||||
import { templateList } from "../../constants/templateList.js";
|
||||
import PaginatedTable from "../Common/PaginatedTable.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 pageSize = ref(10);
|
||||
const tableData = [
|
||||
// 篩選條件
|
||||
const filterFactory = ref("");
|
||||
const filterCategory = ref("");
|
||||
|
||||
const tableData = ref([
|
||||
{
|
||||
index: 1,
|
||||
factory: "四磺子坪",
|
||||
@ -172,24 +207,23 @@ const tableData = [
|
||||
creator: "管理員03",
|
||||
createdAt: "2025-11-13 17:14:05",
|
||||
},
|
||||
];
|
||||
// 篩選選項
|
||||
const factoryFilters = computed(() =>
|
||||
Array.from(new Set(tableData.map((item) => item.factory))).map((factory) => ({
|
||||
text: factory,
|
||||
value: factory,
|
||||
}))
|
||||
);
|
||||
const categoryFilters = computed(() =>
|
||||
Array.from(new Set(tableData.map((item) => item.category))).map(
|
||||
(category) => ({
|
||||
text: category,
|
||||
value: category,
|
||||
})
|
||||
)
|
||||
);
|
||||
const filterFactory = (value, row) => row.factory === value;
|
||||
const filterCategory = (value, row) => row.category === value;
|
||||
]);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
// dialog 狀態與表單
|
||||
const showDialog = ref(false);
|
||||
@ -215,15 +249,23 @@ function resetTaskForm() {
|
||||
}
|
||||
|
||||
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.push({
|
||||
index: tableData.length + 1,
|
||||
tableData.value.push({
|
||||
index: tableData.value.length + 1,
|
||||
factory: taskForm.value.factory,
|
||||
category: taskForm.value.templateName
|
||||
? taskForm.value.templateName.split("每日").length > 1
|
||||
? "點檢表"
|
||||
: "巡檢表"
|
||||
: "",
|
||||
category: category,
|
||||
taskName: taskForm.value.taskName,
|
||||
frequency: taskForm.value.frequency,
|
||||
count: taskForm.value.count,
|
||||
@ -234,6 +276,24 @@ function handleAddTask() {
|
||||
resetTaskForm();
|
||||
}
|
||||
|
||||
// 樣板選擇由 AddTaskDialog 內部處理
|
||||
// 查詢按鈕處理
|
||||
const handleSearch = () => {
|
||||
console.log("搜尋條件:", {
|
||||
factory: filterFactory.value,
|
||||
template: filterCategory.value,
|
||||
});
|
||||
// 篩選邏輯已經在 computed filteredData 中自動執行
|
||||
};
|
||||
</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>
|
||||
<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
|
||||
>
|
||||
</el-row>
|
||||
|
||||
<!-- 新增樣板Dialog -->
|
||||
<AddTemplateDialog
|
||||
v-model:visible="showDialog"
|
||||
:form="addTemplateForm"
|
||||
:duty-log-table-data="dutyLogTableData"
|
||||
:handover-table-data="handoverTableData"
|
||||
:inspection-table-data="inspectionTableData"
|
||||
:verification-table-data="verificationTableData"
|
||||
@confirm="handleAddTemplate"
|
||||
@close="resetAddTemplateForm"
|
||||
/>
|
||||
@ -31,16 +65,10 @@
|
||||
prop="factory"
|
||||
label="電廠"
|
||||
width="200"
|
||||
:filters="factoryFilters"
|
||||
:filter-method="filterFactory"
|
||||
filter-placement="bottom-end"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="template"
|
||||
label="模板"
|
||||
:filters="templateFilters"
|
||||
:filter-method="filterTemplate"
|
||||
filter-placement="bottom-end"
|
||||
/>
|
||||
<el-table-column prop="templateName" label="樣板名稱" />
|
||||
<el-table-column prop="isParent" label="母版" width="100">
|
||||
@ -75,11 +103,16 @@ import {
|
||||
Edit,
|
||||
CopyDocument,
|
||||
View,
|
||||
Search,
|
||||
} from "@element-plus/icons-vue";
|
||||
import PaginatedTable from "../Common/PaginatedTable.vue";
|
||||
import AddTemplateDialog from "./AddTemplateDialog.vue";
|
||||
import { templateList } from "../../constants/templateList.js";
|
||||
|
||||
// 篩選條件
|
||||
const filterFactory = ref("");
|
||||
const filterTemplate = ref("");
|
||||
|
||||
// 對話框顯示
|
||||
const showDialog = ref(false);
|
||||
|
||||
@ -89,6 +122,7 @@ const addTemplateForm = ref({
|
||||
template: "",
|
||||
templateName: "",
|
||||
isParent: "",
|
||||
remark: "",
|
||||
});
|
||||
|
||||
function resetAddTemplateForm() {
|
||||
@ -97,139 +131,61 @@ function resetAddTemplateForm() {
|
||||
template: "",
|
||||
templateName: "",
|
||||
isParent: "",
|
||||
remark: "",
|
||||
};
|
||||
showDialog.value = false;
|
||||
}
|
||||
|
||||
function handleAddTemplate() {
|
||||
function handleAddTemplate(templateData) {
|
||||
// 可依需求推入 tableData 或呼叫 API
|
||||
console.log("新增樣板資料:", templateData);
|
||||
// TODO: 呼叫 API 儲存樣板
|
||||
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 pageSize = ref(10);
|
||||
|
||||
// 樣板列表資料(共用)
|
||||
const tableData = templateList;
|
||||
const allTableData = templateList;
|
||||
|
||||
// 篩選選項
|
||||
const factoryFilters = computed(() =>
|
||||
Array.from(new Set(tableData.map((item) => item.factory))).map((factory) => ({
|
||||
text: factory,
|
||||
value: factory,
|
||||
}))
|
||||
);
|
||||
// 篩選後的資料
|
||||
const tableData = computed(() => {
|
||||
let result = allTableData;
|
||||
|
||||
const templateFilters = computed(() =>
|
||||
Array.from(new Set(tableData.map((item) => item.template))).map(
|
||||
(template) => ({ text: template, value: template })
|
||||
)
|
||||
);
|
||||
// 過濾電廠
|
||||
if (filterFactory.value) {
|
||||
result = result.filter((item) => item.factory === filterFactory.value);
|
||||
}
|
||||
|
||||
// 篩選方法
|
||||
const filterFactory = (value, row) => row.factory === value;
|
||||
const filterTemplate = (value, row) => row.template === value;
|
||||
// 過濾模板
|
||||
if (filterTemplate.value) {
|
||||
result = result.filter((item) => item.template === filterTemplate.value);
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
// 查詢按鈕處理
|
||||
const handleSearch = () => {
|
||||
console.log("搜尋條件:", {
|
||||
factory: filterFactory.value,
|
||||
template: filterTemplate.value,
|
||||
});
|
||||
// 篩選邏輯已經在 computed tableData 中自動執行
|
||||
};
|
||||
</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>
|
||||
<div class="button-row">
|
||||
<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>
|
||||
</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>
|
||||
</template>
|
||||
<div v-if="radio === 'pending'">
|
||||
<!-- 待完成內容 -->
|
||||
<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"
|
||||
/>
|
||||
<PendingTasks />
|
||||
</div>
|
||||
<div v-if="radio === 'completed'">
|
||||
<!-- 已完成內容 -->
|
||||
已完成任務列表
|
||||
<CompletedTasks />
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
@ -38,43 +20,11 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, reactive } from "vue";
|
||||
import { TakeawayBox, Loading, RefreshLeft } from "@element-plus/icons-vue";
|
||||
import dayjs from "dayjs";
|
||||
import { ref } from "vue";
|
||||
import PendingTasks from "../components/PatrolMission/PendingTasks.vue";
|
||||
import CompletedTasks from "../components/PatrolMission/CompletedTasks.vue";
|
||||
|
||||
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>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@ -31,6 +31,9 @@
|
||||
<template v-else-if="tab.name === 'abnormal-record'">
|
||||
異常紀錄
|
||||
</template>
|
||||
<template v-else-if="tab.name === 'operation-record'">
|
||||
<OperationRecordTable />
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
@ -42,6 +45,7 @@
|
||||
import { ref, computed } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
import CustomIframe from "../components/Common/CustomIframe.vue";
|
||||
import OperationRecordTable from "../components/PlantInfo/OperationRecordTable.vue";
|
||||
import {
|
||||
Odometer,
|
||||
Postcard,
|
||||
@ -109,6 +113,12 @@ const tabList = computed(() => [
|
||||
icon: Warning,
|
||||
src: null,
|
||||
},
|
||||
{
|
||||
name: "operation-record",
|
||||
label: "運維紀錄",
|
||||
icon: Operation,
|
||||
src: null,
|
||||
},
|
||||
]);
|
||||
</script>
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user