提交 a31f0e53 作者: zhongye-dev

feat: 完善隔离中心各业务模块功能

- 新增田间调查(FieldSurvey)、疫情处置(PestHandle)、设备总览(DeviceOverview)、监测数据分析(MonitorData)等模块
- 优化报告管理:弹窗只读、应检有害生物名单列表显示、表格列调整、删除审批数量列
- 优化 header:高度加大到 56px,logo 区域与 header 同色
- 优化菜单导航与路由配置
- 新增实验检测、样品检测 store 与数据
- 新增植物检疫计划报告 Word 模板
上级 cb77bc2f
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.3.1", "@element-plus/icons-vue": "^2.3.1",
"element-plus": "^2.6.2", "element-plus": "^2.6.2",
"jszip": "^3.10.2",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"vue": "^3.4.21", "vue": "^3.4.21",
"vue-router": "^4.3.0" "vue-router": "^4.3.0"
...@@ -1115,6 +1116,12 @@ ...@@ -1115,6 +1116,12 @@
"integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"license": "MIT"
},
"node_modules/csstype": { "node_modules/csstype": {
"version": "3.2.3", "version": "3.2.3",
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz",
...@@ -1225,6 +1232,45 @@ ...@@ -1225,6 +1232,45 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0" "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
} }
}, },
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
"license": "MIT"
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"license": "MIT"
},
"node_modules/jszip": {
"version": "3.10.2",
"resolved": "https://registry.npmmirror.com/jszip/-/jszip-3.10.2.tgz",
"integrity": "sha512-3l+rb15IOWtUhU0H5MFqES/T6Kh7abYwjosBey/vD6hDt8zoEffkSC5Ws5SGtgVw3gBx2NEbhTeSW1+kWkpyTQ==",
"license": "(MIT OR GPL-3.0-or-later)",
"dependencies": {
"lie": "~3.3.0",
"pako": "~1.0.2",
"readable-stream": "~2.3.6",
"setimmediate": "^1.0.5"
}
},
"node_modules/lie": {
"version": "3.3.0",
"resolved": "https://registry.npmmirror.com/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"license": "MIT",
"dependencies": {
"immediate": "~3.0.5"
}
},
"node_modules/lodash": { "node_modules/lodash": {
"version": "4.18.1", "version": "4.18.1",
"resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.18.1.tgz", "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.18.1.tgz",
...@@ -1287,6 +1333,12 @@ ...@@ -1287,6 +1333,12 @@
"integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==",
"license": "BSD-3-Clause" "license": "BSD-3-Clause"
}, },
"node_modules/pako": {
"version": "1.0.11",
"resolved": "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"license": "(MIT AND Zlib)"
},
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
...@@ -1343,6 +1395,27 @@ ...@@ -1343,6 +1395,27 @@
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"license": "MIT"
},
"node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"license": "MIT",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/rollup": { "node_modules/rollup": {
"version": "4.63.1", "version": "4.63.1",
"resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.63.1.tgz", "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.63.1.tgz",
...@@ -1389,6 +1462,18 @@ ...@@ -1389,6 +1462,18 @@
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
"node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"license": "MIT"
},
"node_modules/setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
"license": "MIT"
},
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
...@@ -1398,6 +1483,21 @@ ...@@ -1398,6 +1483,21 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/vite": { "node_modules/vite": {
"version": "5.4.21", "version": "5.4.21",
"resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz", "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz",
......
...@@ -9,14 +9,15 @@ ...@@ -9,14 +9,15 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"vue": "^3.4.21", "@element-plus/icons-vue": "^2.3.1",
"vue-router": "^4.3.0",
"pinia": "^2.1.7",
"element-plus": "^2.6.2", "element-plus": "^2.6.2",
"@element-plus/icons-vue": "^2.3.1" "jszip": "^3.10.2",
"pinia": "^2.1.7",
"vue": "^3.4.21",
"vue-router": "^4.3.0"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^5.0.4", "@vitejs/plugin-vue": "^5.0.4",
"vite": "^5.2.0" "vite": "^5.2.0"
} }
} }
\ No newline at end of file
...@@ -23,7 +23,7 @@ input, select, textarea, button { font-family: inherit; font-size: inherit; outl ...@@ -23,7 +23,7 @@ input, select, textarea, button { font-family: inherit; font-size: inherit; outl
/* ========== 顶部导航 ========== */ /* ========== 顶部导航 ========== */
.app-header { .app-header {
height: 40px; height: 56px;
background: #d71e1e; background: #d71e1e;
color: #fff; color: #fff;
display: flex; display: flex;
...@@ -42,19 +42,18 @@ input, select, textarea, button { font-family: inherit; font-size: inherit; outl ...@@ -42,19 +42,18 @@ input, select, textarea, button { font-family: inherit; font-size: inherit; outl
font-size: 18px; font-size: 18px;
font-weight: bold; font-weight: bold;
letter-spacing: 1px; letter-spacing: 1px;
background: #c01a1a; background: #d71e1e;
border-right: 1px solid #b51616;
} }
.app-logo .logo-icon { .app-logo .logo-icon {
width: 22px; height: 22px; width: 30px; height: 30px;
margin-right: 8px; margin-right: 10px;
background: #fff; background: #fff;
color: #d71e1e; color: #d71e1e;
border-radius: 4px; border-radius: 4px;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 13px; font-size: 16px;
font-weight: bold; font-weight: bold;
} }
.app-nav { display: flex; align-items: center; height: 100%; flex: 1; } .app-nav { display: flex; align-items: center; height: 100%; flex: 1; }
...@@ -79,7 +78,7 @@ input, select, textarea, button { font-family: inherit; font-size: inherit; outl ...@@ -79,7 +78,7 @@ input, select, textarea, button { font-family: inherit; font-size: inherit; outl
font-size: 14px; font-size: 14px;
} }
.app-user .user-avatar { .app-user .user-avatar {
width: 24px; height: 24px; width: 30px; height: 30px;
border-radius: 50%; border-radius: 50%;
background: #fff; background: #fff;
color: #d71e1e; color: #d71e1e;
......
...@@ -22,13 +22,33 @@ ...@@ -22,13 +22,33 @@
<span class="menu-arrow">▸</span> <span class="menu-arrow">▸</span>
</div> </div>
<div class="sub-menu" :class="{ expanded: isExpanded(item) }"> <div class="sub-menu" :class="{ expanded: isExpanded(item) }">
<div <template v-for="sub in item.children" :key="sub.id">
v-for="sub in item.children" <div
:key="sub.id" v-if="!sub.children"
class="sub-item" class="sub-item"
:class="{ active: isActive(sub.path) }" :class="{ active: isActive(sub.path) }"
@click="go(sub.path)" @click="go(sub.path)"
>{{ sub.name }}</div> >{{ sub.name }}</div>
<template v-else>
<div
class="sub-item has-sub-sub"
:class="{ expanded: isExpanded(sub), active: isChildActive(sub) }"
@click.stop="toggle(sub)"
>
<span class="sub-text">{{ sub.name }}</span>
<span class="sub-arrow">▸</span>
</div>
<div class="sub-sub-menu" :class="{ expanded: isExpanded(sub) }">
<div
v-for="leaf in sub.children"
:key="leaf.id"
class="sub-sub-item"
:class="{ active: isActive(leaf.path) }"
@click="go(leaf.path)"
>{{ leaf.name }}</div>
</div>
</template>
</template>
</div> </div>
</template> </template>
</template> </template>
......
// 实验检验列表数据(初始为空)
export const experimentTestData = [];
...@@ -22,10 +22,18 @@ export const menuConfig = [ ...@@ -22,10 +22,18 @@ export const menuConfig = [
] ]
}, },
{ {
id: "isolation", name: "隔离检疫", icon: "Lock", path: "/isolation/quarantine" id: "isolation", name: "田间调查", icon: "LocationInformation", path: "/field/survey"
}, },
{ {
id: "disposal", name: "除害处置", icon: "Delete", path: "/disposal" id: "disposal", name: "除害处理", icon: "Delete", path: "/disposal"
},
{
id: "isolation-monitor", name: "隔离监测", icon: "Monitor",
children: [
{ id: "isolation-device", name: "设备总览", path: "/isolation/device" },
{ id: "isolation-data", name: "监测数据", path: "/isolation/data" },
{ id: "isolation-analysis", name: "监测数据分析", path: "/isolation/analysis" }
]
}, },
{ {
id: "analysis", name: "数据分析", icon: "DataAnalysis", path: "/analysis" id: "analysis", name: "数据分析", icon: "DataAnalysis", path: "/analysis"
......
...@@ -13,8 +13,11 @@ const routes = [ ...@@ -13,8 +13,11 @@ const routes = [
{ path: "/sample/out", name: "sample-out", component: () => import("@/views/sample/SampleOut.vue"), meta: { title: "样本出库登记", breadcrumb: "样本管理 / 样本出库登记" } }, { path: "/sample/out", name: "sample-out", component: () => import("@/views/sample/SampleOut.vue"), meta: { title: "样本出库登记", breadcrumb: "样本管理 / 样本出库登记" } },
{ path: "/lab/sample-test", name: "lab-sample-test", component: () => import("@/views/lab/SampleTest.vue"), meta: { title: "样本抽检", breadcrumb: "实验室检测 / 样本抽检" } }, { path: "/lab/sample-test", name: "lab-sample-test", component: () => import("@/views/lab/SampleTest.vue"), meta: { title: "样本抽检", breadcrumb: "实验室检测 / 样本抽检" } },
{ path: "/lab/experiment-test", name: "lab-experiment-test", component: () => import("@/views/lab/ExperimentTest.vue"), meta: { title: "实验检验", breadcrumb: "实验室检测 / 实验检验" } }, { path: "/lab/experiment-test", name: "lab-experiment-test", component: () => import("@/views/lab/ExperimentTest.vue"), meta: { title: "实验检验", breadcrumb: "实验室检测 / 实验检验" } },
{ path: "/isolation/quarantine", name: "isolation-quarantine", component: () => import("@/views/isolation/IsolationQuarantine.vue"), meta: { title: "隔离检疫", breadcrumb: "隔离检疫" } }, { path: "/field/survey", name: "field-survey", component: () => import("@/views/field/FieldSurvey.vue"), meta: { title: "田间调查", breadcrumb: "田间调查" } },
{ path: "/disposal", name: "disposal", component: () => import("@/views/disposal/Disposal.vue"), meta: { title: "除害处置", breadcrumb: "除害处置" } }, { path: "/disposal", name: "disposal-list", component: () => import("@/views/pest/PestHandle.vue"), meta: { title: "除害处理", breadcrumb: "除害处理 / 除害处理" } },
{ path: "/isolation/device", name: "isolation-device", component: () => import("@/views/isolation/DeviceOverview.vue"), meta: { title: "设备总览", breadcrumb: "隔离监测 / 设备总览" } },
{ path: "/isolation/data", name: "isolation-data", component: () => import("@/views/isolation/MonitorData.vue"), meta: { title: "监测数据", breadcrumb: "隔离监测 / 监测数据" } },
{ path: "/isolation/analysis", name: "isolation-analysis", component: () => import("@/views/isolation/MonitorAnalysis.vue"), meta: { title: "监测数据分析", breadcrumb: "隔离监测 / 监测数据分析" } },
{ path: "/analysis", name: "analysis", component: () => import("@/views/analysis/DataAnalysis.vue"), meta: { title: "数据分析", breadcrumb: "数据分析" } }, { path: "/analysis", name: "analysis", component: () => import("@/views/analysis/DataAnalysis.vue"), meta: { title: "数据分析", breadcrumb: "数据分析" } },
{ path: "/risk", name: "risk", component: () => import("@/views/risk/RiskWarning.vue"), meta: { title: "风险预警", breadcrumb: "风险预警" } }, { path: "/risk", name: "risk", component: () => import("@/views/risk/RiskWarning.vue"), meta: { title: "风险预警", breadcrumb: "风险预警" } },
{ path: "/report", name: "report", component: () => import("@/views/report/ReportManage.vue"), meta: { title: "报告管理", breadcrumb: "报告管理" } }, { path: "/report", name: "report", component: () => import("@/views/report/ReportManage.vue"), meta: { title: "报告管理", breadcrumb: "报告管理" } },
...@@ -37,4 +40,4 @@ router.afterEach((to) => { ...@@ -37,4 +40,4 @@ router.afterEach((to) => {
} }
}); });
export default router; export default router;
\ No newline at end of file
// 实验检验数据存储(与田间调查联动:从田间调查保存"现场不能确诊,需要采样进行实验室检验"的记录自动流入)
import { ref, watch } from "vue";
const STORAGE_KEY = "experiment_test_records";
const list = ref([]);
const load = () => {
try {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
list.value = JSON.parse(stored);
}
} catch (e) {
console.error("Failed to load experiment test records:", e);
}
};
const save = () => {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(list.value));
} catch (e) {
console.error("Failed to save experiment test records:", e);
}
};
load();
watch(list, save, { deep: true });
const syncFromSurvey = (surveyRecord) => {
if (!surveyRecord) return;
if (surveyRecord.conclusion !== "现场不能确诊,需要采样进行实验室检验") {
removeBySurveyId(surveyRecord.id);
return;
}
const expRecord = {
cert: surveyRecord.cert,
unit: surveyRecord.unit,
plant: surveyRecord.plant,
variety: surveyRecord.variety,
origin: surveyRecord.origin,
contact: surveyRecord.contact,
phone: surveyRecord.phone,
approveQty: surveyRecord.approveQty,
importQty: surveyRecord.importQty,
qty: "150克",
testDate: surveyRecord.surveyDate,
testOrg: "甘州区农业技术推广中心",
content: "",
conclusion: surveyRecord.labResult || "",
inspector1: surveyRecord.inspector1,
inspector2: surveyRecord.inspector2,
inspectorPhone: surveyRecord.inspectorPhone || "0936-8212469",
entryDate: surveyRecord.entryDate,
sourceSurveyId: surveyRecord.id,
symptomDesc: surveyRecord.symptomDesc || "",
handlingReason: surveyRecord.handlingReason || "",
sampleMethod: surveyRecord.sampleMethod || "",
// 实验检验结果(独立字段,不复制田间调查的 labResult)
labResult: "",
// 默认检验方法
inspectionMethod: "按照 GB/T 3543.2-2009 标准进行实验室检验,镜检结合形态学鉴定方法对样品进行检测分析。",
sampleUnit: surveyRecord.sampleUnit || "甘州区农业技术推广中心",
sampleContact: surveyRecord.sampleContact || "甘州区系统管理员",
samplePhone: surveyRecord.samplePhone || "0936-8212469",
// 田间调查位置信息
surveyProvince: surveyRecord.surveyProvince || "甘肃省",
surveyCity: surveyRecord.surveyCity || "张掖市",
surveyDistrict: surveyRecord.surveyDistrict || "甘州区",
surveyTownship: surveyRecord.surveyTownship || "",
surveyAddress: surveyRecord.surveyAddress || "",
longitude: surveyRecord.longitude || "",
latitude: surveyRecord.latitude || "",
altitude: surveyRecord.altitude || "",
targetPests: surveyRecord.targetPests || "",
// 实验检验报告单专有字段
receiver: surveyRecord.receiver || "",
receiveDate: surveyRecord.receiveDate || "",
inspectionMethod: surveyRecord.inspectionMethod || "按照 GB/T 3543.2-2009 标准进行实验室检验,镜检结合形态学鉴定方法对样品进行检测分析。",
reviewPerson: surveyRecord.reviewPerson || "",
remarks: "" // 实验检验备注(独立字段,不复制田间调查 remarks)
};
const existingIndex = list.value.findIndex((r) => r.sourceSurveyId === surveyRecord.id);
if (existingIndex >= 0) {
list.value[existingIndex] = { ...list.value[existingIndex], ...expRecord };
} else {
const next = (list.value.length ? Math.max(...list.value.map((r) => r.id || 0)) : 0) + 1;
list.value = [...list.value, { id: next, ...expRecord }];
}
};
const removeBySurveyId = (surveyId) => {
const before = list.value.length;
list.value = list.value.filter((r) => r.sourceSurveyId !== surveyId);
return before !== list.value.length;
};
const removeById = (id) => {
list.value = list.value.filter((r) => r.id !== id);
};
const addRecord = (record) => {
const next = (list.value.length ? Math.max(...list.value.map((r) => r.id || 0)) : 0) + 1;
const newRec = { id: next, ...record };
list.value = [...list.value, newRec];
return newRec;
};
const updateRecord = (record) => {
const i = list.value.findIndex((r) => r.id === record.id);
if (i >= 0) {
list.value[i] = { ...record };
return true;
}
return false;
};
export { list, syncFromSurvey, removeBySurveyId, removeById, addRecord, updateRecord, save, load };
// 除害处理数据存储(与田间调查联动:从田间调查保存"现场确诊发现应检有害生物"的记录自动流入)
import { ref, watch } from "vue";
const STORAGE_KEY = "pest_handle_records";
const list = ref([]);
const load = () => {
try {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
list.value = JSON.parse(stored);
}
} catch (e) {
console.error("Failed to load pest disposal records:", e);
}
};
const save = () => {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(list.value));
} catch (e) {
console.error("Failed to save pest disposal records:", e);
}
};
load();
watch(list, save, { deep: true });
// 同步:田间调查"现场确诊发现应检有害生物"的记录 → 除害处理列表
const syncFromSurvey = (surveyRecord) => {
if (!surveyRecord) return;
if (surveyRecord.conclusion !== "现场确诊发现应检有害生物") {
removeBySurveyId(surveyRecord.id);
return;
}
const dispRecord = {
cert: surveyRecord.cert,
unit: surveyRecord.unit,
plant: surveyRecord.plant,
variety: surveyRecord.variety,
origin: surveyRecord.origin,
contact: surveyRecord.contact,
phone: surveyRecord.phone,
approveQty: surveyRecord.approveQty,
importQty: surveyRecord.importQty,
qty: surveyRecord.qty || "150克",
surveyDate: surveyRecord.surveyDate,
surveyOrg: "甘州区农业技术推广中心",
pestDetail: surveyRecord.pestDetail || "",
inspector1: surveyRecord.inspector1,
inspector2: surveyRecord.inspector2,
inspectorPhone: surveyRecord.inspectorPhone || "0936-8212469",
entryDate: surveyRecord.entryDate,
sourceSurveyId: surveyRecord.id,
surveyProvince: surveyRecord.surveyProvince || "甘肃省",
surveyCity: surveyRecord.surveyCity || "张掖市",
surveyDistrict: surveyRecord.surveyDistrict || "甘州区",
surveyTownship: surveyRecord.surveyTownship || "",
surveyAddress: surveyRecord.surveyAddress || "",
longitude: surveyRecord.longitude || "",
latitude: surveyRecord.latitude || "",
altitude: surveyRecord.altitude || "",
targetPests: surveyRecord.targetPests || "",
noticeNo: surveyRecord.noticeNo || "",
noticeTestResult: surveyRecord.noticeTestResult || "",
noticeHandleOpinion: surveyRecord.noticeHandleOpinion || "",
disposalMethod: "",
disposalDate: "",
disposalPerson: "",
disposalResult: "",
remarks: ""
};
const existingIndex = list.value.findIndex((r) => r.sourceSurveyId === surveyRecord.id);
if (existingIndex >= 0) {
list.value[existingIndex] = { ...list.value[existingIndex], ...dispRecord };
} else {
const next = (list.value.length ? Math.max(...list.value.map((r) => r.id || 0)) : 0) + 1;
list.value = [...list.value, { id: next, ...dispRecord }];
}
};
const removeBySurveyId = (surveyId) => {
const before = list.value.length;
list.value = list.value.filter((r) => r.sourceSurveyId !== surveyId);
return before !== list.value.length;
};
const removeById = (id) => {
list.value = list.value.filter((r) => r.id !== id);
};
const addRecord = (record) => {
const next = (list.value.length ? Math.max(...list.value.map((r) => r.id || 0)) : 0) + 1;
const newRec = { id: next, ...record };
list.value = [...list.value, newRec];
return newRec;
};
const updateRecord = (record) => {
const i = list.value.findIndex((r) => r.id === record.id);
if (i >= 0) {
list.value[i] = { ...record };
return true;
}
return false;
};
export { list, syncFromSurvey, removeBySurveyId, removeById, addRecord, updateRecord, save, load };
<template> <template>
<PagePlaceholder title="隔离检疫" desc="隔离区检疫流程开发中" icon="Lock" /> <PagePlaceholder title="监测数据分析" desc="监测数据分析模块正在搭建中" icon="Monitor" />
</template> </template>
<script setup> <script setup>
......
<template> <template>
<div class="page-list"> <div class="page-list">
<div class="filter-box"> <div class="filter-box">
<div class="filter-row"> <div class="filter-row">
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
<input class="ipt" v-model="filters.variety" placeholder="请输入关键字" /> <input class="ipt" v-model="filters.variety" placeholder="请输入关键字" />
</div> </div>
<div class="filter-item"> <div class="filter-item">
<label>检验机构:</label> <label>受检单位:</label>
<input class="ipt" v-model="filters.testOrg" placeholder="请输入关键字" /> <input class="ipt" v-model="filters.testOrg" placeholder="请输入关键字" />
</div> </div>
<div class="filter-item"> <div class="filter-item">
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
<tr> <tr>
<th>序号</th> <th>序号</th>
<th>检疫单证号</th> <th>检疫单证号</th>
<th>申报单位</th> <th>受检单位</th>
<th>植物名称</th> <th>植物名称</th>
<th>品种名称</th> <th>品种名称</th>
<th>抽检数量</th> <th>抽检数量</th>
...@@ -75,7 +75,7 @@ ...@@ -75,7 +75,7 @@
<td class="col-action"> <td class="col-action">
<a class="op-link" @click="onView(r)">查看</a> <a class="op-link" @click="onView(r)">查看</a>
<a class="op-link" @click="onEdit(r)">编辑</a> <a class="op-link" @click="onEdit(r)">编辑</a>
<a class="op-link op-danger" @click="onDelete(r)">删除</a> <a class="op-link" @click="onDelete(r)">删除</a>
</td> </td>
</tr> </tr>
<tr v-if="!pagedData.length"> <tr v-if="!pagedData.length">
...@@ -92,7 +92,7 @@ ...@@ -92,7 +92,7 @@
<button v-for="p in pageNumbers" :key="p" class="page-btn" :class="{ active: p === currentPage }" @click="goPage(p)">{{ p }}</button> <button v-for="p in pageNumbers" :key="p" class="page-btn" :class="{ active: p === currentPage }" @click="goPage(p)">{{ p }}</button>
<button class="page-btn" :disabled="currentPage === totalPages" @click="goPage(currentPage + 1)">下一页</button> <button class="page-btn" :disabled="currentPage === totalPages" @click="goPage(currentPage + 1)">下一页</button>
<button class="page-btn" :disabled="currentPage === totalPages" @click="goPage(totalPages)">末页</button> <button class="page-btn" :disabled="currentPage === totalPages" @click="goPage(totalPages)">末页</button>
<span class="jump">跳至 <input class="jump-input" v-model="jumpInput" @keyup.enter="onJump" /> 页</span> <span class="jump">跳转至 <input class="jump-input" v-model.number="jumpInput" @keyup.enter="onJump" /> 页</span>
</div> </div>
<div v-if="viewVisible" class="modal-mask" @click.self="viewVisible = false"> <div v-if="viewVisible" class="modal-mask" @click.self="viewVisible = false">
...@@ -103,7 +103,7 @@ ...@@ -103,7 +103,7 @@
<div class="section-title">基本信息</div> <div class="section-title">基本信息</div>
<div class="form-row cols-2"> <div class="form-row cols-2">
<div class="form-field"><span class="label">检疫单证号</span><span class="ctrl mb-input">{{ form.cert }}</span></div> <div class="form-field"><span class="label">检疫单证号</span><span class="ctrl mb-input">{{ form.cert }}</span></div>
<div class="form-field"><span class="label">申报单位</span><span class="ctrl mb-input">{{ form.unit }}</span></div> <div class="form-field"><span class="label">受检单位</span><span class="ctrl mb-input">{{ form.unit }}</span></div>
<div class="form-field"><span class="label">联系人</span><span class="ctrl mb-input">{{ form.contact }}</span></div> <div class="form-field"><span class="label">联系人</span><span class="ctrl mb-input">{{ form.contact }}</span></div>
<div class="form-field"><span class="label">联系电话</span><span class="ctrl mb-input">{{ form.phone }}</span></div> <div class="form-field"><span class="label">联系电话</span><span class="ctrl mb-input">{{ form.phone }}</span></div>
<div class="form-field"><span class="label">植物名称</span><span class="ctrl mb-input">{{ form.plant }}</span></div> <div class="form-field"><span class="label">植物名称</span><span class="ctrl mb-input">{{ form.plant }}</span></div>
...@@ -111,7 +111,7 @@ ...@@ -111,7 +111,7 @@
<div class="form-field"><span class="label">品种名称</span><span class="ctrl mb-input">{{ form.variety }}</span></div> <div class="form-field"><span class="label">品种名称</span><span class="ctrl mb-input">{{ form.variety }}</span></div>
<div class="form-field"><span class="label">种苗来源国</span><span class="ctrl mb-input">{{ form.origin }}</span></div> <div class="form-field"><span class="label">种苗来源国</span><span class="ctrl mb-input">{{ form.origin }}</span></div>
<div class="form-field"><span class="label">审批数量</span><span class="ctrl mb-input">{{ form.approveQty }}</span></div> <div class="form-field"><span class="label">审批数量</span><span class="ctrl mb-input">{{ form.approveQty }}</span></div>
<div class="form-field"><span class="label">引进数量</span><span class="ctrl mb-input">{{ form.approveQty }}</span></div> <div class="form-field"><span class="label">引进数量</span><span class="ctrl mb-input">{{ form.importQty }}</span></div>
<div class="form-field"><span class="label">入库时间</span><span class="ctrl mb-input">{{ form.testDate }}</span></div> <div class="form-field"><span class="label">入库时间</span><span class="ctrl mb-input">{{ form.testDate }}</span></div>
<div class="form-field"><span class="label">入库单位</span><span class="ctrl mb-input">{{ form.testOrg }}</span></div> <div class="form-field"><span class="label">入库单位</span><span class="ctrl mb-input">{{ form.testOrg }}</span></div>
</div> </div>
...@@ -147,7 +147,7 @@ ...@@ -147,7 +147,7 @@
<div class="section-title">基本信息</div> <div class="section-title">基本信息</div>
<div class="form-row cols-2"> <div class="form-row cols-2">
<div class="form-field"><span class="label">检疫单证号</span><input class="ctrl" v-model="form.cert" /></div> <div class="form-field"><span class="label">检疫单证号</span><input class="ctrl" v-model="form.cert" /></div>
<div class="form-field"><span class="label">申报单位</span><input class="ctrl" v-model="form.unit" /></div> <div class="form-field"><span class="label">受检单位</span><input class="ctrl" v-model="form.unit" /></div>
<div class="form-field"><span class="label">联系人</span><input class="ctrl" v-model="form.contact" /></div> <div class="form-field"><span class="label">联系人</span><input class="ctrl" v-model="form.contact" /></div>
<div class="form-field"><span class="label">联系电话</span><input class="ctrl" v-model="form.phone" /></div> <div class="form-field"><span class="label">联系电话</span><input class="ctrl" v-model="form.phone" /></div>
<div class="form-field"><span class="label">植物名称</span><input class="ctrl" v-model="form.plant" /></div> <div class="form-field"><span class="label">植物名称</span><input class="ctrl" v-model="form.plant" /></div>
...@@ -155,7 +155,7 @@ ...@@ -155,7 +155,7 @@
<div class="form-field"><span class="label">品种名称</span><input class="ctrl" v-model="form.variety" /></div> <div class="form-field"><span class="label">品种名称</span><input class="ctrl" v-model="form.variety" /></div>
<div class="form-field"><span class="label">种苗来源国</span><input class="ctrl" v-model="form.origin" /></div> <div class="form-field"><span class="label">种苗来源国</span><input class="ctrl" v-model="form.origin" /></div>
<div class="form-field"><span class="label">审批数量</span><input class="ctrl" v-model="form.approveQty" /></div> <div class="form-field"><span class="label">审批数量</span><input class="ctrl" v-model="form.approveQty" /></div>
<div class="form-field"><span class="label">引进数量</span><input class="ctrl" :value="form.approveQty" @input="form.approveQty = $event.target.value" /></div> <div class="form-field"><span class="label">引进数量</span><input class="ctrl" v-model="form.importQty" /></div>
<div class="form-field"><span class="label">入库时间</span><DatePicker v-model="form.testDate" class="ctrl" /></div> <div class="form-field"><span class="label">入库时间</span><DatePicker v-model="form.testDate" class="ctrl" /></div>
<div class="form-field"><span class="label">入库单位</span><input class="ctrl" v-model="form.testOrg" /></div> <div class="form-field"><span class="label">入库单位</span><input class="ctrl" v-model="form.testOrg" /></div>
</div> </div>
...@@ -195,7 +195,7 @@ ...@@ -195,7 +195,7 @@
<div class="section-title">基本信息</div> <div class="section-title">基本信息</div>
<div class="form-row cols-2"> <div class="form-row cols-2">
<div class="form-field"><span class="label">检疫单证号</span><input class="ctrl" v-model="addForm.cert" /></div> <div class="form-field"><span class="label">检疫单证号</span><input class="ctrl" v-model="addForm.cert" /></div>
<div class="form-field"><span class="label">申报单位</span><input class="ctrl" v-model="addForm.unit" /></div> <div class="form-field"><span class="label">受检单位</span><input class="ctrl" v-model="addForm.unit" /></div>
<div class="form-field"><span class="label">联系人</span><input class="ctrl" v-model="addForm.contact" /></div> <div class="form-field"><span class="label">联系人</span><input class="ctrl" v-model="addForm.contact" /></div>
<div class="form-field"><span class="label">联系电话</span><input class="ctrl" v-model="addForm.phone" /></div> <div class="form-field"><span class="label">联系电话</span><input class="ctrl" v-model="addForm.phone" /></div>
<div class="form-field"><span class="label">植物名称</span><input class="ctrl" v-model="addForm.plant" /></div> <div class="form-field"><span class="label">植物名称</span><input class="ctrl" v-model="addForm.plant" /></div>
...@@ -203,7 +203,7 @@ ...@@ -203,7 +203,7 @@
<div class="form-field"><span class="label">品种名称</span><input class="ctrl" v-model="addForm.variety" /></div> <div class="form-field"><span class="label">品种名称</span><input class="ctrl" v-model="addForm.variety" /></div>
<div class="form-field"><span class="label">种苗来源国</span><input class="ctrl" v-model="addForm.origin" /></div> <div class="form-field"><span class="label">种苗来源国</span><input class="ctrl" v-model="addForm.origin" /></div>
<div class="form-field"><span class="label">审批数量</span><input class="ctrl" v-model="addForm.approveQty" /></div> <div class="form-field"><span class="label">审批数量</span><input class="ctrl" v-model="addForm.approveQty" /></div>
<div class="form-field"><span class="label">引进数量</span><input class="ctrl" :value="addForm.approveQty" @input="addForm.approveQty = $event.target.value" /></div> <div class="form-field"><span class="label">引进数量</span><input class="ctrl" v-model="addForm.importQty" /></div>
<div class="form-field"><span class="label">入库时间</span><DatePicker v-model="addForm.testDate" class="ctrl" /></div> <div class="form-field"><span class="label">入库时间</span><DatePicker v-model="addForm.testDate" class="ctrl" /></div>
<div class="form-field"><span class="label">入库单位</span><input class="ctrl" v-model="addForm.testOrg" /></div> <div class="form-field"><span class="label">入库单位</span><input class="ctrl" v-model="addForm.testOrg" /></div>
</div> </div>
...@@ -264,7 +264,7 @@ const appliedFilters = ref(defaultFilters()); ...@@ -264,7 +264,7 @@ const appliedFilters = ref(defaultFilters());
const currentPage = ref(1); const currentPage = ref(1);
const pageSize = 10; const pageSize = 10;
const jumpInput = ref(""); const jumpInput = ref(1);
const viewVisible = ref(false); const viewVisible = ref(false);
const editVisible = ref(false); const editVisible = ref(false);
...@@ -273,7 +273,7 @@ const delVisible = ref(false); ...@@ -273,7 +273,7 @@ const delVisible = ref(false);
const delRow = ref(null); const delRow = ref(null);
const form = ref({}); const form = ref({});
const defaultAddForm = () => ({ cert: "", unit: "", contact: "", phone: "", plant: "", variety: "", origin: "", approveQty: "", qty: "150克", testDate: "", testOrg: "甘州区农业技术推广中心", content: "", conclusion: "", inspector1: "", inspector2: "", inspectorPhone: "", entryDate: "" }); const defaultAddForm = () => ({ cert: "", unit: "", contact: "", phone: "", plant: "", variety: "", origin: "", approveQty: "", importQty: "", qty: "150克", testDate: "", testOrg: "甘州区农业技术推广中心", content: "", conclusion: "", inspector1: "", inspector2: "", inspectorPhone: "", entryDate: "" });
const addForm = ref(defaultAddForm()); const addForm = ref(defaultAddForm());
const filteredData = computed(() => { const filteredData = computed(() => {
...@@ -319,6 +319,7 @@ const goPage = (p) => { ...@@ -319,6 +319,7 @@ const goPage = (p) => {
const onJump = () => { const onJump = () => {
const p = parseInt(jumpInput.value); const p = parseInt(jumpInput.value);
if (!isNaN(p)) goPage(p); if (!isNaN(p)) goPage(p);
else goPage(1);
}; };
const onView = (row) => { form.value = { ...row }; viewVisible.value = true; }; const onView = (row) => { form.value = { ...row }; viewVisible.value = true; };
......
...@@ -171,6 +171,31 @@ input, select, textarea, button { font-family: inherit; font-size: inherit; outl ...@@ -171,6 +171,31 @@ input, select, textarea, button { font-family: inherit; font-size: inherit; outl
border-radius: 50%; border-radius: 50%;
background: #d71e1e; background: #d71e1e;
} }
.sub-item.has-sub-sub { display: flex; align-items: center; justify-content: space-between; }
.sub-item.has-sub-sub .sub-arrow { font-size: 11px; color: #999; transition: transform 0.2s; margin-right: 4px; }
.sub-item.has-sub-sub.expanded .sub-arrow { transform: rotate(90deg); color: #d71e1e; }
.sub-sub-menu { background: #fef6f6; overflow: hidden; max-height: 0; transition: max-height 0.25s ease; }
.sub-sub-menu.expanded { max-height: 500px; }
.sub-sub-item {
display: block;
padding: 8px 16px 8px 60px;
font-size: 14px;
color: #555;
cursor: pointer;
position: relative;
transition: background 0.15s, color 0.15s;
}
.sub-sub-item:hover { background: #fcecec; color: #d71e1e; }
.sub-sub-item.active { background: #fcecec; color: #d71e1e; font-weight: 600; }
.sub-sub-item.active::before {
content: "";
position: absolute;
left: 46px; top: 50%;
transform: translateY(-50%);
width: 4px; height: 4px;
border-radius: 50%;
background: #d71e1e;
}
/* ========== 内容区 ========== */ /* ========== 内容区 ========== */
.content { flex: 1; display: flex; flex-direction: column; min-width: 0; background: #fff; position: relative; } .content { flex: 1; display: flex; flex-direction: column; min-width: 0; background: #fff; position: relative; }
......
Markdown 格式
0% 或
您添加了 0 人 到此讨论。请谨慎行事。
请先完成此评论的编辑!
请 注册 或者 后发表评论