feat: 更新巡檢任務與設定,新增查證功能與選項管理

This commit is contained in:
huliang 2025-11-24 15:42:01 +08:00
parent 211d42cd86
commit 95d963192f
8 changed files with 379 additions and 206 deletions

1
src/components.d.ts vendored
View File

@ -26,7 +26,6 @@ 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']

View File

@ -56,8 +56,9 @@
<el-divider content-position="left">{{ table.title }}</el-divider> <el-divider content-position="left">{{ table.title }}</el-divider>
<el-row :align="'middle'" justify="space-between" style="margin-bottom: 10px;"> <el-row :align="'middle'" justify="space-between" style="margin-bottom: 10px;">
<h3>{{ table.title }}</h3> <h3>{{ table.title }}</h3>
<!-- 只有當 allowAdd 不為 false 時才顯示新增按鈕 -->
<el-button <el-button
v-if="table.allowAdd !== false" v-if="table.allowAdd !== false && table.key !== 'inspection'"
type="primary" type="primary"
size="small" size="small"
@click="openDialog(table.dialogType)" @click="openDialog(table.dialogType)"
@ -73,6 +74,7 @@
:page-sizes="[10, 20]" :page-sizes="[10, 20]"
row-key="index" row-key="index"
> >
<!-- 基本欄位 -->
<el-table-column <el-table-column
v-for="column in table.columns" v-for="column in table.columns"
:key="column.prop" :key="column.prop"
@ -81,16 +83,54 @@
:width="column.width" :width="column.width"
> >
<template #default="scope"> <template #default="scope">
<!-- 如果是巡檢表的數值欄位提供輸入框 --> <span>{{ scope.row[column.prop] }}</span>
<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> </template>
</el-table-column> </el-table-column>
<!-- 動態查證欄位 (僅巡檢表) -->
<template v-if="table.useVerification && verificationFields.length > 0">
<el-table-column
v-for="field in verificationFields"
:key="field.fieldName"
:label="field.fieldName"
width="140"
fixed="right"
>
<template #default="scope">
<!-- 數值輸入 -->
<el-input
v-if="field.type === '數值輸入'"
v-model="scope.row[`verify_${field.fieldName}`]"
size="small"
type="number"
:placeholder="`請輸入${field.fieldName}`"
/>
<!-- 文字輸入 -->
<el-input
v-else-if="field.type === '文字輸入'"
v-model="scope.row[`verify_${field.fieldName}`]"
size="small"
:placeholder="`請輸入${field.fieldName}`"
/>
<!-- 單一選擇 -->
<el-radio-group
v-else-if="field.type === '單一選擇'"
v-model="scope.row[`verify_${field.fieldName}`]"
size="small"
>
<el-radio-button
v-for="option in field.options"
:key="option"
:label="option"
>
{{ option }}
</el-radio-button>
</el-radio-group>
</template>
</el-table-column>
</template>
<!-- 功能欄位 (根據配置決定是否顯示) -->
<el-table-column <el-table-column
v-if="table.allowEdit !== false" v-if="table.allowEdit !== false"
label="功能" label="功能"
@ -148,42 +188,6 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
</template> </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> </template>
</el-form> </el-form>
@ -248,7 +252,6 @@ const activeSchema = computed(() => {
...schema, ...schema,
sections: schema.sections?.filter(s => s.showInFill !== false) || [], sections: schema.sections?.filter(s => s.showInFill !== false) || [],
tables: schema.tables?.filter(t => t.showInFill !== false) || [], tables: schema.tables?.filter(t => t.showInFill !== false) || [],
verification: schema.verification, //
}; };
}); });
@ -257,6 +260,14 @@ const dialogTitle = computed(() => {
return props.taskData?.taskName || "填寫任務"; return props.taskData?.taskName || "填寫任務";
}); });
// ( verification )
const verificationFields = computed(() => {
if (!props.taskData || !props.taskData.tables || !props.taskData.tables.verification) {
return [];
}
return props.taskData.tables.verification || [];
});
// () // ()
const isValueColumn = (prop) => { const isValueColumn = (prop) => {
const infoColumns = ['index', 'system1', 'system2', 'deviceId', 'item', 'unit']; const infoColumns = ['index', 'system1', 'system2', 'deviceId', 'item', 'unit'];
@ -282,11 +293,18 @@ watch(
// , // ,
if (newTaskData.fillData) { if (newTaskData.fillData) {
Object.assign(dynamicFields, newTaskData.fillData.fields || {}); Object.assign(dynamicFields, newTaskData.fillData.fields || {});
Object.assign(dynamicTableData, newTaskData.fillData.tables || initializeTableData(schema)); Object.assign(dynamicTableData, newTaskData.fillData.tables || {});
} else { } else {
// //
// 使 taskData.tables ( API )
if (newTaskData.tables) {
// 使
Object.assign(dynamicTableData, JSON.parse(JSON.stringify(newTaskData.tables)));
} else {
// 使 schema ()
Object.assign(dynamicTableData, initializeTableData(schema)); Object.assign(dynamicTableData, initializeTableData(schema));
} }
}
}, },
{ immediate: true } { immediate: true }
); );

