提交 cb77bc2f 作者: zhongye-dev

Initial commit: 植物检疫检测隔离中心智能管理平台 (Vue 3 + Vite)

上级
node_modules/
dist
.vite
.DS_Store
*.log
.env
.env.local

(!) Some chunks are larger than 500 kB after minification. Consider:
- Using dynamic import() to code-split the application
- Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/configuration-options/#output-manualchunks
- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.
vite v5.4.21 building for production...
transforming...
✓ 1641 modules transformed.
rendering chunks...
computing gzip size...
dist/index.html  0.56 kB │ gzip: 0.41 kB
dist/assets/HomeView-K5NBGBX5.css  0.90 kB │ gzip: 0.43 kB
dist/assets/DatePicker-BJWEANDj.css  1.62 kB │ gzip: 0.60 kB
dist/assets/SampleRegister-cUCycDKY.css  3.77 kB │ gzip: 1.05 kB
dist/assets/EntrustList-BHctiG7M.css  4.14 kB │ gzip: 1.14 kB
dist/assets/index-Bfyqz0HH.css  369.78 kB │ gzip: 49.99 kB
dist/assets/Roles-8iRQ4jVO.js  0.22 kB │ gzip: 0.24 kB
dist/assets/Disposal-SX1a8GIJ.js  0.23 kB │ gzip: 0.25 kB
dist/assets/Users-DeZ6_LtC.js  0.23 kB │ gzip: 0.24 kB
dist/assets/Dict-Dhh9S-5e.js  0.23 kB │ gzip: 0.25 kB
dist/assets/Log-SLmu1Rki.js  0.23 kB │ gzip: 0.26 kB
dist/assets/SampleOut-BELh0L5H.js  0.23 kB │ gzip: 0.26 kB
dist/assets/SampleStorage-VG60SIPg.js  0.23 kB │ gzip: 0.26 kB
dist/assets/AuditCert-BSHGET5x.js  0.23 kB │ gzip: 0.26 kB
dist/assets/EntrustReg-CldFFKnf.js  0.24 kB │ gzip: 0.26 kB
dist/assets/RiskWarning-Brr7UYH6.js  0.24 kB │ gzip: 0.26 kB
dist/assets/ReportManage-DcSkYiAw.js  0.24 kB │ gzip: 0.26 kB
dist/assets/IsolationQuarantine-BlA192gW.js  0.24 kB │ gzip: 0.25 kB
dist/assets/EntrustArchive-p7YNut0M.js  0.24 kB │ gzip: 0.26 kB
dist/assets/EntrustAudit-DfZV14PR.js  0.24 kB │ gzip: 0.25 kB
dist/assets/DataAnalysis-tHh13l4Z.js  0.24 kB │ gzip: 0.26 kB
dist/assets/SampleQuarantine-DD7wstmi.js  0.25 kB │ gzip: 0.27 kB
dist/assets/PagePlaceholder-Diomz7UX.js  0.52 kB │ gzip: 0.40 kB
dist/assets/DatePicker-BFjOmIRQ.js  2.98 kB │ gzip: 1.43 kB
dist/assets/HomeView-CbRi-j9O.js  3.49 kB │ gzip: 1.62 kB
dist/assets/EntrustList-DqBiXNZl.js  10.13 kB │ gzip: 3.61 kB
dist/assets/SampleRegister-HRbdLZXo.js  12.02 kB │ gzip: 3.95 kB
dist/assets/index-wiGqx7ZU.js 1,213.78 kB │ gzip: 389.81 kB
✓ built in 1m 10s
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>植物检疫检测隔离中心智能管理平台</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
\ No newline at end of file
{
"name": "plant-quarantine-platform",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "plant-quarantine-platform",
"version": "1.0.0",
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"element-plus": "^2.6.2",
"pinia": "^2.1.7",
"vue": "^3.4.21",
"vue-router": "^4.3.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.4",
"vite": "^5.2.0"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.29.7",
"resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
"integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.29.7",
"resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
"integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.29.8",
"resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.8.tgz",
"integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.29.8"
},
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/types": {
"version": "7.29.8",
"resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.8.tgz",
"integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.29.7",
"@babel/helper-validator-identifier": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@ctrl/tinycolor": {
"version": "4.2.0",
"resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz",
"integrity": "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==",
"license": "MIT",
"engines": {
"node": ">=14"
}
},
"node_modules/@element-plus/icons-vue": {
"version": "2.3.2",
"resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz",
"integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==",
"license": "MIT",
"peerDependencies": {
"vue": "^3.2.0"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.21.5",
"resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
"integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.21.5",
"resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
"integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.21.5",
"resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
"integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
"integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.21.5",
"resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
"integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
"integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.21.5",
"resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
"integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
"integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.21.5",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
"integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.21.5",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
"integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.21.5",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
"integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.21.5",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
"integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.21.5",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
"integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.21.5",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
"integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.21.5",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
"integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.21.5",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
"integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
"integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
"integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
"integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
"integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.21.5",
"resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
"integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.21.5",
"resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
"integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
"integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@floating-ui/core": {
"version": "1.8.0",
"resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.8.0.tgz",
"integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==",
"license": "MIT",
"dependencies": {
"@floating-ui/utils": "^0.2.12"
}
},
"node_modules/@floating-ui/dom": {
"version": "1.8.0",
"resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.8.0.tgz",
"integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==",
"license": "MIT",
"dependencies": {
"@floating-ui/core": "^1.8.0",
"@floating-ui/utils": "^0.2.12"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.2.12",
"resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.12.tgz",
"integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==",
"license": "MIT"
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.6.0",
"resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz",
"integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",
"license": "MIT"
},
"node_modules/@napi-rs/lzma-linux-x64-gnu": {
"version": "1.5.1",
"resolved": "https://registry.npmmirror.com/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz",
"integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^22.20 || ^24.12 || >=25"
}
},
"node_modules/@popperjs/core": {
"name": "@sxzz/popperjs-es",
"version": "2.11.8",
"resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.8.tgz",
"integrity": "sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.63.1",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.1.tgz",
"integrity": "sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.63.1",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.1.tgz",
"integrity": "sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.63.1",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.1.tgz",
"integrity": "sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.63.1",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.1.tgz",
"integrity": "sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.63.1",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.1.tgz",
"integrity": "sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.63.1",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.1.tgz",
"integrity": "sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.63.1",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.1.tgz",
"integrity": "sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw==",
"cpu": [
"arm"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.63.1",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.1.tgz",
"integrity": "sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw==",
"cpu": [
"arm"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.63.1",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.1.tgz",
"integrity": "sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.63.1",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.1.tgz",
"integrity": "sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.63.1",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.1.tgz",
"integrity": "sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ==",
"cpu": [
"loong64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-loong64-musl": {
"version": "4.63.1",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.1.tgz",
"integrity": "sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA==",
"cpu": [
"loong64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.63.1",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.1.tgz",
"integrity": "sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA==",
"cpu": [
"ppc64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-ppc64-musl": {
"version": "4.63.1",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.1.tgz",
"integrity": "sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA==",
"cpu": [
"ppc64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.63.1",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.1.tgz",
"integrity": "sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w==",
"cpu": [
"riscv64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.63.1",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.1.tgz",
"integrity": "sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ==",
"cpu": [
"riscv64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.63.1",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.1.tgz",
"integrity": "sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A==",
"cpu": [
"s390x"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.63.1",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.1.tgz",
"integrity": "sha512-gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.63.1",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.1.tgz",
"integrity": "sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-openbsd-x64": {
"version": "4.63.1",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.1.tgz",
"integrity": "sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.63.1",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.1.tgz",
"integrity": "sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.63.1",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.1.tgz",
"integrity": "sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.63.1",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.1.tgz",
"integrity": "sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.63.1",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.1.tgz",
"integrity": "sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.63.1",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.1.tgz",
"integrity": "sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@types/estree": {
"version": "1.0.9",
"resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz",
"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/lodash": {
"version": "4.17.25",
"resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.25.tgz",
"integrity": "sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==",
"license": "MIT"
},
"node_modules/@types/lodash-es": {
"version": "4.17.12",
"resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz",
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
"license": "MIT",
"dependencies": {
"@types/lodash": "*"
}
},
"node_modules/@types/web-bluetooth": {
"version": "0.0.21",
"resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
"integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==",
"license": "MIT"
},
"node_modules/@vitejs/plugin-vue": {
"version": "5.2.4",
"resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
"integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.0.0 || >=20.0.0"
},
"peerDependencies": {
"vite": "^5.0.0 || ^6.0.0",
"vue": "^3.2.25"
}
},
"node_modules/@vue/compiler-core": {
"version": "3.5.42",
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.42.tgz",
"integrity": "sha512-2Ye1ilMtKXxl8qZUrQ5j0CdgenFp/HFQmta6rfRyfEsTG69L6Wk+tWuNoHYHMx9E8tF2Slvdg1FuwDvAXdy1LQ==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.29.8",
"@vue/shared": "3.5.42",
"entities": "^7.0.1",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-dom": {
"version": "3.5.42",
"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.42.tgz",
"integrity": "sha512-qbhQZEFmycr+ni/qyuccS4sucNN7VAbDfbkvNxWOX2VfgFm90MNs3/UhRNKoPMEIVn0F8gdlYjLPvqxHwHeQOA==",
"license": "MIT",
"dependencies": {
"@vue/compiler-core": "3.5.42",
"@vue/shared": "3.5.42"
}
},
"node_modules/@vue/compiler-sfc": {
"version": "3.5.42",
"resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.42.tgz",
"integrity": "sha512-fkCAFB4okcAANGMThboWnScp/gzWjU0ZSkVnjTIiplmMDq2uq0tIB3j+xVu4rhv5rvOgBySCysudmbMd6xRRqw==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.29.8",
"@vue/compiler-core": "3.5.42",
"@vue/compiler-dom": "3.5.42",
"@vue/compiler-ssr": "3.5.42",
"@vue/shared": "3.5.42",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.21",
"postcss": "^8.5.19",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-ssr": {
"version": "3.5.42",
"resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.42.tgz",
"integrity": "sha512-xmLk3wLkbizPAiLyomjgFFosf2ys9b5Ghb+oh/k2tnvipNz8OFrQOiTcWCzyK7MpBp9KkyGtfvgfLUivbmuGYA==",
"license": "MIT",
"dependencies": {
"@vue/compiler-dom": "3.5.42",
"@vue/shared": "3.5.42"
}
},
"node_modules/@vue/devtools-api": {
"version": "6.6.4",
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
"license": "MIT"
},
"node_modules/@vue/reactivity": {
"version": "3.5.42",
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.42.tgz",
"integrity": "sha512-TzNNfKpb7hDxbQltwAut8VDQA5YP+BuRlxntHUuRjyKwlMvmAPbs3unhCvieijifY6vFfVBwsS7wG/C7uq+bEQ==",
"license": "MIT",
"dependencies": {
"@vue/shared": "3.5.42"
}
},
"node_modules/@vue/runtime-core": {
"version": "3.5.42",
"resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.42.tgz",
"integrity": "sha512-9uACtuHs7vJGkm5Bp3xu4xRDLFTIYy5DgxpToVjqGIAhAEKwQfsaLvKINhM6nFVp6bZPRFGdDqd1g52MqKsotA==",
"license": "MIT",
"dependencies": {
"@vue/reactivity": "3.5.42",
"@vue/shared": "3.5.42"
}
},
"node_modules/@vue/runtime-dom": {
"version": "3.5.42",
"resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.42.tgz",
"integrity": "sha512-rsCmhiWLaRxGltLwhlCWyYkFn7WAbKRh0q17eZ1A6Dq6eqc2ACQ61IIryxz0LrsvCzHSilLA9JHovVwM8CNE2g==",
"license": "MIT",
"dependencies": {
"@vue/reactivity": "3.5.42",
"@vue/runtime-core": "3.5.42",
"@vue/shared": "3.5.42",
"csstype": "^3.2.3"
}
},
"node_modules/@vue/server-renderer": {
"version": "3.5.42",
"resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.42.tgz",
"integrity": "sha512-2++5dUyYS4gvo7xQXSECUDhB7TS0aOl5SeVfC5qSq1Jgfhjvegw1zqhwTIR3imZ+QYPJQw9gfcFvXGAjGZ7ajQ==",
"license": "MIT",
"dependencies": {
"@vue/compiler-ssr": "3.5.42",
"@vue/runtime-dom": "3.5.42",
"@vue/shared": "3.5.42"
}
},
"node_modules/@vue/shared": {
"version": "3.5.42",
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.42.tgz",
"integrity": "sha512-2rPxex1jQf4jvl9MOHl6YaXCPcrNqz/FstMOEh3QWY+/OME9nQTvl9WYeCwhW7AFjaR0SnngZGlp/wkR6rkI6g==",
"license": "MIT"
},
"node_modules/@vueuse/core": {
"version": "14.4.0",
"resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-14.4.0.tgz",
"integrity": "sha512-X4WHz1HlCzCBoYXesUkifzzWBAcZgXG8Fi5iNPQg/epdzOB3gu8Fawj3hvuwYR1nGcXGnvxwYYcUC/71++svtQ==",
"license": "MIT",
"dependencies": {
"@types/web-bluetooth": "^0.0.21",
"@vueuse/metadata": "14.4.0",
"@vueuse/shared": "14.4.0"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"vue": "^3.5.0"
}
},
"node_modules/@vueuse/metadata": {
"version": "14.4.0",
"resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-14.4.0.tgz",
"integrity": "sha512-swx/255R6JyHZFJhx845iz5CRWDZdCfvkZOpACWc5+c5WHcG24mv8gUT1WIdFQaHt6dq79rvILd9QnCWiyVm9g==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/shared": {
"version": "14.4.0",
"resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-14.4.0.tgz",
"integrity": "sha512-JRgY90Sz8DDtPMsaDflvPMp9xYk69JZAmbuDvAquUVXKr2gEjqtzGNTTthLfckH0BzBqvnu31gb4a8TGLRe79g==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"vue": "^3.5.0"
}
},
"node_modules/async-validator": {
"version": "4.2.5",
"resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz",
"integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==",
"license": "MIT"
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"license": "MIT"
},
"node_modules/dayjs": {
"version": "1.11.23",
"resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.23.tgz",
"integrity": "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==",
"license": "MIT"
},
"node_modules/element-plus": {
"version": "2.14.5",
"resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.14.5.tgz",
"integrity": "sha512-bghYy/S+qg87enHPXELirhEdDqsVAUGcGpbGIeG8dz0kwpIkGz7gYsifulBshXX74iRtHib85XWQj0uSH2A1Yg==",
"license": "MIT",
"dependencies": {
"@ctrl/tinycolor": "^4.2.0",
"@element-plus/icons-vue": "^2.3.2",
"@floating-ui/dom": "^1.8.0",
"@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.8",
"@types/lodash": "^4.17.24",
"@types/lodash-es": "^4.17.12",
"@vueuse/core": "14.4.0",
"async-validator": "^4.2.5",
"dayjs": "^1.11.20",
"lodash": "^4.18.1",
"lodash-es": "^4.18.1",
"lodash-unified": "^1.0.3",
"memoize-one": "^6.0.0",
"normalize-wheel-es": "^1.2.0",
"vue-component-type-helpers": "^3.3.9"
},
"peerDependencies": {
"vue": "^3.3.7"
}
},
"node_modules/entities": {
"version": "7.0.1",
"resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz",
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/esbuild": {
"version": "0.21.5",
"resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz",
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.21.5",
"@esbuild/android-arm": "0.21.5",
"@esbuild/android-arm64": "0.21.5",
"@esbuild/android-x64": "0.21.5",
"@esbuild/darwin-arm64": "0.21.5",
"@esbuild/darwin-x64": "0.21.5",
"@esbuild/freebsd-arm64": "0.21.5",
"@esbuild/freebsd-x64": "0.21.5",
"@esbuild/linux-arm": "0.21.5",
"@esbuild/linux-arm64": "0.21.5",
"@esbuild/linux-ia32": "0.21.5",
"@esbuild/linux-loong64": "0.21.5",
"@esbuild/linux-mips64el": "0.21.5",
"@esbuild/linux-ppc64": "0.21.5",
"@esbuild/linux-riscv64": "0.21.5",
"@esbuild/linux-s390x": "0.21.5",
"@esbuild/linux-x64": "0.21.5",
"@esbuild/netbsd-x64": "0.21.5",
"@esbuild/openbsd-x64": "0.21.5",
"@esbuild/sunos-x64": "0.21.5",
"@esbuild/win32-arm64": "0.21.5",
"@esbuild/win32-ia32": "0.21.5",
"@esbuild/win32-x64": "0.21.5"
}
},
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"license": "MIT"
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/lodash": {
"version": "4.18.1",
"resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.18.1.tgz",
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
"license": "MIT"
},
"node_modules/lodash-es": {
"version": "4.18.1",
"resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.18.1.tgz",
"integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
"license": "MIT"
},
"node_modules/lodash-unified": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz",
"integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==",
"license": "MIT",
"peerDependencies": {
"@types/lodash-es": "*",
"lodash": "*",
"lodash-es": "*"
}
},
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz",
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/memoize-one": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz",
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.18",
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.18.tgz",
"integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/normalize-wheel-es": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
"integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==",
"license": "BSD-3-Clause"
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"license": "ISC"
},
"node_modules/pinia": {
"version": "2.3.1",
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.3.1.tgz",
"integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==",
"license": "MIT",
"dependencies": {
"@vue/devtools-api": "^6.6.3",
"vue-demi": "^0.14.10"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"typescript": ">=4.4.4",
"vue": "^2.7.0 || ^3.5.11"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/postcss": {
"version": "8.5.28",
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.28.tgz",
"integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.18",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/rollup": {
"version": "4.63.1",
"resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.63.1.tgz",
"integrity": "sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.9"
},
"bin": {
"rollup": "dist/bin/rollup"
},
"engines": {
"node": ">=18.0.0",
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@napi-rs/lzma-linux-x64-gnu": "1.5.1",
"@rollup/rollup-android-arm-eabi": "4.63.1",
"@rollup/rollup-android-arm64": "4.63.1",
"@rollup/rollup-darwin-arm64": "4.63.1",
"@rollup/rollup-darwin-x64": "4.63.1",
"@rollup/rollup-freebsd-arm64": "4.63.1",
"@rollup/rollup-freebsd-x64": "4.63.1",
"@rollup/rollup-linux-arm-gnueabihf": "4.63.1",
"@rollup/rollup-linux-arm-musleabihf": "4.63.1",
"@rollup/rollup-linux-arm64-gnu": "4.63.1",
"@rollup/rollup-linux-arm64-musl": "4.63.1",
"@rollup/rollup-linux-loong64-gnu": "4.63.1",
"@rollup/rollup-linux-loong64-musl": "4.63.1",
"@rollup/rollup-linux-ppc64-gnu": "4.63.1",
"@rollup/rollup-linux-ppc64-musl": "4.63.1",
"@rollup/rollup-linux-riscv64-gnu": "4.63.1",
"@rollup/rollup-linux-riscv64-musl": "4.63.1",
"@rollup/rollup-linux-s390x-gnu": "4.63.1",
"@rollup/rollup-linux-x64-gnu": "4.63.1",
"@rollup/rollup-linux-x64-musl": "4.63.1",
"@rollup/rollup-openbsd-x64": "4.63.1",
"@rollup/rollup-openharmony-arm64": "4.63.1",
"@rollup/rollup-win32-arm64-msvc": "4.63.1",
"@rollup/rollup-win32-ia32-msvc": "4.63.1",
"@rollup/rollup-win32-x64-gnu": "4.63.1",
"@rollup/rollup-win32-x64-msvc": "4.63.1",
"fsevents": "~2.3.2"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/vite": {
"version": "5.4.21",
"resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz",
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.43",
"rollup": "^4.20.0"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^18.0.0 || >=20.0.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
},
"peerDependencies": {
"@types/node": "^18.0.0 || >=20.0.0",
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
"sass-embedded": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.4.0"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"less": {
"optional": true
},
"lightningcss": {
"optional": true
},
"sass": {
"optional": true
},
"sass-embedded": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"optional": true
}
}
},
"node_modules/vue": {
"version": "3.5.42",
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.42.tgz",
"integrity": "sha512-4RyHQTbQvOPs3MfvUO1Sg0YRrKNnA0mAVtvpd12Tg1fKDN7OHBUl1IqSn8zGJjK9nI3NkNp8cgTpVrSZC5TTcA==",
"license": "MIT",
"dependencies": {
"@vue/compiler-dom": "3.5.42",
"@vue/compiler-sfc": "3.5.42",
"@vue/runtime-dom": "3.5.42",
"@vue/server-renderer": "3.5.42",
"@vue/shared": "3.5.42"
},
"peerDependencies": {
"typescript": "*"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/vue-component-type-helpers": {
"version": "3.3.11",
"resolved": "https://registry.npmmirror.com/vue-component-type-helpers/-/vue-component-type-helpers-3.3.11.tgz",
"integrity": "sha512-LwcxzeliO9fkQcpJG0PoX8X5kmAhKmH9wkpDLxNabwzkQ9Zeib2YVHwFV4pcWmMLfXVfjr/dSV+DaJ3cIPgSNA==",
"license": "MIT"
},
"node_modules/vue-demi": {
"version": "0.14.10",
"resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
"hasInstallScript": true,
"license": "MIT",
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/vue-router": {
"version": "4.6.4",
"resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.6.4.tgz",
"integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==",
"license": "MIT",
"dependencies": {
"@vue/devtools-api": "^6.6.4"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"vue": "^3.5.0"
}
}
}
}
{
"name": "plant-quarantine-platform",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.4.21",
"vue-router": "^4.3.0",
"pinia": "^2.1.7",
"element-plus": "^2.6.2",
"@element-plus/icons-vue": "^2.3.1"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.4",
"vite": "^5.2.0"
}
}
\ No newline at end of file
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>检疫数据统计</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">📊</div><div class="ph-title">检疫数据统计</div><div class="ph-desc">检疫数据多维统计</div></div></body></html>
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>有害生物分析</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">🐛</div><div class="ph-title">有害生物分析</div><div class="ph-desc">有害生物检出情况分析</div></div></body></html>
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>趋势分析</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">📈</div><div class="ph-title">趋势分析</div><div class="ph-desc">历年趋势与季节性分析</div></div></body></html>
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>证书申请受理</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">📥</div><div class="ph-title">证书申请受理</div><div class="ph-desc">受理检疫证书申请</div></div></body></html>
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>审核流程管理</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">🔍</div><div class="ph-title">审核流程管理</div><div class="ph-desc">证书审核流程管理</div></div></body></html>
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>证书签发</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">🪪</div><div class="ph-title">证书签发</div><div class="ph-desc">检疫证书签发与打印</div></div></body></html>
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>证书查询</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">🔎</div><div class="ph-title">证书查询</div><div class="ph-desc">证书真伪与历史查询</div></div></body></html>
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>处置执行登记</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">⚙️</div><div class="ph-title">处置执行登记</div><div class="ph-desc">处置过程执行情况登记</div></div></body></html>
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>处置方案审批</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">📐</div><div class="ph-title">处置方案审批</div><div class="ph-desc">除害处置方案制定与审批</div></div></body></html>
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>处置档案管理</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">🗂️</div><div class="ph-title">处置档案管理</div><div class="ph-desc">历史处置档案归档与查询</div></div></body></html>
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>委托书归档</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">📑</div><div class="ph-title">委托书归档</div><div class="ph-desc">已办结委托书归档管理</div></div></body></html>
\ No newline at end of file
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>委托书审核</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">📑</div><div class="ph-title">委托书审核</div><div class="ph-desc">委托书审核与流转</div></div></body></html>
\ No newline at end of file
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>委托书列表</title>
<link rel="stylesheet" href="../styles/main.css?v=20260907" />
<style>
body { padding: 16px; background: #fff; }
/* 筛选区 */
.filter-bar { background: #fff; padding: 14px 18px; border: 1px solid #f0f0f0; border-radius: 4px; margin-bottom: 12px; }
.filter-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 18px; margin-bottom: 10px; }
.filter-row:last-child { margin-bottom: 0; }
.filter-item { display: flex; align-items: center; }
.filter-item .lbl { color: #555; font-size: 14px; margin-right: 8px; white-space: nowrap; }
.filter-item .ipt { width: 150px; height: 32px; padding: 0 32px 0 10px; border: 1px solid #d4d4d4; border-radius: 3px; font-size: 14px; background: #fff; }
.filter-item .ipt:focus { border-color: #d71e1e; outline: none; }
.filter-item .ipt-date { cursor: pointer; }
.filter-item .date-sep { color: #999; margin: 0 6px; }
.filter-actions { margin-left: auto; display: flex; gap: 10px; }
.filter-actions .btn { height: 32px; padding: 0 20px; font-size: 14px; }
.radio-group { display: flex; align-items: center; gap: 22px; }
.radio-group label { display: flex; align-items: center; gap: 6px; cursor: pointer; font-size: 14px; color: #555; user-select: none; }
.radio-group input[type=radio] { accent-color: #d71e1e; width: 14px; height: 14px; }
/* 日期输入框包装 */
.date-wrap { position: relative; display: inline-block; }
.date-wrap .date-icon { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); pointer-events: none; display: flex; }
/* 自定义日期选择器 */
.date-picker { position: absolute; z-index: 200; background: #fff; border: 1px solid #e0e0e0; border-radius: 4px; box-shadow: 0 4px 14px rgba(0,0,0,0.08); padding: 12px; width: 280px; user-select: none; display: none; }
.date-picker.show { display: block; }
.dp-header { display: flex; align-items: center; justify-content: space-between; padding-bottom: 8px; border-bottom: 1px solid #f0f0f0; margin-bottom: 8px; }
.dp-nav { display: flex; align-items: center; gap: 4px; color: #666; cursor: pointer; font-size: 14px; }
.dp-nav .arrow { width: 22px; height: 22px; display: inline-flex; align-items: center; justify-content: center; border-radius: 3px; color: #999; }
.dp-nav .arrow:hover { background: #fef2f2; color: #d71e1e; }
.dp-title { font-size: 15px; font-weight: bold; color: #333; flex: 1; text-align: center; }
.dp-week { display: grid; grid-template-columns: repeat(7, 1fr); margin-bottom: 4px; }
.dp-week span { text-align: center; font-size: 12px; color: #999; height: 26px; line-height: 26px; }
.dp-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.dp-day { height: 32px; display: flex; align-items: center; justify-content: center; font-size: 13px; color: #333; cursor: pointer; border-radius: 3px; }
.dp-day:hover { background: #fef2f2; color: #d71e1e; }
.dp-day.other { color: #ccc; }
.dp-day.other:hover { color: #d71e1e; background: #fef2f2; }
.dp-day.today { color: #d71e1e; font-weight: bold; }
.dp-day.selected { background: #d71e1e; color: #fff; }
.dp-day.selected:hover { background: #d71e1e; color: #fff; }
/* 列表 */
.tbl-wrap { background: #fff; border: 1px solid #f0f0f0; border-radius: 4px; }
.tbl-wrap .simple-table { width: 100%; border-collapse: collapse; table-layout: fixed; }
.tbl-wrap .simple-table th { font-size: 15px; font-weight: bold; color: #333; padding: 14px 12px; text-align: center; border-bottom: 1px solid #f0f0f0; }
.tbl-wrap .simple-table td { font-size: 14px; color: #333; padding: 14px 12px; text-align: center; border-bottom: 1px solid #f0f0f0; }
.tbl-wrap .simple-table td.lft, .tbl-wrap .simple-table th.lft { text-align: left; }
.tbl-wrap .simple-table tbody tr:hover td { background: #fef9f9; }
.tbl-wrap .simple-table td.status,
.tbl-wrap .simple-table td.ops { font-size: 14px; color: #333; }
.op-link { color: #333; font-size: 14px; margin: 0 8px; cursor: pointer; }
.op-link:hover { color: #d71e1e; }
.op-sep { color: #ddd; }
/* 分页 */
.pager { display: flex; align-items: center; padding: 16px 20px; font-size: 14px; color: #666; gap: 10px; }
.pager .info { margin-right: auto; }
.pager .pg { display: inline-flex; align-items: center; justify-content: center; min-width: 30px; height: 30px; padding: 0 8px; border: 1px solid #e0e0e0; background: #fff; border-radius: 3px; cursor: pointer; font-size: 14px; color: #555; }
.pager .pg:hover { color: #d71e1e; border-color: #d71e1e; }
.pager .pg.active { background: #d71e1e; color: #fff; border-color: #d71e1e; }
.pager .pg.disabled { color: #ccc; cursor: not-allowed; background: #fafafa; border-color: #f0f0f0; }
.pager .ipt { width: 56px; height: 30px; border: 1px solid #e0e0e0; border-radius: 3px; text-align: center; font-size: 14px; }
.pager .label-text { color: #666; }
.pager .total { color: #d71e1e; font-weight: bold; }
/* 弹窗 */
.modal-mask { position: fixed; inset: 0; background: rgba(0,0,0,0.45); z-index: 500; display: none; align-items: center; justify-content: center; }
.modal-mask.show { display: flex; }
.modal { background: #fff; width: 900px; max-width: 95vw; max-height: 90vh; border-radius: 4px; overflow: hidden; display: flex; flex-direction: column; box-shadow: 0 8px 30px rgba(0,0,0,0.2); }
.modal-header { background: #d71e1e; color: #fff; padding: 10px 18px; display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; }
.modal-title { font-size: 16px; font-weight: bold; }
.modal-close { width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 20px; color: #fff; opacity: 0.9; }
.modal-close:hover { opacity: 1; }
.modal-body { padding: 20px 32px 30px; overflow: auto; flex: 1; }
.modal-body::-webkit-scrollbar { width: 6px; }
.modal-body::-webkit-scrollbar-thumb { background: #d4d4d4; border-radius: 3px; }
.modal-body::-webkit-scrollbar-track { background: #f5f5f5; }
.modal-toolbar { display: flex; align-items: center; margin-bottom: 10px; }
.modal-title-text { flex: 1; text-align: center; font-size: 18px; font-weight: bold; color: #222; }
.doc-title-line { border-bottom: 1px solid #d71e1e; padding-bottom: 8px; margin-bottom: 18px; }
.doc-body { font-size: 14px; color: #333; line-height: 2.2; text-align: justify; }
.doc-body .inline-input { display: inline-block; min-width: 110px; padding: 2px 8px; margin: 0 2px; border: 1px solid #d4d4d4; background: #fafafa; border-radius: 2px; color: #333; }
.doc-section { margin-top: 16px; }
.doc-section-title { font-size: 15px; font-weight: bold; margin-bottom: 8px; }
.doc-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 32px; }
.doc-field { display: flex; align-items: center; }
.doc-field .lbl { width: 90px; text-align: right; color: #555; font-size: 14px; margin-right: 8px; }
.doc-field .val { flex: 1; padding: 6px 10px; border: 1px solid #d4d4d4; background: #fafafa; border-radius: 3px; font-size: 14px; color: #333; }
.print-btn { height: 28px; padding: 0 16px; background: #d71e1e; color: #fff; border: none; border-radius: 3px; cursor: pointer; font-size: 13px; }
.print-btn:hover { opacity: 0.9; }
/* 委托单位顶格独占一行 */
.doc-unit-line { font-size: 15px; color: #222; margin: 6px 0 14px; }
.doc-unit-line .inline-input { min-width: 180px; font-size: 15px; padding: 3px 10px; }
.doc-paragraph { text-indent: 2em; line-height: 2.2; font-size: 14px; color: #333; text-align: justify; margin: 0; }
/* 列表行可点击态 */
.tbl-wrap .simple-table tbody tr.clickable .op-link { cursor: pointer; }
</style>
</head>
<body>
<!-- 筛选区 -->
<div class="filter-bar">
<div class="filter-row">
<div class="filter-item">
<span class="lbl">申报时间:</span>
<span class="date-wrap">
<input type="text" class="ipt ipt-date" data-dp="apply" data-role="start" placeholder="开始日期" readonly />
<svg class="date-icon" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="#bbb" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4.5" width="18" height="16" rx="2"/><path d="M3 9.5h18"/><path d="M8 3v3M16 3v3"/></svg>
</span>
<span class="date-sep">至</span>
<span class="date-wrap">
<input type="text" class="ipt ipt-date" data-dp="apply" data-role="end" placeholder="结束日期" readonly />
<svg class="date-icon" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="#bbb" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4.5" width="18" height="16" rx="2"/><path d="M3 9.5h18"/><path d="M8 3v3M16 3v3"/></svg>
</span>
</div>
<div class="filter-item">
<span class="lbl">检疫单证号:</span>
<input type="text" class="ipt" id="fCert" placeholder="请输入关键字" />
</div>
<div class="filter-item">
<span class="lbl">植物名称:</span>
<input type="text" class="ipt" id="fPlant" placeholder="请输入关键字" />
</div>
<div class="filter-item">
<span class="lbl">申报单位:</span>
<input type="text" class="ipt" id="fUnit" placeholder="请输入关键字" />
</div>
<div class="filter-actions">
<button class="btn" id="btnQuery">查询</button>
<button class="btn btn-default" id="btnReset">重置</button>
</div>
</div>
<div class="filter-row">
<div class="filter-item">
<span class="lbl">受理疫情监测时间:</span>
<span class="date-wrap">
<input type="text" class="ipt ipt-date" data-dp="accept" data-role="start" placeholder="开始日期" readonly />
<svg class="date-icon" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="#bbb" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4.5" width="18" height="16" rx="2"/><path d="M3 9.5h18"/><path d="M8 3v3M16 3v3"/></svg>
</span>
<span class="date-sep">至</span>
<span class="date-wrap">
<input type="text" class="ipt ipt-date" data-dp="accept" data-role="end" placeholder="结束日期" readonly />
<svg class="date-icon" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="#bbb" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4.5" width="18" height="16" rx="2"/><path d="M3 9.5h18"/><path d="M8 3v3M16 3v3"/></svg>
</span>
</div>
<div class="filter-item">
<div class="radio-group" id="statusFilter">
<label><input type="radio" name="st" value="all" /> 全部</label>
<label><input type="radio" name="st" value="wait" checked /> 待田间调查</label>
<label><input type="radio" name="st" value="done" /> 田调结束</label>
</div>
</div>
</div>
</div>
<!-- 列表 -->
<div class="tbl-wrap">
<table class="simple-table">
<colgroup>
<col style="width:60px;">
<col style="width:170px;">
<col style="width:240px;">
<col style="width:140px;">
<col style="width:120px;">
<col style="width:150px;">
<col style="width:170px;">
<col style="width:110px;">
<col style="width:140px;">
</colgroup>
<thead>
<tr>
<th>序号</th>
<th>检疫单证号</th>
<th class="lft">申报单位</th>
<th>植物名称</th>
<th>申报时间</th>
<th>受理疫情监测时间</th>
<th>受理机构</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody id="tblBody"></tbody>
</table>
<div class="pager" id="pager">
<div class="info">共 <span class="total" id="totalCount">13</span> 条记录 当前第 <span id="curPage">1</span> 页 <span id="pageSize">10</span> 条/页</div>
<span class="pg" data-act="first">首页</span>
<span class="pg" data-act="prev">上一页</span>
<span class="pg" data-page="1">1</span>
<span class="pg" data-page="2">2</span>
<span class="pg" data-act="next">下一页</span>
<span class="pg" data-act="last">末页</span>
<span class="label-text">跳转至</span>
<input type="text" class="ipt" id="jumpInput" value="1" />
<span class="label-text">页</span>
</div>
</div>
<!-- 日期选择器 popover (单例复用) -->
<div class="date-picker" id="datePicker"></div>
<!-- 流程详情弹窗 -->
<div class="modal-mask" id="modalMask">
<div class="modal">
<div class="modal-header">
<div class="modal-title">流程详情</div>
<div class="modal-close" id="modalClose">×</div>
</div>
<div class="modal-body">
<div class="modal-toolbar">
<button class="print-btn">打印预览</button>
<div class="modal-title-text">国(境)外引进农业种苗入境后疫情监测委托书</div>
</div>
<div class="doc-title-line"></div>
<div class="doc-body">
<div class="doc-unit-line"><span class="inline-input" id="mUnit">甘州区农业技术推广中心</span>:</div>
<p class="doc-paragraph">为确保国(境)外引进农业种苗检疫安全,防止危险性有害生物随引进种苗传入,按照国家有关国(境)外引进农业种苗检疫管理规定,特委托你单位对第<span class="inline-input" id="mCert"></span>号《国(境)外引进农业种苗检疫审批单》批准引进的<span class="inline-input" id="mPlant"></span>实施疫情监测,有关该批种苗的检疫审批及进口后的种植情况等信息详见《国(境)外引进农业种苗检疫审批单》和《国(境)外引进农业种苗入境后疫情监测申请书》。请你单位按照相关植物疫情监测规程等植物检疫操作规程要求,重点针对种苗引进时禁止携带的有害生物认真开展田间调查和必要的室内检验,并在监测工作结束后及时向我单位提交疫情监测报告。监测期间如发现农业植物疫情,要按照《农业植物疫情报告与发布管理办法》进行报告,并及时开展疫情处置,严防疫情扩散。</p>
</div>
<div class="doc-section">
<div class="doc-section-title">委托单位联系方式</div>
<div class="doc-grid">
<div class="doc-field"><span class="lbl">地址:</span><div class="val">甘肃省张掖市甘州区</div></div>
<div class="doc-field"><span class="lbl">联系人:</span><div class="val" id="mContact">胡琴</div></div>
<div class="doc-field"><span class="lbl">联系电话:</span><div class="val">0931-8665155</div></div>
<div class="doc-field"><span class="lbl">传真:</span><div class="val">0931-8665155</div></div>
<div class="doc-field"><span class="lbl">邮政编码:</span><div class="val">730020</div></div>
</div>
</div>
<div class="doc-section">
<div class="doc-section-title">受理机构信息</div>
<div class="doc-grid">
<div class="doc-field"><span class="lbl">省级植物检疫机构:</span><div class="val" id="mAgency">甘肃省植保植检站</div></div>
<div class="doc-field"><span class="lbl">受理日期:</span><div class="val" id="mAcceptDate">2022-04-26</div></div>
</div>
</div>
</div>
</div>
</div>
<script>
/* ===================== 自定义日期选择器 ===================== */
const WEEK_LABELS = ["日", "一", "二", "三", "四", "五", "六"];
const MONTH_NAMES = ["1 月", "2 月", "3 月", "4 月", "5 月", "6 月", "7 月", "8 月", "9 月", "10 月", "11 月", "12 月"];
function pad(n) { return n < 10 ? "0" + n : "" + n; }
function fmtDate(d) { return d.getFullYear() + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate()); }
function parseDate(s) { if (!s) return null; const m = s.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/); if (!m) return null; return new Date(+m[1], +m[2] - 1, +m[3]); }
const dp = {
el: null, target: null,
viewYear: 0, viewMonth: 0,
init() {
this.el = document.getElementById("datePicker");
document.addEventListener("click", e => {
if (!this.el.classList.contains("show")) return;
if (this.el.contains(e.target)) return;
if (e.target.closest(".ipt-date")) return;
this.hide();
});
document.querySelectorAll(".ipt-date").forEach(inp => {
inp.addEventListener("click", e => {
e.stopPropagation();
const rect = inp.getBoundingClientRect();
this.el.style.top = (window.scrollY + rect.bottom + 4) + "px";
this.el.style.left = (window.scrollX + rect.left) + "px";
const v = parseDate(inp.value) || new Date();
this.viewYear = v.getFullYear();
this.viewMonth = v.getMonth();
this.target = inp;
this.render();
this.el.classList.add("show");
});
});
},
hide() { this.el.classList.remove("show"); this.target = null; },
move(y, m) {
let ny = this.viewYear + y, nm = this.viewMonth + m;
while (nm < 0) { nm += 12; ny -= 1; }
while (nm > 11) { nm -= 12; ny += 1; }
this.viewYear = ny; this.viewMonth = nm;
this.render();
},
render() {
const today = new Date();
const sel = parseDate(this.target ? this.target.value : null);
const first = new Date(this.viewYear, this.viewMonth, 1);
const startWeekday = first.getDay();
const daysInMonth = new Date(this.viewYear, this.viewMonth + 1, 0).getDate();
const prevDays = new Date(this.viewYear, this.viewMonth, 0).getDate();
const cells = [];
for (let i = startWeekday - 1; i >= 0; i--) cells.push({ y: this.viewYear, m: this.viewMonth - 1, d: prevDays - i, other: true });
for (let d = 1; d <= daysInMonth; d++) cells.push({ y: this.viewYear, m: this.viewMonth, d, other: false });
while (cells.length % 7 !== 0 || cells.length < 42) {
const d = cells.length - startWeekday - daysInMonth + 1;
cells.push({ y: this.viewYear, m: this.viewMonth + 1, d, other: true });
}
let html = `
<div class="dp-header">
<span class="dp-nav"><span class="arrow" data-step="-12" title="上一年">«</span><span class="arrow" data-step="-1" title="上一月">‹</span></span>
<div class="dp-title">${this.viewYear} 年 ${MONTH_NAMES[this.viewMonth]}</div>
<span class="dp-nav"><span class="arrow" data-step="1" title="下一月">›</span><span class="arrow" data-step="12" title="下一年">»</span></span>
</div>
<div class="dp-week">${WEEK_LABELS.map((w, i) => `<span>${w}</span>`).join("")}</div>
<div class="dp-grid">
${cells.map(c => {
const dObj = new Date(c.y, c.m, c.d);
const isToday = dObj.toDateString() === today.toDateString();
const isSel = sel && dObj.toDateString() === sel.toDateString();
const wk = dObj.getDay();
const cls = ["dp-day"];
if (c.other) cls.push("other");
if (isToday) cls.push("today");
if (isSel) cls.push("selected");
return `<div class="${cls.join(" ")}" data-d="${c.y}-${c.m}-${c.d}">${c.d}</div>`;
}).join("")}
</div>
`;
this.el.innerHTML = html;
this.el.querySelectorAll(".arrow").forEach(a => {
a.onclick = e => { e.stopPropagation(); this.move(0, +a.dataset.step); };
});
this.el.querySelectorAll(".dp-day").forEach(d => {
d.onclick = e => {
e.stopPropagation();
const [y, m, dd] = d.dataset.d.split("-").map(Number);
const picked = new Date(y, m, dd);
this.target.value = fmtDate(picked);
this.hide();
};
});
}
};
/* ===================== 列表数据 ===================== */
const allData = [
["6204122022007095", "甘肃富赢种子高新科技有限责任公司", "翠菊", "2022-03-09", "2022-04-26", "甘肃省植保植检站", "wait"],
["6205022022007347", "甘肃富赢种子高新科技有限责任公司", "莴苣", "2022-02-21", "2022-05-09", "甘肃省植保植检站", "wait"],
["6205022022007269", "甘肃富赢种子高新科技有限责任公司", "青花菜(西兰花)", "2022-02-21", "2022-05-09", "甘肃省植保植检站", "wait"],
["6204122022007087", "甘肃富赢种子高新科技有限责任公司", "青花菜(西兰花)", "2022-02-21", "2022-05-09", "甘肃省植保植检站", "wait"],
["6202442022007001", "甘肃富赢种子高新科技有限责任公司", "胡萝卜", "2022-02-10", "2022-05-09", "甘肃省植保植检站", "wait"],
["6203122022007067", "甘肃兆尼尔种子有限公司", "辣椒", "2022-01-28", "2022-05-19", "甘肃省植保植检站", "wait"],
["6204122022007065", "甘肃富赢种子高新科技有限责任公司", "翠菊", "2022-01-27", "2022-05-09", "甘肃省植保植检站", "wait"],
["6203072022007093", "酒泉市宜润种子有限责任公司", "番茄", "2022-01-26", "2022-05-07", "甘肃省植保植检站", "wait"],
["6203092022007071", "张掖市瑞真种业有限公司", "五色菊", "2022-01-20", "2022-06-02", "甘肃省植保植检站", "wait"],
["6201162022007023", "张掖市亚太种业有限责任公司", "南瓜", "2022-01-17", "2022-05-06", "甘肃省植保植检站", "wait"],
["6201162022007017", "张掖市瑞真种业有限公司", "甜菜", "2022-01-17", "2022-06-02", "甘肃省植保植检站", "done"],
["6204342022007073", "甘肃富赢种子高新科技有限责任公司", "辣椒", "2022-01-13", "2022-04-26", "甘肃省植保植检站", "done"],
["6207222022007356", "甘肃富赢种子高新科技有限责任公司", "番茄", "2022-02-15", "2022-06-15", "甘肃省植保植检站", "done"]
];
const statusText = { wait: "待田间调查", done: "田调结束" };
const PAGE_SIZE = 10;
let curPage = 1;
/* 过滤条件:每次点击查询时从 DOM 读取 */
const filters = { applyStart: null, applyEnd: null, acceptStart: null, acceptEnd: null, cert: "", plant: "", unit: "", status: "wait" };
function inRange(d, s, e) {
if (!d) return true;
if (s && d < s) return false;
if (e && d > e) return false;
return true;
}
function getFiltered() {
const cert = filters.cert.trim();
const plant = filters.plant.trim();
const unit = filters.unit.trim();
return allData.filter(r => {
if (filters.status !== "all" && r[6] !== filters.status) return false;
if (cert && !r[0].toLowerCase().includes(cert.toLowerCase())) return false;
if (plant && !r[2].toLowerCase().includes(plant.toLowerCase())) return false;
if (unit && !r[1].toLowerCase().includes(unit.toLowerCase())) return false;
if (!inRange(r[3], filters.applyStart, filters.applyEnd)) return false;
if (!inRange(r[4], filters.acceptStart, filters.acceptEnd)) return false;
return true;
});
}
function render() {
const data = getFiltered();
const total = data.length;
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));
if (curPage > totalPages) curPage = totalPages;
const start = (curPage - 1) * PAGE_SIZE;
const slice = data.slice(start, start + PAGE_SIZE);
const baseNo = start;
const tbody = document.getElementById("tblBody");
if (slice.length === 0) {
tbody.innerHTML = `<tr><td colspan="9" style="padding:40px 0;color:#999;">暂无数据</td></tr>`;
} else {
tbody.innerHTML = slice.map((r, i) => `
<tr class="clickable" data-row='${JSON.stringify(r)}'>
<td>${baseNo + i + 1}</td>
<td>${r[0]}</td>
<td class="lft">${r[1]}</td>
<td>${r[2]}</td>
<td>${r[3]}</td>
<td>${r[4]}</td>
<td>${r[5]}</td>
<td class="status">${statusText[r[6]]}</td>
<td class="ops">
<a class="op-link">查看</a><span class="op-sep">|</span>
<a class="op-link">办理</a>
</td>
</tr>
`).join("");
}
document.getElementById("totalCount").textContent = total;
document.getElementById("curPage").textContent = curPage;
document.getElementById("pageSize").textContent = PAGE_SIZE;
document.getElementById("jumpInput").value = curPage;
const pager = document.getElementById("pager");
[...pager.querySelectorAll(".pg,.label-text,.ipt")].forEach(n => n.remove());
const mkPg = (txt, attrs, cls) => {
const s = document.createElement("span");
s.className = "pg" + (cls ? " " + cls : "");
s.textContent = txt;
for (const k in attrs) s.dataset[k] = attrs[k];
pager.appendChild(s);
return s;
};
const mkText = (txt) => { const s = document.createElement("span"); s.className = "label-text"; s.textContent = txt; pager.appendChild(s); return s; };
const mkInput = () => { const i = document.createElement("input"); i.type = "text"; i.className = "ipt"; i.id = "jumpInput"; i.value = curPage; pager.appendChild(i); return i; };
mkPg("首页", { act: "first" }, curPage === 1 ? "disabled" : "");
mkPg("上一页", { act: "prev" }, curPage === 1 ? "disabled" : "");
for (let p = 1; p <= totalPages; p++) mkPg(String(p), { page: p }, p === curPage ? "active" : "");
mkPg("下一页", { act: "next" }, curPage === totalPages ? "disabled" : "");
mkPg("末页", { act: "last" }, curPage === totalPages ? "disabled" : "");
mkText("跳转至"); mkInput(); mkText("页");
bindPager();
}
function bindPager() {
document.querySelectorAll("#pager .pg").forEach(el => {
el.onclick = () => {
if (el.classList.contains("disabled") || el.classList.contains("active")) return;
const totalPages = Math.max(1, Math.ceil(getFiltered().length / PAGE_SIZE));
if (el.dataset.act === "first") curPage = 1;
else if (el.dataset.act === "prev") curPage = Math.max(1, curPage - 1);
else if (el.dataset.act === "next") curPage = Math.min(totalPages, curPage + 1);
else if (el.dataset.act === "last") curPage = totalPages;
else if (el.dataset.page) curPage = parseInt(el.dataset.page, 10);
render();
};
});
const ji = document.getElementById("jumpInput");
ji.onkeydown = (e) => {
if (e.key === "Enter") {
const v = parseInt(ji.value, 10);
if (!isNaN(v)) {
const totalPages = Math.max(1, Math.ceil(getFiltered().length / PAGE_SIZE));
curPage = Math.min(Math.max(1, v), totalPages);
render();
}
}
};
}
/* 收集筛选条件并触发查询 */
function collectFilters() {
filters.applyStart = parseDate(document.querySelector('[data-dp="apply"][data-role="start"]').value);
filters.applyEnd = parseDate(document.querySelector('[data-dp="apply"][data-role="end"]').value);
filters.acceptStart = parseDate(document.querySelector('[data-dp="accept"][data-role="start"]').value);
filters.acceptEnd = parseDate(document.querySelector('[data-dp="accept"][data-role="end"]').value);
filters.cert = document.getElementById("fCert").value;
filters.plant = document.getElementById("fPlant").value;
filters.unit = document.getElementById("fUnit").value;
const checked = document.querySelector('#statusFilter input[name=st]:checked');
filters.status = checked ? checked.value : "all";
}
document.getElementById("btnQuery").onclick = () => { collectFilters(); curPage = 1; render(); };
document.getElementById("btnReset").onclick = () => {
document.querySelectorAll(".filter-bar .ipt").forEach(i => { if (!i.classList.contains("ipt-date")) i.value = ""; else i.value = ""; });
document.querySelectorAll(".ipt-date").forEach(i => i.value = "");
document.querySelector('#statusFilter input[value="wait"]').checked = true;
collectFilters();
curPage = 1;
render();
};
/* 弹窗控制 */
const modalMask = document.getElementById("modalMask");
const modalClose = document.getElementById("modalClose");
function openModal(row) {
document.getElementById("mCert").textContent = row[0];
// 委托单位始终是本隔离中心:甘州区农业技术推广中心
document.getElementById("mPlant").textContent = row[2];
document.getElementById("mAcceptDate").textContent = row[4];
modalMask.classList.add("show");
}
function closeModal() { modalMask.classList.remove("show"); }
modalClose.onclick = closeModal;
modalMask.addEventListener("click", e => { if (e.target === modalMask) closeModal(); });
/* 列表行点击事件委托 */
document.getElementById("tblBody").addEventListener("click", e => {
// 只在点击"查看"时打开弹窗,点击"办理"或其他位置忽略
if (!e.target.classList.contains("op-link") || e.target.textContent.trim() !== "查看") return;
const tr = e.target.closest("tr");
if (!tr || !tr.dataset.row) return;
try { openModal(JSON.parse(tr.dataset.row)); } catch (_) {}
});
/* 初始化 */
dp.init();
collectFilters();
render();
</script>
</body>
</html>
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>委托书登记</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">📑</div><div class="ph-title">委托书登记</div><div class="ph-desc">新建委托书登记记录</div></div></body></html>
\ No newline at end of file
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>首页 - 植物检疫检测隔离中心</title>
<link rel="stylesheet" href="../styles/main.css?v=20260907" />
<style>
body { padding: 8px; background: #fff; }
.welcome {
background: linear-gradient(90deg, #fef2f2 0%, #fff 100%);
border-left: 4px solid #d71e1e;
padding: 8px 16px;
margin-bottom: 8px;
border-radius: 3px;
}
.welcome h2 { color: #d71e1e; font-size: 17px; margin-bottom: 2px; }
.welcome p { color: #666; font-size: 13px; }
.quick-grid {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 8px;
margin-bottom: 8px;
}
.quick {
background: #fff;
border: 1px solid #f0f0f0;
border-radius: 4px;
padding: 6px 6px;
text-align: center;
cursor: pointer;
transition: all 0.15s;
}
.quick:hover { border-color: #d71e1e; box-shadow: 0 2px 8px rgba(215,30,30,0.08); }
.quick .qi { font-size: 18px; margin-bottom: 0; }
.quick .qn { font-size: 12px; color: #555; }
.badge {
display: inline-block;
min-width: 18px;
padding: 0 5px;
height: 16px;
line-height: 16px;
background: #d71e1e;
color: #fff;
border-radius: 8px;
font-size: 11px;
margin-left: 4px;
}
.status-tag {
display: inline-block;
padding: 1px 6px;
border-radius: 2px;
font-size: 12px;
}
.tag-red { background: #fef2f2; color: #d71e1e; }
.tag-orange { background: #fff4e6; color: #fa8c16; }
.tag-green { background: #f0f9eb; color: #52c41a; }
.tag-blue { background: #e6f4ff; color: #1890ff; }
/* 覆盖主样式,让首页更紧凑,完整显示无滚动条 */
.stat-row { gap: 6px; margin-bottom: 6px; }
.stat-card { padding: 6px 12px; }
.stat-icon { width: 32px; height: 32px; font-size: 15px; margin-right: 8px; }
.stat-label { font-size: 12px; margin-bottom: 2px; }
.stat-value { font-size: 18px; }
.stat-extra { font-size: 11px; }
.panel { margin-bottom: 6px; }
.panel-header { padding: 5px 10px; }
.panel-title { font-size: 14px; padding-left: 8px; }
.panel-title::before { width: 3px; height: 12px; }
.panel-body { padding: 6px 10px; }
.row-2 { gap: 6px; }
.chart-box { height: 110px; font-size: 11px; }
.simple-table th, .simple-table td { padding: 3px 6px; }
.simple-table { font-size: 12px; }
.simple-table th { font-size: 11px; }
</style>
</head>
<body>
<div class="welcome">
<h2>欢迎使用植物检疫检测隔离中心智能管理平台</h2>
<p>今日 <span style="color:#d71e1e;font-weight:bold;">12</span> 条新样本待接收,<span style="color:#fa8c16;font-weight:bold;">3</span> 项隔离即将到期,<span style="color:#d71e1e;font-weight:bold;">1</span> 项风险预警待处理。</p>
</div>
<!-- 快捷入口 -->
<div class="quick-grid">
<div class="quick"><div class="qi">🧪</div><div class="qn">样本接收</div></div>
<div class="quick"><div class="qi">🔬</div><div class="qn">检疫任务</div></div>
<div class="quick"><div class="qi">🏥</div><div class="qn">隔离分配</div></div>
<div class="quick"><div class="qi">🔥</div><div class="qn">除害处置</div></div>
<div class="quick"><div class="qi">📊</div><div class="qn">数据分析</div></div>
<div class="quick"><div class="qi">🪪</div><div class="qn">审核发证</div></div>
</div>
<!-- 统计指标 -->
<div class="stat-row">
<div class="stat-card">
<div class="stat-icon">🧪</div>
<div class="stat-info">
<div class="stat-label">本月样本接收</div>
<div class="stat-value">1,286</div>
<div class="stat-extra">较上月 <span style="color:#52c41a">↑ 8.4%</span></div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">🔬</div>
<div class="stat-info">
<div class="stat-label">在检样本</div>
<div class="stat-value">342</div>
<div class="stat-extra">已完成 892 例</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">🏥</div>
<div class="stat-info">
<div class="stat-label">隔离中样本</div>
<div class="stat-value">58</div>
<div class="stat-extra">隔离室使用率 72%</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">⚠️</div>
<div class="stat-info">
<div class="stat-label">风险预警</div>
<div class="stat-value">7</div>
<div class="stat-extra">2 项待处理</div>
</div>
</div>
</div>
<div class="row-2">
<!-- 图表 -->
<div class="panel">
<div class="panel-header">
<div class="panel-title">本月样本检疫趋势</div>
<div class="panel-tools">
<button class="btn-link">日</button>
<button class="btn-link">周</button>
<button class="btn-link" style="font-weight:bold;">月</button>
</div>
</div>
<div class="panel-body"><div class="chart-box">📈 折线图占位(检疫数量 / 阳性检出率趋势)</div></div>
</div>
<div class="panel">
<div class="panel-header">
<div class="panel-title">有害生物检出分布</div>
<div class="panel-tools"><button class="btn-link">查看详情</button></div>
</div>
<div class="panel-body"><div class="chart-box">🥧 饼图占位(按种类/来源地区分布)</div></div>
</div>
</div>
<!-- 待办与预警 -->
<div class="row-2">
<div class="panel">
<div class="panel-header">
<div class="panel-title">待办事项 <span class="badge">5</span></div>
<div class="panel-tools"><button class="btn-link">更多</button></div>
</div>
<div class="panel-body" style="padding:0;">
<table class="simple-table">
<thead>
<tr><th>类型</th><th>内容</th><th>截止时间</th><th>状态</th></tr>
</thead>
<tbody>
<tr><td>样本接收</td><td>S2026-0015 水稻种子样品</td><td>今日 18:00</td><td><span class="status-tag tag-red">紧急</span></td></tr>
<tr><td>检疫任务</td><td>番茄果实检疫(编号 B2026-0088)</td><td>明日 10:00</td><td><span class="status-tag tag-orange">待办</span></td></tr>
<tr><td>隔离监测</td><td>隔离室 3# 第 14 天观察记录</td><td>09-05 09:00</td><td><span class="status-tag tag-orange">待办</span></td></tr>
<tr><td>报告审核</td><td>小麦产地检疫报告(HZ2026-0042)</td><td>09-06</td><td><span class="status-tag tag-blue">审核中</span></td></tr>
<tr><td>证书签发</td><td>出口检疫证书申请(3 份)</td><td>09-07</td><td><span class="status-tag tag-blue">审核中</span></td></tr>
</tbody>
</table>
</div>
</div>
<div class="panel">
<div class="panel-header">
<div class="panel-title">最新预警 <span class="badge" style="background:#fa8c16;">2</span></div>
<div class="panel-tools"><button class="btn-link">查看全部</button></div>
</div>
<div class="panel-body" style="padding:0;">
<table class="simple-table">
<thead>
<tr><th>级别</th><th>预警内容</th><th>触发时间</th><th>状态</th></tr>
</thead>
<tbody>
<tr><td><span class="status-tag tag-red">高</span></td><td>进口大豆检出疑似检疫性有害生物</td><td>10 分钟前</td><td><span class="status-tag tag-red">未处理</span></td></tr>
<tr><td><span class="status-tag tag-orange">中</span></td><td>隔离室 5# 温度异常 32℃</td><td>1 小时前</td><td><span class="status-tag tag-orange">处理中</span></td></tr>
<tr><td><span class="status-tag tag-orange">中</span></td><td>同批次样本连续阳性检出</td><td>2 小时前</td><td><span class="status-tag tag-blue">已派单</span></td></tr>
<tr><td><span class="status-tag tag-blue">低</span></td><td>除害药剂库存低于预警线</td><td>昨日 16:20</td><td><span class="status-tag tag-green">已处理</span></td></tr>
<tr><td><span class="status-tag tag-blue">低</span></td><td>证书签发审批超时</td><td>昨日 14:00</td><td><span class="status-tag tag-green">已处理</span></td></tr>
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>检疫记录登记</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">📑</div><div class="ph-title">检疫记录登记</div><div class="ph-desc">检疫过程与方法的详细记录</div></div></body></html>
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>检疫报告生成</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">📃</div><div class="ph-title">检疫报告生成</div><div class="ph-desc">生成与下载检疫报告</div></div></body></html>
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>检疫结果录入</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">✅</div><div class="ph-title">检疫结果录入</div><div class="ph-desc">检疫结果数据录入与判定</div></div></body></html>
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>检疫任务分配</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">📝</div><div class="ph-title">检疫任务分配</div><div class="ph-desc">分配检疫任务给技术人员</div></div></body></html>
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>隔离期满评估</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">🏁</div><div class="ph-title">隔离期满评估</div><div class="ph-desc">隔离期满评估与放行决策</div></div></body></html>
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>隔离期监测</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">👁️</div><div class="ph-title">隔离期监测</div><div class="ph-desc">隔离期内的日常监测记录</div></div></body></html>
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>隔离计划制定</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">📅</div><div class="ph-title">隔离计划制定</div><div class="ph-desc">制定样本隔离观察计划</div></div></body></html>
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>隔离室分配</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">🚪</div><div class="ph-title">隔离室分配</div><div class="ph-desc">隔离室使用情况与分配</div></div></body></html>
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>报告归档</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">🗄️</div><div class="ph-title">报告归档</div><div class="ph-desc">历史报告归档与检索</div></div></body></html>
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>报告填报</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">✍️</div><div class="ph-title">报告填报</div><div class="ph-desc">填报各类检疫报告</div></div></body></html>
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>报告模板管理</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">📄</div><div class="ph-title">报告模板管理</div><div class="ph-desc">维护报告模板</div></div></body></html>
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>预警处置跟踪</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">🧯</div><div class="ph-title">预警处置跟踪</div><div class="ph-desc">预警处置流程跟踪</div></div></body></html>
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>预警规则配置</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">🛠️</div><div class="ph-title">预警规则配置</div><div class="ph-desc">配置风险预警规则与阈值</div></div></body></html>
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>实时预警</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">🚨</div><div class="ph-title">实时预警</div><div class="ph-desc">实时查看风险预警信息</div></div></body></html>
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>样本出库登记</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">📤</div><div class="ph-title">样本出库登记</div><div class="ph-desc">样本出库、销毁与移交登记</div></div></body></html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>样本接受登记</title>
<link rel="stylesheet" href="../styles/main.css?v=20260907" />
<style>
body { padding: 16px; background: #fff; }
/* 筛选区 */
.filter-bar { background: #fff; padding: 14px 18px; border: 1px solid #f0f0f0; border-radius: 4px; margin-bottom: 12px; }
.filter-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 18px; margin-bottom: 10px; }
.filter-row:last-child { margin-bottom: 0; }
.filter-item { display: flex; align-items: center; }
.filter-item .lbl { color: #555; font-size: 14px; margin-right: 8px; white-space: nowrap; }
.filter-item .ipt { width: 150px; height: 32px; padding: 0 32px 0 10px; border: 1px solid #d4d4d4; border-radius: 3px; font-size: 14px; background: #fff; }
.filter-item .ipt:focus { border-color: #d71e1e; outline: none; }
.filter-item .ipt-date { cursor: pointer; }
.filter-item .date-sep { color: #999; margin: 0 6px; }
.filter-actions { margin-left: auto; display: flex; gap: 10px; }
.filter-actions .btn { height: 32px; padding: 0 20px; font-size: 14px; }
.radio-group { display: flex; align-items: center; gap: 22px; }
.radio-group label { display: flex; align-items: center; gap: 6px; cursor: pointer; font-size: 14px; color: #555; user-select: none; }
.radio-group input[type=radio] { accent-color: #d71e1e; width: 14px; height: 14px; }
/* 日期输入框包装 */
.date-wrap { position: relative; display: inline-block; }
.date-wrap .date-icon { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); pointer-events: none; display: flex; }
/* 自定义日期选择器 */
.date-picker { position: absolute; z-index: 200; background: #fff; border: 1px solid #e0e0e0; border-radius: 4px; box-shadow: 0 4px 14px rgba(0,0,0,0.08); padding: 12px; width: 280px; user-select: none; display: none; }
.date-picker.show { display: block; }
.dp-header { display: flex; align-items: center; justify-content: space-between; padding-bottom: 8px; border-bottom: 1px solid #f0f0f0; margin-bottom: 8px; }
.dp-nav { display: flex; align-items: center; gap: 4px; color: #666; cursor: pointer; font-size: 14px; }
.dp-nav .arrow { width: 22px; height: 22px; display: inline-flex; align-items: center; justify-content: center; border-radius: 3px; color: #999; }
.dp-nav .arrow:hover { background: #fef2f2; color: #d71e1e; }
.dp-title { font-size: 15px; font-weight: bold; color: #333; flex: 1; text-align: center; }
.dp-week { display: grid; grid-template-columns: repeat(7, 1fr); margin-bottom: 4px; }
.dp-week span { text-align: center; font-size: 12px; color: #999; height: 26px; line-height: 26px; }
.dp-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.dp-day { height: 32px; display: flex; align-items: center; justify-content: center; font-size: 13px; color: #333; cursor: pointer; border-radius: 3px; }
.dp-day:hover { background: #fef2f2; color: #d71e1e; }
.dp-day.other { color: #ccc; }
.dp-day.other:hover { color: #d71e1e; background: #fef2f2; }
.dp-day.today { color: #d71e1e; font-weight: bold; }
.dp-day.selected { background: #d71e1e; color: #fff; }
.dp-day.selected:hover { background: #d71e1e; color: #fff; }
/* 列表 */
.tbl-wrap { background: #fff; border: 1px solid #f0f0f0; border-radius: 4px; overflow-x: auto; }
.tbl-wrap .simple-table { width: 100%; min-width: 1380px; border-collapse: collapse; table-layout: fixed; }
.tbl-wrap .simple-table th { font-size: 15px; font-weight: bold; color: #333; padding: 14px 12px; text-align: center; border-bottom: 1px solid #f0f0f0; }
.tbl-wrap .simple-table td { font-size: 14px; color: #333; padding: 14px 12px; text-align: center; border-bottom: 1px solid #f0f0f0; }
.tbl-wrap .simple-table td.lft, .tbl-wrap .simple-table th.lft { text-align: left; }
.tbl-wrap .simple-table tbody tr:hover td { background: #fef9f9; }
.tbl-wrap .simple-table td.status,
.tbl-wrap .simple-table td.ops { font-size: 14px; color: #333; }
.op-link { color: #333; font-size: 14px; margin: 0 8px; cursor: pointer; }
.op-link:hover { color: #d71e1e; }
.op-sep { color: #ddd; }
/* 分页 */
.pager { display: flex; align-items: center; padding: 16px 20px; font-size: 14px; color: #666; gap: 10px; }
.pager .info { margin-right: auto; }
.pager .pg { display: inline-flex; align-items: center; justify-content: center; min-width: 30px; height: 30px; padding: 0 8px; border: 1px solid #e0e0e0; background: #fff; border-radius: 3px; cursor: pointer; font-size: 14px; color: #555; }
.pager .pg:hover { color: #d71e1e; border-color: #d71e1e; }
.pager .pg.active { background: #d71e1e; color: #fff; border-color: #d71e1e; }
.pager .pg.disabled { color: #ccc; cursor: not-allowed; background: #fafafa; border-color: #f0f0f0; }
.pager .ipt { width: 56px; height: 30px; border: 1px solid #e0e0e0; border-radius: 3px; text-align: center; font-size: 14px; }
.pager .label-text { color: #666; }
.pager .total { color: #d71e1e; font-weight: bold; }
/* 弹窗 */
.modal-mask { position: fixed; inset: 0; background: rgba(0,0,0,0.45); z-index: 500; display: none; align-items: center; justify-content: center; }
.modal-mask.show { display: flex; }
.modal { background: #fff; width: 900px; max-width: 95vw; max-height: 90vh; border-radius: 4px; overflow: hidden; display: flex; flex-direction: column; box-shadow: 0 8px 30px rgba(0,0,0,0.2); }
.modal-header { background: #d71e1e; color: #fff; padding: 10px 18px; display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; }
.modal-title { font-size: 16px; font-weight: bold; }
.modal-close { width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 20px; color: #fff; opacity: 0.9; }
.modal-close:hover { opacity: 1; }
.modal-body { padding: 20px 32px 30px; overflow: auto; flex: 1; }
.modal-body::-webkit-scrollbar { width: 6px; }
.modal-body::-webkit-scrollbar-thumb { background: #d4d4d4; border-radius: 3px; }
.modal-body::-webkit-scrollbar-track { background: #f5f5f5; }
.modal-toolbar { display: flex; align-items: center; margin-bottom: 10px; }
.modal-title-text { flex: 1; text-align: center; font-size: 18px; font-weight: bold; color: #222; }
.doc-title-line { border-bottom: 1px solid #d71e1e; padding-bottom: 8px; margin-bottom: 18px; }
.doc-body { font-size: 14px; color: #333; line-height: 2.2; text-align: justify; }
.doc-body .inline-input { display: inline-block; min-width: 110px; padding: 2px 8px; margin: 0 2px; border: 1px solid #d4d4d4; background: #fafafa; border-radius: 2px; color: #333; }
.doc-section { margin-top: 16px; }
.doc-section-title { font-size: 15px; font-weight: bold; margin-bottom: 8px; }
.doc-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 32px; }
.doc-field { display: flex; align-items: center; }
.doc-field .lbl { width: 90px; text-align: right; color: #555; font-size: 14px; margin-right: 8px; }
.doc-field .val { flex: 1; padding: 6px 10px; border: 1px solid #d4d4d4; background: #fafafa; border-radius: 3px; font-size: 14px; color: #333; }
.print-btn { height: 28px; padding: 0 16px; background: #d71e1e; color: #fff; border: none; border-radius: 3px; cursor: pointer; font-size: 13px; }
.modal-section-title { font-size: 15px; font-weight: bold; color: #222; margin: 4px 0 10px; padding-left: 8px; border-left: 3px solid #d71e1e; }
.modal-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 28px; }
.modal-field { display: flex; align-items: center; }
.modal-field .lbl { width: 110px; text-align: right; color: #555; font-size: 14px; margin-right: 8px; }
.modal-field .val { flex: 1; padding: 7px 10px; border: 1px solid #d4d4d4; background: #fafafa; border-radius: 3px; font-size: 14px; color: #333; }
.confirm-mask { position: fixed; inset: 0; background: rgba(0,0,0,0.45); z-index: 600; display: none; align-items: center; justify-content: center; }
.confirm-mask.show { display: flex; }
.confirm { background: #fff; width: 380px; border-radius: 4px; overflow: hidden; box-shadow: 0 8px 30px rgba(0,0,0,0.2); }
.confirm-head { background: #d71e1e; color: #fff; padding: 10px 18px; font-size: 15px; font-weight: bold; }
.confirm-body { padding: 22px 24px 14px; color: #333; font-size: 14px; line-height: 1.8; }
.confirm-foot { padding: 0 24px 18px; display: flex; justify-content: flex-end; gap: 10px; }
.confirm-foot .btn { height: 32px; padding: 0 20px; font-size: 14px; }
.op-link.del { color: #d71e1e; }
.op-link.del:hover { color: #b81818; }
.print-btn:hover { opacity: 0.9; }
/* 委托单位顶格独占一行 */
.doc-unit-line { font-size: 15px; color: #222; margin: 6px 0 14px; }
.doc-unit-line .inline-input { min-width: 180px; font-size: 15px; padding: 3px 10px; }
.doc-paragraph { text-indent: 2em; line-height: 2.2; font-size: 14px; color: #333; text-align: justify; margin: 0; }
/* 列表行可点击态 */
.tbl-wrap .simple-table tbody tr.clickable .op-link { cursor: pointer; }
</style>
</head>
<body>
<!-- 筛选区 -->
<div class="filter-bar">
<div class="filter-row">
<div class="filter-item">
<span class="lbl">接收时间:</span>
<span class="date-wrap">
<input type="text" class="ipt ipt-date" data-dp="recv" data-role="start" placeholder="开始日期" readonly />
<svg class="date-icon" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="#bbb" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4.5" width="18" height="16" rx="2"/><path d="M3 9.5h18"/><path d="M8 3v3M16 3v3"/></svg>
</span>
<span class="date-sep">至</span>
<span class="date-wrap">
<input type="text" class="ipt ipt-date" data-dp="recv" data-role="end" placeholder="结束日期" readonly />
<svg class="date-icon" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="#bbb" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4.5" width="18" height="16" rx="2"/><path d="M3 9.5h18"/><path d="M8 3v3M16 3v3"/></svg>
</span>
</div>
<div class="filter-item">
<span class="lbl">检疫单证号:</span>
<input type="text" class="ipt" id="fCert" placeholder="请输入关键字" />
</div>
<div class="filter-item">
<span class="lbl">植物名称:</span>
<input type="text" class="ipt" id="fPlant" placeholder="请输入关键字" />
</div>
</div>
<div class="filter-row">
<div class="filter-item">
<span class="lbl">品种名称:</span>
<input type="text" class="ipt" id="fVariety" placeholder="请输入关键字" />
</div>
<div class="filter-item">
<span class="lbl">申报单位:</span>
<input type="text" class="ipt" id="fUnit" placeholder="请输入关键字" />
</div>
<div class="filter-actions">
<button class="btn" id="btnQuery">查询</button>
<button class="btn btn-default" id="btnReset">重置</button>
</div>
</div>
</div>
<!-- 列表 -->
<div class="tbl-wrap">
<table class="simple-table">
<colgroup>
<col style="width:60px;">
<col style="width:160px;">
<col style="width:220px;">
<col style="width:120px;">
<col style="width:140px;">
<col style="width:120px;">
<col style="width:120px;">
<col style="width:110px;">
<col style="width:90px;">
<col style="width:120px;">
<col style="width:180px;">
</colgroup>
<thead>
<tr>
<th>序号</th>
<th>检疫单证号</th>
<th class="lft">申报单位</th>
<th>植物名称</th>
<th>品种名称</th>
<th>种苗来源国(地区)</th>
<th>引进数量</th>
<th>接收时间</th>
<th>联系人</th>
<th>联系电话</th>
<th>操作</th>
</tr>
</thead>
<tbody id="tblBody"></tbody>
</table>
<div class="pager" id="pager">
<div class="info">共 <span class="total" id="totalCount">13</span> 条记录 当前第 <span id="curPage">1</span> 页 <span id="pageSize">13</span> 条/页</div>
<span class="pg" data-act="first">首页</span>
<span class="pg" data-act="prev">上一页</span>
<span class="pg" data-page="1">1</span>
<span class="pg" data-page="2">2</span>
<span class="pg" data-act="next">下一页</span>
<span class="pg" data-act="last">末页</span>
<span class="label-text">跳转至</span>
<input type="text" class="ipt" id="jumpInput" value="1" />
<span class="label-text">页</span>
</div>
</div>
<!-- 日期选择器 popover (单例复用) -->
<div class="date-picker" id="datePicker"></div>
<!-- 查看/编辑弹窗 -->
<div class="modal-mask" id="modalMask">
<div class="modal">
<div class="modal-header">
<div class="modal-title" id="modalTitle">样本登记详情</div>
<div class="modal-close" id="modalClose">×</div>
</div>
<div class="modal-body">
<div class="modal-section-title">基本信息</div>
<div class="modal-grid">
<div class="modal-field"><span class="lbl">检疫单证号:</span><div class="val" id="mCert"></div></div>
<div class="modal-field"><span class="lbl">申报单位:</span><div class="val" id="mUnit"></div></div>
<div class="modal-field"><span class="lbl">植物名称:</span><div class="val" id="mPlant"></div></div>
<div class="modal-field"><span class="lbl">品种名称:</span><div class="val" id="mVariety"></div></div>
<div class="modal-field"><span class="lbl">种苗来源国:</span><div class="val" id="mSource"></div></div>
<div class="modal-field"><span class="lbl">引进数量:</span><div class="val" id="mQty"></div></div>
<div class="modal-field"><span class="lbl">接收时间:</span><div class="val" id="mRecvDate"></div></div>
<div class="modal-field"><span class="lbl">接收单位:</span><div class="val">植物检疫检测隔离中心</div></div>
</div>
<div class="modal-section-title" style="margin-top:18px;">联系信息</div>
<div class="modal-grid">
<div class="modal-field"><span class="lbl">联系人:</span><div class="val" id="mContact"></div></div>
<div class="modal-field"><span class="lbl">联系电话:</span><div class="val" id="mPhone"></div></div>
</div>
</div>
</div>
</div>
<!-- 删除确认 -->
<div class="confirm-mask" id="confirmMask">
<div class="confirm">
<div class="confirm-head">删除确认</div>
<div class="confirm-body" id="confirmBody">确定要删除该条记录吗?删除后不可恢复。</div>
<div class="confirm-foot">
<button class="btn btn-default" id="confirmCancel">取消</button>
<button class="btn btn-primary" id="confirmOk">确定删除</button>
</div>
</div>
</div>
<script>
/* ===================== 自定义日期选择器 ===================== */
const WEEK_LABELS = ["日", "一", "二", "三", "四", "五", "六"];
const MONTH_NAMES = ["1 月", "2 月", "3 月", "4 月", "5 月", "6 月", "7 月", "8 月", "9 月", "10 月", "11 月", "12 月"];
function pad(n) { return n < 10 ? "0" + n : "" + n; }
function fmtDate(d) { return d.getFullYear() + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate()); }
function parseDate(s) { if (!s) return null; const m = s.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/); if (!m) return null; return new Date(+m[1], +m[2] - 1, +m[3]); }
const dp = {
el: null, target: null,
viewYear: 0, viewMonth: 0,
init() {
this.el = document.getElementById("datePicker");
document.addEventListener("click", e => {
if (!this.el.classList.contains("show")) return;
if (this.el.contains(e.target)) return;
if (e.target.closest(".ipt-date")) return;
this.hide();
});
document.querySelectorAll(".ipt-date").forEach(inp => {
inp.addEventListener("click", e => {
e.stopPropagation();
const rect = inp.getBoundingClientRect();
this.el.style.top = (window.scrollY + rect.bottom + 4) + "px";
this.el.style.left = (window.scrollX + rect.left) + "px";
const v = parseDate(inp.value) || new Date();
this.viewYear = v.getFullYear();
this.viewMonth = v.getMonth();
this.target = inp;
this.render();
this.el.classList.add("show");
});
});
},
hide() { this.el.classList.remove("show"); this.target = null; },
move(y, m) {
let ny = this.viewYear + y, nm = this.viewMonth + m;
while (nm < 0) { nm += 12; ny -= 1; }
while (nm > 11) { nm -= 12; ny += 1; }
this.viewYear = ny; this.viewMonth = nm;
this.render();
},
render() {
const today = new Date();
const sel = parseDate(this.target ? this.target.value : null);
const first = new Date(this.viewYear, this.viewMonth, 1);
const startWeekday = first.getDay();
const daysInMonth = new Date(this.viewYear, this.viewMonth + 1, 0).getDate();
const prevDays = new Date(this.viewYear, this.viewMonth, 0).getDate();
const cells = [];
for (let i = startWeekday - 1; i >= 0; i--) cells.push({ y: this.viewYear, m: this.viewMonth - 1, d: prevDays - i, other: true });
for (let d = 1; d <= daysInMonth; d++) cells.push({ y: this.viewYear, m: this.viewMonth, d, other: false });
while (cells.length % 7 !== 0 || cells.length < 42) {
const d = cells.length - startWeekday - daysInMonth + 1;
cells.push({ y: this.viewYear, m: this.viewMonth + 1, d, other: true });
}
let html = `
<div class="dp-header">
<span class="dp-nav"><span class="arrow" data-step="-12" title="上一年">«</span><span class="arrow" data-step="-1" title="上一月">‹</span></span>
<div class="dp-title">${this.viewYear} 年 ${MONTH_NAMES[this.viewMonth]}</div>
<span class="dp-nav"><span class="arrow" data-step="1" title="下一月">›</span><span class="arrow" data-step="12" title="下一年">»</span></span>
</div>
<div class="dp-week">${WEEK_LABELS.map((w, i) => `<span>${w}</span>`).join("")}</div>
<div class="dp-grid">
${cells.map(c => {
const dObj = new Date(c.y, c.m, c.d);
const isToday = dObj.toDateString() === today.toDateString();
const isSel = sel && dObj.toDateString() === sel.toDateString();
const wk = dObj.getDay();
const cls = ["dp-day"];
if (c.other) cls.push("other");
if (isToday) cls.push("today");
if (isSel) cls.push("selected");
return `<div class="${cls.join(" ")}" data-d="${c.y}-${c.m}-${c.d}">${c.d}</div>`;
}).join("")}
</div>
`;
this.el.innerHTML = html;
this.el.querySelectorAll(".arrow").forEach(a => {
a.onclick = e => { e.stopPropagation(); this.move(0, +a.dataset.step); };
});
this.el.querySelectorAll(".dp-day").forEach(d => {
d.onclick = e => {
e.stopPropagation();
const [y, m, dd] = d.dataset.d.split("-").map(Number);
const picked = new Date(y, m, dd);
this.target.value = fmtDate(picked);
this.hide();
};
});
}
};
/* ===================== 列表数据 ===================== */
const allData = [
["6204122022007095", "甘肃富赢种子高新科技有限责任公司", "翠菊", "翠菊", "智利", "0.0014 千克", "2022-03-01", "刘玉堂", "18993607501"],
["6205022022007347", "甘肃富赢种子高新科技有限责任公司", "莴苣", "生菜", "美国", "0.2950 千克", "2022-03-20", "刘玉堂", "18993607501"],
["6205022022007269", "甘肃富赢种子高新科技有限责任公司", "青花菜(西兰花)", "西蓝花 VBR007", "美国", "2.3920 千克", "2022-03-20", "刘玉堂", "18993607501"],
["6204122022007087", "甘肃富赢种子高新科技有限责任公司", "青花菜(西兰花)", "西蓝花 VBR005", "智利", "0.3700 千克", "2022-03-20", "刘玉堂", "18993607501"],
["6202442022007001", "甘肃富赢种子高新科技有限责任公司", "胡萝卜", "胡萝卜 VCR324", "南非", "0.6150 千克", "2022-03-20", "刘玉堂", "18993607501"],
["6203122022007067", "甘肃杰尼尔种子有限公司", "辣椒", "80", "西班牙", "10.0 千克", "2022-04-01", "赵文言", "18993755577"],
["6204122022007065", "甘肃富赢种子高新科技有限责任公司", "翠菊", "翠菊", "智利", "0.0014 千克", "2022-02-18", "刘玉堂", "18993607501"],
["6203072022007093", "酒泉市酒洲种子有限责任公司", "番茄", "番茄", "意大利", "1.9540 千克", "2022-03-10", "马世锋", "13993708175"],
["6203092022007071", "张掖市瑞真种业有限公司", "五色菊", "五色菊", "荷兰", "2.0000 千克", "2022-03-20", "吴多志", "13993662953"],
["6201162022007023", "张掖市亚太种业有限责任公司", "南瓜", "南瓜", "日本", "3.4000 千克", "2022-04-02", "姚庆库", "18919369888"],
["6201162022007017", "张掖市瑞真种业有限公司", "甜菜", "甜菜", "日本", "2.0000 千克", "2022-04-20", "吴多志", "13993662953"],
["6204342022007073", "甘肃富赢种子高新科技有限责任公司", "辣椒", "辣椒", "秘鲁", "0.4878 千克", "2022-03-10", "刘玉堂", "18993607501"],
["6204292022007017", "甘肃富赢种子高新科技有限责任公司", "番茄", "番茄", "墨西哥", "0.0590 千克", "2022-03-10", "刘玉堂", "18993607501"]
];
const PAGE_SIZE = 10;
let curPage = 1;
/* 过滤条件:每次点击查询时从 DOM 读取 */
const filters = { recvStart: null, recvEnd: null, cert: "", plant: "", variety: "", unit: "" };
function inRange(d, s, e) {
if (!d) return true;
if (s && d < s) return false;
if (e && d > e) return false;
return true;
}
function getFiltered() {
const cert = filters.cert.trim();
const plant = filters.plant.trim();
const variety = filters.variety.trim();
const unit = filters.unit.trim();
return allData.filter(r => {
if (variety && !r[3].toLowerCase().includes(variety.toLowerCase())) return false;
if (cert && !r[0].toLowerCase().includes(cert.toLowerCase())) return false;
if (plant && !r[2].toLowerCase().includes(plant.toLowerCase())) return false;
if (unit && !r[1].toLowerCase().includes(unit.toLowerCase())) return false;
if (!inRange(r[6], filters.recvStart, filters.recvEnd)) return false;
return true;
});
}
function render() {
const data = getFiltered();
const total = data.length;
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));
if (curPage > totalPages) curPage = totalPages;
const start = (curPage - 1) * PAGE_SIZE;
const slice = data.slice(start, start + PAGE_SIZE);
const baseNo = start;
const tbody = document.getElementById("tblBody");
if (slice.length === 0) {
tbody.innerHTML = `<tr><td colspan="11" style="padding:40px 0;color:#999;">暂无数据</td></tr>`;
} else {
tbody.innerHTML = slice.map((r, i) => `
<tr data-row='${JSON.stringify(r)}'>
<td>${baseNo + i + 1}</td>
<td>${r[0]}</td>
<td class="lft">${r[1]}</td>
<td>${r[2]}</td>
<td>${r[3]}</td>
<td>${r[4]}</td>
<td>${r[5]}</td>
<td>${r[6]}</td>
<td>${r[7]}</td>
<td>${r[8]}</td>
<td class="ops">
<a class="op-link" data-act="view">查看</a><span class="op-sep">|</span>
<a class="op-link" data-act="edit">编辑</a><span class="op-sep">|</span>
<a class="op-link del" data-act="del">删除</a>
</td>
</tr>
`).join("");
}
document.getElementById("totalCount").textContent = total;
document.getElementById("curPage").textContent = curPage;
document.getElementById("pageSize").textContent = PAGE_SIZE;
document.getElementById("jumpInput").value = curPage;
const pager = document.getElementById("pager");
[...pager.querySelectorAll(".pg,.label-text,.ipt")].forEach(n => n.remove());
const mkPg = (txt, attrs, cls) => {
const s = document.createElement("span");
s.className = "pg" + (cls ? " " + cls : "");
s.textContent = txt;
for (const k in attrs) s.dataset[k] = attrs[k];
pager.appendChild(s);
return s;
};
const mkText = (txt) => { const s = document.createElement("span"); s.className = "label-text"; s.textContent = txt; pager.appendChild(s); return s; };
const mkInput = () => { const i = document.createElement("input"); i.type = "text"; i.className = "ipt"; i.id = "jumpInput"; i.value = curPage; pager.appendChild(i); return i; };
mkPg("首页", { act: "first" }, curPage === 1 ? "disabled" : "");
mkPg("上一页", { act: "prev" }, curPage === 1 ? "disabled" : "");
for (let p = 1; p <= totalPages; p++) mkPg(String(p), { page: p }, p === curPage ? "active" : "");
mkPg("下一页", { act: "next" }, curPage === totalPages ? "disabled" : "");
mkPg("末页", { act: "last" }, curPage === totalPages ? "disabled" : "");
mkText("跳转至"); mkInput(); mkText("页");
bindPager();
}
function bindPager() {
document.querySelectorAll("#pager .pg").forEach(el => {
el.onclick = () => {
if (el.classList.contains("disabled") || el.classList.contains("active")) return;
const totalPages = Math.max(1, Math.ceil(getFiltered().length / PAGE_SIZE));
if (el.dataset.act === "first") curPage = 1;
else if (el.dataset.act === "prev") curPage = Math.max(1, curPage - 1);
else if (el.dataset.act === "next") curPage = Math.min(totalPages, curPage + 1);
else if (el.dataset.act === "last") curPage = totalPages;
else if (el.dataset.page) curPage = parseInt(el.dataset.page, 10);
render();
};
});
const ji = document.getElementById("jumpInput");
ji.onkeydown = (e) => {
if (e.key === "Enter") {
const v = parseInt(ji.value, 10);
if (!isNaN(v)) {
const totalPages = Math.max(1, Math.ceil(getFiltered().length / PAGE_SIZE));
curPage = Math.min(Math.max(1, v), totalPages);
render();
}
}
};
}
/* 收集筛选条件并触发查询 */
function collectFilters() {
filters.recvStart = parseDate(document.querySelector('[data-dp="recv"][data-role="start"]').value);
filters.recvEnd = parseDate(document.querySelector('[data-dp="recv"][data-role="end"]').value);
filters.cert = document.getElementById("fCert").value;
filters.plant = document.getElementById("fPlant").value;
filters.unit = document.getElementById("fUnit").value;
filters.variety = document.getElementById("fVariety").value;
filters.unit = document.getElementById("fUnit").value;
}
document.getElementById("btnQuery").onclick = () => { collectFilters(); curPage = 1; render(); };
document.getElementById("btnReset").onclick = () => {
document.querySelectorAll(".filter-bar .ipt").forEach(i => { if (!i.classList.contains("ipt-date")) i.value = ""; else i.value = ""; });
document.querySelectorAll(".ipt-date").forEach(i => i.value = "");
collectFilters();
curPage = 1;
render();
};
/* 弹窗控制 */
const modalMask = document.getElementById("modalMask");
const modalClose = document.getElementById("modalClose");
const modalTitle = document.getElementById("modalTitle");
function fillModal(row) {
document.getElementById("mCert").textContent = row[0];
document.getElementById("mUnit").textContent = row[1];
document.getElementById("mPlant").textContent = row[2];
document.getElementById("mVariety").textContent = row[3];
document.getElementById("mSource").textContent = row[4];
document.getElementById("mQty").textContent = row[5];
document.getElementById("mRecvDate").textContent = row[6];
document.getElementById("mContact").textContent = row[7];
document.getElementById("mPhone").textContent = row[8];
}
function openView(row) { modalTitle.textContent = "样本登记详情"; fillModal(row); modalMask.classList.add("show"); }
function openEdit(row) { modalTitle.textContent = "编辑样本登记"; fillModal(row); modalMask.classList.add("show"); }
function closeModal() { modalMask.classList.remove("show"); }
modalClose.onclick = closeModal;
modalMask.addEventListener("click", e => { if (e.target === modalMask) closeModal(); });
/* 删除确认 */
const confirmMask = document.getElementById("confirmMask");
const confirmBody = document.getElementById("confirmBody");
const confirmOk = document.getElementById("confirmOk");
const confirmCancel = document.getElementById("confirmCancel");
let pendingDelIdx = -1;
function openConfirm(idx, row) {
pendingDelIdx = idx;
confirmBody.innerHTML = `确定要删除 <b style="color:#d71e1e">${row[0]}</b> / ${row[2]} 这条样本登记记录吗?<br/>删除后不可恢复。`;
confirmMask.classList.add("show");
}
function closeConfirm() { confirmMask.classList.remove("show"); pendingDelIdx = -1; }
confirmCancel.onclick = closeConfirm;
confirmMask.addEventListener("click", e => { if (e.target === confirmMask) closeConfirm(); });
confirmOk.onclick = () => {
if (pendingDelIdx >= 0) {
allData.splice(pendingDelIdx, 1);
const totalPages = Math.max(1, Math.ceil(getFiltered().length / PAGE_SIZE));
if (curPage > totalPages) curPage = totalPages;
render();
}
closeConfirm();
};
/* 列表行点击事件委托 */
document.getElementById("tblBody").addEventListener("click", e => {
const link = e.target.closest(".op-link");
const tr = e.target.closest("tr");
if (!tr || !tr.dataset.row) return;
let row;
try { row = JSON.parse(tr.dataset.row); } catch (_) { return; }
if (link) {
const act = link.dataset.act;
if (act === "view") { openView(row); return; }
if (act === "edit") { openEdit(row); return; }
if (act === "del") {
const idx = allData.findIndex(x => x[0] === row[0] && x[6] === row[6] && x[1] === row[1]);
openConfirm(idx >= 0 ? idx : 0, row);
return;
}
}
});
/* 初始化 */
dp.init();
collectFilters();
render();
</script>
</body>
</html>
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>样本存放管理</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">🏷️</div><div class="ph-title">样本存放管理</div><div class="ph-desc">样本存放位置与温湿度记录管理</div></div></body></html>
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>数据字典</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">⚙</div><div class="ph-title">数据字典</div><div class="ph-desc">维护系统下拉选项与枚举值</div></div></body></html>
\ No newline at end of file
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>操作日志</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">⚙</div><div class="ph-title">操作日志</div><div class="ph-desc">查看用户操作审计日志</div></div></body></html>
\ No newline at end of file
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>角色权限</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">⚙</div><div class="ph-title">角色权限</div><div class="ph-desc">角色定义与菜单/数据权限分配</div></div></body></html>
\ No newline at end of file
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0"><title>用户管理</title><link rel="stylesheet" href="../styles/main.css"></head><body><div class="page-placeholder"><div class="ph-icon">⚙</div><div class="ph-title">用户管理</div><div class="ph-desc">系统用户的增删改查与状态管理</div></div></body></html>
\ No newline at end of file
// cache-bust 20260907 - 禁止浏览器缓存
/* 植物检疫检测隔离中心智能管理平台 - 主脚本 */
// ========== 菜单数据 ==========
const MENUS = [
{ id: 'home', name: '首页', icon: '🏠', url: 'pages/home.html' },
{
id: 'entrust', name: '委托书管理', icon: '📑',
children: [
{ id: 'entrust-reg', name: '委托书登记', url: 'pages/entrust-reg.html' },
{ id: 'entrust-list', name: '委托书列表', url: 'pages/entrust-list.html' },
{ id: 'entrust-audit', name: '委托书审核', url: 'pages/entrust-audit.html' },
{ id: 'entrust-archive', name: '委托书归档', url: 'pages/entrust-archive.html' }
]
},
{
id: 'sample', name: '样本管理', icon: '🧪',
children: [
{ id: 'sample-accept', name: '样本接收登记', url: 'pages/sample-register.html' },
{ id: 'sample-storage', name: '样本存放管理', url: 'pages/sample-storage.html' },
{ id: 'sample-out', name: '样本出库登记', url: 'pages/sample-out.html' }
]
},
{
id: 'inspect', name: '样本检疫', icon: '🔬',
children: [
{ id: 'inspect-task', name: '检疫任务分配', url: 'pages/inspect-task.html' },
{ id: 'inspect-record', name: '检疫记录登记', url: 'pages/inspect-record.html' },
{ id: 'inspect-result', name: '检疫结果录入', url: 'pages/inspect-result.html' },
{ id: 'inspect-report', name: '检疫报告生成', url: 'pages/inspect-report.html' }
]
},
{
id: 'isolate', name: '隔离检疫', icon: '🏥',
children: [
{ id: 'isolate-plan', name: '隔离计划制定', url: 'pages/isolate-plan.html' },
{ id: 'isolate-room', name: '隔离室分配', url: 'pages/isolate-room.html' },
{ id: 'isolate-monitor', name: '隔离期监测', url: 'pages/isolate-monitor.html' },
{ id: 'isolate-finish', name: '隔离期满评估', url: 'pages/isolate-finish.html' }
]
},
{
id: 'disposal', name: '除害处置', icon: '🔥',
children: [
{ id: 'disposal-plan', name: '处置方案审批', url: 'pages/disposal-plan.html' },
{ id: 'disposal-exec', name: '处置执行登记', url: 'pages/disposal-exec.html' },
{ id: 'disposal-record', name: '处置档案管理', url: 'pages/disposal-record.html' }
]
},
{
id: 'analysis', name: '数据分析', icon: '📊',
children: [
{ id: 'analysis-inspect', name: '检疫数据统计', url: 'pages/analysis-inspect.html' },
{ id: 'analysis-pest', name: '有害生物分析', url: 'pages/analysis-pest.html' },
{ id: 'analysis-trend', name: '趋势分析', url: 'pages/analysis-trend.html' }
]
},
{
id: 'risk', name: '风险预警', icon: '⚠️',
children: [
{ id: 'risk-rule', name: '预警规则配置', url: 'pages/risk-rule.html' },
{ id: 'risk-warn', name: '实时预警', url: 'pages/risk-warn.html' },
{ id: 'risk-handle', name: '预警处置跟踪', url: 'pages/risk-handle.html' }
]
},
{
id: 'report', name: '报告管理', icon: '📄',
children: [
{ id: 'report-tpl', name: '报告模板管理', url: 'pages/report-tpl.html' },
{ id: 'report-fill', name: '报告填报', url: 'pages/report-fill.html' },
{ id: 'report-archive', name: '报告归档', url: 'pages/report-archive.html' }
]
},
{
id: 'cert', name: '审核发证', icon: '🪪',
children: [
{ id: 'cert-apply', name: '证书申请受理', url: 'pages/cert-apply.html' },
{ id: 'cert-audit', name: '审核流程管理', url: 'pages/cert-audit.html' },
{ id: 'cert-issue', name: '证书签发', url: 'pages/cert-issue.html' },
{ id: 'cert-query', name: '证书查询', url: 'pages/cert-query.html' }
]
},
{
id: 'setting', name: '系统设置', icon: '⚙',
children: [
{ id: 'set-user', name: '用户管理', url: 'pages/set-user.html' },
{ id: 'set-role', name: '角色权限', url: 'pages/set-role.html' },
{ id: 'set-dict', name: '数据字典', url: 'pages/set-dict.html' },
{ id: 'set-log', name: '操作日志', url: 'pages/set-log.html' }
]
}
];
// ========== 渲染左侧菜单 ==========
function renderSidebar() {
const list = document.getElementById('menuList');
if (!list) return;
list.innerHTML = MENUS.map(m => {
if (m.children && m.children.length) {
return `
<div class="menu-group">
<div class="menu-item has-sub" data-id="${m.id}">
<span class="menu-icon">${m.icon}</span>
<span class="menu-text">${m.name}</span>
<span class="menu-arrow">▸</span>
</div>
<div class="sub-menu">
${m.children.map(c => `<a class="sub-item" data-id="${c.id}" data-parent="${m.id}" data-name="${c.name}" data-url="${c.url}">${c.name}</a>`).join('')}
</div>
</div>`;
}
return `
<div class="menu-group">
<div class="menu-item" data-id="${m.id}" data-name="${m.name}" data-url="${m.url}">
<span class="menu-icon">${m.icon}</span>
<span>${m.name}</span>
</div>
</div>`;
}).join('');
// 绑定菜单交互
list.querySelectorAll('.menu-item').forEach(el => {
el.addEventListener('click', e => {
const group = el.parentElement;
const sub = group.querySelector('.sub-menu');
if (sub) {
// 切换展开
const expanded = sub.classList.toggle('expanded');
el.classList.toggle('expanded', expanded);
} else {
// 直接跳转
const id = el.dataset.id;
const name = el.dataset.name;
const url = el.dataset.url;
if (url) {
openTab(id, name, url);
setActive(el);
}
}
});
});
list.querySelectorAll('.sub-item').forEach(el => {
el.addEventListener('click', e => {
e.stopPropagation();
const id = el.dataset.id;
const name = el.dataset.name;
const url = el.dataset.url;
openTab(id, name, url);
list.querySelectorAll('.sub-item').forEach(s => s.classList.remove('active'));
list.querySelectorAll('.menu-item').forEach(s => s.classList.remove('active'));
el.classList.add('active');
const parent = el.closest('.menu-group').querySelector('.menu-item');
if (parent) parent.classList.add('active');
});
});
}
// ========== 顶部导航 ==========
function bindTopNav() {
document.querySelectorAll('.app-nav .nav-item').forEach(el => {
el.addEventListener('click', () => {
document.querySelectorAll('.app-nav .nav-item').forEach(s => s.classList.remove('active'));
el.classList.add('active');
});
});
}
// ========== Tab 管理 ==========
const tabs = []; // {id, name, url}
function openTab(id, name, url) {
// 已存在则激活
const exist = tabs.find(t => t.id === id);
if (exist) {
activateTab(id);
return;
}
tabs.push({ id, name, url });
renderTabs();
activateTab(id);
}
function activateTab(id) {
document.querySelectorAll('.tab').forEach(t => t.classList.toggle('active', t.dataset.id === id));
// 加载对应页面
const tab = tabs.find(t => t.id === id);
if (!tab) return;
// 更新面包屑
updateBreadcrumb(tab.name);
// 加载内容
loadPage(tab.url, tab.id);
}
function closeTab(id) {
const idx = tabs.findIndex(t => t.id === id);
if (idx < 0) return;
tabs.splice(idx, 1);
renderTabs();
if (tabs.length) {
const next = tabs[Math.min(idx, tabs.length - 1)];
activateTab(next.id);
} else {
document.getElementById('contentBody').innerHTML = '';
document.getElementById('breadcrumb').innerHTML = '';
}
}
function renderTabs() {
const bar = document.getElementById('tabsBar');
if (!bar) return;
bar.innerHTML = tabs.map(t => `
<div class="tab" data-id="${t.id}">
<span>${t.name}</span>
<span class="tab-close" data-close="${t.id}">×</span>
</div>
`).join('');
bar.querySelectorAll('.tab').forEach(el => {
el.addEventListener('click', e => {
if (e.target.dataset.close) {
closeTab(e.target.dataset.close);
return;
}
activateTab(el.dataset.id);
});
});
}
function updateBreadcrumb(name) {
const bc = document.getElementById('breadcrumb');
if (!bc) return;
bc.innerHTML = `当前位置:植物检疫检测隔离中心智能管理平台 <span class="sep">›</span> ${name}`;
}
// 复用单一 iframe,避免反复销毁/创建导致的资源请求中止(net::ERR_ABORTED)
let __pageFrame = null;
function loadPage(url, id) {
const body = document.getElementById('contentBody');
if (!body) return;
if (!__pageFrame) {
__pageFrame = document.createElement('iframe');
__pageFrame.id = 'pageFrame';
__pageFrame.style.cssText = 'width:100%;border:0;display:block;overflow:hidden;';
body.appendChild(__pageFrame);
}
// iframe 始终填满 content-body,禁止内部滚动
// 内容由各页面自行控制高度,content-body 本身 overflow:hidden 保证不出滚动条
const fit = () => {
if (__pageFrame && __pageFrame.contentWindow) {
try {
const doc = __pageFrame.contentWindow.document;
const h = Math.max(doc.documentElement.scrollHeight, doc.body ? doc.body.scrollHeight : 0);
if (h) __pageFrame.style.height = h + 'px';
} catch (e) {}
}
};
__pageFrame.addEventListener('load', fit);
// 多次重试以应对异步资源加载
[50, 150, 400, 800, 1500].forEach(t => setTimeout(fit, t));
// 仅更新 src,浏览器会复用同源 frame,不会中止未完成的资源请求
if (__pageFrame.src && __pageFrame.src.split("?")[0] === url) return;
// 防止子页面被浏览器缓存,追加时间戳
const buster = window.__cacheBuster || ("?v=" + Date.now());
__pageFrame.src = url + (url.indexOf("?") >= 0 ? "&" : "?") + buster.replace(/^\?/, "");
// ========== 初始化 ==========
document.addEventListener('DOMContentLoaded', () => {
renderSidebar();
bindTopNav();
// 默认打开首页
openTab('home', '首页', 'pages/home.html');
// 默认展开第一个有子菜单的
const firstGroup = document.querySelector('.menu-group');
if (firstGroup) {
const sub = firstGroup.querySelector('.sub-menu');
if (sub) {
sub.classList.add('expanded');
firstGroup.querySelector('.menu-item').classList.add('expanded');
}
}
});
<template>
<div class="app">
<AppHeader />
<div class="app-body">
<aside class="sidebar">
<Sidebar />
</aside>
<main class="content">
<div class="breadcrumb">{{ breadcrumb }}</div>
<TabsBar />
<div class="content-body">
<router-view v-slot="{ Component }">
<component :is="Component" />
</router-view>
</div>
</main>
</div>
</div>
</template>
<script setup>
import { computed } from "vue";
import { useRoute } from "vue-router";
import AppHeader from "@/components/AppHeader.vue";
import Sidebar from "@/components/Sidebar.vue";
import TabsBar from "@/components/TabsBar.vue";
const route = useRoute();
const breadcrumb = computed(() => "当前位置:" + (route.meta?.breadcrumb || "植物检疫检测隔离中心智能管理平台"));
</script>
\ No newline at end of file
/* cache-bust 20260907 - 禁止浏览器缓存 */
/* ========== 基础重置 ========== */
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { min-height: 100%; }
body {
font-family: "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif;
font-size: 16px;
color: #333;
background: #f5f6f8;
-webkit-font-smoothing: antialiased;
}
a { color: inherit; text-decoration: none; }
ul, ol { list-style: none; }
input, select, textarea, button { font-family: inherit; font-size: inherit; outline: none; }
/* ========== 主布局 ========== */
.app {
display: flex;
flex-direction: column;
min-height: 100vh;
min-width: 1024px;
}
/* ========== 顶部导航 ========== */
.app-header {
height: 40px;
background: #d71e1e;
color: #fff;
display: flex;
align-items: center;
padding: 0;
flex-shrink: 0;
position: relative;
z-index: 100;
}
.app-logo {
width: 380px;
height: 100%;
display: flex;
align-items: center;
padding: 0 20px;
font-size: 18px;
font-weight: bold;
letter-spacing: 1px;
background: #c01a1a;
border-right: 1px solid #b51616;
}
.app-logo .logo-icon {
width: 22px; height: 22px;
margin-right: 8px;
background: #fff;
color: #d71e1e;
border-radius: 4px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 13px;
font-weight: bold;
}
.app-nav { display: flex; align-items: center; height: 100%; flex: 1; }
.app-nav .nav-item {
padding: 0 22px;
height: 100%;
display: flex;
align-items: center;
cursor: pointer;
font-size: 14px;
transition: background 0.2s;
user-select: none;
}
.app-nav .nav-item:hover { background: rgba(255,255,255,0.1); }
.app-nav .nav-item.active { background: #fff; color: #d71e1e; font-weight: bold; }
.app-user {
margin-left: auto;
display: flex;
align-items: center;
padding-right: 20px;
height: 100%;
font-size: 14px;
}
.app-user .user-avatar {
width: 24px; height: 24px;
border-radius: 50%;
background: #fff;
color: #d71e1e;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 12px;
margin-right: 6px;
}
.app-user .user-name { margin-right: 12px; }
.app-user .user-action { padding: 0 8px; cursor: pointer; opacity: 0.9; }
.app-user .user-action:hover { opacity: 1; }
/* ========== 主体区 ========== */
.app-body { flex: 1; display: flex; min-height: 0; }
/* ========== 左侧菜单 ========== */
.sidebar {
width: 200px;
background: #fff;
border-right: 1px solid #e6e6e6;
flex-shrink: 0;
position: relative;
}
.sidebar::-webkit-scrollbar { width: 4px; }
.sidebar::-webkit-scrollbar-thumb { background: #ccc; border-radius: 2px; }
.menu-list { padding: 8px 0 32px; }
.menu-item {
display: flex;
align-items: center;
padding: 0 16px;
height: 40px;
cursor: pointer;
color: #444;
font-size: 16px;
position: relative;
transition: background 0.15s, color 0.15s;
user-select: none;
}
.menu-item:hover { background: #fef2f2; color: #d71e1e; }
.menu-item .menu-icon {
width: 16px; height: 16px;
margin-right: 10px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: #999;
}
.menu-item .menu-text { flex: 1; }
.menu-item .menu-arrow {
margin-left: auto;
font-size: 12px;
color: #bbb;
transition: transform 0.2s;
display: inline-block;
}
.menu-item.has-sub.expanded .menu-arrow { transform: rotate(90deg); color: #d71e1e; }
.menu-item.active .menu-arrow { color: #fff; }
.menu-item.active { background: #d71e1e; color: #fff; font-weight: bold; }
.menu-item.active .menu-icon { color: #fff; }
.menu-item.active::before {
content: "";
position: absolute;
left: 0; top: 0; bottom: 0;
width: 3px;
background: #fff;
}
.sub-menu { background: #fafafa; overflow: hidden; max-height: 0; transition: max-height 0.25s ease; }
.sub-menu.expanded { max-height: 600px; }
.sub-item {
display: block;
padding: 10px 16px 10px 42px;
font-size: 15px;
color: #555;
cursor: pointer;
position: relative;
transition: background 0.15s, color 0.15s;
}
.sub-item:hover { background: #fef2f2; color: #d71e1e; }
.sub-item.active { background: #fef2f2; color: #d71e1e; font-weight: bold; }
.sub-item.active::before {
content: "";
position: absolute;
left: 28px; 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; }
.breadcrumb {
height: 32px;
padding: 0 16px;
display: flex;
align-items: center;
font-size: 14px;
color: #666;
border-bottom: 1px solid #eee;
background: #fafafa;
flex-shrink: 0;
}
.breadcrumb .sep { margin: 0 6px; color: #ccc; }
.breadcrumb .current { color: #d71e1e; }
.tabs-bar {
height: 36px;
display: flex;
align-items: flex-end;
padding: 0 8px;
border-bottom: 2px solid #d71e1e;
background: #fff;
flex-shrink: 0;
}
.tab {
display: flex;
align-items: center;
padding: 0 14px;
height: 32px;
background: #f5f5f5;
border: 1px solid #e0e0e0;
border-bottom: none;
margin-right: 2px;
font-size: 14px;
color: #555;
cursor: pointer;
position: relative;
border-radius: 3px 3px 0 0;
transition: all 0.15s;
}
.tab:hover { color: #d71e1e; }
.tab.active { background: #d71e1e; color: #fff; border-color: #d71e1e; }
.tab .tab-close { margin-left: 8px; font-size: 12px; opacity: 0.7; }
.tab .tab-close:hover { opacity: 1; }
.content-body { flex: 1; overflow: visible; position: relative; background: #fff; padding: 0; min-height: 0; }
.content-body::-webkit-scrollbar { width: 0; height: 0; }
#pageFrame { width: 100%; border: 0; display: block; overflow: hidden; transform-origin: top left; }
.content-body::-webkit-scrollbar-thumb { background: #d4d4d4; border-radius: 3px; }
.content-body::-webkit-scrollbar-track { background: #f5f5f5; }
/* ========== 首页样式 ========== */
.page-home { padding: 16px; }
.action-bar { display: flex; align-items: center; padding: 0 0 12px 0; gap: 8px; }
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
height: 30px;
padding: 0 16px;
border: 1px solid #d71e1e;
background: #d71e1e;
color: #fff;
font-size: 14px;
border-radius: 3px;
cursor: pointer;
transition: opacity 0.15s, background 0.15s;
user-select: none;
}
.btn:hover { opacity: 0.9; }
.btn-default { background: #fff; color: #555; border-color: #d4d4d4; }
.btn-default:hover { color: #d71e1e; border-color: #d71e1e; opacity: 1; }
.btn-link { background: transparent; color: #d71e1e; border: none; padding: 0 6px; }
.btn-link:hover { text-decoration: underline; opacity: 1; }
.stat-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 12px; }
.stat-card {
background: #fff;
border: 1px solid #f0f0f0;
border-radius: 4px;
padding: 16px 20px;
display: flex;
align-items: center;
position: relative;
overflow: hidden;
}
.stat-card::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: #d71e1e; }
.stat-icon {
width: 48px; height: 48px;
border-radius: 50%;
background: #fef2f2;
color: #d71e1e;
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
margin-right: 14px;
flex-shrink: 0;
}
.stat-info { flex: 1; }
.stat-label { font-size: 14px; color: #888; margin-bottom: 4px; }
.stat-value { font-size: 24px; font-weight: bold; color: #d71e1e; line-height: 1.2; }
.stat-extra { font-size: 13px; color: #999; margin-top: 2px; }
.panel { background: #fff; border: 1px solid #f0f0f0; border-radius: 4px; margin-bottom: 12px; }
.panel-header {
display: flex;
align-items: center;
padding: 10px 16px;
border-bottom: 1px solid #f0f0f0;
background: #fafafa;
border-radius: 4px 4px 0 0;
}
.panel-title { font-size: 16px; font-weight: bold; color: #333; position: relative; padding-left: 10px; }
.panel-title::before {
content: "";
position: absolute;
left: 0; top: 50%;
transform: translateY(-50%);
width: 3px; height: 14px;
background: #d71e1e;
border-radius: 1px;
}
.panel-body { padding: 16px; }
.panel-tools { margin-left: auto; }
.panel-tools .btn-link { font-size: 12px; }
.row-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.row-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; }
.chart-box {
height: 280px;
background: #fafafa;
border: 1px dashed #e0e0e0;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
color: #bbb;
font-size: 13px;
}
.simple-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.simple-table th, .simple-table td { padding: 8px 12px; border-bottom: 1px solid #f0f0f0; text-align: left; }
.simple-table th { background: #fafafa; color: #555; font-weight: normal; font-size: 12px; }
.simple-table tr:hover td { background: #fef9f9; }
.page-placeholder { padding: 60px 24px; text-align: center; color: #999; }
.page-placeholder .ph-icon { font-size: 56px; color: #ddd; margin-bottom: 12px; }
.page-placeholder .ph-title { font-size: 16px; color: #555; margin-bottom: 6px; }
.page-placeholder .ph-desc { font-size: 13px; color: #aaa; }
.form-section { padding: 16px 24px; }
.form-title { text-align: center; font-size: 18px; font-weight: bold; margin: 8px 0 24px; color: #222; }
.form-block { margin-bottom: 18px; }
.form-block-title {
font-size: 14px;
font-weight: bold;
color: #333;
margin-bottom: 12px;
padding-left: 8px;
border-left: 3px solid #d71e1e;
}
.form-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px 24px; margin-bottom: 6px; }
.form-row.cols-2 { grid-template-columns: repeat(2, 1fr); }
.form-row.cols-1 { grid-template-columns: 1fr; }
.form-field { display: flex; align-items: center; }
.form-field .label { width: 100px; text-align: right; color: #555; font-size: 13px; margin-right: 8px; flex-shrink: 0; }
.form-field .label .req { color: #d71e1e; margin-right: 2px; }
.form-field .ctrl { flex: 1; }
.form-field input, .form-field select, .form-field textarea {
width: 100%;
height: 30px;
padding: 0 8px;
border: 1px solid #d4d4d4;
border-radius: 3px;
font-size: 14px;
background: #fff;
transition: border-color 0.15s;
}
.form-field input:focus, .form-field select:focus, .form-field textarea:focus { border-color: #d71e1e; }
.form-field textarea { height: 60px; padding: 6px 8px; resize: vertical; }
.form-field input::placeholder, .form-field select::placeholder { color: #bbb; }
/* ========== 滚动条美化(隐藏但保留可滚动能力) ========== */
* { scrollbar-width: thin; scrollbar-color: #d4d4d4 transparent; }
*::-webkit-scrollbar { width: 4px; height: 4px; }
*::-webkit-scrollbar-thumb { background: #d4d4d4; border-radius: 2px; }
*::-webkit-scrollbar-track { background: transparent; }
<template>
<header class="app-header">
<div class="app-logo">
<span class="logo-icon">植</span>
<span class="logo-text">植物检疫检测隔离中心智能管理平台</span>
</div>
<div class="app-user">
<span class="user-avatar">管</span>
<span class="user-name">管理员</span>
<span class="user-action">消息</span>
<span class="user-action">设置</span>
<span class="user-action">退出</span>
</div>
</header>
</template>
<script setup></script>
\ No newline at end of file
<template>
<div class="dp-wrap">
<span class="dp-icon" @click="open">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="#bbb" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="5" width="18" height="16" rx="2" />
<line x1="3" y1="10" x2="21" y2="10" />
<line x1="8" y1="3" x2="8" y2="7" />
<line x1="16" y1="3" x2="16" y2="7" />
</svg>
</span>
<input
class="dp-input"
:value="modelValue"
readonly
:placeholder="placeholder"
@click="open"
/>
<div v-if="visible" class="dp-panel" @click.stop>
<div class="dp-header">
<button type="button" class="dp-nav" @click="changeYear(-1)">«</button>
<button type="button" class="dp-nav" @click="changeMonth(-1)">‹</button>
<span class="dp-title">{{ year }} 年 {{ month + 1 }} 月</span>
<button type="button" class="dp-nav" @click="changeMonth(1)">›</button>
<button type="button" class="dp-nav" @click="changeYear(1)">»</button>
</div>
<div class="dp-weekdays">
<span v-for="w in weekdays" :key="w">{{ w }}</span>
</div>
<div class="dp-grid">
<span
v-for="cell in cells"
:key="cell.key"
class="dp-cell"
:class="{
'is-other': !cell.inMonth,
'is-today': cell.isToday,
'is-selected': cell.isSelected
}"
@click="pick(cell)"
>{{ cell.day }}</span>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onBeforeUnmount } from "vue";
const props = defineProps({ modelValue: String, placeholder: { type: String, default: "请选择日期" } });
const emit = defineEmits(["update:modelValue"]);
const visible = ref(false);
const today = new Date();
const year = ref(today.getFullYear());
const month = ref(today.getMonth());
const weekdays = ["日", "一", "二", "三", "四", "五", "六"];
const fmt = (y, m, d) => `${y}-${String(m + 1).padStart(2, "0")}-${String(d).padStart(2, "0")}`;
const cells = computed(() => {
const first = new Date(year.value, month.value, 1);
const startDay = first.getDay();
const daysInMonth = new Date(year.value, month.value + 1, 0).getDate();
const daysInPrev = new Date(year.value, month.value, 0).getDate();
const arr = [];
for (let i = startDay - 1; i >= 0; i--) {
const d = daysInPrev - i;
const dt = new Date(year.value, month.value - 1, d);
arr.push({ key: `p${d}`, day: d, inMonth: false, date: dt, isToday: false, isSelected: false });
}
for (let d = 1; d <= daysInMonth; d++) {
const dt = new Date(year.value, month.value, d);
const iso = fmt(year.value, month.value, d);
arr.push({
key: iso,
day: d,
inMonth: true,
date: dt,
isToday: dt.toDateString() === today.toDateString(),
isSelected: props.modelValue === iso
});
}
while (arr.length % 7 !== 0) {
const d = arr.length - startDay - daysInMonth + 1;
const dt = new Date(year.value, month.value + 1, d);
arr.push({ key: `n${d}`, day: d, inMonth: false, date: dt, isToday: false, isSelected: false });
}
return arr;
});
const open = () => { visible.value = true; };
const close = () => { visible.value = false; };
const changeMonth = (delta) => {
let m = month.value + delta;
let y = year.value;
if (m < 0) { m = 11; y--; }
if (m > 11) { m = 0; y++; }
month.value = m;
year.value = y;
};
const changeYear = (delta) => { year.value += delta; };
const pick = (cell) => {
const y = cell.date.getFullYear();
const m = cell.date.getMonth();
const d = cell.date.getDate();
emit("update:modelValue", fmt(y, m, d));
visible.value = false;
};
const onDocClick = (e) => {
if (!e.target.closest(".dp-wrap")) close();
};
onMounted(() => document.addEventListener("click", onDocClick));
onBeforeUnmount(() => document.removeEventListener("click", onDocClick));
</script>
<style scoped>
.dp-wrap { position: relative; display: flex; align-items: center; width: 100%; height: 32px; border: 1px solid #d4d4d4; border-radius: 3px; background: #fff; cursor: pointer; }
.dp-wrap:hover { border-color: #b8b8b8; }
.dp-wrap:focus-within { border-color: #d71e1e; }
.dp-icon { display: flex; align-items: center; padding: 0 4px 0 6px; flex-shrink: 0; cursor: pointer; }
.dp-input {
flex: 1; min-width: 0;
height: 30px;
border: none; outline: none;
background: transparent;
font-size: 14px;
color: #555;
padding: 0 4px 0 0;
cursor: pointer;
}
.dp-input::placeholder { color: #999; }
.dp-panel {
position: absolute;
top: 38px; left: 0;
width: 240px;
background: #fff;
border: 1px solid #d4d4d4;
border-radius: 4px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
z-index: 1000;
padding: 8px;
user-select: none;
}
.dp-header { display: flex; align-items: center; justify-content: space-between; padding: 4px 0; }
.dp-nav {
width: 24px; height: 24px;
border: none; background: transparent;
cursor: pointer; color: #555;
font-size: 14px;
}
.dp-nav:hover { color: #d71e1e; }
.dp-title { font-size: 14px; font-weight: bold; }
.dp-weekdays { display: grid; grid-template-columns: repeat(7, 1fr); padding: 6px 0; color: #999; font-size: 12px; }
.dp-weekdays span { text-align: center; }
.dp-grid { display: grid; grid-template-columns: repeat(7, 1fr); }
.dp-cell {
height: 28px;
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
cursor: pointer;
border-radius: 3px;
}
.dp-cell:hover { background: #fef2f2; color: #d71e1e; }
.dp-cell.is-other { color: #ccc; }
.dp-cell.is-today { color: #d71e1e; font-weight: bold; }
.dp-cell.is-selected { background: #d71e1e; color: #fff; font-weight: bold; }
</style>
\ No newline at end of file
<template>
<div class="page-placeholder">
<el-icon class="ph-icon"><component :is="icon" /></el-icon>
<div class="ph-title">{{ title }}</div>
<div class="ph-desc">{{ desc }}</div>
</div>
</template>
<script setup>
defineProps({
title: { type: String, default: "模块开发中" },
desc: { type: String, default: "该模块页面正在搭建中,敬请期待。" },
icon: { type: String, default: "Box" }
});
</script>
\ No newline at end of file
<template>
<div class="menu-list">
<template v-for="item in menuConfig" :key="item.id">
<div
v-if="!item.children"
class="menu-item"
:class="{ active: isActive(item.path) }"
@click="go(item.path)"
>
<el-icon class="menu-icon"><component :is="item.icon" /></el-icon>
<span class="menu-text">{{ item.name }}</span>
</div>
<template v-else>
<div
class="menu-item has-sub"
:class="{ expanded: isExpanded(item), active: isChildActive(item) }"
@click="toggle(item)"
>
<el-icon class="menu-icon"><component :is="item.icon" /></el-icon>
<span class="menu-text">{{ item.name }}</span>
<span class="menu-arrow">▸</span>
</div>
<div class="sub-menu" :class="{ expanded: isExpanded(item) }">
<div
v-for="sub in item.children"
:key="sub.id"
class="sub-item"
:class="{ active: isActive(sub.path) }"
@click="go(sub.path)"
>{{ sub.name }}</div>
</div>
</template>
</template>
</div>
</template>
<script setup>
import { ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { menuConfig } from "@/data/menu";
const route = useRoute();
const router = useRouter();
const expanded = ref([]);
const isActive = (path) => route.path === path;
const isExpanded = (item) => expanded.value.includes(item.id);
const isChildActive = (item) => (item.children || []).some((c) => route.path === c.path);
const toggle = (item) => {
const idx = expanded.value.indexOf(item.id);
if (idx >= 0) expanded.value.splice(idx, 1);
else expanded.value.push(item.id);
};
const go = (path) => {
router.push(path);
};
</script>
\ No newline at end of file
<template>
<div class="tabs-bar">
<div
v-for="t in tabs.tabs"
:key="t.name"
class="tab"
:class="{ active: tabs.active === t.name }"
@click="onClick(t)"
>
<span>{{ t.title }}</span>
<span v-if="t.closable" class="tab-close" @click.stop="onClose(t)">×</span>
</div>
</div>
</template>
<script setup>
import { useTabsStore } from "@/store/tabs";
import { useRouter } from "vue-router";
const tabs = useTabsStore();
const router = useRouter();
const onClick = (t) => {
tabs.setActive(t.name);
router.push(t.path);
};
const onClose = (t) => {
tabs.closeTab(t.name);
};
</script>
\ No newline at end of file
// 委托书列表数据
// 检疫单证号 / 申报单位 / 植物名称 / 申报时间 / 受理日期 / 受理机构
export const entrustListData = [
{ id: 1, cert: "6204122022007095", unit: "甘肃富赢种子高新科技有限责任公司", plant: "翠菊", declareDate: "2022-03-09", acceptDate: "2022-04-26", org: "甘肃省植保植检站", status: "待田间调查" },
{ id: 2, cert: "6205022022007347", unit: "甘肃富赢种子高新科技有限责任公司", plant: "莴苣", declareDate: "2022-02-21", acceptDate: "2022-05-09", org: "甘肃省植保植检站", status: "待田间调查" },
{ id: 3, cert: "6205022022007269", unit: "甘肃富赢种子高新科技有限责任公司", plant: "青花菜(西兰花)", declareDate: "2022-02-21", acceptDate: "2022-05-09", org: "甘肃省植保植检站", status: "待田间调查" },
{ id: 4, cert: "6204122022007087", unit: "甘肃富赢种子高新科技有限责任公司", plant: "青花菜(西兰花)", declareDate: "2022-02-21", acceptDate: "2022-05-09", org: "甘肃省植保植检站", status: "待田间调查" },
{ id: 5, cert: "6202442022007001", unit: "甘肃富赢种子高新科技有限责任公司", plant: "胡萝卜", declareDate: "2022-02-10", acceptDate: "2022-05-09", org: "甘肃省植保植检站", status: "待田间调查" },
{ id: 6, cert: "6203122022007067", unit: "甘肃杰尼尔种子有限公司", plant: "辣椒", declareDate: "2022-01-28", acceptDate: "2022-05-19", org: "甘肃省植保植检站", status: "待田间调查" },
{ id: 7, cert: "6204122022007065", unit: "甘肃富赢种子高新科技有限责任公司", plant: "翠菊", declareDate: "2022-01-27", acceptDate: "2022-05-09", org: "甘肃省植保植检站", status: "待田间调查" },
{ id: 8, cert: "6203072022007093", unit: "酒泉市酒洲种子有限责任公司", plant: "番茄", declareDate: "2022-01-26", acceptDate: "2022-05-07", org: "甘肃省植保植检站", status: "待田间调查" },
{ id: 9, cert: "6203092022007071", unit: "张掖市瑞真种业有限公司", plant: "五色菊", declareDate: "2022-01-20", acceptDate: "2022-06-02", org: "甘肃省植保植检站", status: "待田间调查" },
{ id: 10, cert: "6201162022007023", unit: "张掖市亚太种业有限责任公司", plant: "南瓜", declareDate: "2022-01-17", acceptDate: "2022-05-06", org: "甘肃省植保植检站", status: "待田间调查" }
];
// 左侧导航菜单配置
export const menuConfig = [
{
id: "home", name: "首页", icon: "HomeFilled", path: "/home"
},
{
id: "entrust", name: "委托书管理", icon: "Document", path: "/entrust/list"
},
{
id: "sample", name: "样本管理", icon: "Box",
children: [
{ id: "sample-register", name: "样本登记入库", path: "/sample/register" },
{ id: "sample-storage", name: "样本存放管理", path: "/sample/storage" },
{ id: "sample-out", name: "样本出库登记", path: "/sample/out" }
]
},
{
id: "lab", name: "实验室检测", icon: "MagicStick",
children: [
{ id: "lab-sample-test", name: "样本抽检", path: "/lab/sample-test" },
{ id: "lab-experiment-test", name: "实验检验", path: "/lab/experiment-test" }
]
},
{
id: "isolation", name: "隔离检疫", icon: "Lock", path: "/isolation/quarantine"
},
{
id: "disposal", name: "除害处置", icon: "Delete", path: "/disposal"
},
{
id: "analysis", name: "数据分析", icon: "DataAnalysis", path: "/analysis"
},
{
id: "risk", name: "风险预警", icon: "Warning", path: "/risk"
},
{
id: "report", name: "报告管理", icon: "Notebook", path: "/report"
},
{
id: "audit", name: "审核发证", icon: "Stamp", path: "/audit/cert"
},
{
id: "settings", name: "系统设置", icon: "Setting",
children: [
{ id: "settings-users", name: "用户管理", path: "/settings/users" },
{ id: "settings-roles", name: "角色权限", path: "/settings/roles" },
{ id: "settings-dict", name: "数据字典", path: "/settings/dict" },
{ id: "settings-log", name: "操作日志", path: "/settings/log" }
]
}
];
\ No newline at end of file
// 样本接收登记数据
export const sampleRegisterData = [
{ id: 1, cert: "6204122022007095", unit: "甘肃富赢种子高新科技有限责任公司", plant: "翠菊", variety: "翠菊", origin: "智利", qty: "0.0014 千克", approveQty: "40 千克", storeDate: "2022-03-01", contact: "刘玉堂", phone: "18993607501" },
{ id: 2, cert: "6205022022007347", unit: "甘肃富赢种子高新科技有限责任公司", plant: "莴苣", variety: "生菜", origin: "美国", qty: "0.2950 千克", approveQty: "50 千克", storeDate: "2022-03-20", contact: "刘玉堂", phone: "18993607501" },
{ id: 3, cert: "6205022022007269", unit: "甘肃富赢种子高新科技有限责任公司", plant: "青花菜 (西兰花)", variety: "西蓝花 VBR007", origin: "美国", qty: "2.3920 千克", approveQty: "50 千克", storeDate: "2022-03-20", contact: "刘玉堂", phone: "18993607501" },
{ id: 4, cert: "6204122022007087", unit: "甘肃富赢种子高新科技有限责任公司", plant: "青花菜 (西兰花)", variety: "西蓝花 VBR005", origin: "智利", qty: "0.3700 千克", approveQty: "50 千克", storeDate: "2022-03-20", contact: "刘玉堂", phone: "18993607501" },
{ id: 5, cert: "6202442022007001", unit: "甘肃富赢种子高新科技有限责任公司", plant: "胡萝卜", variety: "胡萝卜 VCR324", origin: "南非", qty: "0.6150 千克", approveQty: "50 千克", storeDate: "2022-03-20", contact: "刘玉堂", phone: "18993607501" },
{ id: 6, cert: "6203122022007067", unit: "甘肃杰尼尔种子有限公司", plant: "辣椒", variety: "80", origin: "西班牙", qty: "10.0 千克", approveQty: "10 千克", storeDate: "2022-04-01", contact: "赵文言", phone: "18993755577" },
{ id: 7, cert: "6204122022007065", unit: "甘肃富赢种子高新科技有限责任公司", plant: "翠菊", variety: "翠菊", origin: "智利", qty: "0.0014 千克", approveQty: "40 千克", storeDate: "2022-02-18", contact: "刘玉堂", phone: "18993607501" },
{ id: 8, cert: "6203072022007093", unit: "酒泉市酒洲种子有限责任公司", plant: "番茄", variety: "番茄", origin: "意大利", qty: "1.9540 千克", approveQty: "50 千克", storeDate: "2022-03-10", contact: "马世锋", phone: "13993708175" },
{ id: 9, cert: "6203092022007071", unit: "张掖市瑞真种业有限公司", plant: "五色菊", variety: "五色菊", origin: "荷兰", qty: "2.0000 千克", approveQty: "5 千克", storeDate: "2022-03-20", contact: "吴多志", phone: "13993662953" },
{ id: 10, cert: "6201162022007023", unit: "张掖市亚太种业有限责任公司", plant: "南瓜", variety: "南瓜", origin: "日本", qty: "3.4000 千克", approveQty: "5 千克", storeDate: "2022-04-02", contact: "姚庆库", phone: "18919369888" }
];
// 样本抽检列表数据(与样本登记入库保持对应,每条 150 克抽检量)
export const sampleTestData = [
{ id: 1, cert: "6204122022007095", unit: "甘肃富赢种子高新科技有限责任公司", plant: "翠菊", variety: "翠菊", origin: "智利", contact: "刘玉堂", phone: "18993607501", approveQty: "40 千克", qty: "150克", testDate: "2022-03-05", testOrg: "甘州区农业技术推广中心", content: "现场对该批种苗进行抽样查验,按 GB/T 3543.2-2009 标准扦取检测样品 150 克,未发现植株携带可见检疫性有害生物症状,实验室镜检未见虫卵及病原孢子。", conclusion: "合格,未检出检疫性有害生物", inspector1: "胡琴", inspector2: "刘洋", inspectorPhone: "0931-8665155", entryDate: "2022-03-05" },
{ id: 2, cert: "6205022022007347", unit: "甘肃富赢种子高新科技有限责任公司", plant: "莴苣", variety: "生菜", origin: "美国", contact: "刘玉堂", phone: "18993607501", approveQty: "50 千克", qty: "150克", testDate: "2022-04-01", testOrg: "甘州区农业技术推广中心", content: "现场对该批种苗进行抽样查验,按 GB/T 3543.2-2009 标准扦取检测样品 150 克,未发现植株携带可见检疫性有害生物症状,实验室镜检未见虫卵及病原孢子。", conclusion: "合格,未检出检疫性有害生物", inspector1: "胡琴", inspector2: "刘洋", inspectorPhone: "0931-8665155", entryDate: "2022-04-01" },
{ id: 3, cert: "6205022022007269", unit: "甘肃富赢种子高新科技有限责任公司", plant: "青花菜 (西兰花)", variety: "西蓝花 VBR007", origin: "美国", contact: "刘玉堂", phone: "18993607501", approveQty: "50 千克", qty: "150克", testDate: "2022-04-01", testOrg: "甘州区农业技术推广中心", content: "现场对该批种苗进行抽样查验,按 GB/T 3543.2-2009 标准扦取检测样品 150 克,未发现植株携带可见检疫性有害生物症状,实验室镜检未见虫卵及病原孢子。", conclusion: "合格,未检出检疫性有害生物", inspector1: "胡琴", inspector2: "刘洋", inspectorPhone: "0931-8665155", entryDate: "2022-04-01" },
{ id: 4, cert: "6204122022007087", unit: "甘肃富赢种子高新科技有限责任公司", plant: "青花菜 (西兰花)", variety: "西蓝花 VBR005", origin: "智利", contact: "刘玉堂", phone: "18993607501", approveQty: "50 千克", qty: "150克", testDate: "2022-04-01", testOrg: "甘州区农业技术推广中心", content: "现场对该批种苗进行抽样查验,按 GB/T 3543.2-2009 标准扦取检测样品 150 克,未发现植株携带可见检疫性有害生物症状,实验室镜检未见虫卵及病原孢子。", conclusion: "合格,未检出检疫性有害生物", inspector1: "胡琴", inspector2: "刘洋", inspectorPhone: "0931-8665155", entryDate: "2022-04-01" },
{ id: 5, cert: "6202442022007001", unit: "甘肃富赢种子高新科技有限责任公司", plant: "胡萝卜", variety: "胡萝卜 VCR324", origin: "南非", contact: "刘玉堂", phone: "18993607501", approveQty: "50 千克", qty: "150克", testDate: "2022-04-05", testOrg: "甘州区农业技术推广中心", content: "现场对该批种苗进行抽样查验,按 GB/T 3543.2-2009 标准扦取检测样品 150 克,未发现植株携带可见检疫性有害生物症状,实验室镜检未见虫卵及病原孢子。", conclusion: "合格,未检出检疫性有害生物", inspector1: "胡琴", inspector2: "刘洋", inspectorPhone: "0931-8665155", entryDate: "2022-04-05" },
{ id: 6, cert: "6203122022007067", unit: "甘肃杰尼尔种子有限公司", plant: "辣椒", variety: "80", origin: "西班牙", contact: "刘玉堂", phone: "18993607501", approveQty: "10 千克", qty: "150克", testDate: "2022-04-15", testOrg: "甘州区农业技术推广中心", content: "现场对该批种苗进行抽样查验,按 GB/T 3543.2-2009 标准扦取检测样品 150 克,未发现植株携带可见检疫性有害生物症状,实验室镜检未见虫卵及病原孢子。", conclusion: "合格,未检出检疫性有害生物", inspector1: "胡琴", inspector2: "刘洋", inspectorPhone: "0931-8665155", entryDate: "2022-04-15" },
{ id: 7, cert: "6204122022007065", unit: "甘肃富赢种子高新科技有限责任公司", plant: "翠菊", variety: "翠菊", origin: "智利", contact: "刘玉堂", phone: "18993607501", approveQty: "40 千克", qty: "150克", testDate: "2022-03-10", testOrg: "甘州区农业技术推广中心", content: "现场对该批种苗进行抽样查验,按 GB/T 3543.2-2009 标准扦取检测样品 150 克,未发现植株携带可见检疫性有害生物症状,实验室镜检未见虫卵及病原孢子。", conclusion: "合格,未检出检疫性有害生物", inspector1: "胡琴", inspector2: "刘洋", inspectorPhone: "0931-8665155", entryDate: "2022-03-10" },
{ id: 8, cert: "6203072022007093", unit: "酒泉市酒洲种子有限责任公司", plant: "番茄", variety: "番茄", origin: "意大利", contact: "刘玉堂", phone: "18993607501", approveQty: "50 千克", qty: "150克", testDate: "2022-03-25", testOrg: "甘州区农业技术推广中心", content: "现场对该批种苗进行抽样查验,按 GB/T 3543.2-2009 标准扦取检测样品 150 克,未发现植株携带可见检疫性有害生物症状,实验室镜检未见虫卵及病原孢子。", conclusion: "合格,未检出检疫性有害生物", inspector1: "胡琴", inspector2: "刘洋", inspectorPhone: "0931-8665155", entryDate: "2022-03-25" },
{ id: 9, cert: "6203092022007071", unit: "张掖市瑞真种业有限公司", plant: "五色菊", variety: "五色菊", origin: "荷兰", contact: "刘玉堂", phone: "18993607501", approveQty: "5 千克", qty: "150克", testDate: "2022-04-05", testOrg: "甘州区农业技术推广中心", content: "现场对该批种苗进行抽样查验,按 GB/T 3543.2-2009 标准扦取检测样品 150 克,未发现植株携带可见检疫性有害生物症状,实验室镜检未见虫卵及病原孢子。", conclusion: "合格,未检出检疫性有害生物", inspector1: "胡琴", inspector2: "刘洋", inspectorPhone: "0931-8665155", entryDate: "2022-04-05" },
{ id: 10, cert: "6201162022007023", unit: "张掖市亚太种业有限责任公司", plant: "南瓜", variety: "南瓜", origin: "日本", contact: "刘玉堂", phone: "18993607501", approveQty: "5 千克", qty: "150克", testDate: "2022-04-10", testOrg: "甘州区农业技术推广中心", content: "现场对该批种苗进行抽样查验,按 GB/T 3543.2-2009 标准扦取检测样品 150 克,未发现植株携带可见检疫性有害生物症状,实验室镜检未见虫卵及病原孢子。", conclusion: "合格,未检出检疫性有害生物", inspector1: "胡琴", inspector2: "刘洋", inspectorPhone: "0931-8665155", entryDate: "2022-04-10" }
];
import { createApp } from "vue";
import { createPinia } from "pinia";
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
import zhCn from "element-plus/dist/locale/zh-cn.mjs";
import * as ElementPlusIconsVue from "@element-plus/icons-vue";
import App from "./App.vue";
import router from "./router";
import "./assets/main.css";
const app = createApp(App);
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component);
}
app.use(createPinia());
app.use(router);
app.use(ElementPlus, { locale: zhCn });
app.mount("#app");
\ No newline at end of file
import { createRouter, createWebHashHistory } from "vue-router";
import { useTabsStore } from "@/store/tabs";
const routes = [
{ path: "/", redirect: "/home" },
{ path: "/home", name: "home", component: () => import("@/views/HomeView.vue"), meta: { title: "首页", breadcrumb: "首页 / 数据看板" } },
{ path: "/entrust/reg", name: "entrust-reg", component: () => import("@/views/entrust/EntrustReg.vue"), meta: { title: "委托书登记", breadcrumb: "委托书管理 / 委托书登记" } },
{ path: "/entrust/list", name: "entrust-list", component: () => import("@/views/entrust/EntrustList.vue"), meta: { title: "委托书管理", breadcrumb: "委托书管理" } },
{ path: "/entrust/audit", name: "entrust-audit", component: () => import("@/views/entrust/EntrustAudit.vue"), meta: { title: "委托书审核", breadcrumb: "委托书管理 / 委托书审核" } },
{ path: "/entrust/archive", name: "entrust-archive", component: () => import("@/views/entrust/EntrustArchive.vue"), meta: { title: "委托书归档", breadcrumb: "委托书管理 / 委托书归档" } },
{ path: "/sample/register", name: "sample-register", component: () => import("@/views/sample/SampleRegister.vue"), meta: { title: "样本登记入库", breadcrumb: "样本管理 / 样本登记入库" } },
{ path: "/sample/storage", name: "sample-storage", component: () => import("@/views/sample/SampleStorage.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/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: "/disposal", name: "disposal", component: () => import("@/views/disposal/Disposal.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: "/report", name: "report", component: () => import("@/views/report/ReportManage.vue"), meta: { title: "报告管理", breadcrumb: "报告管理" } },
{ path: "/audit/cert", name: "audit-cert", component: () => import("@/views/audit/AuditCert.vue"), meta: { title: "审核发证", breadcrumb: "审核发证" } },
{ path: "/settings/users", name: "settings-users", component: () => import("@/views/settings/Users.vue"), meta: { title: "用户管理", breadcrumb: "系统设置 / 用户管理" } },
{ path: "/settings/roles", name: "settings-roles", component: () => import("@/views/settings/Roles.vue"), meta: { title: "角色权限", breadcrumb: "系统设置 / 角色权限" } },
{ path: "/settings/dict", name: "settings-dict", component: () => import("@/views/settings/Dict.vue"), meta: { title: "数据字典", breadcrumb: "系统设置 / 数据字典" } },
{ path: "/settings/log", name: "settings-log", component: () => import("@/views/settings/Log.vue"), meta: { title: "操作日志", breadcrumb: "系统设置 / 操作日志" } }
];
const router = createRouter({
history: createWebHashHistory(),
routes
});
router.afterEach((to) => {
if (to.meta?.title) {
const tabs = useTabsStore();
tabs.openTab({ name: to.name, title: to.meta.title, path: to.fullPath });
}
});
export default router;
\ No newline at end of file
import { defineStore } from "pinia";
export const useTabsStore = defineStore("tabs", {
state: () => ({
tabs: [
{ name: "home", title: "首页", path: "/home", closable: false }
],
active: "home"
}),
actions: {
openTab({ name, title, path }) {
if (!this.tabs.find((t) => t.name === name)) {
this.tabs.push({ name, title, path, closable: true });
}
this.active = name;
},
closeTab(name) {
const idx = this.tabs.findIndex((t) => t.name === name);
if (idx < 0) return;
if (!this.tabs[idx].closable) return;
this.tabs.splice(idx, 1);
if (this.active === name) {
const next = this.tabs[idx] || this.tabs[idx - 1] || this.tabs[0];
if (next) {
this.active = next.name;
this.$router?.push(next.path);
}
}
},
setActive(name) {
this.active = name;
}
}
});
\ No newline at end of file
import { ref, watch } from "vue";
export function usePersistentRef(key, defaultValue) {
const K = "plant_quarantine_" + key;
const load = () => {
try {
const r = localStorage.getItem(K);
if (r) { const p = JSON.parse(r); if (Array.isArray(p) && p.length) return p; }
} catch (e) {}
return JSON.parse(JSON.stringify(defaultValue));
};
const data = ref(load());
watch(data, v => { try { localStorage.setItem(K, JSON.stringify(v)); } catch (e) {} }, { deep: true });
const reset = () => { data.value = JSON.parse(JSON.stringify(defaultValue)); try { localStorage.removeItem(K); } catch (e) {} };
return { data, reset };
}
\ No newline at end of file
<template>
<div class="page-home">
<div class="welcome-bar">
<span>欢迎进入植物检疫检测隔离中心智能管理平台</span>
</div>
<div class="quick-row">
<div class="quick-card" v-for="q in quicks" :key="q.id" @click="goTo(q.path)">
<el-icon class="q-icon"><component :is="q.icon" /></el-icon>
<span>{{ q.name }}</span>
</div>
</div>
<div class="stat-row">
<div class="stat-card" v-for="s in stats" :key="s.label">
<div class="stat-icon"><el-icon><component :is="s.icon" /></el-icon></div>
<div class="stat-info">
<div class="stat-label">{{ s.label }}</div>
<div class="stat-value">{{ s.value }}</div>
<div class="stat-extra">{{ s.extra }}</div>
</div>
</div>
</div>
<div class="row-2">
<div class="panel">
<div class="panel-header"><span class="panel-title">月度检疫统计</span></div>
<div class="panel-body"><div class="chart-box">图表占位 - 月度检疫趋势</div></div>
</div>
<div class="panel">
<div class="panel-header"><span class="panel-title">风险等级分布</span></div>
<div class="panel-body"><div class="chart-box">图表占位 - 风险等级分布</div></div>
</div>
</div>
<div class="row-2">
<div class="panel">
<div class="panel-header"><span class="panel-title">待办事项</span></div>
<div class="panel-body">
<table class="simple-table">
<thead><tr><th>单证号</th><th>类型</th><th>紧急度</th><th>操作</th></tr></thead>
<tbody>
<tr v-for="t in todos" :key="t.id">
<td>{{ t.cert }}</td><td>{{ t.type }}</td><td>{{ t.level }}</td>
<td><a class="op-link">办理</a></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="panel">
<div class="panel-header"><span class="panel-title">风险预警</span></div>
<div class="panel-body">
<table class="simple-table">
<thead><tr><th>预警编号</th><th>类型</th><th>时间</th><th>状态</th></tr></thead>
<tbody>
<tr v-for="w in warnings" :key="w.id">
<td>{{ w.id }}</td><td>{{ w.type }}</td><td>{{ w.time }}</td><td>{{ w.status }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { useRouter } from "vue-router";
const router = useRouter();
const quicks = [
{ id: 1, name: "委托书登记", path: "/entrust/reg", icon: "Document" },
{ id: 2, name: "样本接收", path: "/sample/register", icon: "Box" },
{ id: 3, name: "样本检疫", path: "/sample/quarantine", icon: "MagicStick" },
{ id: 4, name: "数据看板", path: "/analysis", icon: "DataAnalysis" },
{ id: 5, name: "风险预警", path: "/risk", icon: "Warning" },
{ id: 6, name: "审核发证", path: "/audit/cert", icon: "Stamp" }
];
const stats = [
{ label: "今日样本接收", value: "28", extra: "环比 +12%", icon: "Box" },
{ label: "在检样本", value: "142", extra: "较昨日 +6", icon: "MagicStick" },
{ label: "待办任务", value: "37", extra: "其中紧急 5", icon: "Bell" },
{ label: "本月报告", value: "23", extra: "已发证 19", icon: "Notebook" }
];
const todos = [
{ id: 1, cert: "6201122022007218", type: "田间调查", level: "紧急" },
{ id: 2, cert: "6201122022007080", type: "实验室检测", level: "普通" },
{ id: 3, cert: "6201162022007023", type: "结果审核", level: "普通" }
];
const warnings = [
{ id: "W001", type: "异常疫情", time: "2026-09-07 10:21", status: "未处理" },
{ id: "W002", type: "检疫超时", time: "2026-09-06 16:05", status: "处理中" }
];
const goTo = (p) => router.push(p);
</script>
<style scoped>
.welcome-bar {
background: linear-gradient(90deg, #fef2f2, #fff);
border: 1px solid #fde0e0;
border-left: 4px solid #d71e1e;
padding: 16px 20px;
border-radius: 4px;
font-size: 18px;
color: #333;
font-weight: bold;
margin-bottom: 16px;
}
.quick-row {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 12px;
margin-bottom: 16px;
}
.quick-card {
display: flex;
align-items: center;
gap: 10px;
padding: 16px;
background: #fff;
border: 1px solid #f0f0f0;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
}
.quick-card:hover { border-color: #d71e1e; color: #d71e1e; transform: translateY(-2px); }
.q-icon { font-size: 22px; color: #d71e1e; }
.chart-box {
height: 280px;
background: #fafafa;
border: 1px dashed #e0e0e0;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
color: #bbb;
font-size: 14px;
}
.op-link { color: #d71e1e; cursor: pointer; }
</style>
\ No newline at end of file
<template>
<PagePlaceholder title="数据分析" desc="检疫数据多维分析与可视化开发中" icon="DataAnalysis" />
</template>
<script setup>
import PagePlaceholder from "@/components/PagePlaceholder.vue";
</script>
<template>
<PagePlaceholder title="审核发证" desc="检疫结果审核与证书签发开发中" icon="Stamp" />
</template>
<script setup>
import PagePlaceholder from "@/components/PagePlaceholder.vue";
</script>
<template>
<PagePlaceholder title="除害处置" desc="除害处置流程与记录开发中" icon="Delete" />
</template>
<script setup>
import PagePlaceholder from "@/components/PagePlaceholder.vue";
</script>
<template>
<PagePlaceholder title="委托书归档" desc="委托书归档与历史查询开发中" icon="Folder" />
</template>
<script setup>
import PagePlaceholder from "@/components/PagePlaceholder.vue";
</script>
<template>
<PagePlaceholder title="委托书审核" desc="委托书审核工作台开发中" icon="CircleCheck" />
</template>
<script setup>
import PagePlaceholder from "@/components/PagePlaceholder.vue";
</script>
<template>
<div class="page-list">
<div class="filter-box">
<div class="filter-row">
<div class="filter-item">
<label>申报时间:</label>
<DatePicker v-model="filters.declareStart" class="dp-160" placeholder="开始日期" />
<span class="dash">至</span>
<DatePicker v-model="filters.declareEnd" class="dp-160" placeholder="结束日期" />
</div>
<div class="filter-item">
<label>检疫单证号:</label>
<input v-model="filters.cert" placeholder="请输入关键字" class="ipt" />
</div>
<div class="filter-item">
<label>植物名称:</label>
<input v-model="filters.plant" placeholder="请输入关键字" class="ipt" />
</div>
<div class="filter-item">
<label>申报单位:</label>
<input v-model="filters.unit" placeholder="请输入关键字" class="ipt" />
</div>
</div>
<div class="filter-row">
<div class="filter-item">
<label>受理疫情监测时间:</label>
<DatePicker v-model="filters.acceptStart" class="dp-160" placeholder="开始日期" />
<span class="dash">至</span>
<DatePicker v-model="filters.acceptEnd" class="dp-160" placeholder="结束日期" />
</div>
<div class="filter-item filter-radio">
<label class="radio-label" :class="isActive('all')">
<input type="radio" v-model="filters.status" value="all" /> 全部
</label>
<label class="radio-label" :class="isActive('待田间调查')">
<input type="radio" v-model="filters.status" value="待田间调查" /> 待田间调查
</label>
<label class="radio-label" :class="isActive('田调结束')">
<input type="radio" v-model="filters.status" value="田调结束" /> 田调结束
</label>
</div>
<div class="filter-item">
<button class="btn" @click="onQuery">查询</button>
<button class="btn btn-default" @click="onReset">重置</button>
</div>
</div>
</div>
<div class="tbl-wrap">
<table class="simple-table">
<colgroup>
<col style="width:5%" />
<col style="width:13%" />
<col style="width:22%" />
<col style="width:9%" />
<col style="width:11%" />
<col style="width:13%" />
<col style="width:13%" />
<col style="width:6%" />
<col style="width:9%" />
</colgroup>
<thead>
<tr>
<th>序号</th>
<th>检疫单证号</th>
<th>申报单位</th>
<th>植物名称</th>
<th>申报时间</th>
<th>受理日期</th>
<th>受理机构</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="row in pagedData" :key="row.id">
<td>{{ row.id }}</td>
<td>{{ row.cert }}</td>
<td class="col-unit">{{ row.unit }}</td>
<td>{{ row.plant }}</td>
<td>{{ row.declareDate }}</td>
<td>{{ row.acceptDate }}</td>
<td>{{ row.org }}</td>
<td>{{ row.status }}</td>
<td>
<a class="op-link" @click="onView(row)">查看</a>
<a class="op-link" @click="onProcess(row)">办理</a>
</td>
</tr>
<tr v-if="!pagedData.length">
<td colspan="9" class="empty">暂无数据</td>
</tr>
</tbody>
</table>
</div>
<div class="pager">
<span class="total">共 {{ filteredData.length }} 条记录</span>
<button class="pg-btn" :disabled="page === 1" @click="page = 1">首页</button>
<button class="pg-btn" :disabled="page === 1" @click="page--">上一页</button>
<button
v-for="p in pagesToShow"
:key="p"
class="pg-btn"
:class="{ active: p === page }"
@click="page = p"
>{{ p }}</button>
<button class="pg-btn" :disabled="page === totalPages" @click="page++">下一页</button>
<button class="pg-btn" :disabled="page === totalPages" @click="page = totalPages">末页</button>
<span class="pg-jump">
跳转至 <input v-model.number="jumpInput" class="ipt-jump" @keyup.enter="onJump" /> 页
</span>
</div>
<div v-if="modalVisible" class="modal-mask" @click.self="modalVisible = false">
<div class="modal-box">
<div class="modal-top">
<span class="modal-title">流程详情</span>
<span class="modal-close" @click="modalVisible = false">×</span>
</div>
<div class="modal-body">
<div class="modal-toolbar">
<button class="print-btn" @click="onPrint">打印预览</button>
<div class="modal-title-text">国(境)外引进农业种苗入境后疫情监测委托书</div>
</div>
<div class="doc-title-line"></div>
<div class="doc-body">
<div class="doc-unit-line">
<span class="inline-input">甘州区农业技术推广中心</span>:
</div>
<p class="doc-paragraph">为确保国(境)外引进农业种苗检疫安全,防止危险性有害生物随引进种苗传入,按照国家有关国(境)外引进农业种苗检疫管理规定,特委托你单位对第 <span class="inline-input">{{ modalRow.cert }}</span> 号《国(境)外引进农业种苗检疫审批单》批准引进的 <span class="inline-input">{{ modalRow.plant }}</span> 实施疫情监测,有关该批种苗的检疫审批及进口后的种植情况等信息详见《国(境)外引进农业种苗检疫审批单》和《国(境)外引进农业种苗入境后疫情监测申请书》。请你单位按照相关植物疫情监测规程等植物检疫操作规程要求,重点针对种苗引进时禁止携带的有害生物认真开展田间调查和必要的室内检验,并在监测工作结束后及时向我单位提交疫情监测报告。监测期间如发现农业植物疫情,要按照《农业植物疫情报告与发布管理办法》进行报告,并及时开展疫情处置,严防疫情扩散。</p>
</div>
<div class="doc-section">
<div class="doc-section-title">委托单位联系方式</div>
<div class="doc-grid">
<div class="doc-field"><span class="lbl">地址:</span><div class="val">甘肃省张掖市甘州区</div></div>
<div class="doc-field"><span class="lbl">联系人:</span><div class="val">胡琴</div></div>
<div class="doc-field"><span class="lbl">联系电话:</span><div class="val">0931-8665155</div></div>
<div class="doc-field"><span class="lbl">传真:</span><div class="val">0931-8665155</div></div>
<div class="doc-field"><span class="lbl">邮政编码:</span><div class="val">730020</div></div>
</div>
</div>
<div class="doc-section">
<div class="doc-section-title">受理机构信息</div>
<div class="doc-grid">
<div class="doc-field"><span class="lbl">省级植物检疫机构:</span><div class="val">{{ modalRow.org }}</div></div>
<div class="doc-field"><span class="lbl">受理日期:</span><div class="val">{{ modalRow.acceptDate }}</div></div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch } from "vue";
import { ElMessage } from "element-plus";
import DatePicker from "@/components/DatePicker.vue";
import { entrustListData } from "@/data/entrustList";
import { usePersistentRef } from "@/utils/persistentData";
const { data: persistentList } = usePersistentRef("entrust_list", entrustListData);
const defaultFilters = () => ({
declareStart: "",
declareEnd: "",
cert: "",
plant: "",
acceptStart: "",
acceptEnd: "",
unit: "",
status: "待田间调查"
});
// 输入框绑定的草稿(用户随时改,但不立即触发查询)
const filters = ref(defaultFilters());
// 真正用于查询的筛选条件(仅在点击「查询」时由草稿覆盖过来)
const appliedFilters = ref(defaultFilters());
const page = ref(1);
const pageSize = 10;
const jumpInput = ref(1);
const modalVisible = ref(false);
const modalRow = ref({});
const inDateRange = (date, start, end) => {
if (!date) return true;
if (start && date < start) return false;
if (end && date > end) return false;
return true;
};
const filteredData = computed(() => {
return persistentList.value.filter((r) => {
if (appliedFilters.value.status !== "all" && r.status !== appliedFilters.value.status) return false;
if (appliedFilters.value.cert && !r.cert.includes(appliedFilters.value.cert)) return false;
if (appliedFilters.value.plant && !r.plant.includes(appliedFilters.value.plant)) return false;
if (appliedFilters.value.unit && !r.unit.includes(appliedFilters.value.unit)) return false;
if (!inDateRange(r.declareDate, appliedFilters.value.declareStart, appliedFilters.value.declareEnd)) return false;
if (!inDateRange(r.acceptDate, appliedFilters.value.acceptStart, appliedFilters.value.acceptEnd)) return false;
return true;
});
});
const totalPages = computed(() => Math.max(1, Math.ceil(filteredData.value.length / pageSize)));
const pagedData = computed(() => {
const start = (page.value - 1) * pageSize;
return filteredData.value.slice(start, start + pageSize);
});
const pagesToShow = computed(() => {
const total = totalPages.value;
const cur = page.value;
if (total <= 7) return Array.from({ length: total }, (_, i) => i + 1);
const arr = [1, 2, 3, 4, 5];
return arr;
});
watch(filteredData, () => { if (page.value > totalPages.value) page.value = 1; });
const isActive = (s) => filters.value.status === s ? 'radio-label active' : 'radio-label';
const onQuery = () => {
appliedFilters.value = JSON.parse(JSON.stringify(filters.value));
page.value = 1;
ElMessage.success("查询完成,共 " + filteredData.value.length + " 条");
};
const onReset = () => {
filters.value = defaultFilters();
appliedFilters.value = defaultFilters();
page.value = 1;
};
const onJump = () => {
const p = Math.min(Math.max(1, jumpInput.value | 0), totalPages.value);
page.value = p;
};
const onView = (row) => { modalRow.value = row; modalVisible.value = true; };
const onProcess = (row) => { ElMessage.info("进入办理流程:" + row.cert); };
const onPrint = () => { ElMessage.success("打印预览已生成"); };
</script>
<style scoped>
.page-list { padding: 16px 20px; }
.filter-box {
background: #fff;
border: 1px solid #f0f0f0;
border-radius: 4px;
padding: 18px 20px;
margin-bottom: 16px;
}
.filter-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 14px 24px;
margin-bottom: 14px;
}
.filter-row:last-child { margin-bottom: 0; }
.filter-item {
display: flex;
align-items: center;
gap: 8px;
}
.filter-item label { color: #555; font-size: 14px; white-space: nowrap; }
.ipt {
width: 170px;
height: 32px;
padding: 0 8px;
border: 1px solid #d4d4d4;
border-radius: 3px;
font-size: 14px;
}
.ipt:focus { border-color: #d71e1e; }
.dp-160 { width: 150px; }
.dash { color: #999; }
.filter-radio { gap: 20px; }
.radio-label { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; color: #555; font-size: 14px; }
.radio-label input { accent-color: #d71e1e; }
.radio-label.active { color: #d71e1e; font-weight: bold; }
.tbl-wrap {
background: #fff;
border: 1px solid #f0f0f0;
border-radius: 4px;
overflow: visible;
}
.simple-table { width: 100%; border-collapse: collapse; table-layout: fixed; font-size: 14px; }
.simple-table th, .simple-table td { padding: 14px 6px; border-bottom: 1px solid #f0f0f0; text-align: center; line-height: 1.5; vertical-align: middle; }
.simple-table th { background: #fafafa; color: #333; font-weight: bold; font-size: 14px; padding: 14px 6px; }
.simple-table td { font-size: 14px; word-break: break-all; }
.simple-table td.col-unit { text-align: left; padding-left: 12px; }
.simple-table td.col-action { white-space: nowrap; }
.simple-table th { background: #fafafa; color: #333; font-weight: bold; font-size: 15px; }
.simple-table td { color: #333; }
.simple-table tr:hover td { background: #fef9f9; }
.op-link { color: #333; margin: 0 6px; cursor: pointer; }
.op-link:hover { color: #d71e1e; }
.empty { text-align: center; padding: 40px 0; color: #999; }
.pager {
display: flex; align-items: center;
padding: 14px 16px;
gap: 8px;
background: #fff;
border: 1px solid #f0f0f0;
border-top: none;
border-radius: 0 0 4px 4px;
}
.total { color: #666; font-size: 14px; margin-right: auto; }
.pg-btn {
min-width: 30px;
height: 30px;
padding: 0 8px;
border: 1px solid #d4d4d4;
background: #fff;
border-radius: 3px;
cursor: pointer;
font-size: 14px;
color: #555;
}
.pg-btn:hover:not(:disabled) { color: #d71e1e; border-color: #d71e1e; }
.pg-btn.active { background: #d71e1e; color: #fff; border-color: #d71e1e; }
.pg-btn:disabled { cursor: not-allowed; opacity: 0.5; }
.pg-jump { color: #666; font-size: 14px; margin-left: 12px; }
.ipt-jump { width: 50px; height: 28px; padding: 0 4px; border: 1px solid #d4d4d4; border-radius: 3px; text-align: center; }
.modal-mask {
position: fixed; inset: 0;
background: rgba(0,0,0,0.45);
display: flex;
align-items: center;
justify-content: center;
z-index: 500;
}
.modal-box {
background: #fff;
width: 900px;
max-width: 95vw;
max-height: 90vh;
border-radius: 4px;
overflow: hidden;
display: flex;
flex-direction: column;
box-shadow: 0 8px 30px rgba(0,0,0,0.2);
}
.modal-top {
background: #d71e1e;
color: #fff;
padding: 10px 18px;
display: flex;
align-items: center;
justify-content: space-between;
flex-shrink: 0;
}
.modal-title { font-size: 16px; font-weight: bold; }
.modal-close {
width: 22px; height: 22px;
display: flex; align-items: center; justify-content: center;
cursor: pointer; font-size: 18px; color: #fff;
border: 1px solid #fff; border-radius: 2px;
opacity: 0.9;
}
.modal-close:hover { opacity: 1; }
.modal-body { padding: 20px 32px 30px; overflow: auto; flex: 1; }
.modal-body::-webkit-scrollbar { width: 6px; }
.modal-body::-webkit-scrollbar-thumb { background: #d4d4d4; border-radius: 3px; }
.modal-body::-webkit-scrollbar-track { background: #f5f5f5; }
.modal-toolbar { display: flex; align-items: center; margin-bottom: 10px; gap: 20px; }
.print-btn { height: 28px; padding: 0 16px; background: #d71e1e; color: #fff; border: none; border-radius: 3px; cursor: pointer; font-size: 13px; }
.print-btn:hover { opacity: 0.9; }
.modal-title-text { flex: 1; text-align: center; font-size: 18px; font-weight: bold; color: #222; }
.doc-title-line { border-bottom: 1px solid #d71e1e; padding-bottom: 8px; margin-bottom: 18px; }
.doc-body { font-size: 14px; color: #333; line-height: 2.2; text-align: justify; }
.doc-body .inline-input {
display: inline-block; min-width: 110px;
padding: 2px 10px; margin: 0 2px;
border: 1px solid #d4d4d4; background: #fafafa;
border-radius: 3px; color: #333;
text-align: center;
}
.doc-unit-line { font-size: 15px; color: #222; margin: 6px 0 14px; padding-left: 40px; }
.doc-unit-line .inline-input { min-width: 180px; font-size: 15px; padding: 3px 12px; }
.doc-paragraph { text-indent: 2em; line-height: 2.2; font-size: 14px; color: #333; text-align: justify; margin: 0; }
.doc-section { margin-top: 18px; }
.doc-section-title { font-size: 15px; font-weight: bold; color: #222; margin-bottom: 10px; }
.doc-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 32px; }
.doc-field { display: flex; align-items: center; }
.doc-field .lbl { width: 90px; text-align: right; color: #555; font-size: 14px; margin-right: 10px; }
.doc-field .val { flex: 1; padding: 6px 12px; border: 1px solid #d4d4d4; background: #fafafa; border-radius: 3px; font-size: 14px; color: #333; }
</style>
\ No newline at end of file
<template>
<PagePlaceholder title="委托书登记" desc="新增委托书登记流程开发中" icon="Document" />
</template>
<script setup>
import PagePlaceholder from "@/components/PagePlaceholder.vue";
</script>
<template>
<PagePlaceholder title="隔离检疫" desc="隔离区检疫流程开发中" icon="Lock" />
</template>
<script setup>
import PagePlaceholder from "@/components/PagePlaceholder.vue";
</script>
<template>
<div class="page">
<div class="placeholder">该模块待开发</div>
</div>
</template>
<script setup>
</script>
<style scoped>
.page { padding: 16px 20px; }
.placeholder { padding: 60px 0; text-align: center; color: #999; font-size: 14px; background: #fafafa; border-radius: 4px; }
</style>
<template>
<div class="page-list">
<div class="filter-box">
<div class="filter-row">
<div class="filter-item">
<label>抽检日期:</label>
<DatePicker v-model="filters.testStart" class="dp-160" placeholder="开始日期" />
<span class="dash">至</span>
<DatePicker v-model="filters.testEnd" class="dp-160" placeholder="结束日期" />
</div>
<div class="filter-item">
<label>检疫单证号:</label>
<input class="ipt" v-model="filters.cert" placeholder="请输入关键字" />
</div>
<div class="filter-item">
<label>植物名称:</label>
<input class="ipt" v-model="filters.plant" placeholder="请输入关键字" />
</div>
</div>
<div class="filter-row">
<div class="filter-item">
<label>品种名称:</label>
<input class="ipt" v-model="filters.variety" placeholder="请输入关键字" />
</div>
<div class="filter-item">
<label>检验机构:</label>
<input class="ipt" v-model="filters.testOrg" placeholder="请输入关键字" />
</div>
<div class="filter-item">
<button class="btn" @click="onQuery">查询</button>
<button class="btn btn-default" @click="onReset">重置</button>
</div>
<div class="filter-item filter-add">
<button class="btn btn-add" @click="onAdd">+ 新增抽检</button>
</div>
</div>
</div>
<div class="tbl-wrap">
<table class="simple-table">
<colgroup>
<col style="width:5%" />
<col style="width:14%" />
<col style="width:22%" />
<col style="width:9%" />
<col style="width:9%" />
<col style="width:9%" />
<col style="width:11%" />
<col style="width:13%" />
<col style="width:8%" />
</colgroup>
<thead>
<tr>
<th>序号</th>
<th>检疫单证号</th>
<th>申报单位</th>
<th>植物名称</th>
<th>品种名称</th>
<th>抽检数量</th>
<th>抽检日期</th>
<th>检验机构</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(r, i) in pagedData" :key="r.id">
<td>{{ (currentPage - 1) * pageSize + i + 1 }}</td>
<td>{{ r.cert }}</td>
<td class="col-unit">{{ r.unit }}</td>
<td>{{ r.plant }}</td>
<td>{{ r.variety }}</td>
<td>{{ r.qty }}</td>
<td>{{ r.testDate }}</td>
<td>{{ r.testOrg }}</td>
<td class="col-action">
<a class="op-link" @click="onView(r)">查看</a>
<a class="op-link" @click="onEdit(r)">编辑</a>
<a class="op-link op-danger" @click="onDelete(r)">删除</a>
</td>
</tr>
<tr v-if="!pagedData.length">
<td colspan="9" class="empty">暂无数据</td>
</tr>
</tbody>
</table>
</div>
<div class="pagination">
<span class="total" style="margin-right:auto;">共 {{ filteredData.length }} 条记录</span>
<button class="page-btn" :disabled="currentPage === 1" @click="goPage(1)">首页</button>
<button class="page-btn" :disabled="currentPage === 1" @click="goPage(currentPage - 1)">上一页</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(totalPages)">末页</button>
<span class="jump">跳至 <input class="jump-input" v-model="jumpInput" @keyup.enter="onJump" /> 页</span>
</div>
<div v-if="viewVisible" class="modal-mask" @click.self="viewVisible = false">
<div class="modal-box">
<div class="modal-top"><span class="modal-title">样本抽检详情</span><span class="modal-close" @click="viewVisible = false">×</span></div>
<div class="modal-body">
<div class="section">
<div class="section-title">基本信息</div>
<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.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.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">种子</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.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.testDate }}</span></div>
<div class="form-field"><span class="label">入库单位</span><span class="ctrl mb-input">{{ form.testOrg }}</span></div>
</div>
</div>
<div class="section">
<div class="section-title">抽检信息</div>
<div class="form-row cols-2">
<div class="form-field"><span class="label">抽检数量</span><span class="ctrl mb-input">{{ form.qty }}</span></div>
<div class="form-field"><span class="label">抽检日期</span><span class="ctrl mb-input">{{ form.testDate }}</span></div>
<div class="form-field span-2"><span class="label">抽检内容</span><span class="ctrl mb-input area">{{ form.content }}</span></div>
<div class="form-field span-2"><span class="label">抽检结论</span><span class="ctrl mb-input area">{{ form.conclusion }}</span></div>
</div>
</div>
<div class="section">
<div class="section-title">检疫机构信息</div>
<div class="form-row cols-2">
<div class="form-field span-2"><span class="label">检疫机构</span><span class="ctrl mb-input">{{ form.testOrg }}</span></div>
<div class="form-field"><span class="label">检疫员(签名)1</span><span class="ctrl mb-input">{{ form.inspector1 }}</span></div>
<div class="form-field"><span class="label">检疫员(签名)2</span><span class="ctrl mb-input">{{ form.inspector2 }}</span></div>
<div class="form-field"><span class="label">联系电话</span><span class="ctrl mb-input">{{ form.inspectorPhone }}</span></div>
<div class="form-field"><span class="label">录入日期</span><span class="ctrl mb-input">{{ form.entryDate }}</span></div>
</div>
</div>
</div>
</div>
</div>
<div v-if="editVisible" class="modal-mask" @click.self="editVisible = false">
<div class="modal-box">
<div class="modal-top"><span class="modal-title">编辑样本抽检</span><span class="modal-close" @click="editVisible = false">×</span></div>
<div class="modal-body">
<div class="section">
<div class="section-title">基本信息</div>
<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.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.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" value="种子" /></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.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><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>
</div>
<div class="section">
<div class="section-title">抽检信息</div>
<div class="form-row cols-2">
<div class="form-field"><span class="label">抽检数量</span><input class="ctrl" v-model="form.qty" /></div>
<div class="form-field"><span class="label">抽检日期</span><DatePicker v-model="form.testDate" class="ctrl" /></div>
<div class="form-field span-2"><span class="label">抽检内容</span><textarea class="ctrl area-input" v-model="form.content" rows="3"></textarea></div>
<div class="form-field span-2"><span class="label">抽检结论</span><textarea class="ctrl area-input" v-model="form.conclusion" rows="3"></textarea></div>
</div>
</div>
<div class="section">
<div class="section-title">检疫机构信息</div>
<div class="form-row cols-2">
<div class="form-field span-2"><span class="label">检疫机构</span><input class="ctrl" v-model="form.testOrg" /></div>
<div class="form-field"><span class="label">检疫员(签名)1</span><input class="ctrl" v-model="form.inspector1" /></div>
<div class="form-field"><span class="label">检疫员(签名)2</span><input class="ctrl" v-model="form.inspector2" /></div>
<div class="form-field"><span class="label">联系电话</span><input class="ctrl" v-model="form.inspectorPhone" /></div>
<div class="form-field"><span class="label">录入日期</span><DatePicker v-model="form.entryDate" class="ctrl" /></div>
</div>
</div>
<div class="modal-actions">
<button class="btn btn-default" @click="editVisible = false">取消</button>
<button class="btn" @click="onConfirmEdit">保存</button>
</div>
</div>
</div>
</div>
<div v-if="addVisible" class="modal-mask" @click.self="addVisible = false">
<div class="modal-box">
<div class="modal-top"><span class="modal-title">新增抽检</span><span class="modal-close" @click="addVisible = false">×</span></div>
<div class="modal-body">
<div class="section">
<div class="section-title">基本信息</div>
<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.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.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" value="种子" /></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.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><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>
</div>
<div class="section">
<div class="section-title">抽检信息</div>
<div class="form-row cols-2">
<div class="form-field"><span class="label">抽检数量</span><input class="ctrl" v-model="addForm.qty" /></div>
<div class="form-field"><span class="label">抽检日期</span><DatePicker v-model="addForm.testDate" class="ctrl" /></div>
<div class="form-field span-2"><span class="label">抽检内容</span><textarea class="ctrl area-input" v-model="addForm.content" rows="3"></textarea></div>
<div class="form-field span-2"><span class="label">抽检结论</span><textarea class="ctrl area-input" v-model="addForm.conclusion" rows="3"></textarea></div>
</div>
</div>
<div class="section">
<div class="section-title">检疫机构信息</div>
<div class="form-row cols-2">
<div class="form-field span-2"><span class="label">检疫机构</span><input class="ctrl" v-model="addForm.testOrg" /></div>
<div class="form-field"><span class="label">检疫员(签名)1</span><input class="ctrl" v-model="addForm.inspector1" /></div>
<div class="form-field"><span class="label">检疫员(签名)2</span><input class="ctrl" v-model="addForm.inspector2" /></div>
<div class="form-field"><span class="label">联系电话</span><input class="ctrl" v-model="addForm.inspectorPhone" /></div>
<div class="form-field"><span class="label">录入日期</span><DatePicker v-model="addForm.entryDate" class="ctrl" /></div>
</div>
</div>
<div class="modal-actions">
<button class="btn btn-default" @click="addVisible = false">取消</button>
<button class="btn" @click="onConfirmAdd">保存</button>
</div>
</div>
</div>
</div>
<div v-if="delVisible" class="modal-mask" @click.self="delVisible = false">
<div class="modal-box modal-small">
<div class="modal-top"><span class="modal-title">删除确认</span><span class="modal-close" @click="delVisible = false">×</span></div>
<div class="modal-body">
<p class="del-tip">确定要删除检疫单证号为 <b>{{ delRow?.cert }}</b>,植物名称为 <b>{{ delRow?.plant }}</b> 的记录吗?</p>
<div class="modal-actions">
<button class="btn btn-default" @click="delVisible = false">取消</button>
<button class="btn btn-danger" @click="onConfirmDelete">删除</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from "vue";
import { ElMessage } from "element-plus";
import DatePicker from "@/components/DatePicker.vue";
import { sampleTestData } from "@/data/sampleTest.js";
const dataList = ref([...sampleTestData]);
const defaultFilters = () => ({ testStart: "", testEnd: "", cert: "", plant: "", variety: "", testOrg: "" });
const filters = ref(defaultFilters());
const appliedFilters = ref(defaultFilters());
const currentPage = ref(1);
const pageSize = 10;
const jumpInput = ref("");
const viewVisible = ref(false);
const editVisible = ref(false);
const addVisible = ref(false);
const delVisible = ref(false);
const delRow = ref(null);
const form = ref({});
const defaultAddForm = () => ({ cert: "", unit: "", contact: "", phone: "", plant: "", variety: "", origin: "", approveQty: "", qty: "150克", testDate: "", testOrg: "甘州区农业技术推广中心", content: "", conclusion: "", inspector1: "", inspector2: "", inspectorPhone: "", entryDate: "" });
const addForm = ref(defaultAddForm());
const filteredData = computed(() => {
const f = appliedFilters.value;
return dataList.value.filter((r) => {
if (f.cert && !r.cert.toLowerCase().includes(f.cert.toLowerCase())) return false;
if (f.plant && !r.plant.toLowerCase().includes(f.plant.toLowerCase())) return false;
if (f.variety && !r.variety.toLowerCase().includes(f.variety.toLowerCase())) return false;
if (f.testOrg && !r.testOrg.toLowerCase().includes(f.testOrg.toLowerCase())) return false;
if (f.testStart && r.testDate < f.testStart) return false;
if (f.testEnd && r.testDate > f.testEnd) return false;
return true;
});
});
const totalPages = computed(() => Math.max(1, Math.ceil(filteredData.value.length / pageSize)));
const pageNumbers = computed(() => {
const t = totalPages.value;
const c = currentPage.value;
if (t <= 7) return Array.from({ length: t }, (_, i) => i + 1);
if (c <= 4) return [1, 2, 3, 4, 5, "...", t];
if (c >= t - 3) return [1, "...", t - 4, t - 3, t - 2, t - 1, t];
return [1, "...", c - 1, c, c + 1, "...", t];
});
const pagedData = computed(() => {
const s = (currentPage.value - 1) * pageSize;
return filteredData.value.slice(s, s + pageSize);
});
const onQuery = () => {
appliedFilters.value = JSON.parse(JSON.stringify(filters.value));
currentPage.value = 1;
};
const onReset = () => {
filters.value = defaultFilters();
appliedFilters.value = defaultFilters();
currentPage.value = 1;
};
const goPage = (p) => {
if (p < 1 || p > totalPages.value) return;
currentPage.value = p;
};
const onJump = () => {
const p = parseInt(jumpInput.value);
if (!isNaN(p)) goPage(p);
};
const onView = (row) => { form.value = { ...row }; viewVisible.value = true; };
const onEdit = (row) => { form.value = { ...row }; editVisible.value = true; };
const onAdd = () => { addForm.value = defaultAddForm(); addVisible.value = true; };
const onDelete = (row) => { delRow.value = row; delVisible.value = true; };
const onConfirmEdit = () => {
const i = dataList.value.findIndex((r) => r.id === form.value.id);
if (i >= 0) dataList.value[i] = { ...form.value };
editVisible.value = false;
ElMessage.success("保存成功");
};
const onConfirmAdd = () => {
const next = (dataList.value.length ? Math.max(...dataList.value.map((r) => r.id)) : 0) + 1;
dataList.value = [...dataList.value, { id: next, ...addForm.value }];
addVisible.value = false;
ElMessage.success("新增成功");
};
const onConfirmDelete = () => {
dataList.value = dataList.value.filter((r) => r.id !== delRow.value.id);
delVisible.value = false;
ElMessage.success("删除成功");
};
</script>
<style scoped>
.page-list { padding: 16px 20px; }
.filter-box { background: #fff; border: 1px solid #ebeef5; border-radius: 3px; padding: 18px 20px; margin-bottom: 12px; }
.filter-row { display: flex; align-items: center; flex-wrap: wrap; gap: 14px 24px; margin-bottom: 16px; }
.filter-row:last-child { margin-bottom: 0; }
.filter-item { display: flex; align-items: center; gap: 6px; flex-shrink: 0; min-width: 0; }
.filter-item .label { color: #555; font-size: 14px; white-space: nowrap; flex-shrink: 0; min-width: 84px; }
.filter-add { margin-left: auto; }
.dash { color: #999; }
.dp-160 { width: 160px; }
.ipt { width: 160px; height: 32px; padding: 0 8px; border: 1px solid #dcdfe6; border-radius: 3px; font-size: 14px; }
.ipt:focus { border-color: #d71e1e; outline: none; }
.btn { background: #d71e1e; color: #fff; border: 1px solid #d71e1e; height: 32px; padding: 0 18px; border-radius: 3px; font-size: 14px; cursor: pointer; }
.btn:hover { background: #b81818; border-color: #b81818; }
.btn-default { background: #fff; color: #555; border-color: #dcdfe6; }
.btn-default:hover { background: #fafafa; color: #d71e1e; border-color: #d71e1e; }
.btn-add { background: #d71e1e; color: #fff; border: 1px solid #d71e1e; }
.btn-danger { background: #fff; color: #d71e1e; border-color: #d71e1e; }
.btn-danger:hover { background: #d71e1e; color: #fff; }
.tbl-wrap { background: #fff; border: 1px solid #ebeef5; border-radius: 3px; overflow: hidden; }
.simple-table { width: 100%; border-collapse: collapse; table-layout: fixed; font-size: 14px; }
.simple-table th, .simple-table td { padding: 14px 6px; text-align: center; vertical-align: middle; border-bottom: 1px solid #ebeef5; }
.simple-table th { background: #fafafa; font-size: 14px; font-weight: 600; color: #333; }
.simple-table tbody tr:hover { background: #fafafa; }
.col-unit { text-align: left; padding-left: 12px; }
.col-action { white-space: nowrap; }
.op-link { color: #333; font-size: 14px; cursor: pointer; margin: 0 4px; }
.op-link:hover { color: #d71e1e; }
.op-danger { color: #d71e1e; }
.empty { padding: 40px 0; color: #999; }
.pagination { display: flex; align-items: center; justify-content: flex-end; gap: 6px; padding: 14px 16px; background: #fff; border: 1px solid #ebeef5; border-top: 0; border-radius: 0 0 3px 3px; font-size: 14px; }
.total { color: #555; margin-right: 12px; }
.page-btn { min-width: 30px; height: 30px; padding: 0 8px; background: #fff; border: 1px solid #dcdfe6; color: #555; border-radius: 3px; cursor: pointer; font-size: 14px; }
.page-btn:hover:not(:disabled) { color: #d71e1e; border-color: #d71e1e; }
.page-btn.active { background: #d71e1e; color: #fff; border-color: #d71e1e; }
.page-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.jump { margin-left: 12px; color: #555; }
.jump-input { width: 50px; height: 30px; text-align: center; border: 1px solid #dcdfe6; border-radius: 3px; margin: 0 4px; font-size: 14px; }
.modal-mask { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.45); display: flex; align-items: center; justify-content: center; z-index: 1000; }
.modal-box { background: #fff; border-radius: 4px; width: 900px; max-width: 95vw; max-height: 90vh; display: flex; flex-direction: column; box-shadow: 0 8px 30px rgba(0, 0, 0, 0.2); overflow: hidden; }
.modal-small { width: 420px; }
.modal-top { display: flex; align-items: center; justify-content: space-between; padding: 12px 18px; background: #d71e1e; color: #fff; }
.modal-title { font-size: 16px; font-weight: 600; }
.modal-close { width: 22px; height: 22px; line-height: 18px; text-align: center; border: 1px solid #fff; border-radius: 3px; cursor: pointer; font-size: 16px; }
.modal-body { padding: 18px 22px; overflow-y: auto; }
.form-row.cols-2 { display: grid; grid-template-columns: 1fr 1fr; column-gap: 32px; row-gap: 14px; }
.form-field.span-2 { grid-column: 1 / -1; }
.form-field .ctrl.area { min-height: 56px; line-height: 1.7; white-space: pre-wrap; word-break: break-all; }
.area-input { min-height: 56px; line-height: 1.6; padding: 6px 10px; resize: vertical; }
.section { margin-bottom: 22px; }
.section:last-child { margin-bottom: 0; }
.section-title { font-size: 15px; font-weight: 600; color: #d71e1e; margin: 0 0 12px; padding-left: 10px; border-left: 3px solid #d71e1e; line-height: 1; }
.form-field { display: flex; align-items: center; gap: 8px; }
.form-field .label { width: 110px; text-align: right; color: #555; font-size: 14px; flex-shrink: 0; white-space: nowrap; }
.form-field .ctrl { flex: 1; height: 32px; padding: 0 10px; border: 1px solid #dcdfe6; border-radius: 3px; font-size: 14px; }
.mb-input { background: #fafafa; padding: 6px 12px; display: flex; align-items: center; min-height: 32px; line-height: 1.6; align-self: center; }
.modal-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 18px; padding-top: 14px; border-top: 1px solid #ebeef5; }
.del-tip { font-size: 14px; color: #555; line-height: 1.8; padding: 8px 0 16px; }
.del-tip b { color: #d71e1e; }
</style>
<template>
<PagePlaceholder title="报告管理" desc="检疫报告生成与管理开发中" icon="Notebook" />
</template>
<script setup>
import PagePlaceholder from "@/components/PagePlaceholder.vue";
</script>
<template>
<PagePlaceholder title="风险预警" desc="疫情风险预警与监测看板开发中" icon="Warning" />
</template>
<script setup>
import PagePlaceholder from "@/components/PagePlaceholder.vue";
</script>
<template>
<PagePlaceholder title="样本出库登记" desc="样本出库登记与流转记录开发中" icon="Upload" />
</template>
<script setup>
import PagePlaceholder from "@/components/PagePlaceholder.vue";
</script>
<template>
<div class="page-list">
<div class="filter-box">
<div class="filter-row">
<div class="filter-item">
<label>入库时间:</label>
<DatePicker v-model="filters.storeStart" class="dp-160" placeholder="开始日期" />
<span class="dash">至</span>
<DatePicker v-model="filters.storeEnd" class="dp-160" placeholder="结束日期" />
</div>
<div class="filter-item">
<label>检疫单证号:</label>
<input v-model="filters.cert" placeholder="请输入关键字" class="ipt" />
</div>
<div class="filter-item">
<label>植物名称:</label>
<input v-model="filters.plant" placeholder="请输入关键字" class="ipt" />
</div>
</div>
<div class="filter-row">
<div class="filter-item">
<label>品种名称:</label>
<input v-model="filters.variety" placeholder="请输入关键字" class="ipt" />
</div>
<div class="filter-item">
<label>申报单位:</label>
<input v-model="filters.unit" placeholder="请输入关键字" class="ipt" />
</div>
<div class="filter-item">
<button class="btn" @click="onQuery">查询</button>
<button class="btn btn-default" @click="onReset">重置</button>
</div>
<div class="filter-item filter-add">
<button class="btn btn-add" @click="onAdd">+ 新增入库</button>
</div>
</div>
</div>
<div class="tbl-wrap">
<table class="simple-table">
<colgroup>
<col style="width:5%" />
<col style="width:13%" />
<col style="width:20%" />
<col style="width:9%" />
<col style="width:11%" />
<col style="width:12%" />
<col style="width:8%" />
<col style="width:9%" />
<col style="width:6%" />
<col style="width:9%" />
<col style="width:9%" />
</colgroup>
<thead>
<tr>
<th>序号</th>
<th>检疫单证号</th>
<th>申报单位</th>
<th>植物名称</th>
<th>品种名称</th>
<th>种苗来源国(地区)</th>
<th>引进数量</th>
<th>入库时间</th>
<th>联系人</th>
<th>联系电话</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="row in pagedData" :key="row.id">
<td>{{ row.id }}</td>
<td>{{ row.cert }}</td>
<td class="col-unit">{{ row.unit }}</td>
<td>{{ row.plant }}</td>
<td>{{ row.variety }}</td>
<td>{{ row.origin }}</td>
<td>{{ row.qty }}</td>
<td>{{ row.storeDate }}</td>
<td>{{ row.contact }}</td>
<td>{{ row.phone }}</td>
<td class="col-action">
<a class="op-link" @click="onView(row)">查看</a>
<a class="op-link" @click="onEdit(row)">编辑</a>
<a class="op-link" @click="onDelete(row)">删除</a>
</td>
</tr>
<tr v-if="!pagedData.length">
<td colspan="11" class="empty">暂无数据</td>
</tr>
</tbody>
</table>
</div>
<div class="pager">
<span class="total">共 {{ filteredData.length }} 条记录</span>
<button class="pg-btn" :disabled="page === 1" @click="page = 1">首页</button>
<button class="pg-btn" :disabled="page === 1" @click="page--">上一页</button>
<button
v-for="p in pagesToShow"
:key="p"
class="pg-btn"
:class="{ active: p === page }"
@click="page = p"
>{{ p }}</button>
<button class="pg-btn" :disabled="page === totalPages" @click="page++">下一页</button>
<button class="pg-btn" :disabled="page === totalPages" @click="page = totalPages">末页</button>
<span class="pg-jump">
跳转至 <input v-model.number="jumpInput" class="ipt-jump" @keyup.enter="onJump" /> 页
</span>
</div>
<div v-if="viewVisible" class="modal-mask" @click.self="viewVisible = false">
<div class="modal-box">
<div class="modal-top">
<span class="modal-title">样本登记详情</span>
<span class="modal-close" @click="viewVisible = false">×</span>
</div>
<div class="modal-body">
<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.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.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">种子</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.approveQty }}</span></div>
<div class="form-field"><span class="label">引进数量</span><span class="ctrl mb-input">{{ form.qty }}</span></div>
<div class="form-field"><span class="label">入库时间</span><span class="ctrl mb-input">{{ form.storeDate }}</span></div>
<div class="form-field"><span class="label">入库单位</span><span class="ctrl mb-input">甘州区农业技术推广中心</span></div>
</div>
</div>
</div>
</div>
<div v-if="addVisible" class="modal-mask" @click.self="addVisible = false">
<div class="modal-box">
<div class="modal-top">
<span class="modal-title">新增入库</span>
<span class="modal-close" @click="addVisible = false">×</span>
</div>
<div class="modal-body">
<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.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.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.usage" /></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.approveQty" /></div>
<div class="form-field"><span class="label">引进数量</span><input class="ctrl" v-model="addForm.qty" /></div>
<div class="form-field"><span class="label">入库时间</span><DatePicker v-model="addForm.storeDate" class="ctrl" /></div>
<div class="form-field"><span class="label">入库单位</span><input class="ctrl" v-model="addForm.storeUnit" /></div>
</div>
<div class="modal-actions">
<button class="btn btn-default" @click="addVisible = false">取消</button>
<button class="btn" @click="onConfirmAdd">保存</button>
</div>
</div>
</div>
</div>
<div v-if="editVisible" class="modal-mask" @click.self="editVisible = false">
<div class="modal-box">
<div class="modal-top">
<span class="modal-title">编辑样本登记</span>
<span class="modal-close" @click="editVisible = false">×</span>
</div>
<div class="modal-body">
<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.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.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" value="种子" /></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.approveQty" /></div>
<div class="form-field"><span class="label">引进数量</span><input class="ctrl" v-model="form.qty" /></div>
<div class="form-field"><span class="label">入库时间</span><DatePicker v-model="form.storeDate" class="ctrl" /></div>
<div class="form-field"><span class="label">入库单位</span><input class="ctrl" value="甘州区农业技术推广中心" /></div>
</div>
<div class="modal-actions">
<button class="btn btn-default" @click="editVisible = false">取消</button>
<button class="btn" @click="onSave">保存</button>
</div>
</div>
</div>
</div>
<div v-if="delVisible" class="modal-mask" @click.self="delVisible = false">
<div class="modal-box small">
<div class="modal-top">
<span class="modal-title">删除确认</span>
<span class="modal-close" @click="delVisible = false">×</span>
</div>
<div class="modal-body">
<p class="del-tip">确定要删除检疫单证号 <b>{{ form.cert }}</b>(植物:{{ form.plant }})的样本吗?该操作不可恢复。</p>
<div class="modal-actions">
<button class="btn btn-default" @click="delVisible = false">取消</button>
<button class="btn" @click="confirmDelete">确定删除</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch } from "vue";
import { ElMessage } from "element-plus";
import DatePicker from "@/components/DatePicker.vue";
import { sampleRegisterData } from "@/data/sampleRegister";
import { usePersistentRef } from "@/utils/persistentData";
const { data: persistentList } = usePersistentRef("sample_register", sampleRegisterData);
const defaultFilters = () => ({ storeStart: "", storeEnd: "", cert: "", plant: "", variety: "", unit: "" });
// 输入框草稿(用户输入时改,不立即过滤)
const filters = ref(defaultFilters());
// 已应用的筛选条件(仅点击「查询」时更新)
const appliedFilters = ref(defaultFilters());
const page = ref(1);
const pageSize = 10;
const jumpInput = ref(1);
const viewVisible = ref(false);
const editVisible = ref(false);
const addVisible = ref(false);
const defaultAddForm = () => ({ cert: "", unit: "", contact: "", phone: "", plant: "", usage: "种子", variety: "", origin: "", approveQty: "", qty: "", storeDate: "", storeUnit: "甘州区农业技术推广中心" });
const addForm = ref(defaultAddForm());
const delVisible = ref(false);
const form = ref({});
const dataList = persistentList;
const inDateRange = (date, start, end) => {
if (!date) return true;
if (start && date < start) return false;
if (end && date > end) return false;
return true;
};
const filteredData = computed(() => {
return dataList.value.filter((r) => {
if (appliedFilters.value.cert && !r.cert.includes(appliedFilters.value.cert)) return false;
if (appliedFilters.value.plant && !r.plant.includes(appliedFilters.value.plant)) return false;
if (appliedFilters.value.variety && !r.variety.includes(appliedFilters.value.variety)) return false;
if (appliedFilters.value.unit && !r.unit.includes(appliedFilters.value.unit)) return false;
if (!inDateRange(r.storeDate, appliedFilters.value.storeStart, appliedFilters.value.storeEnd)) return false;
return true;
});
});
const totalPages = computed(() => Math.max(1, Math.ceil(filteredData.value.length / pageSize)));
const pagedData = computed(() => {
const start = (page.value - 1) * pageSize;
return filteredData.value.slice(start, start + pageSize);
});
const pagesToShow = computed(() => {
const total = totalPages.value;
const cur = page.value;
if (total <= 7) return Array.from({ length: total }, (_, i) => i + 1);
return [1, 2, 3, 4, 5];
});
watch(filteredData, () => { if (page.value > totalPages.value) page.value = 1; });
const onQuery = () => {
appliedFilters.value = JSON.parse(JSON.stringify(filters.value));
page.value = 1;
ElMessage.success("查询完成,共 " + filteredData.value.length + " 条");
};
const onReset = () => {
filters.value = defaultFilters();
appliedFilters.value = defaultFilters();
page.value = 1;
};
const onJump = () => { page.value = Math.min(Math.max(1, jumpInput.value | 0), totalPages.value); };
const onAdd = () => { addForm.value = defaultAddForm(); addVisible.value = true; };
const onConfirmAdd = () => {
const next = (dataList.value.length ? Math.max(...dataList.value.map((r) => r.id)) : 0) + 1;
dataList.value = [...dataList.value, { id: next, ...addForm.value }];
addVisible.value = false;
ElMessage.success("新增成功");
};
const onView = (row) => { form.value = { ...row }; viewVisible.value = true; };
const onEdit = (row) => { form.value = { ...row }; editVisible.value = true; };
const onDelete = (row) => { form.value = { ...row }; delVisible.value = true; };
const onSave = () => {
const idx = dataList.value.findIndex((r) => r.id === form.value.id);
if (idx >= 0) dataList.value[idx] = { ...form.value };
editVisible.value = false;
ElMessage.success("保存成功");
};
const confirmDelete = () => {
dataList.value = dataList.value.filter((r) => r.id !== form.value.id);
delVisible.value = false;
ElMessage.success("删除成功");
};
</script>
<style scoped>
.page-list { padding: 16px 20px; }
.filter-box { background: #fff; border: 1px solid #f0f0f0; border-radius: 4px; padding: 18px 20px; margin-bottom: 16px; }
.filter-row { display: flex; flex-wrap: wrap; align-items: center; gap: 14px 24px; margin-bottom: 14px; }
.filter-row:last-child { margin-bottom: 0; }
.filter-item { display: flex; align-items: center; gap: 6px; flex-shrink: 0; min-width: 0; }
.filter-add { margin-left: auto; }
.filter-item label { white-space: nowrap; flex-shrink: 0; min-width: 84px; color: #333; font-size: 14px; }
.ipt { width: 160px; height: 32px; padding: 0 8px; border: 1px solid #d4d4d4; border-radius: 3px; font-size: 14px; color: #555; }
.ipt:focus { border-color: #d71e1e; outline: none; }
.ipt::placeholder { color: #999; }
.dp-160 { width: 150px; }
.dash { color: #999; padding: 0 4px; }
.btn { height: 32px; padding: 0 16px; background: #d71e1e; color: #fff; border: none; border-radius: 3px; cursor: pointer; font-size: 14px; }
.btn:hover { opacity: 0.9; }
.btn-default { background: #fff; color: #555; border: 1px solid #d4d4d4; }
.btn-default:hover { color: #d71e1e; border-color: #d71e1e; }
.filter-spacer { display: inline-block; width: 32px; }
.btn-add { background: #d71e1e; color: #fff; border: 1px solid #d71e1e; height: 32px; padding: 0 18px; border-radius: 3px; font-size: 14px; cursor: pointer; }
.btn-add:hover { background: #b81818; border-color: #b81818; }
.tbl-wrap { background: #fff; border: 1px solid #f0f0f0; border-radius: 4px; overflow: visible; }
.simple-table { width: 100%; border-collapse: collapse; table-layout: fixed; font-size: 14px; }
.simple-table th, .simple-table td { padding: 14px 6px; border-bottom: 1px solid #f0f0f0; text-align: center; line-height: 1.5; vertical-align: middle; }
.simple-table th { background: #fafafa; color: #333; font-weight: bold; font-size: 14px; padding: 14px 6px; }
.simple-table td { font-size: 14px; word-break: break-all; }
.simple-table td.col-unit { text-align: left; padding-left: 12px; }
.simple-table td.col-action { white-space: nowrap; }
.simple-table tr:hover td { background: #fef9f9; }
.op-link { color: #333; margin: 0 4px; cursor: pointer; }
.op-link:hover { color: #d71e1e; }
.empty { text-align: center; padding: 40px 0; color: #999; }
.pager { display: flex; align-items: center; padding: 14px 16px; gap: 8px; background: #fff; border: 1px solid #f0f0f0; border-top: none; border-radius: 0 0 4px 4px; }
.total { color: #666; font-size: 14px; margin-right: auto; }
.pg-btn { min-width: 30px; height: 30px; padding: 0 8px; border: 1px solid #d4d4d4; background: #fff; border-radius: 3px; cursor: pointer; font-size: 14px; color: #555; }
.pg-btn:hover:not(:disabled) { color: #d71e1e; border-color: #d71e1e; }
.pg-btn.active { background: #d71e1e; color: #fff; border-color: #d71e1e; }
.pg-btn:disabled { cursor: not-allowed; opacity: 0.5; }
.pg-jump { color: #666; font-size: 14px; margin-left: 12px; }
.ipt-jump { width: 50px; height: 28px; padding: 0 4px; border: 1px solid #d4d4d4; border-radius: 3px; text-align: center; }
.modal-mask { position: fixed; inset: 0; background: rgba(0,0,0,0.45); display: flex; align-items: center; justify-content: center; z-index: 500; }
.modal-box { background: #fff; width: 900px; max-width: 95vw; max-height: 90vh; border-radius: 4px; overflow: hidden; display: flex; flex-direction: column; box-shadow: 0 8px 30px rgba(0,0,0,0.2); }
.modal-box.small { width: 420px; }
.modal-top { background: #d71e1e; color: #fff; padding: 10px 18px; display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; }
.modal-title { font-size: 16px; font-weight: bold; }
.modal-close { width: 22px; height: 22px; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 18px; color: #fff; border: 1px solid #fff; border-radius: 2px; opacity: 0.9; }
.modal-close:hover { opacity: 1; }
.modal-body { padding: 20px 32px 30px; overflow: auto; flex: 1; }
.modal-body::-webkit-scrollbar { width: 6px; }
.modal-body::-webkit-scrollbar-thumb { background: #d4d4d4; border-radius: 3px; }
.modal-body::-webkit-scrollbar-track { background: #f5f5f5; }
.modal-actions { display: flex; justify-content: flex-end; gap: 8px; padding-top: 16px; border-top: 1px solid #f0f0f0; margin-top: 16px; }
.form-row { display: grid; gap: 12px 24px; margin-bottom: 0; }
.form-row.cols-2 { grid-template-columns: 1fr 1fr; }
.form-field { display: flex; align-items: center; margin-bottom: 8px; }
.form-field .label { width: 100px; text-align: right; color: #555; font-size: 14px; margin-right: 8px; flex-shrink: 0; }
.form-field .ctrl { flex: 1; height: 32px; padding: 0 8px; border: 1px solid #d4d4d4; border-radius: 3px; font-size: 14px; color: #555; }
.mb-input { display: inline-flex; align-items: center; background: #fafafa; border-radius: 3px; padding: 0 10px; border: 1px solid #d4d4d4; min-height: 28px; }
.del-tip { line-height: 1.8; font-size: 14px; color: #333; }
</style>
\ No newline at end of file
<template>
<PagePlaceholder title="样本存放管理" desc="样本存放位置与状态管理开发中" icon="Box" />
</template>
<script setup>
import PagePlaceholder from "@/components/PagePlaceholder.vue";
</script>
<template>
<PagePlaceholder title="数据字典" desc="系统数据字典维护开发中" icon="Collection" />
</template>
<script setup>
import PagePlaceholder from "@/components/PagePlaceholder.vue";
</script>
<template>
<PagePlaceholder title="操作日志" desc="用户操作日志查询与审计开发中" icon="Document" />
</template>
<script setup>
import PagePlaceholder from "@/components/PagePlaceholder.vue";
</script>
<template>
<PagePlaceholder title="角色权限" desc="角色与权限分配开发中" icon="UserFilled" />
</template>
<script setup>
import PagePlaceholder from "@/components/PagePlaceholder.vue";
</script>
<template>
<PagePlaceholder title="用户管理" desc="系统用户与账号管理开发中" icon="User" />
</template>
<script setup>
import PagePlaceholder from "@/components/PagePlaceholder.vue";
</script>
/* cache-bust 20260907 - 禁止浏览器缓存 */
/* ========== 基础重置 ========== */
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { min-height: 100%; }
body {
font-family: "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif;
font-size: 16px;
color: #333;
background: #f5f6f8;
-webkit-font-smoothing: antialiased;
}
a { color: inherit; text-decoration: none; }
ul, ol { list-style: none; }
input, select, textarea, button { font-family: inherit; font-size: inherit; outline: none; }
/* ========== 主布局 ========== */
.app {
display: flex;
flex-direction: column;
height: 100vh;
min-width: 1280px;
}
/* ========== 顶部导航 ========== */
.app-header {
height: 40px;
background: #d71e1e;
color: #fff;
display: flex;
align-items: center;
padding: 0;
flex-shrink: 0;
position: relative;
z-index: 100;
}
.app-logo {
width: 380px;
height: 100%;
display: flex;
align-items: center;
padding: 0 20px;
font-size: 18px;
font-weight: bold;
letter-spacing: 1px;
background: #c01a1a;
border-right: 1px solid #b51616;
}
.app-logo .logo-icon {
width: 22px; height: 22px;
margin-right: 8px;
background: #fff;
color: #d71e1e;
border-radius: 4px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 13px;
font-weight: bold;
}
.app-nav { display: flex; align-items: center; height: 100%; flex: 1; }
.app-nav .nav-item {
padding: 0 22px;
height: 100%;
display: flex;
align-items: center;
cursor: pointer;
font-size: 14px;
transition: background 0.2s;
user-select: none;
}
.app-nav .nav-item:hover { background: rgba(255,255,255,0.1); }
.app-nav .nav-item.active { background: #fff; color: #d71e1e; font-weight: bold; }
.app-user {
margin-left: auto;
display: flex;
align-items: center;
padding-right: 20px;
height: 100%;
font-size: 14px;
}
.app-user .user-avatar {
width: 24px; height: 24px;
border-radius: 50%;
background: #fff;
color: #d71e1e;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 12px;
margin-right: 6px;
}
.app-user .user-name { margin-right: 12px; }
.app-user .user-action { padding: 0 8px; cursor: pointer; opacity: 0.9; }
.app-user .user-action:hover { opacity: 1; }
/* ========== 主体区 ========== */
.app-body { flex: 1; display: flex; min-height: 0; }
/* ========== 左侧菜单 ========== */
.sidebar {
width: 200px;
background: #fff;
border-right: 1px solid #e6e6e6;
overflow-y: auto;
flex-shrink: 0;
position: relative;
}
.sidebar::-webkit-scrollbar { width: 4px; }
.sidebar::-webkit-scrollbar-thumb { background: #ccc; border-radius: 2px; }
.menu-list { padding: 8px 0 32px; }
.menu-item {
display: flex;
align-items: center;
padding: 0 16px;
height: 40px;
cursor: pointer;
color: #444;
font-size: 16px;
position: relative;
transition: background 0.15s, color 0.15s;
user-select: none;
}
.menu-item:hover { background: #fef2f2; color: #d71e1e; }
.menu-item .menu-icon {
width: 16px; height: 16px;
margin-right: 10px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: #999;
}
.menu-item .menu-text { flex: 1; }
.menu-item .menu-arrow {
margin-left: auto;
font-size: 12px;
color: #bbb;
transition: transform 0.2s;
display: inline-block;
}
.menu-item.has-sub.expanded .menu-arrow { transform: rotate(90deg); color: #d71e1e; }
.menu-item.active .menu-arrow { color: #fff; }
.menu-item.active { background: #d71e1e; color: #fff; font-weight: bold; }
.menu-item.active .menu-icon { color: #fff; }
.menu-item.active::before {
content: "";
position: absolute;
left: 0; top: 0; bottom: 0;
width: 3px;
background: #fff;
}
.sub-menu { background: #fafafa; overflow: hidden; max-height: 0; transition: max-height 0.25s ease; }
.sub-menu.expanded { max-height: 600px; }
.sub-item {
display: block;
padding: 10px 16px 10px 42px;
font-size: 15px;
color: #555;
cursor: pointer;
position: relative;
transition: background 0.15s, color 0.15s;
}
.sub-item:hover { background: #fef2f2; color: #d71e1e; }
.sub-item.active { background: #fef2f2; color: #d71e1e; font-weight: bold; }
.sub-item.active::before {
content: "";
position: absolute;
left: 28px; 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; }
.breadcrumb {
height: 36px;
padding: 0 16px;
display: flex;
align-items: center;
font-size: 14px;
color: #666;
border-bottom: 1px solid #eee;
background: #fafafa;
flex-shrink: 0;
}
.breadcrumb .sep { margin: 0 6px; color: #ccc; }
.breadcrumb .current { color: #d71e1e; }
.tabs-bar {
height: 38px;
display: flex;
align-items: flex-end;
padding: 0 8px;
border-bottom: 2px solid #d71e1e;
background: #fff;
flex-shrink: 0;
}
.tab {
display: flex;
align-items: center;
padding: 0 14px;
height: 32px;
background: #f5f5f5;
border: 1px solid #e0e0e0;
border-bottom: none;
margin-right: 2px;
font-size: 14px;
color: #555;
cursor: pointer;
position: relative;
border-radius: 3px 3px 0 0;
transition: all 0.15s;
}
.tab:hover { color: #d71e1e; }
.tab.active { background: #d71e1e; color: #fff; border-color: #d71e1e; }
.tab .tab-close { margin-left: 8px; font-size: 12px; opacity: 0.7; }
.tab .tab-close:hover { opacity: 1; }
.content-body { flex: 1; overflow: hidden; position: relative; background: #fff; }
#pageFrame { width: 100%; border: 0; display: block; overflow: hidden; transform-origin: top left; }
.content-body::-webkit-scrollbar-thumb { background: #d4d4d4; border-radius: 3px; }
.content-body::-webkit-scrollbar-track { background: #f5f5f5; }
.content-body::after {
content: "";
position: absolute;
right: 0; top: 0; bottom: 0;
width: 2px;
background: #d71e1e;
pointer-events: none;
}
/* ========== 首页样式 ========== */
.page-home { padding: 16px; }
.action-bar { display: flex; align-items: center; padding: 0 0 12px 0; gap: 8px; }
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
height: 30px;
padding: 0 16px;
border: 1px solid #d71e1e;
background: #d71e1e;
color: #fff;
font-size: 14px;
border-radius: 3px;
cursor: pointer;
transition: opacity 0.15s, background 0.15s;
user-select: none;
}
.btn:hover { opacity: 0.9; }
.btn-default { background: #fff; color: #555; border-color: #d4d4d4; }
.btn-default:hover { color: #d71e1e; border-color: #d71e1e; opacity: 1; }
.btn-link { background: transparent; color: #d71e1e; border: none; padding: 0 6px; }
.btn-link:hover { text-decoration: underline; opacity: 1; }
.stat-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 12px; }
.stat-card {
background: #fff;
border: 1px solid #f0f0f0;
border-radius: 4px;
padding: 16px 20px;
display: flex;
align-items: center;
position: relative;
overflow: hidden;
}
.stat-card::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: #d71e1e; }
.stat-icon {
width: 48px; height: 48px;
border-radius: 50%;
background: #fef2f2;
color: #d71e1e;
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
margin-right: 14px;
flex-shrink: 0;
}
.stat-info { flex: 1; }
.stat-label { font-size: 14px; color: #888; margin-bottom: 4px; }
.stat-value { font-size: 24px; font-weight: bold; color: #d71e1e; line-height: 1.2; }
.stat-extra { font-size: 13px; color: #999; margin-top: 2px; }
.panel { background: #fff; border: 1px solid #f0f0f0; border-radius: 4px; margin-bottom: 12px; }
.panel-header {
display: flex;
align-items: center;
padding: 10px 16px;
border-bottom: 1px solid #f0f0f0;
background: #fafafa;
border-radius: 4px 4px 0 0;
}
.panel-title { font-size: 16px; font-weight: bold; color: #333; position: relative; padding-left: 10px; }
.panel-title::before {
content: "";
position: absolute;
left: 0; top: 50%;
transform: translateY(-50%);
width: 3px; height: 14px;
background: #d71e1e;
border-radius: 1px;
}
.panel-body { padding: 16px; }
.panel-tools { margin-left: auto; }
.panel-tools .btn-link { font-size: 12px; }
.row-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.row-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; }
.chart-box {
height: 280px;
background: #fafafa;
border: 1px dashed #e0e0e0;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
color: #bbb;
font-size: 13px;
}
.simple-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.simple-table th, .simple-table td { padding: 8px 12px; border-bottom: 1px solid #f0f0f0; text-align: left; }
.simple-table th { background: #fafafa; color: #555; font-weight: normal; font-size: 12px; }
.simple-table tr:hover td { background: #fef9f9; }
.page-placeholder { padding: 60px 24px; text-align: center; color: #999; }
.page-placeholder .ph-icon { font-size: 56px; color: #ddd; margin-bottom: 12px; }
.page-placeholder .ph-title { font-size: 16px; color: #555; margin-bottom: 6px; }
.page-placeholder .ph-desc { font-size: 13px; color: #aaa; }
.form-section { padding: 16px 24px; }
.form-title { text-align: center; font-size: 18px; font-weight: bold; margin: 8px 0 24px; color: #222; }
.form-block { margin-bottom: 18px; }
.form-block-title {
font-size: 14px;
font-weight: bold;
color: #333;
margin-bottom: 12px;
padding-left: 8px;
border-left: 3px solid #d71e1e;
}
.form-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px 24px; margin-bottom: 6px; }
.form-row.cols-2 { grid-template-columns: repeat(2, 1fr); }
.form-row.cols-1 { grid-template-columns: 1fr; }
.form-field { display: flex; align-items: center; }
.form-field .label { width: 100px; text-align: right; color: #555; font-size: 13px; margin-right: 8px; flex-shrink: 0; }
.form-field .label .req { color: #d71e1e; margin-right: 2px; }
.form-field .ctrl { flex: 1; }
.form-field input, .form-field select, .form-field textarea {
width: 100%;
height: 30px;
padding: 0 8px;
border: 1px solid #d4d4d4;
border-radius: 3px;
font-size: 14px;
background: #fff;
transition: border-color 0.15s;
}
.form-field input:focus, .form-field select:focus, .form-field textarea:focus { border-color: #d71e1e; }
.form-field textarea { height: 60px; padding: 6px 8px; resize: vertical; }
.form-field input::placeholder, .form-field select::placeholder { color: #bbb; }
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { fileURLToPath, URL } from "node:url";
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url))
}
},
server: {
host: "0.0.0.0",
port: 5173,
open: true
}
});
\ No newline at end of file
VITE v5.4.21 ready in 6984 ms
➜ Local: http://localhost:5173/
➜ Network: http://10.51.0.159:5173/
17:50:50 [vite] hmr update /src/assets/main.css
17:51:12 [vite] hmr update /src/views/entrust/EntrustList.vue?vue&type=style&index=0&scoped=d9669b28&lang.css
17:51:34 [vite] hmr update /src/views/sample/SampleRegister.vue, /src/views/sample/SampleRegister.vue?vue&type=style&index=0&scoped=459b0eb1&lang.css
17:52:00 [vite] hmr update /src/assets/main.css
17:52:24 [vite] hmr update /src/assets/main.css
17:52:25 [vite] hmr update /src/views/entrust/EntrustList.vue?vue&type=style&index=0&scoped=d9669b28&lang.css
17:52:25 [vite] hmr update /src/views/sample/SampleRegister.vue?vue&type=style&index=0&scoped=459b0eb1&lang.css
17:59:30 [vite] hmr update /src/components/Sidebar.vue
18:02:28 [vite] hmr update /src/views/entrust/EntrustList.vue?vue&type=style&index=0&scoped=d9669b28&lang.css, /src/views/entrust/EntrustList.vue
18:02:47 [vite] hmr update /src/assets/main.css
18:05:02 [vite] hmr update /src/views/entrust/EntrustList.vue?vue&type=style&index=0&scoped=d9669b28&lang.css, /src/views/entrust/EntrustList.vue
09:03:02 [vite] hmr update /src/views/entrust/EntrustList.vue?vue&type=style&index=0&scoped=d9669b28&lang.css
09:13:56 [vite] hmr update /src/views/entrust/EntrustList.vue?vue&type=style&index=0&scoped=d9669b28&lang.css
09:14:13 [vite] hmr update /src/views/entrust/EntrustList.vue
09:15:30 [vite] hmr update /src/views/entrust/EntrustList.vue
09:18:19 [vite] hmr update /src/views/entrust/EntrustList.vue?vue&type=style&index=0&scoped=d9669b28&lang.css, /src/views/entrust/EntrustList.vue
09:21:20 [vite] hmr update /src/components/DatePicker.vue, /src/components/DatePicker.vue?vue&type=style&index=0&scoped=877fe5b5&lang.css
09:21:34 [vite] hmr update /src/views/entrust/EntrustList.vue?vue&type=style&index=0&scoped=d9669b28&lang.css, /src/views/entrust/EntrustList.vue
09:23:20 [vite] hmr update /src/components/DatePicker.vue?vue&type=style&index=0&scoped=877fe5b5&lang.css, /src/components/DatePicker.vue
09:23:30 [vite] hmr update /src/views/entrust/EntrustList.vue?vue&type=style&index=0&scoped=d9669b28&lang.css
09:25:57 [vite] hmr update /src/views/entrust/EntrustList.vue
09:28:28 [vite] hmr update /src/components/Sidebar.vue
09:28:48 [vite] hmr update /src/views/sample/SampleRegister.vue?vue&type=style&index=0&scoped=459b0eb1&lang.css, /src/views/sample/SampleRegister.vue
09:30:21 [vite] hmr update /src/components/Sidebar.vue
09:34:33 [vite] hmr update /src/views/entrust/EntrustList.vue
09:34:43 [vite] hmr update /src/views/sample/SampleRegister.vue
09:38:20 [vite] hmr update /src/views/entrust/EntrustList.vue
09:38:27 [vite] hmr update /src/views/sample/SampleRegister.vue
09:38:39 [vite] hmr update /src/views/entrust/EntrustList.vue
09:38:39 [vite] hmr update /src/views/sample/SampleRegister.vue
09:45:37 [vite] hmr update /src/views/entrust/EntrustList.vue
09:46:52 [vite] hmr update /src/views/sample/SampleRegister.vue
09:56:18 [vite] hmr update /src/assets/main.css
10:12:32 [vite] hmr update /src/components/Sidebar.vue
10:12:32 [vite] hmr update /src/views/sample/SampleRegister.vue
10:12:32 [vite] hmr update /src/views/sample/SampleRegister.vue
10:12:32 [vite] hmr update /src/views/sample/SampleRegister.vue
10:13:04 [vite] hmr update /src/views/sample/SampleRegister.vue
10:14:40 [vite] page reload src/router/index.js
10:16:41 [vite] page reload src/router/index.js
10:17:51 [vite] page reload src/router/index.js
10:17:51 [vite] hmr update /src/views/entrust/EntrustList.vue
10:30:57 [vite] hmr update /src/views/sample/SampleRegister.vue
10:38:59 [vite] hmr update /src/views/sample/SampleRegister.vue
10:38:59 [vite] hmr update /src/views/sample/SampleRegister.vue
10:39:16 [vite] hmr update /src/views/sample/SampleRegister.vue
10:46:23 [vite] hmr update /src/views/sample/SampleRegister.vue
10:50:18 [vite] hmr update /src/views/sample/SampleRegister.vue?vue&type=style&index=0&scoped=459b0eb1&lang.css, /src/views/sample/SampleRegister.vue
10:50:34 [vite] hmr update /src/views/sample/SampleRegister.vue
10:51:45 [vite] hmr update /src/views/sample/SampleRegister.vue?vue&type=style&index=0&scoped=459b0eb1&lang.css, /src/views/sample/SampleRegister.vue
10:53:32 [vite] hmr update /src/views/sample/SampleRegister.vue?vue&type=style&index=0&scoped=459b0eb1&lang.css, /src/views/sample/SampleRegister.vue
10:54:24 [vite] hmr update /src/views/sample/SampleRegister.vue?vue&type=style&index=0&scoped=459b0eb1&lang.css, /src/views/sample/SampleRegister.vue
13:52:24 [vite] hmr update /src/components/Sidebar.vue
13:52:24 [vite] page reload src/router/index.js
13:52:24 [vite] hmr update /src/views/sample/SampleQuarantine.vue
14:01:25 [vite] hmr update /src/views/lab/SampleTest.vue, /src/views/lab/SampleTest.vue?vue&type=style&index=0&scoped=d5758afb&lang.css
14:08:15 [vite] hmr update /src/views/lab/SampleTest.vue?vue&type=style&index=0&scoped=d5758afb&lang.css, /src/views/lab/SampleTest.vue
14:08:35 [vite] hmr update /src/components/DatePicker.vue?vue&type=style&index=0&scoped=877fe5b5&lang.css
14:23:57 [vite] hmr update /src/views/lab/SampleTest.vue
14:24:10 [vite] hmr update /src/views/lab/SampleTest.vue?vue&type=style&index=0&scoped=d5758afb&lang.css, /src/views/lab/SampleTest.vue
14:29:05 [vite] hmr update /src/views/lab/SampleTest.vue
14:29:18 [vite] hmr update /src/views/lab/SampleTest.vue?vue&type=style&index=0&scoped=d5758afb&lang.css, /src/views/lab/SampleTest.vue
14:33:19 [vite] hmr update /src/views/lab/SampleTest.vue
14:35:49 [vite] hmr update /src/views/lab/SampleTest.vue?vue&type=style&index=0&scoped=d5758afb&lang.css
14:38:14 [vite] hmr update /src/views/lab/SampleTest.vue, /src/views/lab/SampleTest.vue?vue&type=style&index=0&scoped=d5758afb&lang.css
14:41:10 [vite] hmr update /src/views/lab/ExperimentTest.vue?vue&type=style&index=0&scoped=839bf691&lang.css
14:41:10 [vite] hmr update /src/views/lab/SampleTest.vue?vue&type=style&index=0&scoped=d5758afb&lang.css, /src/views/lab/SampleTest.vue
14:41:10 [vite] hmr update /src/views/sample/SampleRegister.vue?vue&type=style&index=0&scoped=459b0eb1&lang.css, /src/views/sample/SampleRegister.vue
14:43:46 [vite] hmr update /src/views/lab/ExperimentTest.vue
Markdown 格式
0% 或
您添加了 0 人 到此讨论。请谨慎行事。
请先完成此评论的编辑!
请 注册 或者 后发表评论