View File

@ -128,7 +128,7 @@ const pageSize = ref(10);
// //
const filterFactory = ref(""); const filterFactory = ref("");
const filterCategory = ref(""); const filterCategory = ref("");
// table // table ( API )
const allTableData = ref([ const allTableData = ref([
{ {
index: 1, index: 1,
@ -137,6 +137,43 @@ const allTableData = ref([
templateType: "1", // templateSchemas key templateType: "1", // templateSchemas key
taskName: "四磺子坪1.2MW地熱能ORC發電機", taskName: "四磺子坪1.2MW地熱能ORC發電機",
dispatchDate: "2025-11-01", dispatchDate: "2025-11-01",
// API
tables: {
inspection: [
{
index: 1,
system1: "熱源系統",
system2: "#2生產井",
deviceId: "TE_1004",
item: "#2地熱井溫度",
unit: "°C",
lowerLimit: 80,
upperLimit: 120,
dcsValue: 100,
},
{
index: 2,
system1: "熱源系統",
system2: "#2生產井",
deviceId: "TG_1004",
item: "#2地熱井溫度表",
unit: "°C",
lowerLimit: 80,
upperLimit: 120,
dcsValue: 98,
},
// ...
],
verification: [
{ index: 1, fieldName: "現場", type: "數值輸入" },
{
index: 2,
fieldName: "比對結果",
type: "單一選擇",
options: ["符合", "不符合"],
},
],
},
}, },
{ {
index: 2, index: 2,
@ -145,6 +182,30 @@ const allTableData = ref([
templateType: "1", templateType: "1",
taskName: "四磺子坪1.2MW先導地熱能ORC發電機", taskName: "四磺子坪1.2MW先導地熱能ORC發電機",
dispatchDate: "2025-11-01", dispatchDate: "2025-11-01",
tables: {
inspection: [
{
index: 1,
system1: "熱源系統",
system2: "#3生產井",
deviceId: "TE_1005",
item: "#3地熱井溫度",
unit: "°C",
lowerLimit: 80,
upperLimit: 120,
dcsValue: 98,
},
],
verification: [
{ index: 1, fieldName: "現場", type: "數值輸入" },
{
index: 2,
fieldName: "比對結果",
type: "單一選擇",
options: ["符合", "不符合"],
},
],
},
}, },
{ {
index: 3, index: 3,
@ -153,6 +214,11 @@ const allTableData = ref([
templateType: "2", templateType: "2",
taskName: "廠房01維修工程", taskName: "廠房01維修工程",
dispatchDate: "2025-11-02", dispatchDate: "2025-11-02",
tables: {
checkItems: [
// ...
],
},
}, },
]); ]);
@ -188,6 +254,8 @@ const currentTask = ref(null);
// //
const handleFillTask = (task) => { const handleFillTask = (task) => {
console.log("task", task);
currentTask.value = task; currentTask.value = task;
fillDialogVisible.value = true; fillDialogVisible.value = true;
}; };

View File

@ -47,14 +47,6 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :xs="24" :sm="12"> <el-col :xs="24" :sm="12">
<el-form-item label="母版">
<el-select v-model="form.isParent" placeholder="請選擇">
<el-option label="是" value="是" />
<el-option label="否" value="否" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24">
<el-form-item label="樣板名稱"> <el-form-item label="樣板名稱">
<el-input <el-input
v-model="form.templateName" v-model="form.templateName"
@ -62,13 +54,12 @@
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :xs="24" :sm="24"> <el-col :xs="24" :sm="12">
<el-form-item label="備註"> <el-form-item label="母版">
<el-input <el-select v-model="form.isParent" placeholder="請選擇">
v-model="form.remark" <el-option label="是" value="是" />
placeholder="請輸入備註" <el-option label="否" value="否" />
type="textarea" </el-select>
/>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
@ -191,49 +182,6 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
</template> </template>
<!-- 動態渲染驗證區塊 -->
<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>
<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> </template>
</el-form> </el-form>
<template #footer> <template #footer>
@ -270,9 +218,8 @@ const activeSchema = computed(() => {
// //
return { return {
...schema, ...schema,
sections: schema.sections?.filter(s => s.showInTemplate !== false) || [], sections: schema.sections?.filter((s) => s.showInTemplate !== false) || [],
tables: schema.tables?.filter(t => t.showInTemplate !== false) || [], tables: schema.tables?.filter((t) => t.showInTemplate !== false) || [],
verification: schema.verification, //
}; };
}); });

View File

@ -122,7 +122,6 @@ const addTemplateForm = ref({
template: "", template: "",
templateName: "", templateName: "",
isParent: "", isParent: "",
remark: "",
}); });
function resetAddTemplateForm() { function resetAddTemplateForm() {
@ -131,7 +130,6 @@ function resetAddTemplateForm() {
template: "", template: "",
templateName: "", templateName: "",
isParent: "", isParent: "",
remark: "",
}; };
showDialog.value = false; showDialog.value = false;
} }

View File

@ -0,0 +1,72 @@
<template>
<el-dialog
:model-value="visible"
:title="isEdit ? '修改選項' : '新增選項'"
width="400px"
@close="close"
>
<el-form :model="form" label-width="70px">
<el-form-item label="名稱">
<el-input v-model="form.name" placeholder="請輸入選項名稱" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="close">取消</el-button>
<el-button type="primary" @click="confirm">確定</el-button>
</template>
</el-dialog>
</template>
<script setup>
import { reactive, watch } from "vue";
const props = defineProps({
visible: { type: Boolean, default: false },
optionData: { type: Object, default: null }, //
});
const emit = defineEmits(["update:visible", "add", "update"]);
const form = reactive({ name: "" });
const isEdit = reactive({ value: false });
// optionData
watch(
() => props.optionData,
(newData) => {
if (newData) {
isEdit.value = true;
form.name = newData.name;
} else {
isEdit.value = false;
form.name = "";
}
},
{ immediate: true }
);
function reset() {
form.name = "";
isEdit.value = false;
}
function close() {
emit("update:visible", false);
reset();
}
function confirm() {
if (!form.name.trim()) {
return;
}
if (isEdit.value) {
emit("update", { ...form });
} else {
emit("add", { ...form });
}
close();
}
</script>
<style scoped></style>

View File

@ -2,12 +2,12 @@
<el-dialog <el-dialog
:model-value="visible" :model-value="visible"
title="新增查證設定" title="新增查證設定"
width="480px" width="600px"
@close="close" @close="close"
> >
<el-form :model="form" label-width="90px"> <el-form :model="form" label-width="90px">
<el-form-item label="欄位名稱"> <el-form-item label="欄位名稱">
<el-input v-model="form.fieldName" /> <el-input v-model="form.fieldName" placeholder="請填寫欄位名" />
</el-form-item> </el-form-item>
<el-form-item label="類型"> <el-form-item label="類型">
<el-select v-model="form.type" placeholder="選擇類型"> <el-select v-model="form.type" placeholder="選擇類型">
@ -16,29 +16,153 @@
<el-option label="單一選擇" value="單一選擇" /> <el-option label="單一選擇" value="單一選擇" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<!-- 當選擇單一選擇時顯示選項表格 -->
<template v-if="form.type === '單一選擇'">
<el-row :align="'middle'" justify="space-between">
<el-divider content-position="left">選項設定</el-divider>
<h3>單一選擇</h3>
<el-button type="primary" size="small" @click="openOptionDialog">
新增選項
</el-button>
</el-row>
<el-table :data="options" >
<el-table-column
prop="index"
label="項目"
width="80"
/>
<el-table-column prop="name" label="名稱" />
<el-table-column label="編輯" width="160" >
<template #default="scope">
<el-button
size="small"
type="primary"
plain
@click="editOption(scope.row, scope.$index)"
>
修改
</el-button>
<el-button
size="small"
type="danger"
plain
@click="deleteOption(scope.$index)"
>
刪除
</el-button>
</template>
</el-table-column>
</el-table>
</template>
</el-form> </el-form>
<template #footer> <template #footer>
<el-button @click="close">取消</el-button> <el-button @click="close">取消</el-button>
<el-button type="primary" @click="confirm">加入</el-button> <el-button type="primary" @click="confirm">加入</el-button>
</template> </template>
<!-- 選項管理對話框 -->
<VerificationOptionDialog
v-model:visible="optionDialogVisible"
:option-data="editingOption"
@add="addOption"
@update="updateOption"
/>
</el-dialog> </el-dialog>
</template> </template>
<script setup> <script setup>
import { reactive, ref } from "vue";
import VerificationOptionDialog from "./VerificationOptionDialog.vue";
const props = defineProps({ visible: { type: Boolean, default: false } }); const props = defineProps({ visible: { type: Boolean, default: false } });
const emit = defineEmits(["update:visible", "add"]); const emit = defineEmits(["update:visible", "add"]);
import { reactive } from "vue";
const form = reactive({ fieldName: "", type: "" }); const form = reactive({ fieldName: "", type: "" });
const options = ref([
{ index: 1, name: "符合" },
{ index: 2, name: "不符合" },
]); // """"
const optionDialogVisible = ref(false);
const editingOption = ref(null); //
const editingIndex = ref(-1); //
function reset() { function reset() {
form.fieldName = ""; form.fieldName = "";
form.type = ""; form.type = "";
options.value = [
{ index: 1, name: "符合" },
{ index: 2, name: "不符合" },
];
editingOption.value = null;
editingIndex.value = -1;
} }
function close() { function close() {
emit("update:visible", false); emit("update:visible", false);
reset(); reset();
} }
function confirm() { function confirm() {
emit("add", { ...form }); if (!form.fieldName.trim()) {
return;
}
const data = {
fieldName: form.fieldName,
type: form.type,
};
// ,
if (form.type === "單一選擇") {
data.options = options.value.map((opt) => opt.name);
}
emit("add", data);
close(); close();
} }
//
function openOptionDialog() {
editingOption.value = null;
editingIndex.value = -1;
optionDialogVisible.value = true;
}
//
function addOption(option) {
options.value.push({
index: options.value.length + 1,
name: option.name,
});
}
//
function editOption(option, index) {
editingOption.value = { ...option };
editingIndex.value = index;
optionDialogVisible.value = true;
}
//
function updateOption(option) {
if (editingIndex.value !== -1) {
options.value[editingIndex.value].name = option.name;
}
}
//
function deleteOption(index) {
options.value.splice(index, 1);
//
options.value.forEach((opt, idx) => {
opt.index = idx + 1;
});
}
</script> </script>
<style scoped></style>
<style scoped>
.el-divider--horizontal {
margin: 20px 0 15px 0;
}
</style>

View File

@ -16,6 +16,7 @@ export const templateSchemas = {
{ key: "shift", label: "班別", span: 12 }, { key: "shift", label: "班別", span: 12 },
{ key: "weather", label: "天氣", span: 12 }, { key: "weather", label: "天氣", span: 12 },
{ key: "temperature", label: "環境溫度/濕度", span: 12 }, { key: "temperature", label: "環境溫度/濕度", span: 12 },
{ key: "remark", label: "備註", type: "textarea", span: 24 },
], ],
}, },
], ],
@ -58,6 +59,9 @@ export const templateSchemas = {
usePagination: true, usePagination: true,
showInTemplate: true, // 新增樣板時顯示 showInTemplate: true, // 新增樣板時顯示
showInFill: true, // 填寫任務時顯示 showInFill: true, // 填寫任務時顯示
allowAdd: false, // 填寫時不顯示新增按鈕
allowEdit: false, // 填寫時不顯示編輯按鈕
useVerification: true, // 使用查證欄位
columns: [ columns: [
{ prop: "index", label: "項次", width: "60" }, { prop: "index", label: "項次", width: "60" },
{ prop: "system1", label: "系統1" }, { prop: "system1", label: "系統1" },
@ -65,6 +69,9 @@ export const templateSchemas = {
{ prop: "deviceId", label: "設備編號" }, { prop: "deviceId", label: "設備編號" },
{ prop: "item", label: "項目" }, { prop: "item", label: "項目" },
{ prop: "unit", label: "單位" }, { prop: "unit", label: "單位" },
{ prop: "lowerLimit", label: "下限" },
{ prop: "upperLimit", label: "上限" },
{ prop: "dcsValue", label: "DCS" },
], ],
defaultData: [ defaultData: [
{ {
@ -189,20 +196,33 @@ export const templateSchemas = {
}, },
], ],
}, },
], {
verification: { key: "verification",
enabled: true,
showInTemplate: true, // 新增樣板時顯示
showInFill: false, // 填寫任務時不顯示
title: "查證", title: "查證",
addButtonText: "新增查證設定", addButtonText: "新增查證項目",
dialogType: "verification", dialogType: "verification",
usePagination: true,
showInTemplate: true, // 新增樣板時顯示
showInFill: false, // 填寫任務時顯示
columns: [ columns: [
{ prop: "index", label: "項次", width: "60" }, { prop: "index", label: "項次", width: "60" },
{ prop: "fieldName", label: "欄位名稱" }, { prop: "fieldName", label: "欄位名稱" },
{ prop: "type", label: "類型" }, { prop: "type", label: "類型" },
], ],
defaultData: [
{
index: 1,
fieldName: "現場",
label: "數值輸入",
}, },
{
index: 2,
fieldName: "比對結果",
label: "單一選擇",
},
],
},
],
}, },
2: { 2: {
sections: [ sections: [
@ -237,17 +257,6 @@ export const templateSchemas = {
], ],
}, },
], ],
verification: {
enabled: true,
title: "查證",
addButtonText: "新增查證設定",
dialogType: "verification",
columns: [
{ prop: "index", label: "項次", width: "60" },
{ prop: "fieldName", label: "欄位名稱" },
{ prop: "type", label: "類型" },
],
},
}, },
3: { 3: {
sections: [ sections: [
@ -298,17 +307,6 @@ export const templateSchemas = {
], ],
}, },
], ],
verification: {
enabled: true,
title: "查證",
addButtonText: "新增查證設定",
dialogType: "verification",
columns: [
{ prop: "index", label: "項次", width: "60" },
{ prop: "fieldName", label: "欄位名稱" },
{ prop: "type", label: "類型" },
],
},
}, },
4: { 4: {
sections: [ sections: [
@ -344,17 +342,6 @@ export const templateSchemas = {
], ],
}, },
], ],
verification: {
enabled: true,
title: "查證",
addButtonText: "新增查證設定",
dialogType: "verification",
columns: [
{ prop: "index", label: "項次", width: "60" },
{ prop: "fieldName", label: "欄位名稱" },
{ prop: "type", label: "類型" },
],
},
}, },
5: { 5: {
sections: [ sections: [
@ -402,17 +389,6 @@ export const templateSchemas = {
], ],
}, },
], ],
verification: {
enabled: true,
title: "查證",
addButtonText: "新增查證設定",
dialogType: "verification",
columns: [
{ prop: "index", label: "項次", width: "60" },
{ prop: "fieldName", label: "欄位名稱" },
{ prop: "type", label: "類型" },
],
},
}, },
6: { 6: {
sections: [ sections: [
@ -471,17 +447,6 @@ export const templateSchemas = {
], ],
}, },
], ],
verification: {
enabled: true,
title: "查證",
addButtonText: "新增查證設定",
dialogType: "verification",
columns: [
{ prop: "index", label: "項次", width: "60" },
{ prop: "fieldName", label: "欄位名稱" },
{ prop: "type", label: "類型" },
],
},
}, },
7: { 7: {
sections: [ sections: [
@ -544,17 +509,6 @@ export const templateSchemas = {
], ],
}, },
], ],
verification: {
enabled: true,
title: "查證",
addButtonText: "新增查證設定",
dialogType: "verification",
columns: [
{ prop: "index", label: "項次", width: "60" },
{ prop: "fieldName", label: "欄位名稱" },
{ prop: "type", label: "類型" },
],
},
}, },
}; };
@ -590,13 +544,6 @@ export function initializeTableData(schema) {
}); });
} }
// 初始化驗證表格
if (schema.verification?.enabled) {
tableData.verification = schema.verification.defaultData
? JSON.parse(JSON.stringify(schema.verification.defaultData))
: [];
}
return tableData; return tableData;
} }