修改成vue3版本

This commit is contained in:
徐星 2026-03-31 15:49:42 +08:00
parent 9e547e3b49
commit ddb4540968
78 changed files with 6635 additions and 4221 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules

2
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,2 @@
{
}

571
Wenner-ABMN-E60.csv Normal file
View File

@ -0,0 +1,571 @@
A,B,M,N,K,stack,Layer,x,Depth
1.0,4.0,2.0,3.0,6.283185307179586,1.0,1.0,2.5,-0.6
1.0,7.0,3.0,5.0,12.566370614359172,1.0,2.0,4.0,-1.2
1.0,10.0,4.0,7.0,18.84955592153876,1.0,3.0,5.5,-1.8
1.0,13.0,5.0,9.0,25.132741228718345,1.0,4.0,7.0,-2.4
1.0,16.0,6.0,11.0,31.41592653589793,1.0,5.0,8.5,-3.0
1.0,19.0,7.0,13.0,37.69911184307752,1.0,6.0,10.0,-3.6
1.0,22.0,8.0,15.0,43.982297150257104,1.0,7.0,11.5,-4.2
1.0,25.0,9.0,17.0,50.26548245743669,1.0,8.0,13.0,-4.8
1.0,28.0,10.0,19.0,56.548667764616276,1.0,9.0,14.5,-5.4
1.0,31.0,11.0,21.0,62.83185307179586,1.0,10.0,16.0,-6.0
1.0,34.0,12.0,23.0,69.11503837897544,1.0,11.0,17.5,-6.6
1.0,37.0,13.0,25.0,75.39822368615503,1.0,12.0,19.0,-7.2
1.0,40.0,14.0,27.0,81.68140899333461,1.0,13.0,20.5,-7.8
1.0,43.0,15.0,29.0,87.96459430051421,1.0,14.0,22.0,-8.4
1.0,46.0,16.0,31.0,94.2477796076938,1.0,15.0,23.5,-9.0
1.0,49.0,17.0,33.0,100.53096491487338,1.0,16.0,25.0,-9.6
1.0,52.0,18.0,35.0,106.81415022205297,1.0,17.0,26.5,-10.2
1.0,55.0,19.0,37.0,113.09733552923255,1.0,18.0,28.0,-10.8
1.0,58.0,20.0,39.0,119.38052083641215,1.0,19.0,29.5,-11.4
2.0,5.0,3.0,4.0,6.283185307179586,1.0,1.0,3.5,-0.6
2.0,8.0,4.0,6.0,12.566370614359172,1.0,2.0,5.0,-1.2
2.0,11.0,5.0,8.0,18.84955592153876,1.0,3.0,6.5,-1.8
2.0,14.0,6.0,10.0,25.132741228718345,1.0,4.0,8.0,-2.4
2.0,17.0,7.0,12.0,31.41592653589793,1.0,5.0,9.5,-3.0
2.0,20.0,8.0,14.0,37.69911184307752,1.0,6.0,11.0,-3.6
2.0,23.0,9.0,16.0,43.982297150257104,1.0,7.0,12.5,-4.2
2.0,26.0,10.0,18.0,50.26548245743669,1.0,8.0,14.0,-4.8
2.0,29.0,11.0,20.0,56.548667764616276,1.0,9.0,15.5,-5.4
2.0,32.0,12.0,22.0,62.83185307179586,1.0,10.0,17.0,-6.0
2.0,35.0,13.0,24.0,69.11503837897544,1.0,11.0,18.5,-6.6
2.0,38.0,14.0,26.0,75.39822368615503,1.0,12.0,20.0,-7.2
2.0,41.0,15.0,28.0,81.68140899333461,1.0,13.0,21.5,-7.8
2.0,44.0,16.0,30.0,87.96459430051421,1.0,14.0,23.0,-8.4
2.0,47.0,17.0,32.0,94.2477796076938,1.0,15.0,24.5,-9.0
2.0,50.0,18.0,34.0,100.53096491487338,1.0,16.0,26.0,-9.6
2.0,53.0,19.0,36.0,106.81415022205297,1.0,17.0,27.5,-10.2
2.0,56.0,20.0,38.0,113.09733552923255,1.0,18.0,29.0,-10.8
2.0,59.0,21.0,40.0,119.38052083641215,1.0,19.0,30.5,-11.4
3.0,6.0,4.0,5.0,6.283185307179586,1.0,1.0,4.5,-0.6
3.0,9.0,5.0,7.0,12.566370614359172,1.0,2.0,6.0,-1.2
3.0,12.0,6.0,9.0,18.84955592153876,1.0,3.0,7.5,-1.8
3.0,15.0,7.0,11.0,25.132741228718345,1.0,4.0,9.0,-2.4
3.0,18.0,8.0,13.0,31.41592653589793,1.0,5.0,10.5,-3.0
3.0,21.0,9.0,15.0,37.69911184307752,1.0,6.0,12.0,-3.6
3.0,24.0,10.0,17.0,43.982297150257104,1.0,7.0,13.5,-4.2
3.0,27.0,11.0,19.0,50.26548245743669,1.0,8.0,15.0,-4.8
3.0,30.0,12.0,21.0,56.548667764616276,1.0,9.0,16.5,-5.4
3.0,33.0,13.0,23.0,62.83185307179586,1.0,10.0,18.0,-6.0
3.0,36.0,14.0,25.0,69.11503837897544,1.0,11.0,19.5,-6.6
3.0,39.0,15.0,27.0,75.39822368615503,1.0,12.0,21.0,-7.2
3.0,42.0,16.0,29.0,81.68140899333461,1.0,13.0,22.5,-7.8
3.0,45.0,17.0,31.0,87.96459430051421,1.0,14.0,24.0,-8.4
3.0,48.0,18.0,33.0,94.2477796076938,1.0,15.0,25.5,-9.0
3.0,51.0,19.0,35.0,100.53096491487338,1.0,16.0,27.0,-9.6
3.0,54.0,20.0,37.0,106.81415022205297,1.0,17.0,28.5,-10.2
3.0,57.0,21.0,39.0,113.09733552923255,1.0,18.0,30.0,-10.8
3.0,60.0,22.0,41.0,119.38052083641215,1.0,19.0,31.5,-11.4
4.0,7.0,5.0,6.0,6.283185307179586,1.0,1.0,5.5,-0.6
4.0,10.0,6.0,8.0,12.566370614359172,1.0,2.0,7.0,-1.2
4.0,13.0,7.0,10.0,18.84955592153876,1.0,3.0,8.5,-1.8
4.0,16.0,8.0,12.0,25.132741228718345,1.0,4.0,10.0,-2.4
4.0,19.0,9.0,14.0,31.41592653589793,1.0,5.0,11.5,-3.0
4.0,22.0,10.0,16.0,37.69911184307752,1.0,6.0,13.0,-3.6
4.0,25.0,11.0,18.0,43.982297150257104,1.0,7.0,14.5,-4.2
4.0,28.0,12.0,20.0,50.26548245743669,1.0,8.0,16.0,-4.8
4.0,31.0,13.0,22.0,56.548667764616276,1.0,9.0,17.5,-5.4
4.0,34.0,14.0,24.0,62.83185307179586,1.0,10.0,19.0,-6.0
4.0,37.0,15.0,26.0,69.11503837897544,1.0,11.0,20.5,-6.6
4.0,40.0,16.0,28.0,75.39822368615503,1.0,12.0,22.0,-7.2
4.0,43.0,17.0,30.0,81.68140899333461,1.0,13.0,23.5,-7.8
4.0,46.0,18.0,32.0,87.96459430051421,1.0,14.0,25.0,-8.4
4.0,49.0,19.0,34.0,94.2477796076938,1.0,15.0,26.5,-9.0
4.0,52.0,20.0,36.0,100.53096491487338,1.0,16.0,28.0,-9.6
4.0,55.0,21.0,38.0,106.81415022205297,1.0,17.0,29.5,-10.2
4.0,58.0,22.0,40.0,113.09733552923255,1.0,18.0,31.0,-10.8
5.0,8.0,6.0,7.0,6.283185307179586,1.0,1.0,6.5,-0.6
5.0,11.0,7.0,9.0,12.566370614359172,1.0,2.0,8.0,-1.2
5.0,14.0,8.0,11.0,18.84955592153876,1.0,3.0,9.5,-1.8
5.0,17.0,9.0,13.0,25.132741228718345,1.0,4.0,11.0,-2.4
5.0,20.0,10.0,15.0,31.41592653589793,1.0,5.0,12.5,-3.0
5.0,23.0,11.0,17.0,37.69911184307752,1.0,6.0,14.0,-3.6
5.0,26.0,12.0,19.0,43.982297150257104,1.0,7.0,15.5,-4.2
5.0,29.0,13.0,21.0,50.26548245743669,1.0,8.0,17.0,-4.8
5.0,32.0,14.0,23.0,56.548667764616276,1.0,9.0,18.5,-5.4
5.0,35.0,15.0,25.0,62.83185307179586,1.0,10.0,20.0,-6.0
5.0,38.0,16.0,27.0,69.11503837897544,1.0,11.0,21.5,-6.6
5.0,41.0,17.0,29.0,75.39822368615503,1.0,12.0,23.0,-7.2
5.0,44.0,18.0,31.0,81.68140899333461,1.0,13.0,24.5,-7.8
5.0,47.0,19.0,33.0,87.96459430051421,1.0,14.0,26.0,-8.4
5.0,50.0,20.0,35.0,94.2477796076938,1.0,15.0,27.5,-9.0
5.0,53.0,21.0,37.0,100.53096491487338,1.0,16.0,29.0,-9.6
5.0,56.0,22.0,39.0,106.81415022205297,1.0,17.0,30.5,-10.2
5.0,59.0,23.0,41.0,113.09733552923255,1.0,18.0,32.0,-10.8
6.0,9.0,7.0,8.0,6.283185307179586,1.0,1.0,7.5,-0.6
6.0,12.0,8.0,10.0,12.566370614359172,1.0,2.0,9.0,-1.2
6.0,15.0,9.0,12.0,18.84955592153876,1.0,3.0,10.5,-1.8
6.0,18.0,10.0,14.0,25.132741228718345,1.0,4.0,12.0,-2.4
6.0,21.0,11.0,16.0,31.41592653589793,1.0,5.0,13.5,-3.0
6.0,24.0,12.0,18.0,37.69911184307752,1.0,6.0,15.0,-3.6
6.0,27.0,13.0,20.0,43.982297150257104,1.0,7.0,16.5,-4.2
6.0,30.0,14.0,22.0,50.26548245743669,1.0,8.0,18.0,-4.8
6.0,33.0,15.0,24.0,56.548667764616276,1.0,9.0,19.5,-5.4
6.0,36.0,16.0,26.0,62.83185307179586,1.0,10.0,21.0,-6.0
6.0,39.0,17.0,28.0,69.11503837897544,1.0,11.0,22.5,-6.6
6.0,42.0,18.0,30.0,75.39822368615503,1.0,12.0,24.0,-7.2
6.0,45.0,19.0,32.0,81.68140899333461,1.0,13.0,25.5,-7.8
6.0,48.0,20.0,34.0,87.96459430051421,1.0,14.0,27.0,-8.4
6.0,51.0,21.0,36.0,94.2477796076938,1.0,15.0,28.5,-9.0
6.0,54.0,22.0,38.0,100.53096491487338,1.0,16.0,30.0,-9.6
6.0,57.0,23.0,40.0,106.81415022205297,1.0,17.0,31.5,-10.2
6.0,60.0,24.0,42.0,113.09733552923255,1.0,18.0,33.0,-10.8
7.0,10.0,8.0,9.0,6.283185307179586,1.0,1.0,8.5,-0.6
7.0,13.0,9.0,11.0,12.566370614359172,1.0,2.0,10.0,-1.2
7.0,16.0,10.0,13.0,18.84955592153876,1.0,3.0,11.5,-1.8
7.0,19.0,11.0,15.0,25.132741228718345,1.0,4.0,13.0,-2.4
7.0,22.0,12.0,17.0,31.41592653589793,1.0,5.0,14.5,-3.0
7.0,25.0,13.0,19.0,37.69911184307752,1.0,6.0,16.0,-3.6
7.0,28.0,14.0,21.0,43.982297150257104,1.0,7.0,17.5,-4.2
7.0,31.0,15.0,23.0,50.26548245743669,1.0,8.0,19.0,-4.8
7.0,34.0,16.0,25.0,56.548667764616276,1.0,9.0,20.5,-5.4
7.0,37.0,17.0,27.0,62.83185307179586,1.0,10.0,22.0,-6.0
7.0,40.0,18.0,29.0,69.11503837897544,1.0,11.0,23.5,-6.6
7.0,43.0,19.0,31.0,75.39822368615503,1.0,12.0,25.0,-7.2
7.0,46.0,20.0,33.0,81.68140899333461,1.0,13.0,26.5,-7.8
7.0,49.0,21.0,35.0,87.96459430051421,1.0,14.0,28.0,-8.4
7.0,52.0,22.0,37.0,94.2477796076938,1.0,15.0,29.5,-9.0
7.0,55.0,23.0,39.0,100.53096491487338,1.0,16.0,31.0,-9.6
7.0,58.0,24.0,41.0,106.81415022205297,1.0,17.0,32.5,-10.2
8.0,11.0,9.0,10.0,6.283185307179586,1.0,1.0,9.5,-0.6
8.0,14.0,10.0,12.0,12.566370614359172,1.0,2.0,11.0,-1.2
8.0,17.0,11.0,14.0,18.84955592153876,1.0,3.0,12.5,-1.8
8.0,20.0,12.0,16.0,25.132741228718345,1.0,4.0,14.0,-2.4
8.0,23.0,13.0,18.0,31.41592653589793,1.0,5.0,15.5,-3.0
8.0,26.0,14.0,20.0,37.69911184307752,1.0,6.0,17.0,-3.6
8.0,29.0,15.0,22.0,43.982297150257104,1.0,7.0,18.5,-4.2
8.0,32.0,16.0,24.0,50.26548245743669,1.0,8.0,20.0,-4.8
8.0,35.0,17.0,26.0,56.548667764616276,1.0,9.0,21.5,-5.4
8.0,38.0,18.0,28.0,62.83185307179586,1.0,10.0,23.0,-6.0
8.0,41.0,19.0,30.0,69.11503837897544,1.0,11.0,24.5,-6.6
8.0,44.0,20.0,32.0,75.39822368615503,1.0,12.0,26.0,-7.2
8.0,47.0,21.0,34.0,81.68140899333461,1.0,13.0,27.5,-7.8
8.0,50.0,22.0,36.0,87.96459430051421,1.0,14.0,29.0,-8.4
8.0,53.0,23.0,38.0,94.2477796076938,1.0,15.0,30.5,-9.0
8.0,56.0,24.0,40.0,100.53096491487338,1.0,16.0,32.0,-9.6
8.0,59.0,25.0,42.0,106.81415022205297,1.0,17.0,33.5,-10.2
9.0,12.0,10.0,11.0,6.283185307179586,1.0,1.0,10.5,-0.6
9.0,15.0,11.0,13.0,12.566370614359172,1.0,2.0,12.0,-1.2
9.0,18.0,12.0,15.0,18.84955592153876,1.0,3.0,13.5,-1.8
9.0,21.0,13.0,17.0,25.132741228718345,1.0,4.0,15.0,-2.4
9.0,24.0,14.0,19.0,31.41592653589793,1.0,5.0,16.5,-3.0
9.0,27.0,15.0,21.0,37.69911184307752,1.0,6.0,18.0,-3.6
9.0,30.0,16.0,23.0,43.982297150257104,1.0,7.0,19.5,-4.2
9.0,33.0,17.0,25.0,50.26548245743669,1.0,8.0,21.0,-4.8
9.0,36.0,18.0,27.0,56.548667764616276,1.0,9.0,22.5,-5.4
9.0,39.0,19.0,29.0,62.83185307179586,1.0,10.0,24.0,-6.0
9.0,42.0,20.0,31.0,69.11503837897544,1.0,11.0,25.5,-6.6
9.0,45.0,21.0,33.0,75.39822368615503,1.0,12.0,27.0,-7.2
9.0,48.0,22.0,35.0,81.68140899333461,1.0,13.0,28.5,-7.8
9.0,51.0,23.0,37.0,87.96459430051421,1.0,14.0,30.0,-8.4
9.0,54.0,24.0,39.0,94.2477796076938,1.0,15.0,31.5,-9.0
9.0,57.0,25.0,41.0,100.53096491487338,1.0,16.0,33.0,-9.6
9.0,60.0,26.0,43.0,106.81415022205297,1.0,17.0,34.5,-10.2
10.0,13.0,11.0,12.0,6.283185307179586,1.0,1.0,11.5,-0.6
10.0,16.0,12.0,14.0,12.566370614359172,1.0,2.0,13.0,-1.2
10.0,19.0,13.0,16.0,18.84955592153876,1.0,3.0,14.5,-1.8
10.0,22.0,14.0,18.0,25.132741228718345,1.0,4.0,16.0,-2.4
10.0,25.0,15.0,20.0,31.41592653589793,1.0,5.0,17.5,-3.0
10.0,28.0,16.0,22.0,37.69911184307752,1.0,6.0,19.0,-3.6
10.0,31.0,17.0,24.0,43.982297150257104,1.0,7.0,20.5,-4.2
10.0,34.0,18.0,26.0,50.26548245743669,1.0,8.0,22.0,-4.8
10.0,37.0,19.0,28.0,56.548667764616276,1.0,9.0,23.5,-5.4
10.0,40.0,20.0,30.0,62.83185307179586,1.0,10.0,25.0,-6.0
10.0,43.0,21.0,32.0,69.11503837897544,1.0,11.0,26.5,-6.6
10.0,46.0,22.0,34.0,75.39822368615503,1.0,12.0,28.0,-7.2
10.0,49.0,23.0,36.0,81.68140899333461,1.0,13.0,29.5,-7.8
10.0,52.0,24.0,38.0,87.96459430051421,1.0,14.0,31.0,-8.4
10.0,55.0,25.0,40.0,94.2477796076938,1.0,15.0,32.5,-9.0
10.0,58.0,26.0,42.0,100.53096491487338,1.0,16.0,34.0,-9.6
11.0,14.0,12.0,13.0,6.283185307179586,1.0,1.0,12.5,-0.6
11.0,17.0,13.0,15.0,12.566370614359172,1.0,2.0,14.0,-1.2
11.0,20.0,14.0,17.0,18.84955592153876,1.0,3.0,15.5,-1.8
11.0,23.0,15.0,19.0,25.132741228718345,1.0,4.0,17.0,-2.4
11.0,26.0,16.0,21.0,31.41592653589793,1.0,5.0,18.5,-3.0
11.0,29.0,17.0,23.0,37.69911184307752,1.0,6.0,20.0,-3.6
11.0,32.0,18.0,25.0,43.982297150257104,1.0,7.0,21.5,-4.2
11.0,35.0,19.0,27.0,50.26548245743669,1.0,8.0,23.0,-4.8
11.0,38.0,20.0,29.0,56.548667764616276,1.0,9.0,24.5,-5.4
11.0,41.0,21.0,31.0,62.83185307179586,1.0,10.0,26.0,-6.0
11.0,44.0,22.0,33.0,69.11503837897544,1.0,11.0,27.5,-6.6
11.0,47.0,23.0,35.0,75.39822368615503,1.0,12.0,29.0,-7.2
11.0,50.0,24.0,37.0,81.68140899333461,1.0,13.0,30.5,-7.8
11.0,53.0,25.0,39.0,87.96459430051421,1.0,14.0,32.0,-8.4
11.0,56.0,26.0,41.0,94.2477796076938,1.0,15.0,33.5,-9.0
11.0,59.0,27.0,43.0,100.53096491487338,1.0,16.0,35.0,-9.6
12.0,15.0,13.0,14.0,6.283185307179586,1.0,1.0,13.5,-0.6
12.0,18.0,14.0,16.0,12.566370614359172,1.0,2.0,15.0,-1.2
12.0,21.0,15.0,18.0,18.84955592153876,1.0,3.0,16.5,-1.8
12.0,24.0,16.0,20.0,25.132741228718345,1.0,4.0,18.0,-2.4
12.0,27.0,17.0,22.0,31.41592653589793,1.0,5.0,19.5,-3.0
12.0,30.0,18.0,24.0,37.69911184307752,1.0,6.0,21.0,-3.6
12.0,33.0,19.0,26.0,43.982297150257104,1.0,7.0,22.5,-4.2
12.0,36.0,20.0,28.0,50.26548245743669,1.0,8.0,24.0,-4.8
12.0,39.0,21.0,30.0,56.548667764616276,1.0,9.0,25.5,-5.4
12.0,42.0,22.0,32.0,62.83185307179586,1.0,10.0,27.0,-6.0
12.0,45.0,23.0,34.0,69.11503837897544,1.0,11.0,28.5,-6.6
12.0,48.0,24.0,36.0,75.39822368615503,1.0,12.0,30.0,-7.2
12.0,51.0,25.0,38.0,81.68140899333461,1.0,13.0,31.5,-7.8
12.0,54.0,26.0,40.0,87.96459430051421,1.0,14.0,33.0,-8.4
12.0,57.0,27.0,42.0,94.2477796076938,1.0,15.0,34.5,-9.0
12.0,60.0,28.0,44.0,100.53096491487338,1.0,16.0,36.0,-9.6
13.0,16.0,14.0,15.0,6.283185307179586,1.0,1.0,14.5,-0.6
13.0,19.0,15.0,17.0,12.566370614359172,1.0,2.0,16.0,-1.2
13.0,22.0,16.0,19.0,18.84955592153876,1.0,3.0,17.5,-1.8
13.0,25.0,17.0,21.0,25.132741228718345,1.0,4.0,19.0,-2.4
13.0,28.0,18.0,23.0,31.41592653589793,1.0,5.0,20.5,-3.0
13.0,31.0,19.0,25.0,37.69911184307752,1.0,6.0,22.0,-3.6
13.0,34.0,20.0,27.0,43.982297150257104,1.0,7.0,23.5,-4.2
13.0,37.0,21.0,29.0,50.26548245743669,1.0,8.0,25.0,-4.8
13.0,40.0,22.0,31.0,56.548667764616276,1.0,9.0,26.5,-5.4
13.0,43.0,23.0,33.0,62.83185307179586,1.0,10.0,28.0,-6.0
13.0,46.0,24.0,35.0,69.11503837897544,1.0,11.0,29.5,-6.6
13.0,49.0,25.0,37.0,75.39822368615503,1.0,12.0,31.0,-7.2
13.0,52.0,26.0,39.0,81.68140899333461,1.0,13.0,32.5,-7.8
13.0,55.0,27.0,41.0,87.96459430051421,1.0,14.0,34.0,-8.4
13.0,58.0,28.0,43.0,94.2477796076938,1.0,15.0,35.5,-9.0
14.0,17.0,15.0,16.0,6.283185307179586,1.0,1.0,15.5,-0.6
14.0,20.0,16.0,18.0,12.566370614359172,1.0,2.0,17.0,-1.2
14.0,23.0,17.0,20.0,18.84955592153876,1.0,3.0,18.5,-1.8
14.0,26.0,18.0,22.0,25.132741228718345,1.0,4.0,20.0,-2.4
14.0,29.0,19.0,24.0,31.41592653589793,1.0,5.0,21.5,-3.0
14.0,32.0,20.0,26.0,37.69911184307752,1.0,6.0,23.0,-3.6
14.0,35.0,21.0,28.0,43.982297150257104,1.0,7.0,24.5,-4.2
14.0,38.0,22.0,30.0,50.26548245743669,1.0,8.0,26.0,-4.8
14.0,41.0,23.0,32.0,56.548667764616276,1.0,9.0,27.5,-5.4
14.0,44.0,24.0,34.0,62.83185307179586,1.0,10.0,29.0,-6.0
14.0,47.0,25.0,36.0,69.11503837897544,1.0,11.0,30.5,-6.6
14.0,50.0,26.0,38.0,75.39822368615503,1.0,12.0,32.0,-7.2
14.0,53.0,27.0,40.0,81.68140899333461,1.0,13.0,33.5,-7.8
14.0,56.0,28.0,42.0,87.96459430051421,1.0,14.0,35.0,-8.4
14.0,59.0,29.0,44.0,94.2477796076938,1.0,15.0,36.5,-9.0
15.0,18.0,16.0,17.0,6.283185307179586,1.0,1.0,16.5,-0.6
15.0,21.0,17.0,19.0,12.566370614359172,1.0,2.0,18.0,-1.2
15.0,24.0,18.0,21.0,18.84955592153876,1.0,3.0,19.5,-1.8
15.0,27.0,19.0,23.0,25.132741228718345,1.0,4.0,21.0,-2.4
15.0,30.0,20.0,25.0,31.41592653589793,1.0,5.0,22.5,-3.0
15.0,33.0,21.0,27.0,37.69911184307752,1.0,6.0,24.0,-3.6
15.0,36.0,22.0,29.0,43.982297150257104,1.0,7.0,25.5,-4.2
15.0,39.0,23.0,31.0,50.26548245743669,1.0,8.0,27.0,-4.8
15.0,42.0,24.0,33.0,56.548667764616276,1.0,9.0,28.5,-5.4
15.0,45.0,25.0,35.0,62.83185307179586,1.0,10.0,30.0,-6.0
15.0,48.0,26.0,37.0,69.11503837897544,1.0,11.0,31.5,-6.6
15.0,51.0,27.0,39.0,75.39822368615503,1.0,12.0,33.0,-7.2
15.0,54.0,28.0,41.0,81.68140899333461,1.0,13.0,34.5,-7.8
15.0,57.0,29.0,43.0,87.96459430051421,1.0,14.0,36.0,-8.4
15.0,60.0,30.0,45.0,94.2477796076938,1.0,15.0,37.5,-9.0
16.0,19.0,17.0,18.0,6.283185307179586,1.0,1.0,17.5,-0.6
16.0,22.0,18.0,20.0,12.566370614359172,1.0,2.0,19.0,-1.2
16.0,25.0,19.0,22.0,18.84955592153876,1.0,3.0,20.5,-1.8
16.0,28.0,20.0,24.0,25.132741228718345,1.0,4.0,22.0,-2.4
16.0,31.0,21.0,26.0,31.41592653589793,1.0,5.0,23.5,-3.0
16.0,34.0,22.0,28.0,37.69911184307752,1.0,6.0,25.0,-3.6
16.0,37.0,23.0,30.0,43.982297150257104,1.0,7.0,26.5,-4.2
16.0,40.0,24.0,32.0,50.26548245743669,1.0,8.0,28.0,-4.8
16.0,43.0,25.0,34.0,56.548667764616276,1.0,9.0,29.5,-5.4
16.0,46.0,26.0,36.0,62.83185307179586,1.0,10.0,31.0,-6.0
16.0,49.0,27.0,38.0,69.11503837897544,1.0,11.0,32.5,-6.6
16.0,52.0,28.0,40.0,75.39822368615503,1.0,12.0,34.0,-7.2
16.0,55.0,29.0,42.0,81.68140899333461,1.0,13.0,35.5,-7.8
16.0,58.0,30.0,44.0,87.96459430051421,1.0,14.0,37.0,-8.4
17.0,20.0,18.0,19.0,6.283185307179586,1.0,1.0,18.5,-0.6
17.0,23.0,19.0,21.0,12.566370614359172,1.0,2.0,20.0,-1.2
17.0,26.0,20.0,23.0,18.84955592153876,1.0,3.0,21.5,-1.8
17.0,29.0,21.0,25.0,25.132741228718345,1.0,4.0,23.0,-2.4
17.0,32.0,22.0,27.0,31.41592653589793,1.0,5.0,24.5,-3.0
17.0,35.0,23.0,29.0,37.69911184307752,1.0,6.0,26.0,-3.6
17.0,38.0,24.0,31.0,43.982297150257104,1.0,7.0,27.5,-4.2
17.0,41.0,25.0,33.0,50.26548245743669,1.0,8.0,29.0,-4.8
17.0,44.0,26.0,35.0,56.548667764616276,1.0,9.0,30.5,-5.4
17.0,47.0,27.0,37.0,62.83185307179586,1.0,10.0,32.0,-6.0
17.0,50.0,28.0,39.0,69.11503837897544,1.0,11.0,33.5,-6.6
17.0,53.0,29.0,41.0,75.39822368615503,1.0,12.0,35.0,-7.2
17.0,56.0,30.0,43.0,81.68140899333461,1.0,13.0,36.5,-7.8
17.0,59.0,31.0,45.0,87.96459430051421,1.0,14.0,38.0,-8.4
18.0,21.0,19.0,20.0,6.283185307179586,1.0,1.0,19.5,-0.6
18.0,24.0,20.0,22.0,12.566370614359172,1.0,2.0,21.0,-1.2
18.0,27.0,21.0,24.0,18.84955592153876,1.0,3.0,22.5,-1.8
18.0,30.0,22.0,26.0,25.132741228718345,1.0,4.0,24.0,-2.4
18.0,33.0,23.0,28.0,31.41592653589793,1.0,5.0,25.5,-3.0
18.0,36.0,24.0,30.0,37.69911184307752,1.0,6.0,27.0,-3.6
18.0,39.0,25.0,32.0,43.982297150257104,1.0,7.0,28.5,-4.2
18.0,42.0,26.0,34.0,50.26548245743669,1.0,8.0,30.0,-4.8
18.0,45.0,27.0,36.0,56.548667764616276,1.0,9.0,31.5,-5.4
18.0,48.0,28.0,38.0,62.83185307179586,1.0,10.0,33.0,-6.0
18.0,51.0,29.0,40.0,69.11503837897544,1.0,11.0,34.5,-6.6
18.0,54.0,30.0,42.0,75.39822368615503,1.0,12.0,36.0,-7.2
18.0,57.0,31.0,44.0,81.68140899333461,1.0,13.0,37.5,-7.8
18.0,60.0,32.0,46.0,87.96459430051421,1.0,14.0,39.0,-8.4
19.0,22.0,20.0,21.0,6.283185307179586,1.0,1.0,20.5,-0.6
19.0,25.0,21.0,23.0,12.566370614359172,1.0,2.0,22.0,-1.2
19.0,28.0,22.0,25.0,18.84955592153876,1.0,3.0,23.5,-1.8
19.0,31.0,23.0,27.0,25.132741228718345,1.0,4.0,25.0,-2.4
19.0,34.0,24.0,29.0,31.41592653589793,1.0,5.0,26.5,-3.0
19.0,37.0,25.0,31.0,37.69911184307752,1.0,6.0,28.0,-3.6
19.0,40.0,26.0,33.0,43.982297150257104,1.0,7.0,29.5,-4.2
19.0,43.0,27.0,35.0,50.26548245743669,1.0,8.0,31.0,-4.8
19.0,46.0,28.0,37.0,56.548667764616276,1.0,9.0,32.5,-5.4
19.0,49.0,29.0,39.0,62.83185307179586,1.0,10.0,34.0,-6.0
19.0,52.0,30.0,41.0,69.11503837897544,1.0,11.0,35.5,-6.6
19.0,55.0,31.0,43.0,75.39822368615503,1.0,12.0,37.0,-7.2
19.0,58.0,32.0,45.0,81.68140899333461,1.0,13.0,38.5,-7.8
20.0,23.0,21.0,22.0,6.283185307179586,1.0,1.0,21.5,-0.6
20.0,26.0,22.0,24.0,12.566370614359172,1.0,2.0,23.0,-1.2
20.0,29.0,23.0,26.0,18.84955592153876,1.0,3.0,24.5,-1.8
20.0,32.0,24.0,28.0,25.132741228718345,1.0,4.0,26.0,-2.4
20.0,35.0,25.0,30.0,31.41592653589793,1.0,5.0,27.5,-3.0
20.0,38.0,26.0,32.0,37.69911184307752,1.0,6.0,29.0,-3.6
20.0,41.0,27.0,34.0,43.982297150257104,1.0,7.0,30.5,-4.2
20.0,44.0,28.0,36.0,50.26548245743669,1.0,8.0,32.0,-4.8
20.0,47.0,29.0,38.0,56.548667764616276,1.0,9.0,33.5,-5.4
20.0,50.0,30.0,40.0,62.83185307179586,1.0,10.0,35.0,-6.0
20.0,53.0,31.0,42.0,69.11503837897544,1.0,11.0,36.5,-6.6
20.0,56.0,32.0,44.0,75.39822368615503,1.0,12.0,38.0,-7.2
20.0,59.0,33.0,46.0,81.68140899333461,1.0,13.0,39.5,-7.8
21.0,24.0,22.0,23.0,6.283185307179586,1.0,1.0,22.5,-0.6
21.0,27.0,23.0,25.0,12.566370614359172,1.0,2.0,24.0,-1.2
21.0,30.0,24.0,27.0,18.84955592153876,1.0,3.0,25.5,-1.8
21.0,33.0,25.0,29.0,25.132741228718345,1.0,4.0,27.0,-2.4
21.0,36.0,26.0,31.0,31.41592653589793,1.0,5.0,28.5,-3.0
21.0,39.0,27.0,33.0,37.69911184307752,1.0,6.0,30.0,-3.6
21.0,42.0,28.0,35.0,43.982297150257104,1.0,7.0,31.5,-4.2
21.0,45.0,29.0,37.0,50.26548245743669,1.0,8.0,33.0,-4.8
21.0,48.0,30.0,39.0,56.548667764616276,1.0,9.0,34.5,-5.4
21.0,51.0,31.0,41.0,62.83185307179586,1.0,10.0,36.0,-6.0
21.0,54.0,32.0,43.0,69.11503837897544,1.0,11.0,37.5,-6.6
21.0,57.0,33.0,45.0,75.39822368615503,1.0,12.0,39.0,-7.2
21.0,60.0,34.0,47.0,81.68140899333461,1.0,13.0,40.5,-7.8
22.0,25.0,23.0,24.0,6.283185307179586,1.0,1.0,23.5,-0.6
22.0,28.0,24.0,26.0,12.566370614359172,1.0,2.0,25.0,-1.2
22.0,31.0,25.0,28.0,18.84955592153876,1.0,3.0,26.5,-1.8
22.0,34.0,26.0,30.0,25.132741228718345,1.0,4.0,28.0,-2.4
22.0,37.0,27.0,32.0,31.41592653589793,1.0,5.0,29.5,-3.0
22.0,40.0,28.0,34.0,37.69911184307752,1.0,6.0,31.0,-3.6
22.0,43.0,29.0,36.0,43.982297150257104,1.0,7.0,32.5,-4.2
22.0,46.0,30.0,38.0,50.26548245743669,1.0,8.0,34.0,-4.8
22.0,49.0,31.0,40.0,56.548667764616276,1.0,9.0,35.5,-5.4
22.0,52.0,32.0,42.0,62.83185307179586,1.0,10.0,37.0,-6.0
22.0,55.0,33.0,44.0,69.11503837897544,1.0,11.0,38.5,-6.6
22.0,58.0,34.0,46.0,75.39822368615503,1.0,12.0,40.0,-7.2
23.0,26.0,24.0,25.0,6.283185307179586,1.0,1.0,24.5,-0.6
23.0,29.0,25.0,27.0,12.566370614359172,1.0,2.0,26.0,-1.2
23.0,32.0,26.0,29.0,18.84955592153876,1.0,3.0,27.5,-1.8
23.0,35.0,27.0,31.0,25.132741228718345,1.0,4.0,29.0,-2.4
23.0,38.0,28.0,33.0,31.41592653589793,1.0,5.0,30.5,-3.0
23.0,41.0,29.0,35.0,37.69911184307752,1.0,6.0,32.0,-3.6
23.0,44.0,30.0,37.0,43.982297150257104,1.0,7.0,33.5,-4.2
23.0,47.0,31.0,39.0,50.26548245743669,1.0,8.0,35.0,-4.8
23.0,50.0,32.0,41.0,56.548667764616276,1.0,9.0,36.5,-5.4
23.0,53.0,33.0,43.0,62.83185307179586,1.0,10.0,38.0,-6.0
23.0,56.0,34.0,45.0,69.11503837897544,1.0,11.0,39.5,-6.6
23.0,59.0,35.0,47.0,75.39822368615503,1.0,12.0,41.0,-7.2
24.0,27.0,25.0,26.0,6.283185307179586,1.0,1.0,25.5,-0.6
24.0,30.0,26.0,28.0,12.566370614359172,1.0,2.0,27.0,-1.2
24.0,33.0,27.0,30.0,18.84955592153876,1.0,3.0,28.5,-1.8
24.0,36.0,28.0,32.0,25.132741228718345,1.0,4.0,30.0,-2.4
24.0,39.0,29.0,34.0,31.41592653589793,1.0,5.0,31.5,-3.0
24.0,42.0,30.0,36.0,37.69911184307752,1.0,6.0,33.0,-3.6
24.0,45.0,31.0,38.0,43.982297150257104,1.0,7.0,34.5,-4.2
24.0,48.0,32.0,40.0,50.26548245743669,1.0,8.0,36.0,-4.8
24.0,51.0,33.0,42.0,56.548667764616276,1.0,9.0,37.5,-5.4
24.0,54.0,34.0,44.0,62.83185307179586,1.0,10.0,39.0,-6.0
24.0,57.0,35.0,46.0,69.11503837897544,1.0,11.0,40.5,-6.6
24.0,60.0,36.0,48.0,75.39822368615503,1.0,12.0,42.0,-7.2
25.0,28.0,26.0,27.0,6.283185307179586,1.0,1.0,26.5,-0.6
25.0,31.0,27.0,29.0,12.566370614359172,1.0,2.0,28.0,-1.2
25.0,34.0,28.0,31.0,18.84955592153876,1.0,3.0,29.5,-1.8
25.0,37.0,29.0,33.0,25.132741228718345,1.0,4.0,31.0,-2.4
25.0,40.0,30.0,35.0,31.41592653589793,1.0,5.0,32.5,-3.0
25.0,43.0,31.0,37.0,37.69911184307752,1.0,6.0,34.0,-3.6
25.0,46.0,32.0,39.0,43.982297150257104,1.0,7.0,35.5,-4.2
25.0,49.0,33.0,41.0,50.26548245743669,1.0,8.0,37.0,-4.8
25.0,52.0,34.0,43.0,56.548667764616276,1.0,9.0,38.5,-5.4
25.0,55.0,35.0,45.0,62.83185307179586,1.0,10.0,40.0,-6.0
25.0,58.0,36.0,47.0,69.11503837897544,1.0,11.0,41.5,-6.6
26.0,29.0,27.0,28.0,6.283185307179586,1.0,1.0,27.5,-0.6
26.0,32.0,28.0,30.0,12.566370614359172,1.0,2.0,29.0,-1.2
26.0,35.0,29.0,32.0,18.84955592153876,1.0,3.0,30.5,-1.8
26.0,38.0,30.0,34.0,25.132741228718345,1.0,4.0,32.0,-2.4
26.0,41.0,31.0,36.0,31.41592653589793,1.0,5.0,33.5,-3.0
26.0,44.0,32.0,38.0,37.69911184307752,1.0,6.0,35.0,-3.6
26.0,47.0,33.0,40.0,43.982297150257104,1.0,7.0,36.5,-4.2
26.0,50.0,34.0,42.0,50.26548245743669,1.0,8.0,38.0,-4.8
26.0,53.0,35.0,44.0,56.548667764616276,1.0,9.0,39.5,-5.4
26.0,56.0,36.0,46.0,62.83185307179586,1.0,10.0,41.0,-6.0
26.0,59.0,37.0,48.0,69.11503837897544,1.0,11.0,42.5,-6.6
27.0,30.0,28.0,29.0,6.283185307179586,1.0,1.0,28.5,-0.6
27.0,33.0,29.0,31.0,12.566370614359172,1.0,2.0,30.0,-1.2
27.0,36.0,30.0,33.0,18.84955592153876,1.0,3.0,31.5,-1.8
27.0,39.0,31.0,35.0,25.132741228718345,1.0,4.0,33.0,-2.4
27.0,42.0,32.0,37.0,31.41592653589793,1.0,5.0,34.5,-3.0
27.0,45.0,33.0,39.0,37.69911184307752,1.0,6.0,36.0,-3.6
27.0,48.0,34.0,41.0,43.982297150257104,1.0,7.0,37.5,-4.2
27.0,51.0,35.0,43.0,50.26548245743669,1.0,8.0,39.0,-4.8
27.0,54.0,36.0,45.0,56.548667764616276,1.0,9.0,40.5,-5.4
27.0,57.0,37.0,47.0,62.83185307179586,1.0,10.0,42.0,-6.0
27.0,60.0,38.0,49.0,69.11503837897544,1.0,11.0,43.5,-6.6
28.0,31.0,29.0,30.0,6.283185307179586,1.0,1.0,29.5,-0.6
28.0,34.0,30.0,32.0,12.566370614359172,1.0,2.0,31.0,-1.2
28.0,37.0,31.0,34.0,18.84955592153876,1.0,3.0,32.5,-1.8
28.0,40.0,32.0,36.0,25.132741228718345,1.0,4.0,34.0,-2.4
28.0,43.0,33.0,38.0,31.41592653589793,1.0,5.0,35.5,-3.0
28.0,46.0,34.0,40.0,37.69911184307752,1.0,6.0,37.0,-3.6
28.0,49.0,35.0,42.0,43.982297150257104,1.0,7.0,38.5,-4.2
28.0,52.0,36.0,44.0,50.26548245743669,1.0,8.0,40.0,-4.8
28.0,55.0,37.0,46.0,56.548667764616276,1.0,9.0,41.5,-5.4
28.0,58.0,38.0,48.0,62.83185307179586,1.0,10.0,43.0,-6.0
29.0,32.0,30.0,31.0,6.283185307179586,1.0,1.0,30.5,-0.6
29.0,35.0,31.0,33.0,12.566370614359172,1.0,2.0,32.0,-1.2
29.0,38.0,32.0,35.0,18.84955592153876,1.0,3.0,33.5,-1.8
29.0,41.0,33.0,37.0,25.132741228718345,1.0,4.0,35.0,-2.4
29.0,44.0,34.0,39.0,31.41592653589793,1.0,5.0,36.5,-3.0
29.0,47.0,35.0,41.0,37.69911184307752,1.0,6.0,38.0,-3.6
29.0,50.0,36.0,43.0,43.982297150257104,1.0,7.0,39.5,-4.2
29.0,53.0,37.0,45.0,50.26548245743669,1.0,8.0,41.0,-4.8
29.0,56.0,38.0,47.0,56.548667764616276,1.0,9.0,42.5,-5.4
29.0,59.0,39.0,49.0,62.83185307179586,1.0,10.0,44.0,-6.0
30.0,33.0,31.0,32.0,6.283185307179586,1.0,1.0,31.5,-0.6
30.0,36.0,32.0,34.0,12.566370614359172,1.0,2.0,33.0,-1.2
30.0,39.0,33.0,36.0,18.84955592153876,1.0,3.0,34.5,-1.8
30.0,42.0,34.0,38.0,25.132741228718345,1.0,4.0,36.0,-2.4
30.0,45.0,35.0,40.0,31.41592653589793,1.0,5.0,37.5,-3.0
30.0,48.0,36.0,42.0,37.69911184307752,1.0,6.0,39.0,-3.6
30.0,51.0,37.0,44.0,43.982297150257104,1.0,7.0,40.5,-4.2
30.0,54.0,38.0,46.0,50.26548245743669,1.0,8.0,42.0,-4.8
30.0,57.0,39.0,48.0,56.548667764616276,1.0,9.0,43.5,-5.4
30.0,60.0,40.0,50.0,62.83185307179586,1.0,10.0,45.0,-6.0
31.0,34.0,32.0,33.0,6.283185307179586,1.0,1.0,32.5,-0.6
31.0,37.0,33.0,35.0,12.566370614359172,1.0,2.0,34.0,-1.2
31.0,40.0,34.0,37.0,18.84955592153876,1.0,3.0,35.5,-1.8
31.0,43.0,35.0,39.0,25.132741228718345,1.0,4.0,37.0,-2.4
31.0,46.0,36.0,41.0,31.41592653589793,1.0,5.0,38.5,-3.0
31.0,49.0,37.0,43.0,37.69911184307752,1.0,6.0,40.0,-3.6
31.0,52.0,38.0,45.0,43.982297150257104,1.0,7.0,41.5,-4.2
31.0,55.0,39.0,47.0,50.26548245743669,1.0,8.0,43.0,-4.8
31.0,58.0,40.0,49.0,56.548667764616276,1.0,9.0,44.5,-5.4
32.0,35.0,33.0,34.0,6.283185307179586,1.0,1.0,33.5,-0.6
32.0,38.0,34.0,36.0,12.566370614359172,1.0,2.0,35.0,-1.2
32.0,41.0,35.0,38.0,18.84955592153876,1.0,3.0,36.5,-1.8
32.0,44.0,36.0,40.0,25.132741228718345,1.0,4.0,38.0,-2.4
32.0,47.0,37.0,42.0,31.41592653589793,1.0,5.0,39.5,-3.0
32.0,50.0,38.0,44.0,37.69911184307752,1.0,6.0,41.0,-3.6
32.0,53.0,39.0,46.0,43.982297150257104,1.0,7.0,42.5,-4.2
32.0,56.0,40.0,48.0,50.26548245743669,1.0,8.0,44.0,-4.8
32.0,59.0,41.0,50.0,56.548667764616276,1.0,9.0,45.5,-5.4
33.0,36.0,34.0,35.0,6.283185307179586,1.0,1.0,34.5,-0.6
33.0,39.0,35.0,37.0,12.566370614359172,1.0,2.0,36.0,-1.2
33.0,42.0,36.0,39.0,18.84955592153876,1.0,3.0,37.5,-1.8
33.0,45.0,37.0,41.0,25.132741228718345,1.0,4.0,39.0,-2.4
33.0,48.0,38.0,43.0,31.41592653589793,1.0,5.0,40.5,-3.0
33.0,51.0,39.0,45.0,37.69911184307752,1.0,6.0,42.0,-3.6
33.0,54.0,40.0,47.0,43.982297150257104,1.0,7.0,43.5,-4.2
33.0,57.0,41.0,49.0,50.26548245743669,1.0,8.0,45.0,-4.8
33.0,60.0,42.0,51.0,56.548667764616276,1.0,9.0,46.5,-5.4
34.0,37.0,35.0,36.0,6.283185307179586,1.0,1.0,35.5,-0.6
34.0,40.0,36.0,38.0,12.566370614359172,1.0,2.0,37.0,-1.2
34.0,43.0,37.0,40.0,18.84955592153876,1.0,3.0,38.5,-1.8
34.0,46.0,38.0,42.0,25.132741228718345,1.0,4.0,40.0,-2.4
34.0,49.0,39.0,44.0,31.41592653589793,1.0,5.0,41.5,-3.0
34.0,52.0,40.0,46.0,37.69911184307752,1.0,6.0,43.0,-3.6
34.0,55.0,41.0,48.0,43.982297150257104,1.0,7.0,44.5,-4.2
34.0,58.0,42.0,50.0,50.26548245743669,1.0,8.0,46.0,-4.8
35.0,38.0,36.0,37.0,6.283185307179586,1.0,1.0,36.5,-0.6
35.0,41.0,37.0,39.0,12.566370614359172,1.0,2.0,38.0,-1.2
35.0,44.0,38.0,41.0,18.84955592153876,1.0,3.0,39.5,-1.8
35.0,47.0,39.0,43.0,25.132741228718345,1.0,4.0,41.0,-2.4
35.0,50.0,40.0,45.0,31.41592653589793,1.0,5.0,42.5,-3.0
35.0,53.0,41.0,47.0,37.69911184307752,1.0,6.0,44.0,-3.6
35.0,56.0,42.0,49.0,43.982297150257104,1.0,7.0,45.5,-4.2
35.0,59.0,43.0,51.0,50.26548245743669,1.0,8.0,47.0,-4.8
36.0,39.0,37.0,38.0,6.283185307179586,1.0,1.0,37.5,-0.6
36.0,42.0,38.0,40.0,12.566370614359172,1.0,2.0,39.0,-1.2
36.0,45.0,39.0,42.0,18.84955592153876,1.0,3.0,40.5,-1.8
36.0,48.0,40.0,44.0,25.132741228718345,1.0,4.0,42.0,-2.4
36.0,51.0,41.0,46.0,31.41592653589793,1.0,5.0,43.5,-3.0
36.0,54.0,42.0,48.0,37.69911184307752,1.0,6.0,45.0,-3.6
36.0,57.0,43.0,50.0,43.982297150257104,1.0,7.0,46.5,-4.2
36.0,60.0,44.0,52.0,50.26548245743669,1.0,8.0,48.0,-4.8
37.0,40.0,38.0,39.0,6.283185307179586,1.0,1.0,38.5,-0.6
37.0,43.0,39.0,41.0,12.566370614359172,1.0,2.0,40.0,-1.2
37.0,46.0,40.0,43.0,18.84955592153876,1.0,3.0,41.5,-1.8
37.0,49.0,41.0,45.0,25.132741228718345,1.0,4.0,43.0,-2.4
37.0,52.0,42.0,47.0,31.41592653589793,1.0,5.0,44.5,-3.0
37.0,55.0,43.0,49.0,37.69911184307752,1.0,6.0,46.0,-3.6
37.0,58.0,44.0,51.0,43.982297150257104,1.0,7.0,47.5,-4.2
38.0,41.0,39.0,40.0,6.283185307179586,1.0,1.0,39.5,-0.6
38.0,44.0,40.0,42.0,12.566370614359172,1.0,2.0,41.0,-1.2
38.0,47.0,41.0,44.0,18.84955592153876,1.0,3.0,42.5,-1.8
38.0,50.0,42.0,46.0,25.132741228718345,1.0,4.0,44.0,-2.4
38.0,53.0,43.0,48.0,31.41592653589793,1.0,5.0,45.5,-3.0
38.0,56.0,44.0,50.0,37.69911184307752,1.0,6.0,47.0,-3.6
38.0,59.0,45.0,52.0,43.982297150257104,1.0,7.0,48.5,-4.2
39.0,42.0,40.0,41.0,6.283185307179586,1.0,1.0,40.5,-0.6
39.0,45.0,41.0,43.0,12.566370614359172,1.0,2.0,42.0,-1.2
39.0,48.0,42.0,45.0,18.84955592153876,1.0,3.0,43.5,-1.8
39.0,51.0,43.0,47.0,25.132741228718345,1.0,4.0,45.0,-2.4
39.0,54.0,44.0,49.0,31.41592653589793,1.0,5.0,46.5,-3.0
39.0,57.0,45.0,51.0,37.69911184307752,1.0,6.0,48.0,-3.6
39.0,60.0,46.0,53.0,43.982297150257104,1.0,7.0,49.5,-4.2
40.0,43.0,41.0,42.0,6.283185307179586,1.0,1.0,41.5,-0.6
40.0,46.0,42.0,44.0,12.566370614359172,1.0,2.0,43.0,-1.2
40.0,49.0,43.0,46.0,18.84955592153876,1.0,3.0,44.5,-1.8
40.0,52.0,44.0,48.0,25.132741228718345,1.0,4.0,46.0,-2.4
40.0,55.0,45.0,50.0,31.41592653589793,1.0,5.0,47.5,-3.0
40.0,58.0,46.0,52.0,37.69911184307752,1.0,6.0,49.0,-3.6
41.0,44.0,42.0,43.0,6.283185307179586,1.0,1.0,42.5,-0.6
41.0,47.0,43.0,45.0,12.566370614359172,1.0,2.0,44.0,-1.2
41.0,50.0,44.0,47.0,18.84955592153876,1.0,3.0,45.5,-1.8
41.0,53.0,45.0,49.0,25.132741228718345,1.0,4.0,47.0,-2.4
41.0,56.0,46.0,51.0,31.41592653589793,1.0,5.0,48.5,-3.0
41.0,59.0,47.0,53.0,37.69911184307752,1.0,6.0,50.0,-3.6
42.0,45.0,43.0,44.0,6.283185307179586,1.0,1.0,43.5,-0.6
42.0,48.0,44.0,46.0,12.566370614359172,1.0,2.0,45.0,-1.2
42.0,51.0,45.0,48.0,18.84955592153876,1.0,3.0,46.5,-1.8
42.0,54.0,46.0,50.0,25.132741228718345,1.0,4.0,48.0,-2.4
42.0,57.0,47.0,52.0,31.41592653589793,1.0,5.0,49.5,-3.0
42.0,60.0,48.0,54.0,37.69911184307752,1.0,6.0,51.0,-3.6
43.0,46.0,44.0,45.0,6.283185307179586,1.0,1.0,44.5,-0.6
43.0,49.0,45.0,47.0,12.566370614359172,1.0,2.0,46.0,-1.2
43.0,52.0,46.0,49.0,18.84955592153876,1.0,3.0,47.5,-1.8
43.0,55.0,47.0,51.0,25.132741228718345,1.0,4.0,49.0,-2.4
43.0,58.0,48.0,53.0,31.41592653589793,1.0,5.0,50.5,-3.0
44.0,47.0,45.0,46.0,6.283185307179586,1.0,1.0,45.5,-0.6
44.0,50.0,46.0,48.0,12.566370614359172,1.0,2.0,47.0,-1.2
44.0,53.0,47.0,50.0,18.84955592153876,1.0,3.0,48.5,-1.8
44.0,56.0,48.0,52.0,25.132741228718345,1.0,4.0,50.0,-2.4
44.0,59.0,49.0,54.0,31.41592653589793,1.0,5.0,51.5,-3.0
45.0,48.0,46.0,47.0,6.283185307179586,1.0,1.0,46.5,-0.6
45.0,51.0,47.0,49.0,12.566370614359172,1.0,2.0,48.0,-1.2
45.0,54.0,48.0,51.0,18.84955592153876,1.0,3.0,49.5,-1.8
45.0,57.0,49.0,53.0,25.132741228718345,1.0,4.0,51.0,-2.4
45.0,60.0,50.0,55.0,31.41592653589793,1.0,5.0,52.5,-3.0
46.0,49.0,47.0,48.0,6.283185307179586,1.0,1.0,47.5,-0.6
46.0,52.0,48.0,50.0,12.566370614359172,1.0,2.0,49.0,-1.2
46.0,55.0,49.0,52.0,18.84955592153876,1.0,3.0,50.5,-1.8
46.0,58.0,50.0,54.0,25.132741228718345,1.0,4.0,52.0,-2.4
47.0,50.0,48.0,49.0,6.283185307179586,1.0,1.0,48.5,-0.6
47.0,53.0,49.0,51.0,12.566370614359172,1.0,2.0,50.0,-1.2
47.0,56.0,50.0,53.0,18.84955592153876,1.0,3.0,51.5,-1.8
47.0,59.0,51.0,55.0,25.132741228718345,1.0,4.0,53.0,-2.4
48.0,51.0,49.0,50.0,6.283185307179586,1.0,1.0,49.5,-0.6
48.0,54.0,50.0,52.0,12.566370614359172,1.0,2.0,51.0,-1.2
48.0,57.0,51.0,54.0,18.84955592153876,1.0,3.0,52.5,-1.8
48.0,60.0,52.0,56.0,25.132741228718345,1.0,4.0,54.0,-2.4
49.0,52.0,50.0,51.0,6.283185307179586,1.0,1.0,50.5,-0.6
49.0,55.0,51.0,53.0,12.566370614359172,1.0,2.0,52.0,-1.2
49.0,58.0,52.0,55.0,18.84955592153876,1.0,3.0,53.5,-1.8
50.0,53.0,51.0,52.0,6.283185307179586,1.0,1.0,51.5,-0.6
50.0,56.0,52.0,54.0,12.566370614359172,1.0,2.0,53.0,-1.2
50.0,59.0,53.0,56.0,18.84955592153876,1.0,3.0,54.5,-1.8
51.0,54.0,52.0,53.0,6.283185307179586,1.0,1.0,52.5,-0.6
51.0,57.0,53.0,55.0,12.566370614359172,1.0,2.0,54.0,-1.2
51.0,60.0,54.0,57.0,18.84955592153876,1.0,3.0,55.5,-1.8
52.0,55.0,53.0,54.0,6.283185307179586,1.0,1.0,53.5,-0.6
52.0,58.0,54.0,56.0,12.566370614359172,1.0,2.0,55.0,-1.2
53.0,56.0,54.0,55.0,6.283185307179586,1.0,1.0,54.5,-0.6
53.0,59.0,55.0,57.0,12.566370614359172,1.0,2.0,56.0,-1.2
54.0,57.0,55.0,56.0,6.283185307179586,1.0,1.0,55.5,-0.6
54.0,60.0,56.0,58.0,12.566370614359172,1.0,2.0,57.0,-1.2
55.0,58.0,56.0,57.0,6.283185307179586,1.0,1.0,56.5,-0.6
56.0,59.0,57.0,58.0,6.283185307179586,1.0,1.0,57.5,-0.6
57.0,60.0,58.0,59.0,6.283185307179586,1.0,1.0,58.5,-0.6
1 A B M N K stack Layer x Depth
2 1.0 4.0 2.0 3.0 6.283185307179586 1.0 1.0 2.5 -0.6
3 1.0 7.0 3.0 5.0 12.566370614359172 1.0 2.0 4.0 -1.2
4 1.0 10.0 4.0 7.0 18.84955592153876 1.0 3.0 5.5 -1.8
5 1.0 13.0 5.0 9.0 25.132741228718345 1.0 4.0 7.0 -2.4
6 1.0 16.0 6.0 11.0 31.41592653589793 1.0 5.0 8.5 -3.0
7 1.0 19.0 7.0 13.0 37.69911184307752 1.0 6.0 10.0 -3.6
8 1.0 22.0 8.0 15.0 43.982297150257104 1.0 7.0 11.5 -4.2
9 1.0 25.0 9.0 17.0 50.26548245743669 1.0 8.0 13.0 -4.8
10 1.0 28.0 10.0 19.0 56.548667764616276 1.0 9.0 14.5 -5.4
11 1.0 31.0 11.0 21.0 62.83185307179586 1.0 10.0 16.0 -6.0
12 1.0 34.0 12.0 23.0 69.11503837897544 1.0 11.0 17.5 -6.6
13 1.0 37.0 13.0 25.0 75.39822368615503 1.0 12.0 19.0 -7.2
14 1.0 40.0 14.0 27.0 81.68140899333461 1.0 13.0 20.5 -7.8
15 1.0 43.0 15.0 29.0 87.96459430051421 1.0 14.0 22.0 -8.4
16 1.0 46.0 16.0 31.0 94.2477796076938 1.0 15.0 23.5 -9.0
17 1.0 49.0 17.0 33.0 100.53096491487338 1.0 16.0 25.0 -9.6
18 1.0 52.0 18.0 35.0 106.81415022205297 1.0 17.0 26.5 -10.2
19 1.0 55.0 19.0 37.0 113.09733552923255 1.0 18.0 28.0 -10.8
20 1.0 58.0 20.0 39.0 119.38052083641215 1.0 19.0 29.5 -11.4
21 2.0 5.0 3.0 4.0 6.283185307179586 1.0 1.0 3.5 -0.6
22 2.0 8.0 4.0 6.0 12.566370614359172 1.0 2.0 5.0 -1.2
23 2.0 11.0 5.0 8.0 18.84955592153876 1.0 3.0 6.5 -1.8
24 2.0 14.0 6.0 10.0 25.132741228718345 1.0 4.0 8.0 -2.4
25 2.0 17.0 7.0 12.0 31.41592653589793 1.0 5.0 9.5 -3.0
26 2.0 20.0 8.0 14.0 37.69911184307752 1.0 6.0 11.0 -3.6
27 2.0 23.0 9.0 16.0 43.982297150257104 1.0 7.0 12.5 -4.2
28 2.0 26.0 10.0 18.0 50.26548245743669 1.0 8.0 14.0 -4.8
29 2.0 29.0 11.0 20.0 56.548667764616276 1.0 9.0 15.5 -5.4
30 2.0 32.0 12.0 22.0 62.83185307179586 1.0 10.0 17.0 -6.0
31 2.0 35.0 13.0 24.0 69.11503837897544 1.0 11.0 18.5 -6.6
32 2.0 38.0 14.0 26.0 75.39822368615503 1.0 12.0 20.0 -7.2
33 2.0 41.0 15.0 28.0 81.68140899333461 1.0 13.0 21.5 -7.8
34 2.0 44.0 16.0 30.0 87.96459430051421 1.0 14.0 23.0 -8.4
35 2.0 47.0 17.0 32.0 94.2477796076938 1.0 15.0 24.5 -9.0
36 2.0 50.0 18.0 34.0 100.53096491487338 1.0 16.0 26.0 -9.6
37 2.0 53.0 19.0 36.0 106.81415022205297 1.0 17.0 27.5 -10.2
38 2.0 56.0 20.0 38.0 113.09733552923255 1.0 18.0 29.0 -10.8
39 2.0 59.0 21.0 40.0 119.38052083641215 1.0 19.0 30.5 -11.4
40 3.0 6.0 4.0 5.0 6.283185307179586 1.0 1.0 4.5 -0.6
41 3.0 9.0 5.0 7.0 12.566370614359172 1.0 2.0 6.0 -1.2
42 3.0 12.0 6.0 9.0 18.84955592153876 1.0 3.0 7.5 -1.8
43 3.0 15.0 7.0 11.0 25.132741228718345 1.0 4.0 9.0 -2.4
44 3.0 18.0 8.0 13.0 31.41592653589793 1.0 5.0 10.5 -3.0
45 3.0 21.0 9.0 15.0 37.69911184307752 1.0 6.0 12.0 -3.6
46 3.0 24.0 10.0 17.0 43.982297150257104 1.0 7.0 13.5 -4.2
47 3.0 27.0 11.0 19.0 50.26548245743669 1.0 8.0 15.0 -4.8
48 3.0 30.0 12.0 21.0 56.548667764616276 1.0 9.0 16.5 -5.4
49 3.0 33.0 13.0 23.0 62.83185307179586 1.0 10.0 18.0 -6.0
50 3.0 36.0 14.0 25.0 69.11503837897544 1.0 11.0 19.5 -6.6
51 3.0 39.0 15.0 27.0 75.39822368615503 1.0 12.0 21.0 -7.2
52 3.0 42.0 16.0 29.0 81.68140899333461 1.0 13.0 22.5 -7.8
53 3.0 45.0 17.0 31.0 87.96459430051421 1.0 14.0 24.0 -8.4
54 3.0 48.0 18.0 33.0 94.2477796076938 1.0 15.0 25.5 -9.0
55 3.0 51.0 19.0 35.0 100.53096491487338 1.0 16.0 27.0 -9.6
56 3.0 54.0 20.0 37.0 106.81415022205297 1.0 17.0 28.5 -10.2
57 3.0 57.0 21.0 39.0 113.09733552923255 1.0 18.0 30.0 -10.8
58 3.0 60.0 22.0 41.0 119.38052083641215 1.0 19.0 31.5 -11.4
59 4.0 7.0 5.0 6.0 6.283185307179586 1.0 1.0 5.5 -0.6
60 4.0 10.0 6.0 8.0 12.566370614359172 1.0 2.0 7.0 -1.2
61 4.0 13.0 7.0 10.0 18.84955592153876 1.0 3.0 8.5 -1.8
62 4.0 16.0 8.0 12.0 25.132741228718345 1.0 4.0 10.0 -2.4
63 4.0 19.0 9.0 14.0 31.41592653589793 1.0 5.0 11.5 -3.0
64 4.0 22.0 10.0 16.0 37.69911184307752 1.0 6.0 13.0 -3.6
65 4.0 25.0 11.0 18.0 43.982297150257104 1.0 7.0 14.5 -4.2
66 4.0 28.0 12.0 20.0 50.26548245743669 1.0 8.0 16.0 -4.8
67 4.0 31.0 13.0 22.0 56.548667764616276 1.0 9.0 17.5 -5.4
68 4.0 34.0 14.0 24.0 62.83185307179586 1.0 10.0 19.0 -6.0
69 4.0 37.0 15.0 26.0 69.11503837897544 1.0 11.0 20.5 -6.6
70 4.0 40.0 16.0 28.0 75.39822368615503 1.0 12.0 22.0 -7.2
71 4.0 43.0 17.0 30.0 81.68140899333461 1.0 13.0 23.5 -7.8
72 4.0 46.0 18.0 32.0 87.96459430051421 1.0 14.0 25.0 -8.4
73 4.0 49.0 19.0 34.0 94.2477796076938 1.0 15.0 26.5 -9.0
74 4.0 52.0 20.0 36.0 100.53096491487338 1.0 16.0 28.0 -9.6
75 4.0 55.0 21.0 38.0 106.81415022205297 1.0 17.0 29.5 -10.2
76 4.0 58.0 22.0 40.0 113.09733552923255 1.0 18.0 31.0 -10.8
77 5.0 8.0 6.0 7.0 6.283185307179586 1.0 1.0 6.5 -0.6
78 5.0 11.0 7.0 9.0 12.566370614359172 1.0 2.0 8.0 -1.2
79 5.0 14.0 8.0 11.0 18.84955592153876 1.0 3.0 9.5 -1.8
80 5.0 17.0 9.0 13.0 25.132741228718345 1.0 4.0 11.0 -2.4
81 5.0 20.0 10.0 15.0 31.41592653589793 1.0 5.0 12.5 -3.0
82 5.0 23.0 11.0 17.0 37.69911184307752 1.0 6.0 14.0 -3.6
83 5.0 26.0 12.0 19.0 43.982297150257104 1.0 7.0 15.5 -4.2
84 5.0 29.0 13.0 21.0 50.26548245743669 1.0 8.0 17.0 -4.8
85 5.0 32.0 14.0 23.0 56.548667764616276 1.0 9.0 18.5 -5.4
86 5.0 35.0 15.0 25.0 62.83185307179586 1.0 10.0 20.0 -6.0
87 5.0 38.0 16.0 27.0 69.11503837897544 1.0 11.0 21.5 -6.6
88 5.0 41.0 17.0 29.0 75.39822368615503 1.0 12.0 23.0 -7.2
89 5.0 44.0 18.0 31.0 81.68140899333461 1.0 13.0 24.5 -7.8
90 5.0 47.0 19.0 33.0 87.96459430051421 1.0 14.0 26.0 -8.4
91 5.0 50.0 20.0 35.0 94.2477796076938 1.0 15.0 27.5 -9.0
92 5.0 53.0 21.0 37.0 100.53096491487338 1.0 16.0 29.0 -9.6
93 5.0 56.0 22.0 39.0 106.81415022205297 1.0 17.0 30.5 -10.2
94 5.0 59.0 23.0 41.0 113.09733552923255 1.0 18.0 32.0 -10.8
95 6.0 9.0 7.0 8.0 6.283185307179586 1.0 1.0 7.5 -0.6
96 6.0 12.0 8.0 10.0 12.566370614359172 1.0 2.0 9.0 -1.2
97 6.0 15.0 9.0 12.0 18.84955592153876 1.0 3.0 10.5 -1.8
98 6.0 18.0 10.0 14.0 25.132741228718345 1.0 4.0 12.0 -2.4
99 6.0 21.0 11.0 16.0 31.41592653589793 1.0 5.0 13.5 -3.0
100 6.0 24.0 12.0 18.0 37.69911184307752 1.0 6.0 15.0 -3.6
101 6.0 27.0 13.0 20.0 43.982297150257104 1.0 7.0 16.5 -4.2
102 6.0 30.0 14.0 22.0 50.26548245743669 1.0 8.0 18.0 -4.8
103 6.0 33.0 15.0 24.0 56.548667764616276 1.0 9.0 19.5 -5.4
104 6.0 36.0 16.0 26.0 62.83185307179586 1.0 10.0 21.0 -6.0
105 6.0 39.0 17.0 28.0 69.11503837897544 1.0 11.0 22.5 -6.6
106 6.0 42.0 18.0 30.0 75.39822368615503 1.0 12.0 24.0 -7.2
107 6.0 45.0 19.0 32.0 81.68140899333461 1.0 13.0 25.5 -7.8
108 6.0 48.0 20.0 34.0 87.96459430051421 1.0 14.0 27.0 -8.4
109 6.0 51.0 21.0 36.0 94.2477796076938 1.0 15.0 28.5 -9.0
110 6.0 54.0 22.0 38.0 100.53096491487338 1.0 16.0 30.0 -9.6
111 6.0 57.0 23.0 40.0 106.81415022205297 1.0 17.0 31.5 -10.2
112 6.0 60.0 24.0 42.0 113.09733552923255 1.0 18.0 33.0 -10.8
113 7.0 10.0 8.0 9.0 6.283185307179586 1.0 1.0 8.5 -0.6
114 7.0 13.0 9.0 11.0 12.566370614359172 1.0 2.0 10.0 -1.2
115 7.0 16.0 10.0 13.0 18.84955592153876 1.0 3.0 11.5 -1.8
116 7.0 19.0 11.0 15.0 25.132741228718345 1.0 4.0 13.0 -2.4
117 7.0 22.0 12.0 17.0 31.41592653589793 1.0 5.0 14.5 -3.0
118 7.0 25.0 13.0 19.0 37.69911184307752 1.0 6.0 16.0 -3.6
119 7.0 28.0 14.0 21.0 43.982297150257104 1.0 7.0 17.5 -4.2
120 7.0 31.0 15.0 23.0 50.26548245743669 1.0 8.0 19.0 -4.8
121 7.0 34.0 16.0 25.0 56.548667764616276 1.0 9.0 20.5 -5.4
122 7.0 37.0 17.0 27.0 62.83185307179586 1.0 10.0 22.0 -6.0
123 7.0 40.0 18.0 29.0 69.11503837897544 1.0 11.0 23.5 -6.6
124 7.0 43.0 19.0 31.0 75.39822368615503 1.0 12.0 25.0 -7.2
125 7.0 46.0 20.0 33.0 81.68140899333461 1.0 13.0 26.5 -7.8
126 7.0 49.0 21.0 35.0 87.96459430051421 1.0 14.0 28.0 -8.4
127 7.0 52.0 22.0 37.0 94.2477796076938 1.0 15.0 29.5 -9.0
128 7.0 55.0 23.0 39.0 100.53096491487338 1.0 16.0 31.0 -9.6
129 7.0 58.0 24.0 41.0 106.81415022205297 1.0 17.0 32.5 -10.2
130 8.0 11.0 9.0 10.0 6.283185307179586 1.0 1.0 9.5 -0.6
131 8.0 14.0 10.0 12.0 12.566370614359172 1.0 2.0 11.0 -1.2
132 8.0 17.0 11.0 14.0 18.84955592153876 1.0 3.0 12.5 -1.8
133 8.0 20.0 12.0 16.0 25.132741228718345 1.0 4.0 14.0 -2.4
134 8.0 23.0 13.0 18.0 31.41592653589793 1.0 5.0 15.5 -3.0
135 8.0 26.0 14.0 20.0 37.69911184307752 1.0 6.0 17.0 -3.6
136 8.0 29.0 15.0 22.0 43.982297150257104 1.0 7.0 18.5 -4.2
137 8.0 32.0 16.0 24.0 50.26548245743669 1.0 8.0 20.0 -4.8
138 8.0 35.0 17.0 26.0 56.548667764616276 1.0 9.0 21.5 -5.4
139 8.0 38.0 18.0 28.0 62.83185307179586 1.0 10.0 23.0 -6.0
140 8.0 41.0 19.0 30.0 69.11503837897544 1.0 11.0 24.5 -6.6
141 8.0 44.0 20.0 32.0 75.39822368615503 1.0 12.0 26.0 -7.2
142 8.0 47.0 21.0 34.0 81.68140899333461 1.0 13.0 27.5 -7.8
143 8.0 50.0 22.0 36.0 87.96459430051421 1.0 14.0 29.0 -8.4
144 8.0 53.0 23.0 38.0 94.2477796076938 1.0 15.0 30.5 -9.0
145 8.0 56.0 24.0 40.0 100.53096491487338 1.0 16.0 32.0 -9.6
146 8.0 59.0 25.0 42.0 106.81415022205297 1.0 17.0 33.5 -10.2
147 9.0 12.0 10.0 11.0 6.283185307179586 1.0 1.0 10.5 -0.6
148 9.0 15.0 11.0 13.0 12.566370614359172 1.0 2.0 12.0 -1.2
149 9.0 18.0 12.0 15.0 18.84955592153876 1.0 3.0 13.5 -1.8
150 9.0 21.0 13.0 17.0 25.132741228718345 1.0 4.0 15.0 -2.4
151 9.0 24.0 14.0 19.0 31.41592653589793 1.0 5.0 16.5 -3.0
152 9.0 27.0 15.0 21.0 37.69911184307752 1.0 6.0 18.0 -3.6
153 9.0 30.0 16.0 23.0 43.982297150257104 1.0 7.0 19.5 -4.2
154 9.0 33.0 17.0 25.0 50.26548245743669 1.0 8.0 21.0 -4.8
155 9.0 36.0 18.0 27.0 56.548667764616276 1.0 9.0 22.5 -5.4
156 9.0 39.0 19.0 29.0 62.83185307179586 1.0 10.0 24.0 -6.0
157 9.0 42.0 20.0 31.0 69.11503837897544 1.0 11.0 25.5 -6.6
158 9.0 45.0 21.0 33.0 75.39822368615503 1.0 12.0 27.0 -7.2
159 9.0 48.0 22.0 35.0 81.68140899333461 1.0 13.0 28.5 -7.8
160 9.0 51.0 23.0 37.0 87.96459430051421 1.0 14.0 30.0 -8.4
161 9.0 54.0 24.0 39.0 94.2477796076938 1.0 15.0 31.5 -9.0
162 9.0 57.0 25.0 41.0 100.53096491487338 1.0 16.0 33.0 -9.6
163 9.0 60.0 26.0 43.0 106.81415022205297 1.0 17.0 34.5 -10.2
164 10.0 13.0 11.0 12.0 6.283185307179586 1.0 1.0 11.5 -0.6
165 10.0 16.0 12.0 14.0 12.566370614359172 1.0 2.0 13.0 -1.2
166 10.0 19.0 13.0 16.0 18.84955592153876 1.0 3.0 14.5 -1.8
167 10.0 22.0 14.0 18.0 25.132741228718345 1.0 4.0 16.0 -2.4
168 10.0 25.0 15.0 20.0 31.41592653589793 1.0 5.0 17.5 -3.0
169 10.0 28.0 16.0 22.0 37.69911184307752 1.0 6.0 19.0 -3.6
170 10.0 31.0 17.0 24.0 43.982297150257104 1.0 7.0 20.5 -4.2
171 10.0 34.0 18.0 26.0 50.26548245743669 1.0 8.0 22.0 -4.8
172 10.0 37.0 19.0 28.0 56.548667764616276 1.0 9.0 23.5 -5.4
173 10.0 40.0 20.0 30.0 62.83185307179586 1.0 10.0 25.0 -6.0
174 10.0 43.0 21.0 32.0 69.11503837897544 1.0 11.0 26.5 -6.6
175 10.0 46.0 22.0 34.0 75.39822368615503 1.0 12.0 28.0 -7.2
176 10.0 49.0 23.0 36.0 81.68140899333461 1.0 13.0 29.5 -7.8
177 10.0 52.0 24.0 38.0 87.96459430051421 1.0 14.0 31.0 -8.4
178 10.0 55.0 25.0 40.0 94.2477796076938 1.0 15.0 32.5 -9.0
179 10.0 58.0 26.0 42.0 100.53096491487338 1.0 16.0 34.0 -9.6
180 11.0 14.0 12.0 13.0 6.283185307179586 1.0 1.0 12.5 -0.6
181 11.0 17.0 13.0 15.0 12.566370614359172 1.0 2.0 14.0 -1.2
182 11.0 20.0 14.0 17.0 18.84955592153876 1.0 3.0 15.5 -1.8
183 11.0 23.0 15.0 19.0 25.132741228718345 1.0 4.0 17.0 -2.4
184 11.0 26.0 16.0 21.0 31.41592653589793 1.0 5.0 18.5 -3.0
185 11.0 29.0 17.0 23.0 37.69911184307752 1.0 6.0 20.0 -3.6
186 11.0 32.0 18.0 25.0 43.982297150257104 1.0 7.0 21.5 -4.2
187 11.0 35.0 19.0 27.0 50.26548245743669 1.0 8.0 23.0 -4.8
188 11.0 38.0 20.0 29.0 56.548667764616276 1.0 9.0 24.5 -5.4
189 11.0 41.0 21.0 31.0 62.83185307179586 1.0 10.0 26.0 -6.0
190 11.0 44.0 22.0 33.0 69.11503837897544 1.0 11.0 27.5 -6.6
191 11.0 47.0 23.0 35.0 75.39822368615503 1.0 12.0 29.0 -7.2
192 11.0 50.0 24.0 37.0 81.68140899333461 1.0 13.0 30.5 -7.8
193 11.0 53.0 25.0 39.0 87.96459430051421 1.0 14.0 32.0 -8.4
194 11.0 56.0 26.0 41.0 94.2477796076938 1.0 15.0 33.5 -9.0
195 11.0 59.0 27.0 43.0 100.53096491487338 1.0 16.0 35.0 -9.6
196 12.0 15.0 13.0 14.0 6.283185307179586 1.0 1.0 13.5 -0.6
197 12.0 18.0 14.0 16.0 12.566370614359172 1.0 2.0 15.0 -1.2
198 12.0 21.0 15.0 18.0 18.84955592153876 1.0 3.0 16.5 -1.8
199 12.0 24.0 16.0 20.0 25.132741228718345 1.0 4.0 18.0 -2.4
200 12.0 27.0 17.0 22.0 31.41592653589793 1.0 5.0 19.5 -3.0
201 12.0 30.0 18.0 24.0 37.69911184307752 1.0 6.0 21.0 -3.6
202 12.0 33.0 19.0 26.0 43.982297150257104 1.0 7.0 22.5 -4.2
203 12.0 36.0 20.0 28.0 50.26548245743669 1.0 8.0 24.0 -4.8
204 12.0 39.0 21.0 30.0 56.548667764616276 1.0 9.0 25.5 -5.4
205 12.0 42.0 22.0 32.0 62.83185307179586 1.0 10.0 27.0 -6.0
206 12.0 45.0 23.0 34.0 69.11503837897544 1.0 11.0 28.5 -6.6
207 12.0 48.0 24.0 36.0 75.39822368615503 1.0 12.0 30.0 -7.2
208 12.0 51.0 25.0 38.0 81.68140899333461 1.0 13.0 31.5 -7.8
209 12.0 54.0 26.0 40.0 87.96459430051421 1.0 14.0 33.0 -8.4
210 12.0 57.0 27.0 42.0 94.2477796076938 1.0 15.0 34.5 -9.0
211 12.0 60.0 28.0 44.0 100.53096491487338 1.0 16.0 36.0 -9.6
212 13.0 16.0 14.0 15.0 6.283185307179586 1.0 1.0 14.5 -0.6
213 13.0 19.0 15.0 17.0 12.566370614359172 1.0 2.0 16.0 -1.2
214 13.0 22.0 16.0 19.0 18.84955592153876 1.0 3.0 17.5 -1.8
215 13.0 25.0 17.0 21.0 25.132741228718345 1.0 4.0 19.0 -2.4
216 13.0 28.0 18.0 23.0 31.41592653589793 1.0 5.0 20.5 -3.0
217 13.0 31.0 19.0 25.0 37.69911184307752 1.0 6.0 22.0 -3.6
218 13.0 34.0 20.0 27.0 43.982297150257104 1.0 7.0 23.5 -4.2
219 13.0 37.0 21.0 29.0 50.26548245743669 1.0 8.0 25.0 -4.8
220 13.0 40.0 22.0 31.0 56.548667764616276 1.0 9.0 26.5 -5.4
221 13.0 43.0 23.0 33.0 62.83185307179586 1.0 10.0 28.0 -6.0
222 13.0 46.0 24.0 35.0 69.11503837897544 1.0 11.0 29.5 -6.6
223 13.0 49.0 25.0 37.0 75.39822368615503 1.0 12.0 31.0 -7.2
224 13.0 52.0 26.0 39.0 81.68140899333461 1.0 13.0 32.5 -7.8
225 13.0 55.0 27.0 41.0 87.96459430051421 1.0 14.0 34.0 -8.4
226 13.0 58.0 28.0 43.0 94.2477796076938 1.0 15.0 35.5 -9.0
227 14.0 17.0 15.0 16.0 6.283185307179586 1.0 1.0 15.5 -0.6
228 14.0 20.0 16.0 18.0 12.566370614359172 1.0 2.0 17.0 -1.2
229 14.0 23.0 17.0 20.0 18.84955592153876 1.0 3.0 18.5 -1.8
230 14.0 26.0 18.0 22.0 25.132741228718345 1.0 4.0 20.0 -2.4
231 14.0 29.0 19.0 24.0 31.41592653589793 1.0 5.0 21.5 -3.0
232 14.0 32.0 20.0 26.0 37.69911184307752 1.0 6.0 23.0 -3.6
233 14.0 35.0 21.0 28.0 43.982297150257104 1.0 7.0 24.5 -4.2
234 14.0 38.0 22.0 30.0 50.26548245743669 1.0 8.0 26.0 -4.8
235 14.0 41.0 23.0 32.0 56.548667764616276 1.0 9.0 27.5 -5.4
236 14.0 44.0 24.0 34.0 62.83185307179586 1.0 10.0 29.0 -6.0
237 14.0 47.0 25.0 36.0 69.11503837897544 1.0 11.0 30.5 -6.6
238 14.0 50.0 26.0 38.0 75.39822368615503 1.0 12.0 32.0 -7.2
239 14.0 53.0 27.0 40.0 81.68140899333461 1.0 13.0 33.5 -7.8
240 14.0 56.0 28.0 42.0 87.96459430051421 1.0 14.0 35.0 -8.4
241 14.0 59.0 29.0 44.0 94.2477796076938 1.0 15.0 36.5 -9.0
242 15.0 18.0 16.0 17.0 6.283185307179586 1.0 1.0 16.5 -0.6
243 15.0 21.0 17.0 19.0 12.566370614359172 1.0 2.0 18.0 -1.2
244 15.0 24.0 18.0 21.0 18.84955592153876 1.0 3.0 19.5 -1.8
245 15.0 27.0 19.0 23.0 25.132741228718345 1.0 4.0 21.0 -2.4
246 15.0 30.0 20.0 25.0 31.41592653589793 1.0 5.0 22.5 -3.0
247 15.0 33.0 21.0 27.0 37.69911184307752 1.0 6.0 24.0 -3.6
248 15.0 36.0 22.0 29.0 43.982297150257104 1.0 7.0 25.5 -4.2
249 15.0 39.0 23.0 31.0 50.26548245743669 1.0 8.0 27.0 -4.8
250 15.0 42.0 24.0 33.0 56.548667764616276 1.0 9.0 28.5 -5.4
251 15.0 45.0 25.0 35.0 62.83185307179586 1.0 10.0 30.0 -6.0
252 15.0 48.0 26.0 37.0 69.11503837897544 1.0 11.0 31.5 -6.6
253 15.0 51.0 27.0 39.0 75.39822368615503 1.0 12.0 33.0 -7.2
254 15.0 54.0 28.0 41.0 81.68140899333461 1.0 13.0 34.5 -7.8
255 15.0 57.0 29.0 43.0 87.96459430051421 1.0 14.0 36.0 -8.4
256 15.0 60.0 30.0 45.0 94.2477796076938 1.0 15.0 37.5 -9.0
257 16.0 19.0 17.0 18.0 6.283185307179586 1.0 1.0 17.5 -0.6
258 16.0 22.0 18.0 20.0 12.566370614359172 1.0 2.0 19.0 -1.2
259 16.0 25.0 19.0 22.0 18.84955592153876 1.0 3.0 20.5 -1.8
260 16.0 28.0 20.0 24.0 25.132741228718345 1.0 4.0 22.0 -2.4
261 16.0 31.0 21.0 26.0 31.41592653589793 1.0 5.0 23.5 -3.0
262 16.0 34.0 22.0 28.0 37.69911184307752 1.0 6.0 25.0 -3.6
263 16.0 37.0 23.0 30.0 43.982297150257104 1.0 7.0 26.5 -4.2
264 16.0 40.0 24.0 32.0 50.26548245743669 1.0 8.0 28.0 -4.8
265 16.0 43.0 25.0 34.0 56.548667764616276 1.0 9.0 29.5 -5.4
266 16.0 46.0 26.0 36.0 62.83185307179586 1.0 10.0 31.0 -6.0
267 16.0 49.0 27.0 38.0 69.11503837897544 1.0 11.0 32.5 -6.6
268 16.0 52.0 28.0 40.0 75.39822368615503 1.0 12.0 34.0 -7.2
269 16.0 55.0 29.0 42.0 81.68140899333461 1.0 13.0 35.5 -7.8
270 16.0 58.0 30.0 44.0 87.96459430051421 1.0 14.0 37.0 -8.4
271 17.0 20.0 18.0 19.0 6.283185307179586 1.0 1.0 18.5 -0.6
272 17.0 23.0 19.0 21.0 12.566370614359172 1.0 2.0 20.0 -1.2
273 17.0 26.0 20.0 23.0 18.84955592153876 1.0 3.0 21.5 -1.8
274 17.0 29.0 21.0 25.0 25.132741228718345 1.0 4.0 23.0 -2.4
275 17.0 32.0 22.0 27.0 31.41592653589793 1.0 5.0 24.5 -3.0
276 17.0 35.0 23.0 29.0 37.69911184307752 1.0 6.0 26.0 -3.6
277 17.0 38.0 24.0 31.0 43.982297150257104 1.0 7.0 27.5 -4.2
278 17.0 41.0 25.0 33.0 50.26548245743669 1.0 8.0 29.0 -4.8
279 17.0 44.0 26.0 35.0 56.548667764616276 1.0 9.0 30.5 -5.4
280 17.0 47.0 27.0 37.0 62.83185307179586 1.0 10.0 32.0 -6.0
281 17.0 50.0 28.0 39.0 69.11503837897544 1.0 11.0 33.5 -6.6
282 17.0 53.0 29.0 41.0 75.39822368615503 1.0 12.0 35.0 -7.2
283 17.0 56.0 30.0 43.0 81.68140899333461 1.0 13.0 36.5 -7.8
284 17.0 59.0 31.0 45.0 87.96459430051421 1.0 14.0 38.0 -8.4
285 18.0 21.0 19.0 20.0 6.283185307179586 1.0 1.0 19.5 -0.6
286 18.0 24.0 20.0 22.0 12.566370614359172 1.0 2.0 21.0 -1.2
287 18.0 27.0 21.0 24.0 18.84955592153876 1.0 3.0 22.5 -1.8
288 18.0 30.0 22.0 26.0 25.132741228718345 1.0 4.0 24.0 -2.4
289 18.0 33.0 23.0 28.0 31.41592653589793 1.0 5.0 25.5 -3.0
290 18.0 36.0 24.0 30.0 37.69911184307752 1.0 6.0 27.0 -3.6
291 18.0 39.0 25.0 32.0 43.982297150257104 1.0 7.0 28.5 -4.2
292 18.0 42.0 26.0 34.0 50.26548245743669 1.0 8.0 30.0 -4.8
293 18.0 45.0 27.0 36.0 56.548667764616276 1.0 9.0 31.5 -5.4
294 18.0 48.0 28.0 38.0 62.83185307179586 1.0 10.0 33.0 -6.0
295 18.0 51.0 29.0 40.0 69.11503837897544 1.0 11.0 34.5 -6.6
296 18.0 54.0 30.0 42.0 75.39822368615503 1.0 12.0 36.0 -7.2
297 18.0 57.0 31.0 44.0 81.68140899333461 1.0 13.0 37.5 -7.8
298 18.0 60.0 32.0 46.0 87.96459430051421 1.0 14.0 39.0 -8.4
299 19.0 22.0 20.0 21.0 6.283185307179586 1.0 1.0 20.5 -0.6
300 19.0 25.0 21.0 23.0 12.566370614359172 1.0 2.0 22.0 -1.2
301 19.0 28.0 22.0 25.0 18.84955592153876 1.0 3.0 23.5 -1.8
302 19.0 31.0 23.0 27.0 25.132741228718345 1.0 4.0 25.0 -2.4
303 19.0 34.0 24.0 29.0 31.41592653589793 1.0 5.0 26.5 -3.0
304 19.0 37.0 25.0 31.0 37.69911184307752 1.0 6.0 28.0 -3.6
305 19.0 40.0 26.0 33.0 43.982297150257104 1.0 7.0 29.5 -4.2
306 19.0 43.0 27.0 35.0 50.26548245743669 1.0 8.0 31.0 -4.8
307 19.0 46.0 28.0 37.0 56.548667764616276 1.0 9.0 32.5 -5.4
308 19.0 49.0 29.0 39.0 62.83185307179586 1.0 10.0 34.0 -6.0
309 19.0 52.0 30.0 41.0 69.11503837897544 1.0 11.0 35.5 -6.6
310 19.0 55.0 31.0 43.0 75.39822368615503 1.0 12.0 37.0 -7.2
311 19.0 58.0 32.0 45.0 81.68140899333461 1.0 13.0 38.5 -7.8
312 20.0 23.0 21.0 22.0 6.283185307179586 1.0 1.0 21.5 -0.6
313 20.0 26.0 22.0 24.0 12.566370614359172 1.0 2.0 23.0 -1.2
314 20.0 29.0 23.0 26.0 18.84955592153876 1.0 3.0 24.5 -1.8
315 20.0 32.0 24.0 28.0 25.132741228718345 1.0 4.0 26.0 -2.4
316 20.0 35.0 25.0 30.0 31.41592653589793 1.0 5.0 27.5 -3.0
317 20.0 38.0 26.0 32.0 37.69911184307752 1.0 6.0 29.0 -3.6
318 20.0 41.0 27.0 34.0 43.982297150257104 1.0 7.0 30.5 -4.2
319 20.0 44.0 28.0 36.0 50.26548245743669 1.0 8.0 32.0 -4.8
320 20.0 47.0 29.0 38.0 56.548667764616276 1.0 9.0 33.5 -5.4
321 20.0 50.0 30.0 40.0 62.83185307179586 1.0 10.0 35.0 -6.0
322 20.0 53.0 31.0 42.0 69.11503837897544 1.0 11.0 36.5 -6.6
323 20.0 56.0 32.0 44.0 75.39822368615503 1.0 12.0 38.0 -7.2
324 20.0 59.0 33.0 46.0 81.68140899333461 1.0 13.0 39.5 -7.8
325 21.0 24.0 22.0 23.0 6.283185307179586 1.0 1.0 22.5 -0.6
326 21.0 27.0 23.0 25.0 12.566370614359172 1.0 2.0 24.0 -1.2
327 21.0 30.0 24.0 27.0 18.84955592153876 1.0 3.0 25.5 -1.8
328 21.0 33.0 25.0 29.0 25.132741228718345 1.0 4.0 27.0 -2.4
329 21.0 36.0 26.0 31.0 31.41592653589793 1.0 5.0 28.5 -3.0
330 21.0 39.0 27.0 33.0 37.69911184307752 1.0 6.0 30.0 -3.6
331 21.0 42.0 28.0 35.0 43.982297150257104 1.0 7.0 31.5 -4.2
332 21.0 45.0 29.0 37.0 50.26548245743669 1.0 8.0 33.0 -4.8
333 21.0 48.0 30.0 39.0 56.548667764616276 1.0 9.0 34.5 -5.4
334 21.0 51.0 31.0 41.0 62.83185307179586 1.0 10.0 36.0 -6.0
335 21.0 54.0 32.0 43.0 69.11503837897544 1.0 11.0 37.5 -6.6
336 21.0 57.0 33.0 45.0 75.39822368615503 1.0 12.0 39.0 -7.2
337 21.0 60.0 34.0 47.0 81.68140899333461 1.0 13.0 40.5 -7.8
338 22.0 25.0 23.0 24.0 6.283185307179586 1.0 1.0 23.5 -0.6
339 22.0 28.0 24.0 26.0 12.566370614359172 1.0 2.0 25.0 -1.2
340 22.0 31.0 25.0 28.0 18.84955592153876 1.0 3.0 26.5 -1.8
341 22.0 34.0 26.0 30.0 25.132741228718345 1.0 4.0 28.0 -2.4
342 22.0 37.0 27.0 32.0 31.41592653589793 1.0 5.0 29.5 -3.0
343 22.0 40.0 28.0 34.0 37.69911184307752 1.0 6.0 31.0 -3.6
344 22.0 43.0 29.0 36.0 43.982297150257104 1.0 7.0 32.5 -4.2
345 22.0 46.0 30.0 38.0 50.26548245743669 1.0 8.0 34.0 -4.8
346 22.0 49.0 31.0 40.0 56.548667764616276 1.0 9.0 35.5 -5.4
347 22.0 52.0 32.0 42.0 62.83185307179586 1.0 10.0 37.0 -6.0
348 22.0 55.0 33.0 44.0 69.11503837897544 1.0 11.0 38.5 -6.6
349 22.0 58.0 34.0 46.0 75.39822368615503 1.0 12.0 40.0 -7.2
350 23.0 26.0 24.0 25.0 6.283185307179586 1.0 1.0 24.5 -0.6
351 23.0 29.0 25.0 27.0 12.566370614359172 1.0 2.0 26.0 -1.2
352 23.0 32.0 26.0 29.0 18.84955592153876 1.0 3.0 27.5 -1.8
353 23.0 35.0 27.0 31.0 25.132741228718345 1.0 4.0 29.0 -2.4
354 23.0 38.0 28.0 33.0 31.41592653589793 1.0 5.0 30.5 -3.0
355 23.0 41.0 29.0 35.0 37.69911184307752 1.0 6.0 32.0 -3.6
356 23.0 44.0 30.0 37.0 43.982297150257104 1.0 7.0 33.5 -4.2
357 23.0 47.0 31.0 39.0 50.26548245743669 1.0 8.0 35.0 -4.8
358 23.0 50.0 32.0 41.0 56.548667764616276 1.0 9.0 36.5 -5.4
359 23.0 53.0 33.0 43.0 62.83185307179586 1.0 10.0 38.0 -6.0
360 23.0 56.0 34.0 45.0 69.11503837897544 1.0 11.0 39.5 -6.6
361 23.0 59.0 35.0 47.0 75.39822368615503 1.0 12.0 41.0 -7.2
362 24.0 27.0 25.0 26.0 6.283185307179586 1.0 1.0 25.5 -0.6
363 24.0 30.0 26.0 28.0 12.566370614359172 1.0 2.0 27.0 -1.2
364 24.0 33.0 27.0 30.0 18.84955592153876 1.0 3.0 28.5 -1.8
365 24.0 36.0 28.0 32.0 25.132741228718345 1.0 4.0 30.0 -2.4
366 24.0 39.0 29.0 34.0 31.41592653589793 1.0 5.0 31.5 -3.0
367 24.0 42.0 30.0 36.0 37.69911184307752 1.0 6.0 33.0 -3.6
368 24.0 45.0 31.0 38.0 43.982297150257104 1.0 7.0 34.5 -4.2
369 24.0 48.0 32.0 40.0 50.26548245743669 1.0 8.0 36.0 -4.8
370 24.0 51.0 33.0 42.0 56.548667764616276 1.0 9.0 37.5 -5.4
371 24.0 54.0 34.0 44.0 62.83185307179586 1.0 10.0 39.0 -6.0
372 24.0 57.0 35.0 46.0 69.11503837897544 1.0 11.0 40.5 -6.6
373 24.0 60.0 36.0 48.0 75.39822368615503 1.0 12.0 42.0 -7.2
374 25.0 28.0 26.0 27.0 6.283185307179586 1.0 1.0 26.5 -0.6
375 25.0 31.0 27.0 29.0 12.566370614359172 1.0 2.0 28.0 -1.2
376 25.0 34.0 28.0 31.0 18.84955592153876 1.0 3.0 29.5 -1.8
377 25.0 37.0 29.0 33.0 25.132741228718345 1.0 4.0 31.0 -2.4
378 25.0 40.0 30.0 35.0 31.41592653589793 1.0 5.0 32.5 -3.0
379 25.0 43.0 31.0 37.0 37.69911184307752 1.0 6.0 34.0 -3.6
380 25.0 46.0 32.0 39.0 43.982297150257104 1.0 7.0 35.5 -4.2
381 25.0 49.0 33.0 41.0 50.26548245743669 1.0 8.0 37.0 -4.8
382 25.0 52.0 34.0 43.0 56.548667764616276 1.0 9.0 38.5 -5.4
383 25.0 55.0 35.0 45.0 62.83185307179586 1.0 10.0 40.0 -6.0
384 25.0 58.0 36.0 47.0 69.11503837897544 1.0 11.0 41.5 -6.6
385 26.0 29.0 27.0 28.0 6.283185307179586 1.0 1.0 27.5 -0.6
386 26.0 32.0 28.0 30.0 12.566370614359172 1.0 2.0 29.0 -1.2
387 26.0 35.0 29.0 32.0 18.84955592153876 1.0 3.0 30.5 -1.8
388 26.0 38.0 30.0 34.0 25.132741228718345 1.0 4.0 32.0 -2.4
389 26.0 41.0 31.0 36.0 31.41592653589793 1.0 5.0 33.5 -3.0
390 26.0 44.0 32.0 38.0 37.69911184307752 1.0 6.0 35.0 -3.6
391 26.0 47.0 33.0 40.0 43.982297150257104 1.0 7.0 36.5 -4.2
392 26.0 50.0 34.0 42.0 50.26548245743669 1.0 8.0 38.0 -4.8
393 26.0 53.0 35.0 44.0 56.548667764616276 1.0 9.0 39.5 -5.4
394 26.0 56.0 36.0 46.0 62.83185307179586 1.0 10.0 41.0 -6.0
395 26.0 59.0 37.0 48.0 69.11503837897544 1.0 11.0 42.5 -6.6
396 27.0 30.0 28.0 29.0 6.283185307179586 1.0 1.0 28.5 -0.6
397 27.0 33.0 29.0 31.0 12.566370614359172 1.0 2.0 30.0 -1.2
398 27.0 36.0 30.0 33.0 18.84955592153876 1.0 3.0 31.5 -1.8
399 27.0 39.0 31.0 35.0 25.132741228718345 1.0 4.0 33.0 -2.4
400 27.0 42.0 32.0 37.0 31.41592653589793 1.0 5.0 34.5 -3.0
401 27.0 45.0 33.0 39.0 37.69911184307752 1.0 6.0 36.0 -3.6
402 27.0 48.0 34.0 41.0 43.982297150257104 1.0 7.0 37.5 -4.2
403 27.0 51.0 35.0 43.0 50.26548245743669 1.0 8.0 39.0 -4.8
404 27.0 54.0 36.0 45.0 56.548667764616276 1.0 9.0 40.5 -5.4
405 27.0 57.0 37.0 47.0 62.83185307179586 1.0 10.0 42.0 -6.0
406 27.0 60.0 38.0 49.0 69.11503837897544 1.0 11.0 43.5 -6.6
407 28.0 31.0 29.0 30.0 6.283185307179586 1.0 1.0 29.5 -0.6
408 28.0 34.0 30.0 32.0 12.566370614359172 1.0 2.0 31.0 -1.2
409 28.0 37.0 31.0 34.0 18.84955592153876 1.0 3.0 32.5 -1.8
410 28.0 40.0 32.0 36.0 25.132741228718345 1.0 4.0 34.0 -2.4
411 28.0 43.0 33.0 38.0 31.41592653589793 1.0 5.0 35.5 -3.0
412 28.0 46.0 34.0 40.0 37.69911184307752 1.0 6.0 37.0 -3.6
413 28.0 49.0 35.0 42.0 43.982297150257104 1.0 7.0 38.5 -4.2
414 28.0 52.0 36.0 44.0 50.26548245743669 1.0 8.0 40.0 -4.8
415 28.0 55.0 37.0 46.0 56.548667764616276 1.0 9.0 41.5 -5.4
416 28.0 58.0 38.0 48.0 62.83185307179586 1.0 10.0 43.0 -6.0
417 29.0 32.0 30.0 31.0 6.283185307179586 1.0 1.0 30.5 -0.6
418 29.0 35.0 31.0 33.0 12.566370614359172 1.0 2.0 32.0 -1.2
419 29.0 38.0 32.0 35.0 18.84955592153876 1.0 3.0 33.5 -1.8
420 29.0 41.0 33.0 37.0 25.132741228718345 1.0 4.0 35.0 -2.4
421 29.0 44.0 34.0 39.0 31.41592653589793 1.0 5.0 36.5 -3.0
422 29.0 47.0 35.0 41.0 37.69911184307752 1.0 6.0 38.0 -3.6
423 29.0 50.0 36.0 43.0 43.982297150257104 1.0 7.0 39.5 -4.2
424 29.0 53.0 37.0 45.0 50.26548245743669 1.0 8.0 41.0 -4.8
425 29.0 56.0 38.0 47.0 56.548667764616276 1.0 9.0 42.5 -5.4
426 29.0 59.0 39.0 49.0 62.83185307179586 1.0 10.0 44.0 -6.0
427 30.0 33.0 31.0 32.0 6.283185307179586 1.0 1.0 31.5 -0.6
428 30.0 36.0 32.0 34.0 12.566370614359172 1.0 2.0 33.0 -1.2
429 30.0 39.0 33.0 36.0 18.84955592153876 1.0 3.0 34.5 -1.8
430 30.0 42.0 34.0 38.0 25.132741228718345 1.0 4.0 36.0 -2.4
431 30.0 45.0 35.0 40.0 31.41592653589793 1.0 5.0 37.5 -3.0
432 30.0 48.0 36.0 42.0 37.69911184307752 1.0 6.0 39.0 -3.6
433 30.0 51.0 37.0 44.0 43.982297150257104 1.0 7.0 40.5 -4.2
434 30.0 54.0 38.0 46.0 50.26548245743669 1.0 8.0 42.0 -4.8
435 30.0 57.0 39.0 48.0 56.548667764616276 1.0 9.0 43.5 -5.4
436 30.0 60.0 40.0 50.0 62.83185307179586 1.0 10.0 45.0 -6.0
437 31.0 34.0 32.0 33.0 6.283185307179586 1.0 1.0 32.5 -0.6
438 31.0 37.0 33.0 35.0 12.566370614359172 1.0 2.0 34.0 -1.2
439 31.0 40.0 34.0 37.0 18.84955592153876 1.0 3.0 35.5 -1.8
440 31.0 43.0 35.0 39.0 25.132741228718345 1.0 4.0 37.0 -2.4
441 31.0 46.0 36.0 41.0 31.41592653589793 1.0 5.0 38.5 -3.0
442 31.0 49.0 37.0 43.0 37.69911184307752 1.0 6.0 40.0 -3.6
443 31.0 52.0 38.0 45.0 43.982297150257104 1.0 7.0 41.5 -4.2
444 31.0 55.0 39.0 47.0 50.26548245743669 1.0 8.0 43.0 -4.8
445 31.0 58.0 40.0 49.0 56.548667764616276 1.0 9.0 44.5 -5.4
446 32.0 35.0 33.0 34.0 6.283185307179586 1.0 1.0 33.5 -0.6
447 32.0 38.0 34.0 36.0 12.566370614359172 1.0 2.0 35.0 -1.2
448 32.0 41.0 35.0 38.0 18.84955592153876 1.0 3.0 36.5 -1.8
449 32.0 44.0 36.0 40.0 25.132741228718345 1.0 4.0 38.0 -2.4
450 32.0 47.0 37.0 42.0 31.41592653589793 1.0 5.0 39.5 -3.0
451 32.0 50.0 38.0 44.0 37.69911184307752 1.0 6.0 41.0 -3.6
452 32.0 53.0 39.0 46.0 43.982297150257104 1.0 7.0 42.5 -4.2
453 32.0 56.0 40.0 48.0 50.26548245743669 1.0 8.0 44.0 -4.8
454 32.0 59.0 41.0 50.0 56.548667764616276 1.0 9.0 45.5 -5.4
455 33.0 36.0 34.0 35.0 6.283185307179586 1.0 1.0 34.5 -0.6
456 33.0 39.0 35.0 37.0 12.566370614359172 1.0 2.0 36.0 -1.2
457 33.0 42.0 36.0 39.0 18.84955592153876 1.0 3.0 37.5 -1.8
458 33.0 45.0 37.0 41.0 25.132741228718345 1.0 4.0 39.0 -2.4
459 33.0 48.0 38.0 43.0 31.41592653589793 1.0 5.0 40.5 -3.0
460 33.0 51.0 39.0 45.0 37.69911184307752 1.0 6.0 42.0 -3.6
461 33.0 54.0 40.0 47.0 43.982297150257104 1.0 7.0 43.5 -4.2
462 33.0 57.0 41.0 49.0 50.26548245743669 1.0 8.0 45.0 -4.8
463 33.0 60.0 42.0 51.0 56.548667764616276 1.0 9.0 46.5 -5.4
464 34.0 37.0 35.0 36.0 6.283185307179586 1.0 1.0 35.5 -0.6
465 34.0 40.0 36.0 38.0 12.566370614359172 1.0 2.0 37.0 -1.2
466 34.0 43.0 37.0 40.0 18.84955592153876 1.0 3.0 38.5 -1.8
467 34.0 46.0 38.0 42.0 25.132741228718345 1.0 4.0 40.0 -2.4
468 34.0 49.0 39.0 44.0 31.41592653589793 1.0 5.0 41.5 -3.0
469 34.0 52.0 40.0 46.0 37.69911184307752 1.0 6.0 43.0 -3.6
470 34.0 55.0 41.0 48.0 43.982297150257104 1.0 7.0 44.5 -4.2
471 34.0 58.0 42.0 50.0 50.26548245743669 1.0 8.0 46.0 -4.8
472 35.0 38.0 36.0 37.0 6.283185307179586 1.0 1.0 36.5 -0.6
473 35.0 41.0 37.0 39.0 12.566370614359172 1.0 2.0 38.0 -1.2
474 35.0 44.0 38.0 41.0 18.84955592153876 1.0 3.0 39.5 -1.8
475 35.0 47.0 39.0 43.0 25.132741228718345 1.0 4.0 41.0 -2.4
476 35.0 50.0 40.0 45.0 31.41592653589793 1.0 5.0 42.5 -3.0
477 35.0 53.0 41.0 47.0 37.69911184307752 1.0 6.0 44.0 -3.6
478 35.0 56.0 42.0 49.0 43.982297150257104 1.0 7.0 45.5 -4.2
479 35.0 59.0 43.0 51.0 50.26548245743669 1.0 8.0 47.0 -4.8
480 36.0 39.0 37.0 38.0 6.283185307179586 1.0 1.0 37.5 -0.6
481 36.0 42.0 38.0 40.0 12.566370614359172 1.0 2.0 39.0 -1.2
482 36.0 45.0 39.0 42.0 18.84955592153876 1.0 3.0 40.5 -1.8
483 36.0 48.0 40.0 44.0 25.132741228718345 1.0 4.0 42.0 -2.4
484 36.0 51.0 41.0 46.0 31.41592653589793 1.0 5.0 43.5 -3.0
485 36.0 54.0 42.0 48.0 37.69911184307752 1.0 6.0 45.0 -3.6
486 36.0 57.0 43.0 50.0 43.982297150257104 1.0 7.0 46.5 -4.2
487 36.0 60.0 44.0 52.0 50.26548245743669 1.0 8.0 48.0 -4.8
488 37.0 40.0 38.0 39.0 6.283185307179586 1.0 1.0 38.5 -0.6
489 37.0 43.0 39.0 41.0 12.566370614359172 1.0 2.0 40.0 -1.2
490 37.0 46.0 40.0 43.0 18.84955592153876 1.0 3.0 41.5 -1.8
491 37.0 49.0 41.0 45.0 25.132741228718345 1.0 4.0 43.0 -2.4
492 37.0 52.0 42.0 47.0 31.41592653589793 1.0 5.0 44.5 -3.0
493 37.0 55.0 43.0 49.0 37.69911184307752 1.0 6.0 46.0 -3.6
494 37.0 58.0 44.0 51.0 43.982297150257104 1.0 7.0 47.5 -4.2
495 38.0 41.0 39.0 40.0 6.283185307179586 1.0 1.0 39.5 -0.6
496 38.0 44.0 40.0 42.0 12.566370614359172 1.0 2.0 41.0 -1.2
497 38.0 47.0 41.0 44.0 18.84955592153876 1.0 3.0 42.5 -1.8
498 38.0 50.0 42.0 46.0 25.132741228718345 1.0 4.0 44.0 -2.4
499 38.0 53.0 43.0 48.0 31.41592653589793 1.0 5.0 45.5 -3.0
500 38.0 56.0 44.0 50.0 37.69911184307752 1.0 6.0 47.0 -3.6
501 38.0 59.0 45.0 52.0 43.982297150257104 1.0 7.0 48.5 -4.2
502 39.0 42.0 40.0 41.0 6.283185307179586 1.0 1.0 40.5 -0.6
503 39.0 45.0 41.0 43.0 12.566370614359172 1.0 2.0 42.0 -1.2
504 39.0 48.0 42.0 45.0 18.84955592153876 1.0 3.0 43.5 -1.8
505 39.0 51.0 43.0 47.0 25.132741228718345 1.0 4.0 45.0 -2.4
506 39.0 54.0 44.0 49.0 31.41592653589793 1.0 5.0 46.5 -3.0
507 39.0 57.0 45.0 51.0 37.69911184307752 1.0 6.0 48.0 -3.6
508 39.0 60.0 46.0 53.0 43.982297150257104 1.0 7.0 49.5 -4.2
509 40.0 43.0 41.0 42.0 6.283185307179586 1.0 1.0 41.5 -0.6
510 40.0 46.0 42.0 44.0 12.566370614359172 1.0 2.0 43.0 -1.2
511 40.0 49.0 43.0 46.0 18.84955592153876 1.0 3.0 44.5 -1.8
512 40.0 52.0 44.0 48.0 25.132741228718345 1.0 4.0 46.0 -2.4
513 40.0 55.0 45.0 50.0 31.41592653589793 1.0 5.0 47.5 -3.0
514 40.0 58.0 46.0 52.0 37.69911184307752 1.0 6.0 49.0 -3.6
515 41.0 44.0 42.0 43.0 6.283185307179586 1.0 1.0 42.5 -0.6
516 41.0 47.0 43.0 45.0 12.566370614359172 1.0 2.0 44.0 -1.2
517 41.0 50.0 44.0 47.0 18.84955592153876 1.0 3.0 45.5 -1.8
518 41.0 53.0 45.0 49.0 25.132741228718345 1.0 4.0 47.0 -2.4
519 41.0 56.0 46.0 51.0 31.41592653589793 1.0 5.0 48.5 -3.0
520 41.0 59.0 47.0 53.0 37.69911184307752 1.0 6.0 50.0 -3.6
521 42.0 45.0 43.0 44.0 6.283185307179586 1.0 1.0 43.5 -0.6
522 42.0 48.0 44.0 46.0 12.566370614359172 1.0 2.0 45.0 -1.2
523 42.0 51.0 45.0 48.0 18.84955592153876 1.0 3.0 46.5 -1.8
524 42.0 54.0 46.0 50.0 25.132741228718345 1.0 4.0 48.0 -2.4
525 42.0 57.0 47.0 52.0 31.41592653589793 1.0 5.0 49.5 -3.0
526 42.0 60.0 48.0 54.0 37.69911184307752 1.0 6.0 51.0 -3.6
527 43.0 46.0 44.0 45.0 6.283185307179586 1.0 1.0 44.5 -0.6
528 43.0 49.0 45.0 47.0 12.566370614359172 1.0 2.0 46.0 -1.2
529 43.0 52.0 46.0 49.0 18.84955592153876 1.0 3.0 47.5 -1.8
530 43.0 55.0 47.0 51.0 25.132741228718345 1.0 4.0 49.0 -2.4
531 43.0 58.0 48.0 53.0 31.41592653589793 1.0 5.0 50.5 -3.0
532 44.0 47.0 45.0 46.0 6.283185307179586 1.0 1.0 45.5 -0.6
533 44.0 50.0 46.0 48.0 12.566370614359172 1.0 2.0 47.0 -1.2
534 44.0 53.0 47.0 50.0 18.84955592153876 1.0 3.0 48.5 -1.8
535 44.0 56.0 48.0 52.0 25.132741228718345 1.0 4.0 50.0 -2.4
536 44.0 59.0 49.0 54.0 31.41592653589793 1.0 5.0 51.5 -3.0
537 45.0 48.0 46.0 47.0 6.283185307179586 1.0 1.0 46.5 -0.6
538 45.0 51.0 47.0 49.0 12.566370614359172 1.0 2.0 48.0 -1.2
539 45.0 54.0 48.0 51.0 18.84955592153876 1.0 3.0 49.5 -1.8
540 45.0 57.0 49.0 53.0 25.132741228718345 1.0 4.0 51.0 -2.4
541 45.0 60.0 50.0 55.0 31.41592653589793 1.0 5.0 52.5 -3.0
542 46.0 49.0 47.0 48.0 6.283185307179586 1.0 1.0 47.5 -0.6
543 46.0 52.0 48.0 50.0 12.566370614359172 1.0 2.0 49.0 -1.2
544 46.0 55.0 49.0 52.0 18.84955592153876 1.0 3.0 50.5 -1.8
545 46.0 58.0 50.0 54.0 25.132741228718345 1.0 4.0 52.0 -2.4
546 47.0 50.0 48.0 49.0 6.283185307179586 1.0 1.0 48.5 -0.6
547 47.0 53.0 49.0 51.0 12.566370614359172 1.0 2.0 50.0 -1.2
548 47.0 56.0 50.0 53.0 18.84955592153876 1.0 3.0 51.5 -1.8
549 47.0 59.0 51.0 55.0 25.132741228718345 1.0 4.0 53.0 -2.4
550 48.0 51.0 49.0 50.0 6.283185307179586 1.0 1.0 49.5 -0.6
551 48.0 54.0 50.0 52.0 12.566370614359172 1.0 2.0 51.0 -1.2
552 48.0 57.0 51.0 54.0 18.84955592153876 1.0 3.0 52.5 -1.8
553 48.0 60.0 52.0 56.0 25.132741228718345 1.0 4.0 54.0 -2.4
554 49.0 52.0 50.0 51.0 6.283185307179586 1.0 1.0 50.5 -0.6
555 49.0 55.0 51.0 53.0 12.566370614359172 1.0 2.0 52.0 -1.2
556 49.0 58.0 52.0 55.0 18.84955592153876 1.0 3.0 53.5 -1.8
557 50.0 53.0 51.0 52.0 6.283185307179586 1.0 1.0 51.5 -0.6
558 50.0 56.0 52.0 54.0 12.566370614359172 1.0 2.0 53.0 -1.2
559 50.0 59.0 53.0 56.0 18.84955592153876 1.0 3.0 54.5 -1.8
560 51.0 54.0 52.0 53.0 6.283185307179586 1.0 1.0 52.5 -0.6
561 51.0 57.0 53.0 55.0 12.566370614359172 1.0 2.0 54.0 -1.2
562 51.0 60.0 54.0 57.0 18.84955592153876 1.0 3.0 55.5 -1.8
563 52.0 55.0 53.0 54.0 6.283185307179586 1.0 1.0 53.5 -0.6
564 52.0 58.0 54.0 56.0 12.566370614359172 1.0 2.0 55.0 -1.2
565 53.0 56.0 54.0 55.0 6.283185307179586 1.0 1.0 54.5 -0.6
566 53.0 59.0 55.0 57.0 12.566370614359172 1.0 2.0 56.0 -1.2
567 54.0 57.0 55.0 56.0 6.283185307179586 1.0 1.0 55.5 -0.6
568 54.0 60.0 56.0 58.0 12.566370614359172 1.0 2.0 57.0 -1.2
569 55.0 58.0 56.0 57.0 6.283185307179586 1.0 1.0 56.5 -0.6
570 56.0 59.0 57.0 58.0 6.283185307179586 1.0 1.0 57.5 -0.6
571 57.0 60.0 58.0 59.0 6.283185307179586 1.0 1.0 58.5 -0.6

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

36
dist/assets/Dashboard-CLuwGZTT.js vendored Normal file
View File

@ -0,0 +1,36 @@
import{c as n,d as _,a as o,b as e,F as c,r as d,o as s,t as l,n as r,e as u,u as v,f as y,g as f,h as F}from"./index-C436_g8x.js";import{S as w}from"./server-Del2gqI5.js";import{C}from"./clock-gKlC1TlO.js";import{U as S}from"./upload-DdEqBuGd.js";/**
* @license lucide-vue-next v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const D=n("circle-check-big",[["path",{d:"M21.801 10A10 10 0 1 1 17 3.335",key:"yps3ct"}],["path",{d:"m9 11 3 3L22 4",key:"1pflzl"}]]);/**
* @license lucide-vue-next v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const N=n("package-check",[["path",{d:"m16 16 2 2 4-4",key:"gfu2re"}],["path",{d:"M21 10V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l2-1.14",key:"e7tb2h"}],["path",{d:"m7.5 4.27 9 5.15",key:"1c824w"}],["polyline",{points:"3.29 7 12 12 20.71 7",key:"ousv84"}],["line",{x1:"12",x2:"12",y1:"22",y2:"12",key:"a4e8g8"}]]);/**
* @license lucide-vue-next v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const z=n("target",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["circle",{cx:"12",cy:"12",r:"6",key:"1vlfrh"}],["circle",{cx:"12",cy:"12",r:"2",key:"1c9p78"}]]);/**
* @license lucide-vue-next v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const A=n("trending-down",[["polyline",{points:"22 17 13.5 8.5 8.5 13.5 2 7",key:"1r2t7k"}],["polyline",{points:"16 17 22 17 22 11",key:"11uiuu"}]]);/**
* @license lucide-vue-next v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const V=n("trending-up",[["polyline",{points:"22 7 13.5 15.5 8.5 10.5 2 17",key:"126l90"}],["polyline",{points:"16 7 22 7 22 13",key:"kwv8wd"}]]);/**
* @license lucide-vue-next v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const M=n("wifi",[["path",{d:"M12 20h.01",key:"zekei9"}],["path",{d:"M2 8.82a15 15 0 0 1 20 0",key:"dnpr2z"}],["path",{d:"M5 12.859a10 10 0 0 1 14 0",key:"1x1e6c"}],["path",{d:"M8.5 16.429a5 5 0 0 1 7 0",key:"1bycff"}]]);/**
* @license lucide-vue-next v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const j=n("wrench",[["path",{d:"M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z",key:"cbrjhi"}]]),B={class:"p-6"},L={class:"grid grid-cols-4 gap-6 mb-6"},T={class:"flex items-start justify-between"},U={class:"flex-1"},W={class:"text-sm mb-2",style:{color:"rgba(0, 0, 0, 0.65)"}},E={class:"text-3xl font-semibold mb-2"},G={class:"text-sm"},I={class:"bg-white p-6 rounded-lg mb-6",style:{"box-shadow":"0 1px 2px rgba(0, 0, 0, 0.05)"}},P={style:{display:"flex","flex-direction":"column",gap:"16px"}},R={style:{width:"60px","text-align":"right","font-size":"14px",color:"rgba(0,0,0,0.65)","flex-shrink":"0"}},q={style:{flex:"1","background-color":"#F5F5F5","border-radius":"4px",height:"24px",overflow:"hidden"}},H={style:{width:"40px","font-size":"14px",color:"rgba(0,0,0,0.85)"}},J={class:"bg-white p-6 rounded-lg",style:{"box-shadow":"0 1px 2px rgba(0, 0, 0, 0.05)"}},K={class:"grid grid-cols-2 gap-6"},O={class:"flex items-center justify-between mb-4"},Q={class:"text-base font-medium"},X={class:"px-2 py-1 rounded text-xs",style:{"background-color":"#F0F2F5",color:"rgba(0, 0, 0, 0.65)"}},Y={class:"flex-1"},Z={class:"text-sm mb-1"},$={class:"text-sm",style:{color:"rgba(0, 0, 0, 0.45)"}},ee={class:"flex items-center gap-3"},te={key:0,class:"text-xs",style:{color:"rgba(0, 0, 0, 0.45)"}},se={key:0,class:"w-full mt-3 text-center text-sm",style:{color:"#1890FF"}},ce=_({__name:"Dashboard",setup(le){const b=[{label:"",value:"5,234",trend:"up",trendValue:"+5.2%",color:"#1890FF",icon:w},{label:"",value:"4,856",trend:"up",trendValue:"+2.8%",color:"#52C41A",icon:M},{label:"",value:"4,912",trend:"up",trendValue:"+1.5%",color:"#1890FF",icon:D},{label:"",value:"156",color:"#722ED1",icon:N},{label:"",value:"23",trend:"down",trendValue:"-12.3%",color:"#FF4D4F",icon:j},{label:"",value:"56",color:"#FA8C16",icon:z},{label:"",value:"45",color:"#FAAD14",icon:C},{label:"",value:"8",color:"#13C2C2",icon:S}],x=[{name:"",value:45,color:"#52C41A"},{name:"",value:378,color:"#FF4D4F"},{name:"",value:286,color:"#FAAD14"},{name:"",value:7,color:"#8C8C8C"}],m=F(()=>Math.max(...x.map(h=>h.value))),g=[{title:"",count:8,tasks:[{deviceSN:"SN2024030710",description:"v2.3.5",time:"1"},{deviceSN:"SN2024030711",description:"v2.3.5",time:"1"}]},{title:"",count:5,tasks:[{deviceSN:"SN2024030530",description:"",time:"4"},{deviceSN:"SN2024030531",description:"",time:"6"}]}];return(h,a)=>(s(),o("div",B,[a[3]||(a[3]=e("div",{class:"mb-6"},[e("h2",{class:"text-2xl font-semibold mb-1"},""),e("p",{class:"text-sm",style:{color:"rgba(0, 0, 0, 0.45)"}},"")],-1)),e("div",L,[(s(),o(c,null,d(b,(t,p)=>e("div",{key:p,class:"bg-white p-6 rounded-lg",style:{"box-shadow":"0 1px 2px rgba(0, 0, 0, 0.05)"}},[e("div",T,[e("div",U,[e("div",W,l(t.label),1),e("div",E,l(t.value),1),t.trend&&t.trendValue?(s(),o("div",{key:0,class:"flex items-center gap-1",style:r({color:t.trend==="up"?"#52C41A":"#FF4D4F"})},[t.trend==="up"?(s(),u(v(V),{key:0,size:14})):(s(),u(v(A),{key:1,size:14})),e("span",G,l(t.trendValue),1)],4)):y("",!0)]),e("div",{class:"w-12 h-12 rounded-lg flex items-center justify-center",style:r({backgroundColor:t.color+"15"})},[(s(),u(f(t.icon),{size:24,style:r({color:t.color})},null,8,["style"]))],4)])])),64))]),e("div",I,[a[0]||(a[0]=e("h3",{class:"text-lg font-semibold mb-6"},"",-1)),e("div",P,[(s(),o(c,null,d(x,t=>e("div",{key:t.name,style:{display:"flex","align-items":"center",gap:"12px"}},[e("div",R,l(t.name),1),e("div",q,[e("div",{style:r({width:t.value/m.value*100+"%",height:"100%",backgroundColor:t.color,borderRadius:"0 4px 4px 0",transition:"width 0.3s ease",minWidth:t.value>0?"2px":"0"})},null,4)]),e("div",H,l(t.value),1)])),64))])]),e("div",J,[a[2]||(a[2]=e("h3",{class:"text-lg font-semibold mb-6"},"",-1)),e("div",K,[(s(),o(c,null,d(g,(t,p)=>e("div",{key:p},[e("div",O,[e("h4",Q,l(t.title),1),e("span",X,l(t.count),1)]),e("div",null,[(s(!0),o(c,null,d(t.tasks,(i,k)=>(s(),o("div",{key:k,class:"flex items-start justify-between py-3",style:{"border-bottom":"1px solid #F0F0F0"}},[e("div",Y,[e("div",Z,l(i.deviceSN),1),e("div",$,l(i.description),1)]),e("div",ee,[i.time?(s(),o("span",te,l(i.time),1)):y("",!0),a[1]||(a[1]=e("button",{class:"text-sm",style:{color:"#1890FF"}},"",-1))])]))),128))]),t.tasks.length<t.count?(s(),o("button",se," "+l(t.count)+" ",1)):y("",!0)])),64))])])]))}});export{ce as default};

6
dist/assets/DeviceDetail-BcuqN-1h.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/assets/DeviceList-DhiwP2uf.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
import{d as o,a,b as e,t as l,o as r}from"./index-C436_g8x.js";const n={class:"p-6"},d={class:"text-2xl font-semibold mb-4"},c=o({__name:"PlaceholderPage",props:{title:{}},setup(s){return(i,t)=>(r(),a("div",n,[e("h2",d,l(s.title),1),t[0]||(t[0]=e("div",{class:"bg-white p-6 rounded-lg",style:{"box-shadow":"0 1px 2px rgba(0,0,0,0.05)"}},[e("p",{style:{color:"rgba(0,0,0,0.45)"}},"此页面正在开发中...")],-1))]))}});export{c as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

6
dist/assets/arrow-left-DgHW7lI4.js vendored Normal file
View File

@ -0,0 +1,6 @@
import{c as e}from"./index-C436_g8x.js";/**
* @license lucide-vue-next v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const t=e("arrow-left",[["path",{d:"m12 19-7-7 7-7",key:"1l729n"}],["path",{d:"M19 12H5",key:"x3x0zl"}]]);export{t as A};

6
dist/assets/circle-check-Ddc7xHK2.js vendored Normal file
View File

@ -0,0 +1,6 @@
import{c}from"./index-C436_g8x.js";/**
* @license lucide-vue-next v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const r=c("circle-check",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]]);export{r as C};

6
dist/assets/circle-stop-ij390Vuh.js vendored Normal file
View File

@ -0,0 +1,6 @@
import{c}from"./index-C436_g8x.js";/**
* @license lucide-vue-next v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const r=c("circle-stop",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["rect",{x:"9",y:"9",width:"6",height:"6",rx:"1",key:"1ssd4o"}]]);export{r as C};

6
dist/assets/clock-gKlC1TlO.js vendored Normal file
View File

@ -0,0 +1,6 @@
import{c}from"./index-C436_g8x.js";/**
* @license lucide-vue-next v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const o=c("clock",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["polyline",{points:"12 6 12 12 16 14",key:"68esgv"}]]);export{o as C};

6
dist/assets/download-Bug6XpZq.js vendored Normal file
View File

@ -0,0 +1,6 @@
import{c as o}from"./index-C436_g8x.js";/**
* @license lucide-vue-next v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const a=o("download",[["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["polyline",{points:"7 10 12 15 17 10",key:"2ggqvy"}],["line",{x1:"12",x2:"12",y1:"15",y2:"3",key:"1vk2je"}]]);export{a as D};

56
dist/assets/index-C436_g8x.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/assets/index-CHZHmOOh.css vendored Normal file

File diff suppressed because one or more lines are too long

6
dist/assets/info-D3XHB5ch.js vendored Normal file
View File

@ -0,0 +1,6 @@
import{c}from"./index-C436_g8x.js";/**
* @license lucide-vue-next v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const o=c("info",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 16v-4",key:"1dtifu"}],["path",{d:"M12 8h.01",key:"e9boi3"}]]);export{o as I};

6
dist/assets/plus-vY9kAeXh.js vendored Normal file
View File

@ -0,0 +1,6 @@
import{c as e}from"./index-C436_g8x.js";/**
* @license lucide-vue-next v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const a=e("plus",[["path",{d:"M5 12h14",key:"1ays0h"}],["path",{d:"M12 5v14",key:"s699le"}]]);export{a as P};

6
dist/assets/search-BZymCmWs.js vendored Normal file
View File

@ -0,0 +1,6 @@
import{c}from"./index-C436_g8x.js";/**
* @license lucide-vue-next v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const r=c("search",[["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}],["path",{d:"m21 21-4.3-4.3",key:"1qie3q"}]]);export{r as S};

6
dist/assets/server-Del2gqI5.js vendored Normal file
View File

@ -0,0 +1,6 @@
import{c as e}from"./index-C436_g8x.js";/**
* @license lucide-vue-next v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const y=e("server",[["rect",{width:"20",height:"8",x:"2",y:"2",rx:"2",ry:"2",key:"ngkwjq"}],["rect",{width:"20",height:"8",x:"2",y:"14",rx:"2",ry:"2",key:"iecqi9"}],["line",{x1:"6",x2:"6.01",y1:"6",y2:"6",key:"16zg32"}],["line",{x1:"6",x2:"6.01",y1:"18",y2:"18",key:"nzw8ys"}]]);export{y as S};

6
dist/assets/trash-2-B47UwHOB.js vendored Normal file
View File

@ -0,0 +1,6 @@
import{c as e}from"./index-C436_g8x.js";/**
* @license lucide-vue-next v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const a=e("trash-2",[["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6",key:"4alrt4"}],["path",{d:"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2",key:"v07s0e"}],["line",{x1:"10",x2:"10",y1:"11",y2:"17",key:"1uufr5"}],["line",{x1:"14",x2:"14",y1:"11",y2:"17",key:"xtxkd"}]]);export{a as T};

View File

@ -0,0 +1,6 @@
import{c as e}from"./index-C436_g8x.js";/**
* @license lucide-vue-next v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const t=e("triangle-alert",[["path",{d:"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3",key:"wmoenq"}],["path",{d:"M12 9v4",key:"juzpu7"}],["path",{d:"M12 17h.01",key:"p32p05"}]]);export{t as T};

6
dist/assets/upload-DdEqBuGd.js vendored Normal file
View File

@ -0,0 +1,6 @@
import{c as o}from"./index-C436_g8x.js";/**
* @license lucide-vue-next v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const a=o("upload",[["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["polyline",{points:"17 8 12 3 7 8",key:"t8dd8p"}],["line",{x1:"12",x2:"12",y1:"3",y2:"15",key:"widbto"}]]);export{a as U};

13
dist/index.html vendored Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>设备管理平台</title>
<script type="module" crossorigin src="/assets/index-C436_g8x.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CHZHmOOh.css">
</head>
<body>
<div id="app"></div>
</body>
</html>

View File

@ -1,15 +1,12 @@
<!DOCTYPE html>
<!DOCTYPE html> <html lang="zh-CN">
<html lang="en"> <head>
<head> <meta charset="UTF-8" />
<meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>设备管理平台</title>
<title>Enterprise SaaS Dashboard Design</title> </head>
</head> <body>
<div id="app"></div>
<body> <script type="module" src="/src/main.ts"></script>
<div id="root"></div> </body>
<script type="module" src="/src/main.tsx"></script> </html>
</body>
</html>

2052
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,90 +1,22 @@
{ {
"name": "@figma/my-make-file", "name": "device-management-platform",
"private": true, "private": true,
"version": "0.0.1", "version": "0.0.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite",
"build": "vite build", "build": "vite build",
"dev": "vite" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@emotion/react": "11.14.0", "vue": "^3.5.13",
"@emotion/styled": "11.14.1", "vue-router": "^4.5.0",
"@mui/icons-material": "7.3.5", "lucide-vue-next": "^0.487.0"
"@mui/material": "7.3.5",
"@popperjs/core": "2.11.8",
"@radix-ui/react-accordion": "1.2.3",
"@radix-ui/react-alert-dialog": "1.1.6",
"@radix-ui/react-aspect-ratio": "1.1.2",
"@radix-ui/react-avatar": "1.1.3",
"@radix-ui/react-checkbox": "1.1.4",
"@radix-ui/react-collapsible": "1.1.3",
"@radix-ui/react-context-menu": "2.2.6",
"@radix-ui/react-dialog": "1.1.6",
"@radix-ui/react-dropdown-menu": "2.1.6",
"@radix-ui/react-hover-card": "1.1.6",
"@radix-ui/react-label": "2.1.2",
"@radix-ui/react-menubar": "1.1.6",
"@radix-ui/react-navigation-menu": "1.2.5",
"@radix-ui/react-popover": "1.1.6",
"@radix-ui/react-progress": "1.1.2",
"@radix-ui/react-radio-group": "1.2.3",
"@radix-ui/react-scroll-area": "1.2.3",
"@radix-ui/react-select": "2.1.6",
"@radix-ui/react-separator": "1.1.2",
"@radix-ui/react-slider": "1.2.3",
"@radix-ui/react-slot": "1.1.2",
"@radix-ui/react-switch": "1.1.3",
"@radix-ui/react-tabs": "1.1.3",
"@radix-ui/react-toggle-group": "1.1.2",
"@radix-ui/react-toggle": "1.1.2",
"@radix-ui/react-tooltip": "1.1.8",
"canvas-confetti": "1.9.4",
"class-variance-authority": "0.7.1",
"clsx": "2.1.1",
"cmdk": "1.1.1",
"date-fns": "3.6.0",
"embla-carousel-react": "8.6.0",
"input-otp": "1.4.2",
"lucide-react": "0.487.0",
"motion": "12.23.24",
"next-themes": "0.4.6",
"react-day-picker": "8.10.1",
"react-dnd": "16.0.1",
"react-dnd-html5-backend": "16.0.1",
"react-hook-form": "7.55.0",
"react-popper": "2.3.0",
"react-resizable-panels": "2.1.7",
"react-responsive-masonry": "2.7.1",
"react-router": "7.13.0",
"react-slick": "0.31.0",
"recharts": "2.15.2",
"sonner": "2.0.3",
"tailwind-merge": "3.2.0",
"tw-animate-css": "1.3.8",
"vaul": "1.1.2"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^5.2.3",
"@tailwindcss/vite": "4.1.12", "@tailwindcss/vite": "4.1.12",
"@vitejs/plugin-react": "4.7.0",
"tailwindcss": "4.1.12", "tailwindcss": "4.1.12",
"vite": "6.3.5" "vite": "6.3.5"
},
"peerDependencies": {
"react": "18.3.1",
"react-dom": "18.3.1"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
},
"pnpm": {
"overrides": {
"vite": "6.3.5"
}
} }
} }

View File

@ -1,15 +0,0 @@
/**
* PostCSS Configuration
*
* Tailwind CSS v4 (via @tailwindcss/vite) automatically sets up all required
* PostCSS plugins you do NOT need to include `tailwindcss` or `autoprefixer` here.
*
* This file only exists for adding additional PostCSS plugins, if needed.
* For example:
*
* import postcssNested from 'postcss-nested'
* export default { plugins: [postcssNested()] }
*
* Otherwise, you can leave this file empty.
*/
export default {}

View File

@ -1,6 +0,0 @@
import { RouterProvider } from "react-router";
import { router } from "./routes";
export default function App() {
return <RouterProvider router={router} />;
}

3
src/app/App.vue Normal file
View File

@ -0,0 +1,3 @@
<template>
<router-view />
</template>

View File

@ -1,13 +0,0 @@
import { Outlet } from "react-router";
import { Sidebar } from "./components/Sidebar";
export function Layout() {
return (
<div className="flex h-screen overflow-hidden">
<Sidebar />
<main className="flex-1 overflow-auto" style={{ backgroundColor: '#F0F2F5' }}>
<Outlet />
</main>
</div>
);
}

16
src/app/Layout.vue Normal file
View File

@ -0,0 +1,16 @@
<template>
<div class="flex h-screen overflow-hidden">
<Sidebar />
<div class="flex-1 flex flex-col overflow-hidden">
<Header />
<main class="flex-1 overflow-auto" style="background-color: #F0F2F5">
<router-view />
</main>
</div>
</div>
</template>
<script setup lang="ts">
import Sidebar from './components/Sidebar.vue'
import Header from './components/Header.vue'
</script>

View File

@ -0,0 +1,47 @@
<template>
<header class="h-12 flex items-center justify-end px-6 flex-shrink-0 gap-4"
style="background-color: #fff; border-bottom: 1px solid #e8e8e8">
<router-link to="/reports" class="text-gray-500 hover:text-gray-900 transition-colors" title="数据统计">
<BarChart3 :size="20" />
</router-link>
<router-link to="/cost-reports" class="text-gray-500 hover:text-gray-900 transition-colors" title="运营报告">
<FileText :size="20" />
</router-link>
<div class="w-px h-5 bg-gray-200" />
<div ref="menuRef" class="relative">
<button @click="open = !open" class="flex items-center gap-2 cursor-pointer">
<div class="w-8 h-8 rounded-full flex items-center justify-center text-white text-sm"
style="background-color: #1890FF"></div>
<span class="text-sm text-gray-700">管理员</span>
</button>
<div v-if="open" class="absolute right-0 top-10 w-36 rounded shadow-lg py-1 z-50"
style="background-color: #fff; border: 1px solid #e8e8e8">
<router-link v-for="item in systemMenuItems" :key="item.path" :to="item.path" @click="open = false"
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors">
{{ item.label }}
</router-link>
</div>
</div>
</header>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { BarChart3, FileText } from 'lucide-vue-next'
const open = ref(false)
const menuRef = ref<HTMLDivElement>()
const systemMenuItems = [
{ path: '/users', label: '用户管理' },
{ path: '/roles', label: '角色权限' },
{ path: '/logs', label: '操作日志' },
{ path: '/settings', label: '系统设置' },
]
const handleClick = (e: MouseEvent) => {
if (menuRef.value && !menuRef.value.contains(e.target as Node)) open.value = false
}
onMounted(() => document.addEventListener('mousedown', handleClick))
onUnmounted(() => document.removeEventListener('mousedown', handleClick))
</script>

View File

@ -1,114 +0,0 @@
import { Link, useLocation } from "react-router";
import {
LayoutDashboard,
Server,
Settings,
FileText,
Users,
BarChart3,
ClipboardList,
Database,
Key,
Smartphone,
Gauge,
Trash2,
FileCode,
Sliders,
} from "lucide-react";
const menuItems = [
{ path: "/", icon: LayoutDashboard, label: "首页" },
{ path: "/models", icon: Server, label: "型号管理" },
{ path: "/devices", icon: Database, label: "设备列表" },
{
path: "/registration",
icon: ClipboardList,
label: "设备登记",
},
{ path: "/licenses", icon: Key, label: "授权管理" },
{ path: "/activation", icon: Smartphone, label: "激活管理" },
{ path: "/calibration", icon: Gauge, label: "校准管理" },
{ path: "/scrap", icon: Trash2, label: "报废管理" },
{ path: "/config-files", icon: FileCode, label: "配置文件" },
{ path: "/reports", icon: BarChart3, label: "数据报表" },
{ path: "/logs", icon: FileText, label: "操作日志" },
{ path: "/settings", icon: Settings, label: "系统设置" },
];
export function Sidebar() {
const location = useLocation();
return (
<aside
className="w-[240px] h-screen flex-shrink-0"
style={{ backgroundColor: "#001529" }}
>
<div className="flex flex-col h-full">
{/* Logo */}
<div
className="h-16 flex items-center px-6"
style={{
borderBottom: "1px solid rgba(255,255,255,0.1)",
}}
>
<h1 className="text-white text-xl font-semibold">
</h1>
</div>
{/* Menu Items */}
<nav className="flex-1 py-6">
{menuItems.map((item) => {
const Icon = item.icon;
const isActive = location.pathname === item.path;
return (
<Link
key={item.path}
to={item.path}
className="flex items-center gap-3 px-6 py-3 transition-colors"
style={{
color: isActive
? "#fff"
: "rgba(255, 255, 255, 0.85)",
backgroundColor: isActive
? "#1890FF"
: "transparent",
}}
>
<Icon size={18} />
<span>{item.label}</span>
</Link>
);
})}
</nav>
{/* User Info */}
<div
className="p-6"
style={{
borderTop: "1px solid rgba(255,255,255,0.1)",
}}
>
<div className="flex items-center gap-3">
<div
className="w-8 h-8 rounded-full flex items-center justify-center"
style={{ backgroundColor: "#1890FF" }}
>
<span className="text-white text-sm"></span>
</div>
<div>
<div className="text-white text-sm"></div>
<div
className="text-xs"
style={{ color: "rgba(255, 255, 255, 0.65)" }}
>
admin@example.com
</div>
</div>
</div>
</div>
</div>
</aside>
);
}

View File

@ -0,0 +1,47 @@
<template>
<aside class="w-[200px] h-screen flex-shrink-0 overflow-y-auto" style="background-color: #001529">
<div class="h-1" style="background-color: #1890FF" />
<router-link to="/" class="block px-4 py-3 text-sm font-medium transition-colors"
:style="{ color: route.path === '/' ? '#fff' : 'rgba(255,255,255,0.85)', backgroundColor: route.path === '/' ? '#1890FF' : 'transparent', borderBottom: '1px solid rgba(255,255,255,0.06)' }">
首页
</router-link>
<nav class="py-0">
<div v-for="group in menuGroups" :key="group.title">
<button @click="toggleGroup(group.title)"
class="w-full text-left px-4 py-2.5 text-sm font-medium cursor-pointer"
style="color: rgba(255,255,255,0.85); border-bottom: 1px solid rgba(255,255,255,0.06)">
{{ group.title }}
</button>
<template v-if="!collapsedGroups.has(group.title)">
<router-link v-for="item in group.items" :key="item.path" :to="item.path"
class="block px-8 py-2 text-sm transition-colors"
:style="{ color: route.path === item.path ? '#fff' : 'rgba(255,255,255,0.65)', backgroundColor: route.path === item.path ? '#1890FF' : 'transparent' }">
{{ item.label }}
</router-link>
</template>
</div>
</nav>
</aside>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const collapsedGroups = ref(new Set<string>())
const toggleGroup = (title: string) => {
if (collapsedGroups.value.has(title)) collapsedGroups.value.delete(title)
else collapsedGroups.value.add(title)
}
const menuGroups = [
{ title: '设备', items: [{ path: '/devices', label: '设备列表' }, { path: '/models', label: '型号管理' }] },
{ title: '授权', items: [{ path: '/licenses', label: '授权管理' }, { path: '/activation', label: '激活管理' }] },
{ title: '固件', items: [{ path: '/firmware', label: '固件库' }, { path: '/firmware-upgrade', label: '升级任务' }] },
{ title: '配置', items: [{ path: '/config-files', label: '配置管理' }] },
{ title: '校准', items: [{ path: '/calibration', label: '校准记录' }] },
{ title: '维修', items: [{ path: '/repair-orders', label: '维修工单' }, { path: '/repair-stats', label: '维修统计' }, { path: '/scrap', label: '报废回收' }] },
]
</script>

View File

@ -1,27 +0,0 @@
import React, { useState } from 'react'
const ERROR_IMG_SRC =
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODgiIGhlaWdodD0iODgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBvcGFjaXR5PSIuMyIgZmlsbD0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIzLjciPjxyZWN0IHg9IjE2IiB5PSIxNiIgd2lkdGg9IjU2IiBoZWlnaHQ9IjU2IiByeD0iNiIvPjxwYXRoIGQ9Im0xNiA1OCAxNi0xOCAzMiAzMiIvPjxjaXJjbGUgY3g9IjUzIiBjeT0iMzUiIHI9IjciLz48L3N2Zz4KCg=='
export function ImageWithFallback(props: React.ImgHTMLAttributes<HTMLImageElement>) {
const [didError, setDidError] = useState(false)
const handleError = () => {
setDidError(true)
}
const { src, alt, style, className, ...rest } = props
return didError ? (
<div
className={`inline-block bg-gray-100 text-center align-middle ${className ?? ''}`}
style={style}
>
<div className="flex items-center justify-center w-full h-full">
<img src={ERROR_IMG_SRC} alt="Error loading image" {...rest} data-original-url={src} />
</div>
</div>
) : (
<img src={src} alt={alt} className={className} style={style} {...rest} onError={handleError} />
)
}

View File

@ -1,492 +0,0 @@
import {
Info,
Smartphone,
Wifi,
Download,
CheckCircle2,
FileText,
} from "lucide-react";
interface ActivationRecord {
sn: string;
model: string;
activationDate: string;
status: "已激活" | "激活失败" | "待激活";
operator: string;
}
export default function ActivationManagement() {
const activationRecords: ActivationRecord[] = [
{
sn: "GD30-2025-000001",
model: "GD30 高密度电法仪",
activationDate: "2025-02-10 14:30",
status: "已激活",
operator: "王工程师",
},
{
sn: "GT20-2025-000045",
model: "GT20 瞬变电磁仪",
activationDate: "2025-02-09 10:15",
status: "已激活",
operator: "李工程师",
},
{
sn: "GM10-2025-000023",
model: "GM10 大地电磁仪",
activationDate: "2025-02-08 16:20",
status: "待激活",
operator: "张工程师",
},
];
const getStatusStyle = (
status: ActivationRecord["status"],
) => {
switch (status) {
case "已激活":
return {
backgroundColor: "#F6FFED",
color: "#52C41A",
border: "1px solid #B7EB8F",
};
case "激活失败":
return {
backgroundColor: "#FFF1F0",
color: "#FF4D4F",
border: "1px solid #FFCCC7",
};
case "待激活":
return {
backgroundColor: "#FFFBE6",
color: "#FAAD14",
border: "1px solid #FFE58F",
};
}
};
return (
<div className="p-6">
{/* Page Header */}
<div className="mb-6">
<h2 className="text-2xl font-semibold mb-1">
</h2>
<p
className="text-sm"
style={{ color: "rgba(0, 0, 0, 0.45)" }}
>
</p>
</div>
{/* Info Banner */}
<div
className="mb-6 p-4 rounded-lg flex items-start gap-3"
style={{
backgroundColor: "#E6F7FF",
border: "1px solid #91D5FF",
}}
>
<Info
size={20}
style={{
color: "#1890FF",
flexShrink: 0,
marginTop: 2,
}}
/>
<div style={{ color: "#0050B3" }}>
<div className="font-medium mb-1">
APP激活流程说明
</div>
<div className="text-sm">
APP开机时自动读取设备UID SN匹配
</div>
</div>
</div>
{/* Filter Card */}
<div
className="bg-white p-6 rounded-lg mb-6"
style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }}
>
<div className="grid grid-cols-4 gap-4">
<div>
<label
className="block text-sm mb-2"
style={{ color: "rgba(0, 0, 0, 0.65)" }}
>
SN号
</label>
<input
type="text"
className="w-full px-3 py-2 border rounded"
style={{ borderColor: "#D9D9D9" }}
placeholder="输入设备SN号搜索"
/>
</div>
<div>
<label
className="block text-sm mb-2"
style={{ color: "rgba(0, 0, 0, 0.65)" }}
>
</label>
<select
className="w-full px-3 py-2 border rounded"
style={{
borderColor: "#D9D9D9",
backgroundColor: "#fff",
}}
>
<option></option>
<option></option>
<option></option>
<option></option>
</select>
</div>
<div>
<label
className="block text-sm mb-2"
style={{ color: "rgba(0, 0, 0, 0.65)" }}
>
</label>
<input
type="date"
className="w-full px-3 py-2 border rounded"
style={{ borderColor: "#D9D9D9" }}
/>
</div>
<div className="flex items-end">
<button
className="w-full px-4 py-2 rounded text-white"
style={{ backgroundColor: "#1890FF" }}
>
</button>
</div>
</div>
</div>
{/* Activation Records List */}
<div
className="bg-white rounded-lg mb-6"
style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }}
>
<div
className="p-6 border-b"
style={{ borderColor: "#F0F0F0" }}
>
<h3 className="text-lg font-semibold"></h3>
</div>
<div className="overflow-x-auto">
<table className="w-full">
<thead style={{ backgroundColor: "#FAFAFA" }}>
<tr>
<th
className="px-6 py-3 text-left text-sm font-medium"
style={{ color: "rgba(0, 0, 0, 0.85)" }}
>
SN号
</th>
<th
className="px-6 py-3 text-left text-sm font-medium"
style={{ color: "rgba(0, 0, 0, 0.85)" }}
>
</th>
<th
className="px-6 py-3 text-left text-sm font-medium"
style={{ color: "rgba(0, 0, 0, 0.85)" }}
>
</th>
<th
className="px-6 py-3 text-left text-sm font-medium"
style={{ color: "rgba(0, 0, 0, 0.85)" }}
>
</th>
<th
className="px-6 py-3 text-left text-sm font-medium"
style={{ color: "rgba(0, 0, 0, 0.85)" }}
>
</th>
<th
className="px-6 py-3 text-left text-sm font-medium"
style={{ color: "rgba(0, 0, 0, 0.85)" }}
>
</th>
</tr>
</thead>
<tbody>
{activationRecords.map((record, index) => (
<tr
key={index}
className="border-b"
style={{ borderColor: "#F0F0F0" }}
>
<td className="px-6 py-4">{record.sn}</td>
<td
className="px-6 py-4"
style={{ color: "rgba(0, 0, 0, 0.65)" }}
>
{record.model}
</td>
<td
className="px-6 py-4"
style={{ color: "rgba(0, 0, 0, 0.65)" }}
>
{record.activationDate}
</td>
<td
className="px-6 py-4"
style={{ color: "rgba(0, 0, 0, 0.65)" }}
>
{record.operator}
</td>
<td className="px-6 py-4">
<span
className="px-2 py-1 rounded text-xs"
style={getStatusStyle(record.status)}
>
{record.status}
</span>
</td>
<td className="px-6 py-4">
<div className="flex items-center gap-3">
<button
className="text-sm"
style={{ color: "#1890FF" }}
>
</button>
{record.status === "待激活" && (
<button
className="text-sm"
style={{ color: "#52C41A" }}
>
</button>
)}
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* Activation Process Card */}
<div
className="bg-white p-6 rounded-lg mb-4"
style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }}
>
<h3 className="text-lg font-semibold mb-6">
APP激活流程 &
</h3>
<div className="flex items-center justify-between gap-6">
<div
className="flex-1 p-6 rounded-lg text-center"
style={{
backgroundColor: "#E6F7FF",
border: "2px solid #1890FF",
}}
>
<div
className="w-12 h-12 rounded-full flex items-center justify-center mx-auto mb-3"
style={{
backgroundColor: "#1890FF",
color: "#fff",
fontSize: "20px",
fontWeight: "bold",
}}
>
</div>
<div
className="font-medium mb-2"
style={{ color: "#1890FF" }}
>
UID
</div>
<div
className="text-sm"
style={{ color: "#0050B3" }}
>
APP读取设备UID
</div>
</div>
<div style={{ color: "#D9D9D9", fontSize: "24px" }}>
</div>
<div
className="flex-1 p-6 rounded-lg text-center"
style={{
backgroundColor: "#F9F0FF",
border: "2px solid #722ED1",
}}
>
<div
className="w-12 h-12 rounded-full flex items-center justify-center mx-auto mb-3"
style={{
backgroundColor: "#722ED1",
color: "#fff",
fontSize: "20px",
fontWeight: "bold",
}}
>
</div>
<div
className="font-medium mb-2"
style={{ color: "#722ED1" }}
>
SN匹配
</div>
<div
className="text-sm"
style={{ color: "#531DAB" }}
>
SN匹配验证
</div>
</div>
<div style={{ color: "#D9D9D9", fontSize: "24px" }}>
</div>
<div
className="flex-1 p-6 rounded-lg text-center"
style={{
backgroundColor: "#F6FFED",
border: "2px solid #52C41A",
}}
>
<div
className="w-12 h-12 rounded-full flex items-center justify-center mx-auto mb-3"
style={{
backgroundColor: "#52C41A",
color: "#fff",
fontSize: "20px",
fontWeight: "bold",
}}
>
</div>
<div
className="font-medium mb-2"
style={{ color: "#52C41A" }}
>
</div>
<div
className="text-sm"
style={{ color: "#389E0D" }}
>
</div>
</div>
<div style={{ color: "#D9D9D9", fontSize: "24px" }}>
</div>
<div
className="flex-1 p-6 rounded-lg text-center"
style={{
backgroundColor: "#FFFBE6",
border: "2px solid #FAAD14",
}}
>
<div
className="w-12 h-12 rounded-full flex items-center justify-center mx-auto mb-3"
style={{
backgroundColor: "#FAAD14",
color: "#fff",
fontSize: "20px",
fontWeight: "bold",
}}
>
</div>
<div
className="font-medium mb-2"
style={{ color: "#FAAD14" }}
>
</div>
<div
className="text-sm"
style={{ color: "#D46B08" }}
>
</div>
</div>
<div style={{ color: "#D9D9D9", fontSize: "24px" }}>
</div>
<div
className="flex-1 p-6 rounded-lg text-center"
style={{
backgroundColor: "#E6FFFB",
border: "2px solid #13C2C2",
}}
>
<div
className="w-12 h-12 rounded-full flex items-center justify-center mx-auto mb-3"
style={{
backgroundColor: "#13C2C2",
color: "#fff",
fontSize: "20px",
fontWeight: "bold",
}}
>
</div>
<div
className="font-medium mb-2"
style={{ color: "#13C2C2" }}
>
</div>
<div
className="text-sm"
style={{ color: "#006D75" }}
>
</div>
</div>
</div>
</div>
{/* Boot Auth Check Banner */}
<div
className="p-4 rounded-lg flex items-start gap-3"
style={{
backgroundColor: "#FFFBE6",
border: "1px solid #FFE58F",
}}
>
<Info
size={20}
style={{
color: "#FAAD14",
flexShrink: 0,
marginTop: 2,
}}
/>
<div style={{ color: "#D46B08" }}>
<div className="font-medium"></div>
<div className="text-sm mt-1">
APP自动连接
</div>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,247 @@
<script setup lang="ts">
import { Info } from 'lucide-vue-next'
interface ActivationRecord {
sn: string
model: string
activationDate: string
status: '已激活' | '激活失败' | '待激活'
operator: string
}
const activationRecords: ActivationRecord[] = [
{
sn: 'GD30-2025-000001',
model: 'GD30 高密度电法仪',
activationDate: '2025-02-10 14:30',
status: '已激活',
operator: '王工程师',
},
{
sn: 'GT20-2025-000045',
model: 'GT20 瞬变电磁仪',
activationDate: '2025-02-09 10:15',
status: '已激活',
operator: '李工程师',
},
{
sn: 'GM10-2025-000023',
model: 'GM10 大地电磁仪',
activationDate: '2025-02-08 16:20',
status: '待激活',
operator: '张工程师',
},
]
const getStatusStyle = (status: ActivationRecord['status']) => {
switch (status) {
case '已激活':
return {
backgroundColor: '#F6FFED',
color: '#52C41A',
border: '1px solid #B7EB8F',
}
case '激活失败':
return {
backgroundColor: '#FFF1F0',
color: '#FF4D4F',
border: '1px solid #FFCCC7',
}
case '待激活':
return {
backgroundColor: '#FFFBE6',
color: '#FAAD14',
border: '1px solid #FFE58F',
}
}
}
</script>
<template>
<div class="p-6">
<!-- Page Header -->
<div class="mb-6">
<h2 class="text-2xl font-semibold mb-1">激活管理</h2>
<p class="text-sm" style="color: rgba(0, 0, 0, 0.45)">管理设备激活流程与记录</p>
</div>
<!-- Info Banner -->
<div
class="mb-6 p-4 rounded-lg flex items-start gap-3"
style="background-color: #E6F7FF; border: 1px solid #91D5FF"
>
<Info :size="20" style="color: #1890FF; flex-shrink: 0; margin-top: 2px" />
<div style="color: #0050B3">
<div class="font-medium mb-1">APP激活流程说明</div>
<div class="text-sm">
APP读取设备UID 与后台SN匹配 下载授权文件和配置 生成设备配置
激活设备激活成功后设备每次开机会自动检测授权文件更新
</div>
</div>
</div>
<!-- Filter Card -->
<div class="bg-white p-6 rounded-lg mb-6" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<div class="grid grid-cols-4 gap-4">
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.65)">设备SN号</label>
<input
type="text"
class="w-full px-3 py-2 border rounded"
style="border-color: #D9D9D9"
placeholder="输入设备SN号搜索"
/>
</div>
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.65)">激活状态</label>
<select
class="w-full px-3 py-2 border rounded"
style="border-color: #D9D9D9; background-color: #fff"
>
<option>全部</option>
<option>已激活</option>
<option>待激活</option>
<option>激活失败</option>
</select>
</div>
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.65)">激活日期</label>
<input
type="date"
class="w-full px-3 py-2 border rounded"
style="border-color: #D9D9D9"
/>
</div>
<div class="flex items-end">
<button class="w-full px-4 py-2 rounded text-white" style="background-color: #1890FF">
查询
</button>
</div>
</div>
</div>
<!-- Activation Records List -->
<div class="bg-white rounded-lg mb-6" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<div class="p-6 border-b" style="border-color: #F0F0F0">
<h3 class="text-lg font-semibold">激活记录</h3>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead style="background-color: #FAFAFA">
<tr>
<th class="px-6 py-3 text-left text-sm font-medium" style="color: rgba(0, 0, 0, 0.85)">设备SN号</th>
<th class="px-6 py-3 text-left text-sm font-medium" style="color: rgba(0, 0, 0, 0.85)">型号</th>
<th class="px-6 py-3 text-left text-sm font-medium" style="color: rgba(0, 0, 0, 0.85)">激活日期</th>
<th class="px-6 py-3 text-left text-sm font-medium" style="color: rgba(0, 0, 0, 0.85)">操作人员</th>
<th class="px-6 py-3 text-left text-sm font-medium" style="color: rgba(0, 0, 0, 0.85)">状态</th>
<th class="px-6 py-3 text-left text-sm font-medium" style="color: rgba(0, 0, 0, 0.85)">操作</th>
</tr>
</thead>
<tbody>
<tr
v-for="(record, index) in activationRecords"
:key="index"
class="border-b"
style="border-color: #F0F0F0"
>
<td class="px-6 py-4">{{ record.sn }}</td>
<td class="px-6 py-4" style="color: rgba(0, 0, 0, 0.65)">{{ record.model }}</td>
<td class="px-6 py-4" style="color: rgba(0, 0, 0, 0.65)">{{ record.activationDate }}</td>
<td class="px-6 py-4" style="color: rgba(0, 0, 0, 0.65)">{{ record.operator }}</td>
<td class="px-6 py-4">
<span class="px-2 py-1 rounded text-xs" :style="getStatusStyle(record.status)">
{{ record.status }}
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-3">
<button class="text-sm" style="color: #1890FF">详情</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Activation Process Card -->
<div class="bg-white p-6 rounded-lg mb-4" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<h3 class="text-lg font-semibold mb-6">APP激活流程 &amp; 开机授权检测</h3>
<div class="flex items-center justify-between gap-6">
<div
class="flex-1 p-6 rounded-lg text-center"
style="background-color: #E6F7FF; border: 2px solid #1890FF"
>
<div
class="w-12 h-12 rounded-full flex items-center justify-center mx-auto mb-3"
style="background-color: #1890FF; color: #fff; font-size: 20px; font-weight: bold"
></div>
<div class="font-medium mb-2" style="color: #1890FF">读取UID</div>
<div class="text-sm" style="color: #0050B3">APP读取设备UID</div>
</div>
<div style="color: #D9D9D9; font-size: 24px"></div>
<div
class="flex-1 p-6 rounded-lg text-center"
style="background-color: #F9F0FF; border: 2px solid #722ED1"
>
<div
class="w-12 h-12 rounded-full flex items-center justify-center mx-auto mb-3"
style="background-color: #722ED1; color: #fff; font-size: 20px; font-weight: bold"
></div>
<div class="font-medium mb-2" style="color: #722ED1">SN匹配</div>
<div class="text-sm" style="color: #531DAB">后台SN匹配验证</div>
</div>
<div style="color: #D9D9D9; font-size: 24px"></div>
<div
class="flex-1 p-6 rounded-lg text-center"
style="background-color: #F6FFED; border: 2px solid #52C41A"
>
<div
class="w-12 h-12 rounded-full flex items-center justify-center mx-auto mb-3"
style="background-color: #52C41A; color: #fff; font-size: 20px; font-weight: bold"
></div>
<div class="font-medium mb-2" style="color: #52C41A">下载文件</div>
<div class="text-sm" style="color: #389E0D">下载授权和配置</div>
</div>
<div style="color: #D9D9D9; font-size: 24px"></div>
<div
class="flex-1 p-6 rounded-lg text-center"
style="background-color: #FFFBE6; border: 2px solid #FAAD14"
>
<div
class="w-12 h-12 rounded-full flex items-center justify-center mx-auto mb-3"
style="background-color: #FAAD14; color: #fff; font-size: 20px; font-weight: bold"
></div>
<div class="font-medium mb-2" style="color: #FAAD14">生成配置</div>
<div class="text-sm" style="color: #D46B08">生成设备配置文件</div>
</div>
<div style="color: #D9D9D9; font-size: 24px"></div>
<div
class="flex-1 p-6 rounded-lg text-center"
style="background-color: #E6FFFB; border: 2px solid #13C2C2"
>
<div
class="w-12 h-12 rounded-full flex items-center justify-center mx-auto mb-3"
style="background-color: #13C2C2; color: #fff; font-size: 20px; font-weight: bold"
></div>
<div class="font-medium mb-2" style="color: #13C2C2">激活设备</div>
<div class="text-sm" style="color: #006D75">完成激活流程</div>
</div>
</div>
</div>
<!-- Boot Auth Check Banner -->
<div
class="p-4 rounded-lg flex items-start gap-3"
style="background-color: #FFFBE6; border: 1px solid #FFE58F"
>
<Info :size="20" style="color: #FAAD14; flex-shrink: 0; margin-top: 2px" />
<div style="color: #D46B08">
<div class="font-medium">开机授权检测机制</div>
<div class="text-sm mt-1">
设备每次开机 APP自动连接 检测授权文件是否有更新 有更新则自动下载
</div>
</div>
</div>
</div>
</template>

View File

@ -1,444 +0,0 @@
import {
Info,
AlertTriangle,
Clock,
Download,
} from "lucide-react";
interface CalibrationRecord {
boardSn: string;
deviceSn: string;
calibrationDate: string;
calibrator: string;
expiryDate: string;
status: "合格" | "不合格" | "待校准";
}
export default function CalibrationRecords() {
const calibrationRecords: CalibrationRecord[] = [
{
boardSn: "AC20240308001",
deviceSn: "GD30-2025-000001",
calibrationDate: "2024-03-01",
calibrator: "王工程师",
expiryDate: "2025-03-01",
status: "合格",
},
{
boardSn: "AC20240308002",
deviceSn: "GT20-2025-000045",
calibrationDate: "2024-03-05",
calibrator: "李工程师",
expiryDate: "2025-03-05",
status: "合格",
},
{
boardSn: "AC20240308003",
deviceSn: "GTXD-2025-000023",
calibrationDate: "2024-03-08",
calibrator: "张工程师",
expiryDate: "2025-03-08",
status: "合格",
},
];
const getStatusStyle = (
status: CalibrationRecord["status"],
) => {
switch (status) {
case "合格":
return {
backgroundColor: "#F6FFED",
color: "#52C41A",
border: "1px solid #B7EB8F",
};
case "不合格":
return {
backgroundColor: "#FFF1F0",
color: "#FF4D4F",
border: "1px solid #FFCCC7",
};
case "待校准":
return {
backgroundColor: "#FFFBE6",
color: "#FAAD14",
border: "1px solid #FFE58F",
};
}
};
return (
<div className="p-6">
{/* Page Header */}
<div className="mb-6">
<h2 className="text-2xl font-semibold mb-1">
</h2>
<p
className="text-sm"
style={{ color: "rgba(0, 0, 0, 0.45)" }}
>
</p>
</div>
{/* Info Banner */}
<div
className="mb-6 p-4 rounded-lg flex items-start gap-3"
style={{
backgroundColor: "#F9F0FF",
border: "1px solid #D3ADF7",
}}
>
<Info
size={20}
style={{
color: "#722ED1",
flexShrink: 0,
marginTop: 2,
}}
/>
<div style={{ color: "#531DAB" }}>
<div className="font-medium"></div>
<div className="text-sm mt-1">
1
</div>
</div>
</div>
{/* Stat Cards */}
<div className="grid grid-cols-4 gap-6 mb-6">
<div
className="bg-white p-6 rounded-lg"
style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }}
>
<div
className="text-sm mb-2"
style={{ color: "rgba(0, 0, 0, 0.45)" }}
>
</div>
<div
className="text-3xl font-semibold"
style={{ color: "#1890FF" }}
>
1,245
</div>
</div>
<div
className="bg-white p-6 rounded-lg"
style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }}
>
<div
className="text-sm mb-2"
style={{ color: "rgba(0, 0, 0, 0.45)" }}
>
</div>
<div
className="text-3xl font-semibold"
style={{ color: "#FAAD14" }}
>
23
</div>
</div>
<div
className="bg-white p-6 rounded-lg"
style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }}
>
<div
className="text-sm mb-2"
style={{ color: "rgba(0, 0, 0, 0.45)" }}
>
</div>
<div
className="text-3xl font-semibold"
style={{ color: "#1890FF" }}
>
8
</div>
</div>
<div
className="bg-white p-6 rounded-lg"
style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }}
>
<div
className="text-sm mb-2"
style={{ color: "rgba(0, 0, 0, 0.45)" }}
>
</div>
<div
className="text-3xl font-semibold"
style={{ color: "#FF4D4F" }}
>
15
</div>
</div>
</div>
{/* Filter Card */}
<div
className="bg-white p-6 rounded-lg mb-6"
style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }}
>
<div className="grid grid-cols-4 gap-4">
<div>
<label
className="block text-sm mb-2"
style={{ color: "rgba(0, 0, 0, 0.65)" }}
>
SN号
</label>
<input
type="text"
className="w-full px-3 py-2 border rounded"
style={{ borderColor: "#D9D9D9" }}
placeholder="输入采集板SN号搜索"
/>
</div>
<div>
<label
className="block text-sm mb-2"
style={{ color: "rgba(0, 0, 0, 0.65)" }}
>
</label>
<select
className="w-full px-3 py-2 border rounded"
style={{
borderColor: "#D9D9D9",
backgroundColor: "#fff",
}}
>
<option></option>
<option></option>
<option></option>
<option></option>
</select>
</div>
<div>
<label
className="block text-sm mb-2"
style={{ color: "rgba(0, 0, 0, 0.65)" }}
>
</label>
<select
className="w-full px-3 py-2 border rounded"
style={{
borderColor: "#D9D9D9",
backgroundColor: "#fff",
}}
>
<option></option>
<option></option>
<option></option>
<option></option>
</select>
</div>
<div className="flex items-end">
<button
className="w-full px-4 py-2 rounded text-white"
style={{ backgroundColor: "#1890FF" }}
>
</button>
</div>
</div>
</div>
{/* Calibration Records List */}
<div
className="bg-white rounded-lg mb-6"
style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }}
>
<div
className="p-6 border-b"
style={{ borderColor: "#F0F0F0" }}
>
<h3 className="text-lg font-semibold"></h3>
</div>
<div className="overflow-x-auto">
<table className="w-full">
<thead style={{ backgroundColor: "#FAFAFA" }}>
<tr>
<th
className="px-6 py-3 text-left text-sm font-medium"
style={{ color: "rgba(0, 0, 0, 0.85)" }}
>
SN号
</th>
<th
className="px-6 py-3 text-left text-sm font-medium"
style={{ color: "rgba(0, 0, 0, 0.85)" }}
>
</th>
<th
className="px-6 py-3 text-left text-sm font-medium"
style={{ color: "rgba(0, 0, 0, 0.85)" }}
>
</th>
<th
className="px-6 py-3 text-left text-sm font-medium"
style={{ color: "rgba(0, 0, 0, 0.85)" }}
>
</th>
<th
className="px-6 py-3 text-left text-sm font-medium"
style={{ color: "rgba(0, 0, 0, 0.85)" }}
>
</th>
<th
className="px-6 py-3 text-left text-sm font-medium"
style={{ color: "rgba(0, 0, 0, 0.85)" }}
>
</th>
<th
className="px-6 py-3 text-left text-sm font-medium"
style={{ color: "rgba(0, 0, 0, 0.85)" }}
>
</th>
</tr>
</thead>
<tbody>
{calibrationRecords.map((record, index) => (
<tr
key={index}
className="border-b"
style={{ borderColor: "#F0F0F0" }}
>
<td className="px-6 py-4">
{record.boardSn}
</td>
<td
className="px-6 py-4"
style={{ color: "rgba(0, 0, 0, 0.65)" }}
>
{record.deviceSn}
</td>
<td
className="px-6 py-4"
style={{ color: "rgba(0, 0, 0, 0.65)" }}
>
{record.calibrationDate}
</td>
<td
className="px-6 py-4"
style={{ color: "rgba(0, 0, 0, 0.65)" }}
>
{record.expiryDate}
</td>
<td
className="px-6 py-4"
style={{ color: "rgba(0, 0, 0, 0.65)" }}
>
{record.calibrator}
</td>
<td className="px-6 py-4">
<span
className="px-2 py-1 rounded text-xs"
style={getStatusStyle(record.status)}
>
{record.status}
</span>
</td>
<td className="px-6 py-4">
<div className="flex items-center gap-3">
<button
className="text-sm"
style={{ color: "#1890FF" }}
>
</button>
<button
className="text-sm"
style={{ color: "#1890FF" }}
>
</button>
<button
className="text-sm"
style={{ color: "#1890FF" }}
>
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* Pagination */}
<div
className="bg-white p-4 rounded-lg flex items-center justify-between"
style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }}
>
<div
className="text-sm"
style={{ color: "rgba(0, 0, 0, 0.65)" }}
>
1-10 / 1,245
</div>
<div className="flex items-center gap-2">
<button
className="px-3 py-1 rounded border"
style={{
borderColor: "#D9D9D9",
color: "rgba(0, 0, 0, 0.45)",
}}
disabled
>
</button>
<button
className="px-3 py-1 rounded"
style={{
backgroundColor: "#1890FF",
color: "#fff",
}}
>
1
</button>
<button
className="px-3 py-1 rounded border"
style={{
borderColor: "#D9D9D9",
color: "rgba(0, 0, 0, 0.85)",
}}
>
2
</button>
<button
className="px-3 py-1 rounded border"
style={{
borderColor: "#D9D9D9",
color: "rgba(0, 0, 0, 0.85)",
}}
>
3
</button>
<button
className="px-3 py-1 rounded border"
style={{
borderColor: "#D9D9D9",
color: "rgba(0, 0, 0, 0.85)",
}}
>
</button>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,219 @@
<script setup lang="ts">
import { Info } from 'lucide-vue-next'
interface CalibrationRecord {
boardSn: string
deviceSn: string
calibrationDate: string
calibrator: string
expiryDate: string
status: '合格' | '不合格' | '待校准'
}
const calibrationRecords: CalibrationRecord[] = [
{
boardSn: 'AC20240308001',
deviceSn: 'GD30-2025-000001',
calibrationDate: '2024-03-01',
calibrator: '王工程师',
expiryDate: '2025-03-01',
status: '合格',
},
{
boardSn: 'AC20240308002',
deviceSn: 'GT20-2025-000045',
calibrationDate: '2024-03-05',
calibrator: '李工程师',
expiryDate: '2025-03-05',
status: '合格',
},
{
boardSn: 'AC20240308003',
deviceSn: 'GTXD-2025-000023',
calibrationDate: '2024-03-08',
calibrator: '张工程师',
expiryDate: '2025-03-08',
status: '合格',
},
]
const getStatusStyle = (status: CalibrationRecord['status']) => {
switch (status) {
case '合格':
return {
backgroundColor: '#F6FFED',
color: '#52C41A',
border: '1px solid #B7EB8F',
}
case '不合格':
return {
backgroundColor: '#FFF1F0',
color: '#FF4D4F',
border: '1px solid #FFCCC7',
}
case '待校准':
return {
backgroundColor: '#FFFBE6',
color: '#FAAD14',
border: '1px solid #FFE58F',
}
}
}
</script>
<template>
<div class="p-6">
<!-- Page Header -->
<div class="mb-6">
<h2 class="text-2xl font-semibold mb-1">采集板校准记录</h2>
<p class="text-sm" style="color: rgba(0, 0, 0, 0.45)">管理采集板校准数据</p>
</div>
<!-- Info Banner -->
<div
class="mb-6 p-4 rounded-lg flex items-start gap-3"
style="background-color: #F9F0FF; border: 1px solid #D3ADF7"
>
<Info :size="20" style="color: #722ED1; flex-shrink: 0; margin-top: 2px" />
<div style="color: #531DAB">
<div class="font-medium">校准说明</div>
<div class="text-sm mt-1">
校准仅针对采集板其他板卡无需校准
</div>
</div>
</div>
<!-- Stat Cards -->
<div class="grid grid-cols-4 gap-6 mb-6">
<div class="bg-white p-6 rounded-lg" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">采集板校准总数</div>
<div class="text-3xl font-semibold" style="color: #1890FF">1,245</div>
</div>
<div class="bg-white p-6 rounded-lg" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">待校准采集板</div>
<div class="text-3xl font-semibold" style="color: #FAAD14">23</div>
</div>
<div class="bg-white p-6 rounded-lg" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">校准中</div>
<div class="text-3xl font-semibold" style="color: #1890FF">8</div>
</div>
<div class="bg-white p-6 rounded-lg" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">校准即将到期</div>
<div class="text-3xl font-semibold" style="color: #FF4D4F">15</div>
</div>
</div>
<!-- Filter Card -->
<div class="bg-white p-6 rounded-lg mb-6" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<div class="grid grid-cols-4 gap-4">
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.65)">采集板SN号</label>
<input
type="text"
class="w-full px-3 py-2 border rounded"
style="border-color: #D9D9D9"
placeholder="输入采集板SN号搜索"
/>
</div>
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.65)">校准状态</label>
<select
class="w-full px-3 py-2 border rounded"
style="border-color: #D9D9D9; background-color: #fff"
>
<option>全部</option>
<option>合格</option>
<option>不合格</option>
<option>待校准</option>
</select>
</div>
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.65)">校准人员</label>
<select
class="w-full px-3 py-2 border rounded"
style="border-color: #D9D9D9; background-color: #fff"
>
<option>全部</option>
<option>王工程师</option>
<option>李工程师</option>
<option>张工程师</option>
</select>
</div>
<div class="flex items-end">
<button class="w-full px-4 py-2 rounded text-white" style="background-color: #1890FF">
查询
</button>
</div>
</div>
</div>
<!-- Calibration Records List -->
<div class="bg-white rounded-lg mb-6" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<div class="p-6 border-b" style="border-color: #F0F0F0">
<h3 class="text-lg font-semibold">校准记录</h3>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead style="background-color: #FAFAFA">
<tr>
<th class="px-6 py-3 text-left text-sm font-medium" style="color: rgba(0, 0, 0, 0.85)">采集板SN号</th>
<th class="px-6 py-3 text-left text-sm font-medium" style="color: rgba(0, 0, 0, 0.85)">所属设备</th>
<th class="px-6 py-3 text-left text-sm font-medium" style="color: rgba(0, 0, 0, 0.85)">校准日期</th>
<th class="px-6 py-3 text-left text-sm font-medium" style="color: rgba(0, 0, 0, 0.85)">到期日期</th>
<th class="px-6 py-3 text-left text-sm font-medium" style="color: rgba(0, 0, 0, 0.85)">校准人员</th>
<th class="px-6 py-3 text-left text-sm font-medium" style="color: rgba(0, 0, 0, 0.85)">状态</th>
<th class="px-6 py-3 text-left text-sm font-medium" style="color: rgba(0, 0, 0, 0.85)">操作</th>
</tr>
</thead>
<tbody>
<tr
v-for="(record, index) in calibrationRecords"
:key="index"
class="border-b"
style="border-color: #F0F0F0"
>
<td class="px-6 py-4">{{ record.boardSn }}</td>
<td class="px-6 py-4" style="color: rgba(0, 0, 0, 0.65)">{{ record.deviceSn }}</td>
<td class="px-6 py-4" style="color: rgba(0, 0, 0, 0.65)">{{ record.calibrationDate }}</td>
<td class="px-6 py-4" style="color: rgba(0, 0, 0, 0.65)">{{ record.expiryDate }}</td>
<td class="px-6 py-4" style="color: rgba(0, 0, 0, 0.65)">{{ record.calibrator }}</td>
<td class="px-6 py-4">
<span class="px-2 py-1 rounded text-xs" :style="getStatusStyle(record.status)">
{{ record.status }}
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-3">
<button class="text-sm" style="color: #1890FF">详情</button>
<button class="text-sm" style="color: #1890FF">下载报告</button>
<button class="text-sm" style="color: #1890FF">重新校准</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Pagination -->
<div
class="bg-white p-4 rounded-lg flex items-center justify-between"
style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)"
>
<div class="text-sm" style="color: rgba(0, 0, 0, 0.65)">显示 1-10 / 1,245 </div>
<div class="flex items-center gap-2">
<button
class="px-3 py-1 rounded border"
style="border-color: #D9D9D9; color: rgba(0, 0, 0, 0.45)"
disabled
>
上一页
</button>
<button class="px-3 py-1 rounded" style="background-color: #1890FF; color: #fff">1</button>
<button class="px-3 py-1 rounded border" style="border-color: #D9D9D9; color: rgba(0, 0, 0, 0.85)">2</button>
<button class="px-3 py-1 rounded border" style="border-color: #D9D9D9; color: rgba(0, 0, 0, 0.85)">3</button>
<button class="px-3 py-1 rounded border" style="border-color: #D9D9D9; color: rgba(0, 0, 0, 0.85)">下一页</button>
</div>
</div>
</div>
</template>

View File

@ -1,391 +0,0 @@
import {
Info,
Plus,
Download,
Upload,
Search,
} from "lucide-react";
interface ConfigFile {
configId: string;
model: string;
version: string;
createdTime: string;
status: "生效" | "已停用";
description: string;
}
export default function ConfigFileManagement() {
const configFiles: ConfigFile[] = [
{
configId: "CFG-GD30-v1.2.0",
model: "GD30 高密度电法仪",
version: "v1.2.0",
createdTime: "2024-03-01 10:30",
status: "生效",
description: "优化采集参数,提升数据精度",
},
{
configId: "CFG-GT20-v1.5.3",
model: "GT20 瞬变电磁仪",
version: "v1.5.3",
createdTime: "2024-02-28 14:20",
status: "生效",
description: "增加新的波形配置选项",
},
];
const getStatusStyle = (status: ConfigFile["status"]) => {
switch (status) {
case "生效":
return {
backgroundColor: "#F6FFED",
color: "#52C41A",
border: "1px solid #B7EB8F",
};
case "已停用":
return {
backgroundColor: "#FAFAFA",
color: "rgba(0, 0, 0, 0.45)",
border: "1px solid #D9D9D9",
};
}
};
return (
<div className="p-6">
{/* Page Header */}
<div className="mb-6">
<div className="flex items-center justify-between mb-2">
<h2 className="text-2xl font-semibold">
</h2>
<div className="flex items-center gap-3">
<button
className="px-4 py-2 rounded flex items-center gap-2"
style={{
border: "1px solid #D9D9D9",
color: "rgba(0, 0, 0, 0.85)",
}}
>
<Upload size={16} />
</button>
<button
className="px-4 py-2 rounded text-white flex items-center gap-2"
style={{ backgroundColor: "#1890FF" }}
>
<Plus size={16} />
</button>
</div>
</div>
<p
className="text-sm"
style={{ color: "rgba(0, 0, 0, 0.45)" }}
>
</p>
</div>
{/* Info Banner */}
<div
className="mb-6 p-4 rounded-lg flex items-start gap-3"
style={{
backgroundColor: "#E6F7FF",
border: "1px solid #91D5FF",
}}
>
<Info
size={20}
style={{
color: "#1890FF",
flexShrink: 0,
marginTop: 2,
}}
/>
<div style={{ color: "#0050B3" }}>
<div className="font-medium mb-1"></div>
<div className="text-sm">
1
</div>
</div>
</div>
{/* Filter Card */}
<div
className="bg-white p-6 rounded-lg mb-6"
style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }}
>
<div className="grid grid-cols-4 gap-4">
<div>
<label
className="block text-sm mb-2"
style={{ color: "rgba(0, 0, 0, 0.65)" }}
>
</label>
<select
className="w-full px-3 py-2 border rounded"
style={{
borderColor: "#D9D9D9",
backgroundColor: "#fff",
}}
>
<option></option>
<option>GD30 </option>
<option>GT20 </option>
<option>GTXD </option>
</select>
</div>
<div>
<label
className="block text-sm mb-2"
style={{ color: "rgba(0, 0, 0, 0.65)" }}
>
</label>
<select
className="w-full px-3 py-2 border rounded"
style={{
borderColor: "#D9D9D9",
backgroundColor: "#fff",
}}
>
<option></option>
<option>v1.0.x</option>
<option>v1.1.x</option>
<option>v1.2.x</option>
</select>
</div>
<div>
<label
className="block text-sm mb-2"
style={{ color: "rgba(0, 0, 0, 0.65)" }}
>
</label>
<input
type="text"
className="w-full px-3 py-2 border rounded"
style={{ borderColor: "#D9D9D9" }}
placeholder="搜索配置ID或描述"
/>
</div>
<div className="flex items-end">
<button
className="w-full px-4 py-2 rounded text-white flex items-center justify-center gap-2"
style={{ backgroundColor: "#1890FF" }}
>
<Search size={16} />
</button>
</div>
</div>
</div>
{/* Config File List */}
<div
className="bg-white rounded-lg mb-6"
style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }}
>
<div
className="p-6 border-b"
style={{ borderColor: "#F0F0F0" }}
>
<h3 className="text-lg font-semibold">
</h3>
</div>
<div className="overflow-x-auto">
<table className="w-full">
<thead style={{ backgroundColor: "#FAFAFA" }}>
<tr>
<th
className="px-6 py-3 text-left text-sm font-medium"
style={{ color: "rgba(0, 0, 0, 0.85)" }}
>
ID
</th>
<th
className="px-6 py-3 text-left text-sm font-medium"
style={{ color: "rgba(0, 0, 0, 0.85)" }}
>
</th>
<th
className="px-6 py-3 text-left text-sm font-medium"
style={{ color: "rgba(0, 0, 0, 0.85)" }}
>
</th>
<th
className="px-6 py-3 text-left text-sm font-medium"
style={{ color: "rgba(0, 0, 0, 0.85)" }}
>
</th>
<th
className="px-6 py-3 text-left text-sm font-medium"
style={{ color: "rgba(0, 0, 0, 0.85)" }}
>
</th>
<th
className="px-6 py-3 text-left text-sm font-medium"
style={{ color: "rgba(0, 0, 0, 0.85)" }}
>
</th>
<th
className="px-6 py-3 text-left text-sm font-medium"
style={{ color: "rgba(0, 0, 0, 0.85)" }}
>
</th>
</tr>
</thead>
<tbody>
{configFiles.map((config, index) => (
<tr
key={index}
className="border-b"
style={{ borderColor: "#F0F0F0" }}
>
<td
className="px-6 py-4"
style={{ color: "#1890FF" }}
>
{config.configId}
</td>
<td className="px-6 py-4">{config.model}</td>
<td
className="px-6 py-4"
style={{ color: "rgba(0, 0, 0, 0.65)" }}
>
{config.version}
</td>
<td
className="px-6 py-4"
style={{ color: "rgba(0, 0, 0, 0.65)" }}
>
{config.createdTime}
</td>
<td
className="px-6 py-4"
style={{ color: "rgba(0, 0, 0, 0.65)" }}
>
{config.description}
</td>
<td className="px-6 py-4">
<span
className="px-2 py-1 rounded text-xs"
style={getStatusStyle(config.status)}
>
{config.status}
</span>
</td>
<td className="px-6 py-4">
<div className="flex items-center gap-3">
<button
className="text-sm"
style={{ color: "#1890FF" }}
>
</button>
<button
className="text-sm"
style={{ color: "#1890FF" }}
>
</button>
<button
className="text-sm"
style={{ color: "#1890FF" }}
>
</button>
<button
className="text-sm"
style={{ color: "#1890FF" }}
>
</button>
<button
className="text-sm"
style={{ color: "#FF4D4F" }}
>
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* Pagination */}
<div
className="bg-white p-4 rounded-lg flex items-center justify-between"
style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }}
>
<div
className="text-sm"
style={{ color: "rgba(0, 0, 0, 0.65)" }}
>
1-10 / 48
</div>
<div className="flex items-center gap-2">
<button
className="px-3 py-1 rounded border"
style={{
borderColor: "#D9D9D9",
color: "rgba(0, 0, 0, 0.45)",
}}
disabled
>
</button>
<button
className="px-3 py-1 rounded"
style={{
backgroundColor: "#1890FF",
color: "#fff",
}}
>
1
</button>
<button
className="px-3 py-1 rounded border"
style={{
borderColor: "#D9D9D9",
color: "rgba(0, 0, 0, 0.85)",
}}
>
2
</button>
<button
className="px-3 py-1 rounded border"
style={{
borderColor: "#D9D9D9",
color: "rgba(0, 0, 0, 0.85)",
}}
>
3
</button>
<button
className="px-3 py-1 rounded border"
style={{
borderColor: "#D9D9D9",
color: "rgba(0, 0, 0, 0.85)",
}}
>
</button>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,201 @@
<script setup lang="ts">
import { Info, Plus, Search } from 'lucide-vue-next'
import { useRouter } from 'vue-router'
const router = useRouter()
interface ConfigFile {
configId: string
model: string
version: string
createdTime: string
status: '生效' | '已停用'
}
const configFiles: ConfigFile[] = [
{
configId: 'CFG-GD30-v1.2.0',
model: 'GD30 高密度电法仪',
version: 'v1.2.0',
createdTime: '2024-03-01 10:30',
status: '生效',
},
{
configId: 'CFG-GT20-v1.5.3',
model: 'GT20 瞬变电磁仪',
version: 'v1.5.3',
createdTime: '2024-02-28 14:20',
status: '生效',
},
]
const getStatusStyle = (status: ConfigFile['status']) => {
switch (status) {
case '生效':
return {
backgroundColor: '#F6FFED',
color: '#52C41A',
border: '1px solid #B7EB8F',
}
case '已停用':
return {
backgroundColor: '#FAFAFA',
color: 'rgba(0, 0, 0, 0.45)',
border: '1px solid #D9D9D9',
}
}
}
</script>
<template>
<div class="p-6">
<!-- Page Header -->
<div class="mb-6">
<div class="flex items-center justify-between mb-2">
<h2 class="text-2xl font-semibold">配置文件管理</h2>
<div class="flex items-center gap-3">
<button
class="px-4 py-2 rounded text-white flex items-center gap-2"
style="background-color: #1890FF"
@click="router.push('/config-files/new')"
>
<Plus :size="16" />
新建配置
</button>
</div>
</div>
<p class="text-sm" style="color: rgba(0, 0, 0, 0.45)">管理设备型号配置文件</p>
</div>
<!-- Info Banner -->
<div
class="mb-6 p-4 rounded-lg flex items-start gap-3"
style="background-color: #E6F7FF; border: 1px solid #91D5FF"
>
<Info :size="20" style="color: #1890FF; flex-shrink: 0; margin-top: 2px" />
<div style="color: #0050B3">
<div class="font-medium mb-1">配置文件说明</div>
<div class="text-sm">
配置文件按设备型号绑定适配包含发射参数采集参数网络参数等每个型号可以有1个配置版本
</div>
</div>
</div>
<!-- Filter Card -->
<div class="bg-white p-6 rounded-lg mb-6" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<div class="grid grid-cols-4 gap-4">
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.65)">适配型号</label>
<select
class="w-full px-3 py-2 border rounded"
style="border-color: #D9D9D9; background-color: #fff"
>
<option>全部型号</option>
<option>GD30 高密度电法仪</option>
<option>GT20 瞬变电磁仪</option>
<option>GM10 大地电磁仪</option>
</select>
</div>
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.65)">配置版本</label>
<select
class="w-full px-3 py-2 border rounded"
style="border-color: #D9D9D9; background-color: #fff"
>
<option>全部版本</option>
<option>v1.0.x</option>
<option>v1.1.x</option>
<option>v1.2.x</option>
</select>
</div>
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.65)">关键字</label>
<input
type="text"
class="w-full px-3 py-2 border rounded"
style="border-color: #D9D9D9"
placeholder="搜索配置ID或描述"
/>
</div>
<div class="flex items-end">
<button
class="w-full px-4 py-2 rounded text-white flex items-center justify-center gap-2"
style="background-color: #1890FF"
>
<Search :size="16" />
查询
</button>
</div>
</div>
</div>
<!-- Config File List -->
<div class="bg-white rounded-lg mb-6" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<div class="p-6 border-b" style="border-color: #F0F0F0">
<h3 class="text-lg font-semibold">配置文件列表</h3>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead style="background-color: #FAFAFA">
<tr>
<th class="px-6 py-3 text-left text-sm font-medium" style="color: rgba(0, 0, 0, 0.85)">配置ID</th>
<th class="px-6 py-3 text-left text-sm font-medium" style="color: rgba(0, 0, 0, 0.85)">适配型号</th>
<th class="px-6 py-3 text-left text-sm font-medium" style="color: rgba(0, 0, 0, 0.85)">版本</th>
<th class="px-6 py-3 text-left text-sm font-medium" style="color: rgba(0, 0, 0, 0.85)">创建时间</th>
<th class="px-6 py-3 text-left text-sm font-medium" style="color: rgba(0, 0, 0, 0.85)">状态</th>
<th class="px-6 py-3 text-left text-sm font-medium" style="color: rgba(0, 0, 0, 0.85)">操作</th>
</tr>
</thead>
<tbody>
<tr
v-for="(config, index) in configFiles"
:key="index"
class="border-b"
style="border-color: #F0F0F0"
>
<td class="px-6 py-4" style="color: #1890FF">{{ config.configId }}</td>
<td class="px-6 py-4">{{ config.model }}</td>
<td class="px-6 py-4" style="color: rgba(0, 0, 0, 0.65)">{{ config.version }}</td>
<td class="px-6 py-4" style="color: rgba(0, 0, 0, 0.65)">{{ config.createdTime }}</td>
<td class="px-6 py-4">
<span class="px-2 py-1 rounded text-xs" :style="getStatusStyle(config.status)">
{{ config.status }}
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-3">
<button class="text-sm" style="color: #1890FF">详情</button>
<button class="text-sm" style="color: #1890FF">编辑</button>
<button class="text-sm" style="color: #1890FF">下发</button>
<button class="text-sm" style="color: #1890FF">下载</button>
<button class="text-sm" style="color: #FF4D4F">删除</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Pagination -->
<div
class="bg-white p-4 rounded-lg flex items-center justify-between"
style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)"
>
<div class="text-sm" style="color: rgba(0, 0, 0, 0.65)">显示 1-10 / 48 </div>
<div class="flex items-center gap-2">
<button
class="px-3 py-1 rounded border"
style="border-color: #D9D9D9; color: rgba(0, 0, 0, 0.45)"
disabled
>
上一页
</button>
<button class="px-3 py-1 rounded" style="background-color: #1890FF; color: #fff">1</button>
<button class="px-3 py-1 rounded border" style="border-color: #D9D9D9; color: rgba(0, 0, 0, 0.85)">2</button>
<button class="px-3 py-1 rounded border" style="border-color: #D9D9D9; color: rgba(0, 0, 0, 0.85)">3</button>
<button class="px-3 py-1 rounded border" style="border-color: #D9D9D9; color: rgba(0, 0, 0, 0.85)">下一页</button>
</div>
</div>
</div>
</template>

View File

@ -1,187 +0,0 @@
import {
TrendingUp,
TrendingDown,
Server,
Wifi,
CheckCircle,
PackageCheck,
Wrench,
Target,
Clock,
Upload
} from "lucide-react";
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Cell } from "recharts";
interface MetricCardProps {
label: string;
value: string;
trend?: "up" | "down";
trendValue?: string;
color?: string;
icon: React.ElementType;
}
function MetricCard({ label, value, trend, trendValue, color = "#1890FF", icon: Icon }: MetricCardProps) {
return (
<div className="bg-white p-6 rounded-lg" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.65)' }}>{label}</div>
<div className="text-3xl font-semibold mb-2">{value}</div>
{trend && trendValue && (
<div className="flex items-center gap-1" style={{ color: trend === "up" ? "#52C41A" : "#FF4D4F" }}>
{trend === "up" ? <TrendingUp size={14} /> : <TrendingDown size={14} />}
<span className="text-sm">{trendValue}</span>
</div>
)}
</div>
<div className="w-12 h-12 rounded-lg flex items-center justify-center" style={{ backgroundColor: `${color}15` }}>
<Icon size={24} style={{ color }} />
</div>
</div>
</div>
);
}
interface TaskItemProps {
deviceSN: string;
description: string;
time?: string;
}
function TaskItem({ deviceSN, description, time }: TaskItemProps) {
return (
<div className="flex items-start justify-between py-3" style={{ borderBottom: '1px solid #F0F0F0' }}>
<div className="flex-1">
<div className="text-sm mb-1">{deviceSN}</div>
<div className="text-sm" style={{ color: 'rgba(0, 0, 0, 0.45)' }}>{description}</div>
</div>
<div className="flex items-center gap-3">
{time && <span className="text-xs" style={{ color: 'rgba(0, 0, 0, 0.45)' }}>{time}</span>}
<button className="text-sm" style={{ color: '#1890FF' }}></button>
</div>
</div>
);
}
export default function Dashboard() {
const metrics = [
{ label: "设备总数", value: "5,234", trend: "up" as const, trendValue: "+5.2%", color: "#1890FF", icon: Server },
{ label: "在线设备", value: "4,856", trend: "up" as const, trendValue: "+2.8%", color: "#52C41A", icon: Wifi },
{ label: "已激活", value: "4,912", trend: "up" as const, trendValue: "+1.5%", color: "#1890FF", icon: CheckCircle },
{ label: "有新版本", value: "156", color: "#722ED1", icon: PackageCheck },
{ label: "维修中", value: "23", trend: "down" as const, trendValue: "-12.3%", color: "#FF4D4F", icon: Wrench },
{ label: "待校准", value: "56", color: "#FA8C16", icon: Target },
{ label: "授权即将到期", value: "45", color: "#FAAD14", icon: Clock },
{ label: "升级中", value: "8", color: "#13C2C2", icon: Upload },
];
const deviceStatusData = [
{ name: "在线", value: 4856, color: "#52C41A" },
{ name: "离线", value: 378, color: "#FF4D4F" },
{ name: "维修", value: 23, color: "#FAAD14" },
{ name: "报废", value: 77, color: "#8C8C8C" },
];
const taskGroups = [
{
title: "待校准设备",
count: 12,
tasks: [
{ deviceSN: "SN2024030801", description: "温度传感器校准到期", time: "2小时前" },
{ deviceSN: "SN2024030802", description: "压力传感器校准到期", time: "3小时前" },
{ deviceSN: "SN2024030803", description: "湿度传感器校准到期", time: "5小时前" },
],
},
{
title: "固件升级通知",
count: 8,
tasks: [
{ deviceSN: "SN2024030710", description: "固件版本v2.3.5可用", time: "1天前" },
{ deviceSN: "SN2024030711", description: "固件版本v2.3.5可用", time: "1天前" },
],
},
{
title: "待授权审批",
count: 15,
tasks: [
{ deviceSN: "SN2024030620", description: "设备授权申请待审批", time: "30分钟前" },
{ deviceSN: "SN2024030621", description: "设备授权申请待审批", time: "1小时前" },
{ deviceSN: "SN2024030622", description: "设备授权延期申请", time: "2小时前" },
],
},
{
title: "维修工单",
count: 5,
tasks: [
{ deviceSN: "SN2024030530", description: "设备故障报修", time: "4小时前" },
{ deviceSN: "SN2024030531", description: "定期维护到期", time: "6小时前" },
],
},
];
return (
<div className="p-6">
{/* Page Header */}
<div className="mb-6">
<h2 className="text-2xl font-semibold mb-1"></h2>
<p className="text-sm" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></p>
</div>
{/* Metric Cards */}
<div className="grid grid-cols-4 gap-6 mb-6">
{metrics.map((metric, index) => (
<MetricCard key={index} {...metric} />
))}
</div>
{/* Device Status Chart */}
<div className="bg-white p-6 rounded-lg mb-6" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<h3 className="text-lg font-semibold mb-6"></h3>
<ResponsiveContainer width="100%" height={300}>
<BarChart data={deviceStatusData} layout="vertical">
<CartesianGrid strokeDasharray="3 3" stroke="#F0F0F0" />
<XAxis type="number" />
<YAxis dataKey="name" type="category" width={60} />
<Tooltip />
<Bar dataKey="value" radius={[0, 4, 4, 0]}>
{deviceStatusData.map((entry) => (
<Cell key={`bar-cell-${entry.name}`} fill={entry.color} />
))}
</Bar>
</BarChart>
</ResponsiveContainer>
</div>
{/* Pending Tasks */}
<div className="bg-white p-6 rounded-lg" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<h3 className="text-lg font-semibold mb-6"></h3>
<div className="grid grid-cols-2 gap-6">
{taskGroups.map((group, groupIndex) => (
<div key={groupIndex}>
<div className="flex items-center justify-between mb-4">
<h4 className="text-base font-medium">{group.title}</h4>
<span
className="px-2 py-1 rounded text-xs"
style={{ backgroundColor: '#F0F2F5', color: 'rgba(0, 0, 0, 0.65)' }}
>
{group.count}
</span>
</div>
<div>
{group.tasks.map((task, taskIndex) => (
<TaskItem key={taskIndex} {...task} />
))}
</div>
{group.tasks.length < group.count && (
<button className="w-full mt-3 text-center text-sm" style={{ color: '#1890FF' }}>
{group.count}
</button>
)}
</div>
))}
</div>
</div>
</div>
);
}

169
src/app/pages/Dashboard.vue Normal file
View File

@ -0,0 +1,169 @@
<template>
<div class="p-6">
<!-- Page Header -->
<div class="mb-6">
<h2 class="text-2xl font-semibold mb-1">首页</h2>
<p class="text-sm" style="color: rgba(0, 0, 0, 0.45)">设备管理数据总览</p>
</div>
<!-- Metric Cards -->
<div class="grid grid-cols-4 gap-6 mb-6">
<div
v-for="(metric, index) in metrics"
:key="index"
class="bg-white p-6 rounded-lg"
style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)"
>
<div class="flex items-start justify-between">
<div class="flex-1">
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.65)">{{ metric.label }}</div>
<div class="text-3xl font-semibold mb-2">{{ metric.value }}</div>
<div
v-if="metric.trend && metric.trendValue"
class="flex items-center gap-1"
:style="{ color: metric.trend === 'up' ? '#52C41A' : '#FF4D4F' }"
>
<TrendingUp v-if="metric.trend === 'up'" :size="14" />
<TrendingDown v-else :size="14" />
<span class="text-sm">{{ metric.trendValue }}</span>
</div>
</div>
<div
class="w-12 h-12 rounded-lg flex items-center justify-center"
:style="{ backgroundColor: metric.color + '15' }"
>
<component :is="metric.icon" :size="24" :style="{ color: metric.color }" />
</div>
</div>
</div>
</div>
<!-- Device Status Chart (CSS-based horizontal bars replacing recharts) -->
<div class="bg-white p-6 rounded-lg mb-6" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<h3 class="text-lg font-semibold mb-6">设备状态分布</h3>
<div style="display: flex; flex-direction: column; gap: 16px;">
<div
v-for="item in deviceStatusData"
:key="item.name"
style="display: flex; align-items: center; gap: 12px;"
>
<div style="width: 60px; text-align: right; font-size: 14px; color: rgba(0,0,0,0.65); flex-shrink: 0;">
{{ item.name }}
</div>
<div style="flex: 1; background-color: #F5F5F5; border-radius: 4px; height: 24px; overflow: hidden;">
<div
:style="{
width: (item.value / maxStatusValue * 100) + '%',
height: '100%',
backgroundColor: item.color,
borderRadius: '0 4px 4px 0',
transition: 'width 0.3s ease',
minWidth: item.value > 0 ? '2px' : '0'
}"
></div>
</div>
<div style="width: 40px; font-size: 14px; color: rgba(0,0,0,0.85);">
{{ item.value }}
</div>
</div>
</div>
</div>
<!-- Pending Tasks -->
<div class="bg-white p-6 rounded-lg" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<h3 class="text-lg font-semibold mb-6">待处理任务</h3>
<div class="grid grid-cols-2 gap-6">
<div v-for="(group, groupIndex) in taskGroups" :key="groupIndex">
<div class="flex items-center justify-between mb-4">
<h4 class="text-base font-medium">{{ group.title }}</h4>
<span
class="px-2 py-1 rounded text-xs"
style="background-color: #F0F2F5; color: rgba(0, 0, 0, 0.65)"
>
{{ group.count }}
</span>
</div>
<div>
<div
v-for="(task, taskIndex) in group.tasks"
:key="taskIndex"
class="flex items-start justify-between py-3"
style="border-bottom: 1px solid #F0F0F0"
>
<div class="flex-1">
<div class="text-sm mb-1">{{ task.deviceSN }}</div>
<div class="text-sm" style="color: rgba(0, 0, 0, 0.45)">{{ task.description }}</div>
</div>
<div class="flex items-center gap-3">
<span v-if="task.time" class="text-xs" style="color: rgba(0, 0, 0, 0.45)">{{ task.time }}</span>
<button class="text-sm" style="color: #1890FF">处理</button>
</div>
</div>
</div>
<button
v-if="group.tasks.length < group.count"
class="w-full mt-3 text-center text-sm"
style="color: #1890FF"
>
查看全部 {{ group.count }}
</button>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import {
TrendingUp,
TrendingDown,
Server,
Wifi,
CheckCircle,
PackageCheck,
Wrench,
Target,
Clock,
Upload,
} from 'lucide-vue-next'
const metrics = [
{ label: '设备总数', value: '5,234', trend: 'up' as const, trendValue: '+5.2%', color: '#1890FF', icon: Server },
{ label: '装配中', value: '4,856', trend: 'up' as const, trendValue: '+2.8%', color: '#52C41A', icon: Wifi },
{ label: '已激活', value: '4,912', trend: 'up' as const, trendValue: '+1.5%', color: '#1890FF', icon: CheckCircle },
{ label: '有新版本', value: '156', color: '#722ED1', icon: PackageCheck },
{ label: '维修中', value: '23', trend: 'down' as const, trendValue: '-12.3%', color: '#FF4D4F', icon: Wrench },
{ label: '报废', value: '56', color: '#FA8C16', icon: Target },
{ label: '授权即将到期', value: '45', color: '#FAAD14', icon: Clock },
{ label: '升级中', value: '8', color: '#13C2C2', icon: Upload },
]
const deviceStatusData = [
{ name: '已装配', value: 45, color: '#52C41A' },
{ name: '已出厂', value: 378, color: '#FF4D4F' },
{ name: '已激活', value: 286, color: '#FAAD14' },
{ name: '报废', value: 7, color: '#8C8C8C' },
]
const maxStatusValue = computed(() => Math.max(...deviceStatusData.map((d) => d.value)))
const taskGroups = [
{
title: '固件升级通知',
count: 8,
tasks: [
{ deviceSN: 'SN2024030710', description: '固件版本v2.3.5可用', time: '1天前' },
{ deviceSN: 'SN2024030711', description: '固件版本v2.3.5可用', time: '1天前' },
],
},
{
title: '维修工单',
count: 5,
tasks: [
{ deviceSN: 'SN2024030530', description: '设备故障报修', time: '4小时前' },
{ deviceSN: 'SN2024030531', description: '电池故障', time: '6小时前' },
],
},
]
</script>

View File

@ -1,268 +0,0 @@
import { ArrowLeft, StopCircle, Edit2, Download, Clock } from "lucide-react";
import { useNavigate } from "react-router";
export default function DeviceDetail() {
const navigate = useNavigate();
const maintenanceHistory = [
{ date: "2024-03-01", type: "固件升级", operator: "王工程师", description: "升级固件至v2.3.5,解决数据采集异常问题" },
{ date: "2024-02-15", type: "主板更换", operator: "李工程师", description: "更换主控板MB20231215001 → 新MB20240215001" },
{ date: "2024-01-20", type: "常规保养", operator: "张工程师", description: "清洁设备,检查线路连接,测试功能正常" },
];
return (
<div className="p-6">
{/* Page Header */}
<div className="mb-6">
<div className="flex items-center gap-4 mb-2">
<button
onClick={() => navigate(-1)}
className="p-2 rounded hover:bg-gray-100 transition-colors"
style={{ color: 'rgba(0, 0, 0, 0.65)' }}
>
<ArrowLeft size={20} />
</button>
<h2 className="text-2xl font-semibold">GD30-2025-000001</h2>
<span
className="px-3 py-1 rounded text-sm"
style={{ backgroundColor: '#F6FFED', color: '#52C41A', border: '1px solid #B7EB8F' }}
>
</span>
</div>
<div className="flex items-center gap-3 ml-12">
<button
className="px-4 py-2 rounded flex items-center gap-2"
style={{ border: '1px solid #D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }}
>
<Edit2 size={16} />
</button>
<button
className="px-4 py-2 rounded flex items-center gap-2"
style={{ border: '1px solid #D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }}
>
<Download size={16} />
</button>
<button
className="px-4 py-2 rounded flex items-center gap-2"
style={{ border: '1px solid #FF4D4F', color: '#FF4D4F' }}
>
<StopCircle size={16} />
线
</button>
</div>
</div>
{/* Basic Info Card */}
<div className="bg-white p-6 rounded-lg mb-6" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<h3 className="text-lg font-semibold mb-6"></h3>
<div className="grid grid-cols-3 gap-x-12 gap-y-6">
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div>GD30 </div>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}>SN号</div>
<div>GD30-2025-000001</div>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<span
className="inline-block px-2 py-1 rounded text-xs"
style={{ backgroundColor: '#F6FFED', color: '#52C41A', border: '1px solid #B7EB8F' }}
>
</span>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div>2025-01-15</div>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div>2025-02-01</div>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div>2025-02-10</div>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div></div>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div></div>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div></div>
</div>
</div>
</div>
{/* Hardware Topology Card */}
<div className="bg-white p-6 rounded-lg mb-6" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<h3 className="text-lg font-semibold mb-6"></h3>
<div className="flex items-center justify-center gap-8">
<div className="text-center">
<div
className="w-32 h-32 rounded-lg flex flex-col items-center justify-center mb-3"
style={{ backgroundColor: '#E6F7FF', border: '2px solid #1890FF' }}
>
<div className="text-lg font-semibold" style={{ color: '#1890FF' }}></div>
<div className="text-xs mt-2" style={{ color: '#1890FF' }}>MB-V2.3</div>
</div>
<div className="text-xs" style={{ color: 'rgba(0, 0, 0, 0.45)' }}>MB20240308001</div>
</div>
<div style={{ color: '#D9D9D9', fontSize: '24px' }}></div>
<div className="text-center">
<div
className="w-32 h-32 rounded-lg flex flex-col items-center justify-center mb-3"
style={{ backgroundColor: '#F0F5FF', border: '2px solid #597EF7' }}
>
<div className="text-lg font-semibold" style={{ color: '#597EF7' }}></div>
<div className="text-xs mt-2" style={{ color: '#597EF7' }}>AC-V1.8</div>
</div>
<div className="text-xs" style={{ color: 'rgba(0, 0, 0, 0.45)' }}>AC20240308002</div>
</div>
<div style={{ color: '#D9D9D9', fontSize: '24px' }}></div>
<div className="text-center">
<div
className="w-32 h-32 rounded-lg flex flex-col items-center justify-center mb-3"
style={{ backgroundColor: '#F6FFED', border: '2px solid #52C41A' }}
>
<div className="text-lg font-semibold" style={{ color: '#52C41A' }}></div>
<div className="text-xs mt-2" style={{ color: '#52C41A' }}>CT-V1.5</div>
</div>
<div className="text-xs" style={{ color: 'rgba(0, 0, 0, 0.45)' }}>CT20240308003</div>
</div>
</div>
</div>
{/* License Info Card */}
<div className="bg-white p-6 rounded-lg mb-6" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<h3 className="text-lg font-semibold mb-6"></h3>
<div className="grid grid-cols-3 gap-x-12 gap-y-6">
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}>ID</div>
<div>LIC-2025-0001</div>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<span
className="inline-block px-2 py-1 rounded text-xs"
style={{ backgroundColor: '#F6FFED', color: '#52C41A', border: '1px solid #B7EB8F' }}
>
</span>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div></div>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div>2025-02-10</div>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div>2026-02-10</div>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div style={{ color: '#52C41A' }}>317</div>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div>, , </div>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<button className="text-sm" style={{ color: '#1890FF' }}>auth_gd30_v2.3.lic</button>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<button className="text-sm" style={{ color: '#1890FF' }}></button>
</div>
</div>
</div>
{/* Firmware Info Card */}
<div className="bg-white p-6 rounded-lg mb-6" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<h3 className="text-lg font-semibold mb-6"></h3>
<div className="grid grid-cols-3 gap-x-12 gap-y-6">
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div>v2.3.5</div>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div>2024-03-01 10:30</div>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div>OTA</div>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div>v1.2.0</div>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div>2024-03-01 10:35</div>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div className="flex items-center gap-3">
<button className="text-sm" style={{ color: '#1890FF' }}></button>
<button className="text-sm" style={{ color: '#1890FF' }}></button>
</div>
</div>
</div>
</div>
{/* Maintenance History Card */}
<div className="bg-white p-6 rounded-lg" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<div className="flex items-center justify-between mb-6">
<h3 className="text-lg font-semibold"></h3>
<button className="text-sm" style={{ color: '#1890FF' }}></button>
</div>
<div className="relative">
{/* Timeline line */}
<div
className="absolute left-6 top-6 bottom-6 w-0.5"
style={{ backgroundColor: '#F0F0F0' }}
></div>
<div className="space-y-6">
{maintenanceHistory.map((entry, index) => (
<div key={index} className="flex gap-4">
<div className="flex flex-col items-center flex-shrink-0">
<div
className="w-12 h-12 rounded-full flex items-center justify-center relative z-10"
style={{ backgroundColor: '#E6F7FF', border: '2px solid #1890FF' }}
>
<Clock size={20} style={{ color: '#1890FF' }} />
</div>
</div>
<div className="flex-1 pt-2">
<div className="flex items-center gap-3 mb-2">
<span className="font-medium">{entry.type}</span>
<span className="text-sm" style={{ color: 'rgba(0, 0, 0, 0.45)' }}>{entry.date}</span>
<span className="text-sm" style={{ color: 'rgba(0, 0, 0, 0.45)' }}>{entry.operator}</span>
</div>
<div className="text-sm" style={{ color: 'rgba(0, 0, 0, 0.65)' }}>
{entry.description}
</div>
</div>
</div>
))}
</div>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,234 @@
<template>
<div class="p-6">
<!-- Page Header -->
<div class="mb-6">
<div class="flex items-center gap-4 mb-2">
<button
class="p-2 rounded hover:bg-gray-100 transition-colors"
style="color: rgba(0, 0, 0, 0.65)"
@click="router.go(-1)"
>
<ArrowLeft :size="20" />
</button>
<h2 class="text-2xl font-semibold">GD30-2025-000001</h2>
<span
class="px-3 py-1 rounded text-sm"
style="background-color: #F6FFED; color: #52C41A; border: 1px solid #B7EB8F"
>
已激活
</span>
</div>
</div>
<!-- Basic Info Card -->
<div class="bg-white p-6 rounded-lg mb-6" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<h3 class="text-lg font-semibold mb-6">基本信息</h3>
<div class="grid grid-cols-3 gap-x-12 gap-y-6">
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">设备型号</div>
<div>GD30 高密度电法仪</div>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">主机SN号</div>
<div>GD30-2025-000001</div>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">设备状态</div>
<span
class="inline-block px-2 py-1 rounded text-xs"
style="background-color: #F6FFED; color: #52C41A; border: 1px solid #B7EB8F"
>
已激活
</span>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">生产日期</div>
<div>2025-01-15</div>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">出厂日期</div>
<div>2025-02-01</div>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">激活日期</div>
<div>2025-02-10</div>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">装配人员</div>
<div>张工程师</div>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">测试人员</div>
<div>李工程师</div>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">客户名称</div>
<div>北京地质研究院</div>
</div>
</div>
</div>
<!-- Hardware Topology Card -->
<div class="bg-white p-6 rounded-lg mb-6" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<h3 class="text-lg font-semibold mb-6">硬件拓扑</h3>
<div class="flex items-center justify-center gap-6">
<template v-for="(board, i) in boards" :key="board.name">
<div class="flex items-center gap-6">
<div class="text-center">
<div
class="w-28 h-28 rounded-lg flex flex-col items-center justify-center mb-3"
:style="{ backgroundColor: board.bg, border: '2px solid ' + board.color }"
>
<div class="text-base font-semibold" :style="{ color: board.color }">{{ board.name }}</div>
<div class="text-xs mt-2" :style="{ color: board.color }">{{ board.version }}</div>
</div>
<div class="text-xs" style="color: rgba(0, 0, 0, 0.45)">{{ board.sn }}</div>
</div>
<div v-if="Number(i) < boards.length - 1" style="color: #D9D9D9; font-size: 24px"></div>
</div>
</template>
</div>
</div>
<!-- License Info Card -->
<div class="bg-white p-6 rounded-lg mb-6" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<h3 class="text-lg font-semibold mb-6">授权信息</h3>
<div class="grid grid-cols-3 gap-x-12 gap-y-6">
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">授权ID</div>
<div>LIC-2025-0001</div>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">授权状态</div>
<span
class="inline-block px-2 py-1 rounded text-xs"
style="background-color: #F6FFED; color: #52C41A; border: 1px solid #B7EB8F"
>
已激活
</span>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">授权类型</div>
<div>正式授权</div>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">生效日期</div>
<div>2025-02-10</div>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">到期日期</div>
<div>2026-02-10</div>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">剩余天数</div>
<div style="color: #52C41A">317</div>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">授权功能模块</div>
<div>1D(SP/VES/IP) / 2D(SP/ERT/IP) / 3D(SP/ERT/IP)</div>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">授权文件</div>
<button class="text-sm" style="color: #1890FF">auth_gd30_v2.3.lic</button>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">操作</div>
<button class="text-sm" style="color: #1890FF">续期</button>
</div>
</div>
</div>
<!-- Firmware Info Card -->
<div class="bg-white p-6 rounded-lg mb-6" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<h3 class="text-lg font-semibold mb-6">固件信息</h3>
<div class="grid grid-cols-3 gap-x-12 gap-y-6">
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">当前固件版本</div>
<div>v2.3.5</div>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">最后更新时间</div>
<div>2024-03-01 10:30</div>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">更新方式</div>
<div>远程OTA</div>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">配置文件版本</div>
<div>v1.2.0</div>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">配置同步时间</div>
<div>2024-03-01 10:35</div>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">操作</div>
<div class="flex items-center gap-3">
<button class="text-sm" style="color: #1890FF">推送固件</button>
<button class="text-sm" style="color: #1890FF">下发配置</button>
</div>
</div>
</div>
</div>
<!-- Maintenance History Card -->
<div class="bg-white p-6 rounded-lg" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<div class="flex items-center justify-between mb-6">
<h3 class="text-lg font-semibold">维修历史</h3>
<button class="text-sm" style="color: #1890FF">查看全部</button>
</div>
<div class="relative">
<!-- Timeline line -->
<div
class="absolute left-6 top-6 bottom-6 w-0.5"
style="background-color: #F0F0F0"
></div>
<div class="space-y-6">
<div v-for="(entry, index) in maintenanceHistory" :key="index" class="flex gap-4">
<div class="flex flex-col items-center flex-shrink-0">
<div
class="w-12 h-12 rounded-full flex items-center justify-center relative z-10"
style="background-color: #E6F7FF; border: 2px solid #1890FF"
>
<Clock :size="20" style="color: #1890FF" />
</div>
</div>
<div class="flex-1 pt-2">
<div class="flex items-center gap-3 mb-2">
<span class="font-medium">{{ entry.type }}</span>
<span class="text-sm" style="color: rgba(0, 0, 0, 0.45)">{{ entry.date }}</span>
<span class="text-sm" style="color: rgba(0, 0, 0, 0.45)">操作人{{ entry.operator }}</span>
</div>
<div class="text-sm" style="color: rgba(0, 0, 0, 0.65)">
{{ entry.description }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router'
import { ArrowLeft, StopCircle, Edit2, Download, Clock } from 'lucide-vue-next'
const router = useRouter()
const boards = [
{ name: '主板', version: 'MB-V2.3', sn: 'MB20240308001', bg: '#E6F7FF', color: '#1890FF' },
{ name: '采集板', version: 'RX-V1.8', sn: 'RX20240308002', bg: '#F0F5FF', color: '#597EF7' },
{ name: '发射板', version: 'TX-V1.2', sn: 'TX20240308003', bg: '#FFF7E6', color: '#FA8C16' },
{ name: '测控板', version: 'MC-V1.5', sn: 'MC20240308004', bg: '#F6FFED', color: '#52C41A' },
{ name: '升压板', version: 'BO-V1.0', sn: 'BO20240308005', bg: '#FFF0F6', color: '#EB2F96' },
]
const maintenanceHistory = [
{ date: '2024-03-01', type: '固件升级', operator: '王工程师', description: '升级固件至v2.3.5,解决数据采集异常问题' },
{ date: '2024-02-15', type: '主板更换', operator: '李工程师', description: '更换主控板MB20231215001 → 新MB20240215001' },
{ date: '2024-01-20', type: '常规保养', operator: '张工程师', description: '清洁设备,检查线路连接,测试功能正常' },
]
</script>

View File

@ -1,281 +0,0 @@
import { useState } from "react";
import {
Plus,
Upload,
Download,
Search,
StopCircle
} from "lucide-react";
import { useNavigate } from "react-router";
interface Device {
sn: string;
model: string;
status: "已激活" | "已出厂" | "装配中";
firmwareVersion: string;
registrationDate: string;
}
export default function DeviceList() {
const navigate = useNavigate();
const [currentPage] = useState(1);
const [pageSize] = useState(10);
const totalDevices = 5234;
const devices: Device[] = [
{
sn: "GD30-20240308-001",
model: "GD30 地质探测仪",
status: "已激活",
firmwareVersion: "v2.3.5",
registrationDate: "2024-03-08 14:30",
},
{
sn: "GT20-20240307-045",
model: "GT20 物探仪",
status: "已出厂",
firmwareVersion: "v1.8.2",
registrationDate: "2024-03-07 10:15",
},
{
sn: "GD30-20240308-002",
model: "GD30 地质探测仪",
status: "装配中",
firmwareVersion: "v2.3.4",
registrationDate: "2024-03-08 16:20",
},
];
const getStatusStyle = (status: Device["status"]) => {
switch (status) {
case "已激活":
return {
backgroundColor: '#F6FFED',
color: '#52C41A',
border: '1px solid #B7EB8F'
};
case "已出厂":
return {
backgroundColor: '#FFF7E6',
color: '#FA8C16',
border: '1px solid #FFD591'
};
case "装配中":
return {
backgroundColor: '#E6F7FF',
color: '#1890FF',
border: '1px solid #91D5FF'
};
}
};
const startIndex = (currentPage - 1) * pageSize + 1;
const endIndex = Math.min(currentPage * pageSize, totalDevices);
return (
<div className="p-6">
{/* Page Header */}
<div className="mb-6">
<div className="flex items-center justify-between mb-2">
<h2 className="text-2xl font-semibold"></h2>
<div className="flex items-center gap-3">
<button
className="px-4 py-2 rounded flex items-center gap-2"
style={{ border: '1px solid #D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }}
>
<Upload size={16} />
BOM导入
</button>
<button
className="px-4 py-2 rounded flex items-center gap-2"
style={{ border: '1px solid #D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }}
>
<Download size={16} />
</button>
<button
className="px-4 py-2 rounded text-white flex items-center gap-2"
style={{ backgroundColor: '#1890FF' }}
onClick={() => navigate('/registration')}
>
<Plus size={16} />
</button>
</div>
</div>
<p className="text-sm" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></p>
</div>
{/* Filter Card */}
<div className="bg-white p-6 rounded-lg mb-6" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<div className="grid grid-cols-5 gap-4">
<div>
<label className="block text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.65)' }}>
</label>
<select
className="w-full px-3 py-2 border rounded"
style={{ borderColor: '#D9D9D9', backgroundColor: '#fff' }}
>
<option></option>
<option>GD30 </option>
<option>GT20 </option>
<option>GTXD </option>
</select>
</div>
<div>
<label className="block text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.65)' }}>
</label>
<select
className="w-full px-3 py-2 border rounded"
style={{ borderColor: '#D9D9D9', backgroundColor: '#fff' }}
>
<option></option>
<option></option>
<option></option>
<option></option>
</select>
</div>
<div>
<label className="block text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.65)' }}>
</label>
<input
type="date"
className="w-full px-3 py-2 border rounded"
style={{ borderColor: '#D9D9D9' }}
/>
</div>
<div>
<label className="block text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.65)' }}>
</label>
<select
className="w-full px-3 py-2 border rounded"
style={{ borderColor: '#D9D9D9', backgroundColor: '#fff' }}
>
<option></option>
<option>GD30-2024</option>
<option>GT20-2023</option>
<option>GTXD-2022</option>
</select>
</div>
<div className="flex items-end">
<button
className="w-full px-4 py-2 rounded text-white flex items-center justify-center gap-2"
style={{ backgroundColor: '#1890FF' }}
>
<Search size={16} />
</button>
</div>
</div>
</div>
{/* Device List */}
<div className="space-y-4 mb-6">
{devices.map((device, index) => (
<div
key={index}
className="bg-white rounded-lg p-6"
style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}
>
<div className="flex items-start justify-between mb-4">
<div className="flex-1 grid grid-cols-5 gap-4">
<div>
<div className="text-xs mb-1" style={{ color: 'rgba(0, 0, 0, 0.45)' }}>SN号</div>
<div className="font-medium">{device.sn}</div>
</div>
<div>
<div className="text-xs mb-1" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div>{device.model}</div>
</div>
<div>
<div className="text-xs mb-1" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<span
className="inline-block px-2 py-1 rounded text-xs"
style={getStatusStyle(device.status)}
>
{device.status}
</span>
</div>
<div>
<div className="text-xs mb-1" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div>{device.firmwareVersion}</div>
</div>
<div>
<div className="text-xs mb-1" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div style={{ color: 'rgba(0, 0, 0, 0.65)' }}>{device.registrationDate}</div>
</div>
</div>
</div>
<div className="flex items-center gap-3 pt-4 border-t" style={{ borderColor: '#F0F0F0' }}>
<button className="text-sm" style={{ color: '#1890FF' }}></button>
<button className="text-sm" style={{ color: '#1890FF' }}>BOM</button>
{device.status === "已激活" && (
<>
<span style={{ color: '#D9D9D9' }}>|</span>
<button
className="text-sm flex items-center gap-1"
style={{ color: '#FF4D4F' }}
>
<StopCircle size={14} />
线
</button>
</>
)}
</div>
</div>
))}
</div>
{/* Pagination */}
<div className="bg-white p-4 rounded-lg flex items-center justify-between" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<div className="text-sm" style={{ color: 'rgba(0, 0, 0, 0.65)' }}>
{startIndex}-{endIndex} / {totalDevices.toLocaleString()}
</div>
<div className="flex items-center gap-2">
<button
className="px-3 py-1 rounded border"
style={{ borderColor: '#D9D9D9', color: 'rgba(0, 0, 0, 0.45)' }}
disabled
>
</button>
<button
className="px-3 py-1 rounded"
style={{ backgroundColor: '#1890FF', color: '#fff' }}
>
1
</button>
<button
className="px-3 py-1 rounded border"
style={{ borderColor: '#D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }}
>
2
</button>
<button
className="px-3 py-1 rounded border"
style={{ borderColor: '#D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }}
>
3
</button>
<span style={{ color: 'rgba(0, 0, 0, 0.45)' }}>...</span>
<button
className="px-3 py-1 rounded border"
style={{ borderColor: '#D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }}
>
524
</button>
<button
className="px-3 py-1 rounded border"
style={{ borderColor: '#D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }}
>
</button>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,228 @@
<template>
<div class="p-6">
<!-- Page Header -->
<div class="mb-6">
<div class="flex items-center justify-between mb-2">
<h2 class="text-2xl font-semibold">设备列表</h2>
<div class="flex items-center gap-3">
<button
class="px-4 py-2 rounded flex items-center gap-2"
style="border: 1px solid #D9D9D9; color: rgba(0, 0, 0, 0.85)"
>
<Upload :size="16" />
BOM导入
</button>
<button
class="px-4 py-2 rounded flex items-center gap-2"
style="border: 1px solid #D9D9D9; color: rgba(0, 0, 0, 0.85)"
>
<Download :size="16" />
导出
</button>
<button
class="px-4 py-2 rounded text-white flex items-center gap-2"
style="background-color: #1890FF"
@click="router.push('/registration')"
>
<Plus :size="16" />
登记设备
</button>
</div>
</div>
<p class="text-sm" style="color: rgba(0, 0, 0, 0.45)">管理所有设备信息</p>
</div>
<!-- Filter Card -->
<div class="bg-white p-6 rounded-lg mb-6" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<div class="grid grid-cols-5 gap-4">
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.65)">设备型号</label>
<select
class="w-full px-3 py-2 border rounded"
style="border-color: #D9D9D9; background-color: #fff"
>
<option>全部</option>
<option>GD30 高密度电法仪</option>
<option>GT20 瞬变电磁仪</option>
<option>GM10 大地电磁仪</option>
</select>
</div>
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.65)">设备状态</label>
<select
class="w-full px-3 py-2 border rounded"
style="border-color: #D9D9D9; background-color: #fff"
>
<option>全部</option>
<option>已激活</option>
<option>已出厂</option>
<option>装配中</option>
</select>
</div>
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.65)">生产日期</label>
<input
type="date"
class="w-full px-3 py-2 border rounded"
style="border-color: #D9D9D9"
/>
</div>
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.65)">产品型号</label>
<select
class="w-full px-3 py-2 border rounded"
style="border-color: #D9D9D9; background-color: #fff"
>
<option>全部</option>
<option>GD30-2024</option>
<option>GT20-2023</option>
<option>GTXD-2022</option>
</select>
</div>
<div class="flex items-end">
<button
class="w-full px-4 py-2 rounded text-white flex items-center justify-center gap-2"
style="background-color: #1890FF"
>
<Search :size="16" />
搜索
</button>
</div>
</div>
</div>
<!-- Device List -->
<div class="space-y-4 mb-6">
<div
v-for="(device, index) in devices"
:key="index"
class="bg-white rounded-lg p-6"
style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)"
>
<div class="flex items-start justify-between mb-4">
<div class="flex-1 grid grid-cols-5 gap-4">
<div>
<div class="text-xs mb-1" style="color: rgba(0, 0, 0, 0.45)">设备SN号</div>
<div class="font-medium">{{ device.sn }}</div>
</div>
<div>
<div class="text-xs mb-1" style="color: rgba(0, 0, 0, 0.45)">型号</div>
<div>{{ device.model }}</div>
</div>
<div>
<div class="text-xs mb-1" style="color: rgba(0, 0, 0, 0.45)">状态</div>
<span
class="inline-block px-2 py-1 rounded text-xs"
:style="getStatusStyle(device.status)"
>
{{ device.status }}
</span>
</div>
<div>
<div class="text-xs mb-1" style="color: rgba(0, 0, 0, 0.45)">固件版本</div>
<div>{{ device.firmwareVersion }}</div>
</div>
<div>
<div class="text-xs mb-1" style="color: rgba(0, 0, 0, 0.45)">生产日期</div>
<div style="color: rgba(0, 0, 0, 0.65)">{{ device.registrationDate }}</div>
</div>
</div>
</div>
<div class="flex items-center gap-3 pt-4 border-t" style="border-color: #F0F0F0">
<button class="text-sm" style="color: #1890FF" @click="router.push('/devices/' + device.sn)">详情</button>
<button class="text-sm" style="color: #1890FF">BOM</button>
<template v-if="device.status === '已激活'">
<span style="color: #D9D9D9">|</span>
<button class="text-sm flex items-center gap-1" style="color: #FF4D4F">
<StopCircle :size="14" />
下线
</button>
</template>
</div>
</div>
</div>
<!-- Pagination -->
<div
class="bg-white p-4 rounded-lg flex items-center justify-between"
style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)"
>
<div class="text-sm" style="color: rgba(0, 0, 0, 0.65)">
显示 {{ startIndex }}-{{ endIndex }} / {{ totalDevices.toLocaleString() }}
</div>
<div class="flex items-center gap-2">
<button
class="px-3 py-1 rounded border"
style="border-color: #D9D9D9; color: rgba(0, 0, 0, 0.45)"
disabled
>
上一页
</button>
<button class="px-3 py-1 rounded" style="background-color: #1890FF; color: #fff">1</button>
<button class="px-3 py-1 rounded border" style="border-color: #D9D9D9; color: rgba(0, 0, 0, 0.85)">2</button>
<button class="px-3 py-1 rounded border" style="border-color: #D9D9D9; color: rgba(0, 0, 0, 0.85)">3</button>
<span style="color: rgba(0, 0, 0, 0.45)">...</span>
<button class="px-3 py-1 rounded border" style="border-color: #D9D9D9; color: rgba(0, 0, 0, 0.85)">524</button>
<button class="px-3 py-1 rounded border" style="border-color: #D9D9D9; color: rgba(0, 0, 0, 0.85)">下一页</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
import { Plus, Upload, Download, Search, StopCircle } from 'lucide-vue-next'
const router = useRouter()
interface Device {
sn: string
model: string
status: '已激活' | '已出厂' | '装配中'
firmwareVersion: string
registrationDate: string
}
const currentPage = ref(1)
const pageSize = ref(10)
const totalDevices = 5234
const devices: Device[] = [
{
sn: 'GD30-20240308-001',
model: 'GD30 高密度电法仪',
status: '已激活',
firmwareVersion: 'v2.3.5',
registrationDate: '2024-03-08 14:30',
},
{
sn: 'GT20-20240307-045',
model: 'GT20 瞬变电磁仪',
status: '已出厂',
firmwareVersion: 'v1.8.2',
registrationDate: '2024-03-07 10:15',
},
{
sn: 'GD30-20240308-002',
model: 'GD30 高密度电法仪',
status: '装配中',
firmwareVersion: 'v2.3.4',
registrationDate: '2024-03-08 16:20',
},
]
function getStatusStyle(status: Device['status']) {
switch (status) {
case '已激活':
return { backgroundColor: '#F6FFED', color: '#52C41A', border: '1px solid #B7EB8F' }
case '已出厂':
return { backgroundColor: '#FFF7E6', color: '#FA8C16', border: '1px solid #FFD591' }
case '装配中':
return { backgroundColor: '#E6F7FF', color: '#1890FF', border: '1px solid #91D5FF' }
}
}
const startIndex = computed(() => (currentPage.value - 1) * pageSize.value + 1)
const endIndex = computed(() => Math.min(currentPage.value * pageSize.value, totalDevices))
</script>

View File

@ -1,331 +0,0 @@
import { useState } from "react";
import {
Info,
Server,
CheckCircle2,
AlertTriangle,
Layers,
Camera,
ShieldCheck
} from "lucide-react";
interface StatCardProps {
label: string;
value: string;
icon: React.ElementType;
color?: string;
}
function StatCard({ label, value, icon: Icon, color = "#1890FF" }: StatCardProps) {
return (
<div className="bg-white p-6 rounded-lg" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<div className="flex items-center justify-between">
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.65)' }}>{label}</div>
<div className="text-3xl font-semibold">{value}</div>
</div>
<div className="w-12 h-12 rounded-lg flex items-center justify-center" style={{ backgroundColor: `${color}15` }}>
<Icon size={24} style={{ color }} />
</div>
</div>
</div>
);
}
export default function DeviceModelManagement() {
const [activeTab, setActiveTab] = useState("GD30");
const stats = [
{ label: "型号总数", value: "12", icon: Server, color: "#1890FF" },
{ label: "在产型号", value: "8", icon: CheckCircle2, color: "#52C41A" },
{ label: "停产型号", value: "3", icon: AlertTriangle, color: "#FAAD14" },
{ label: "关联设备总数", value: "5,234", icon: Layers, color: "#722ED1" },
];
const modelData = [
{
name: "GD30 地质探测仪",
code: "GD30-2024",
authFile: "auth_gd30_v2.3.lic",
configFile: "config_gd30_v1.5.json",
firmwareVersion: "v2.3.5",
deviceCount: 2456,
status: "在产" as const,
},
{
name: "GT20 物探仪",
code: "GT20-2023",
authFile: "auth_gt20_v1.8.lic",
configFile: "config_gt20_v1.2.json",
firmwareVersion: "v1.8.2",
deviceCount: 1823,
status: "在产" as const,
},
{
name: "GTXD 探测仪",
code: "GTXD-2022",
authFile: "auth_gtxd_v1.5.lic",
configFile: "config_gtxd_v1.0.json",
firmwareVersion: "v1.5.1",
deviceCount: 955,
status: "停产" as const,
},
];
const checklistData = {
GD30: [
{ id: 1, item: "主板安装及固定", needPhoto: true, versionCheck: true },
{ id: 2, item: "采集板连接", needPhoto: true, versionCheck: true },
{ id: 3, item: "测控板安装", needPhoto: true, versionCheck: true },
{ id: 4, item: "电源线连接检查", needPhoto: true, versionCheck: false },
{ id: 5, item: "外壳密封性检测", needPhoto: true, versionCheck: false },
],
GT20: [
{ id: 1, item: "核心板安装", needPhoto: true, versionCheck: true },
{ id: 2, item: "扩展板连接", needPhoto: true, versionCheck: true },
{ id: 3, item: "信号线路检测", needPhoto: true, versionCheck: false },
{ id: 4, item: "天线模块安装", needPhoto: true, versionCheck: false },
{ id: 5, item: "整体功能测试", needPhoto: false, versionCheck: false },
],
GTXD: [
{ id: 1, item: "主控板安装", needPhoto: true, versionCheck: true },
{ id: 2, item: "传感器模块连接", needPhoto: true, versionCheck: false },
{ id: 3, item: "接口板安装", needPhoto: true, versionCheck: true },
{ id: 4, item: "线缆整理", needPhoto: true, versionCheck: false },
{ id: 5, item: "系统初始化检测", needPhoto: false, versionCheck: false },
],
};
const boardVersionData = [
{
boardType: "主板",
requiredVersion: "v2.3.x",
validationRule: "版本号前缀必须为 v2.3",
status: "active",
},
{
boardType: "采集板",
requiredVersion: "v1.8.x",
validationRule: "版本号前缀必须为 v1.8",
status: "active",
},
{
boardType: "测控板",
requiredVersion: "v1.5.x",
validationRule: "版本号前缀必须为 v1.5",
status: "active",
},
];
return (
<div className="p-6">
{/* Page Header */}
<div className="mb-6">
<h2 className="text-2xl font-semibold mb-1"></h2>
<p className="text-sm" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></p>
</div>
{/* Info Banner */}
<div
className="mb-6 p-4 rounded-lg flex items-start gap-3"
style={{ backgroundColor: '#E6F7FF', border: '1px solid #91D5FF' }}
>
<Info size={20} style={{ color: '#1890FF', flexShrink: 0, marginTop: 2 }} />
<div style={{ color: '#0050B3' }}>
</div>
</div>
{/* Stat Cards */}
<div className="grid grid-cols-4 gap-6 mb-6">
{stats.map((stat, index) => (
<StatCard key={index} {...stat} />
))}
</div>
{/* Model Table */}
<div className="bg-white rounded-lg mb-6" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<div className="p-6 border-b" style={{ borderColor: '#F0F0F0' }}>
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold"></h3>
<button
className="px-4 py-2 rounded text-white"
style={{ backgroundColor: '#1890FF' }}
>
</button>
</div>
</div>
<div className="overflow-x-auto">
<table className="w-full">
<thead style={{ backgroundColor: '#FAFAFA' }}>
<tr>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}></th>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}></th>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}></th>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}></th>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}></th>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}></th>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}></th>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}></th>
</tr>
</thead>
<tbody>
{modelData.map((model, index) => (
<tr
key={index}
className="border-b"
style={{ borderColor: '#F0F0F0' }}
>
<td className="px-6 py-4">{model.name}</td>
<td className="px-6 py-4" style={{ color: 'rgba(0, 0, 0, 0.65)' }}>{model.code}</td>
<td className="px-6 py-4">
<button className="text-sm" style={{ color: '#1890FF' }}>{model.authFile}</button>
</td>
<td className="px-6 py-4">
<button className="text-sm" style={{ color: '#1890FF' }}>{model.configFile}</button>
</td>
<td className="px-6 py-4">
<button className="text-sm" style={{ color: '#1890FF' }}>{model.firmwareVersion}</button>
</td>
<td className="px-6 py-4">{model.deviceCount.toLocaleString()}</td>
<td className="px-6 py-4">
<span
className="px-2 py-1 rounded text-xs"
style={{
backgroundColor: model.status === "在产" ? '#F6FFED' : '#FFFBE6',
color: model.status === "在产" ? '#52C41A' : '#FAAD14',
border: `1px solid ${model.status === "在产" ? '#B7EB8F' : '#FFE58F'}`
}}
>
{model.status}
</span>
</td>
<td className="px-6 py-4">
<div className="flex items-center gap-3">
<button className="text-sm" style={{ color: '#1890FF' }}></button>
<button className="text-sm" style={{ color: '#1890FF' }}></button>
<button className="text-sm" style={{ color: '#1890FF' }}></button>
<button className="text-sm" style={{ color: '#1890FF' }}></button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* Assembly Checklist */}
<div className="bg-white rounded-lg mb-6" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<div className="p-6 border-b" style={{ borderColor: '#F0F0F0' }}>
<h3 className="text-lg font-semibold">Checklist模板</h3>
</div>
{/* Tabs */}
<div className="flex border-b" style={{ borderColor: '#F0F0F0' }}>
{Object.keys(checklistData).map((model) => (
<button
key={model}
onClick={() => setActiveTab(model)}
className="px-6 py-3 text-sm font-medium transition-colors"
style={{
color: activeTab === model ? '#1890FF' : 'rgba(0, 0, 0, 0.65)',
borderBottom: activeTab === model ? '2px solid #1890FF' : 'none',
marginBottom: activeTab === model ? '-1px' : '0',
}}
>
{model}
</button>
))}
</div>
{/* Checklist Content */}
<div className="p-6">
<div className="space-y-3">
{checklistData[activeTab as keyof typeof checklistData].map((item) => (
<div
key={item.id}
className="flex items-center gap-4 p-4 rounded"
style={{ backgroundColor: '#FAFAFA' }}
>
<div
className="w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0"
style={{ backgroundColor: '#1890FF', color: 'white' }}
>
{item.id}
</div>
<div className="flex-1">{item.item}</div>
<div className="flex items-center gap-2">
{item.needPhoto && (
<span
className="px-3 py-1 rounded text-xs flex items-center gap-1"
style={{ backgroundColor: '#E6F7FF', color: '#1890FF' }}
>
<Camera size={12} />
</span>
)}
{item.versionCheck && (
<span
className="px-3 py-1 rounded text-xs flex items-center gap-1"
style={{ backgroundColor: '#F9F0FF', color: '#722ED1' }}
>
<ShieldCheck size={12} />
</span>
)}
</div>
</div>
))}
</div>
</div>
</div>
{/* Board Version Requirements */}
<div className="bg-white rounded-lg" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<div className="p-6 border-b" style={{ borderColor: '#F0F0F0' }}>
<h3 className="text-lg font-semibold"></h3>
</div>
<div className="overflow-x-auto">
<table className="w-full">
<thead style={{ backgroundColor: '#FAFAFA' }}>
<tr>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}></th>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}></th>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}></th>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}></th>
</tr>
</thead>
<tbody>
{boardVersionData.map((board, index) => (
<tr
key={index}
className="border-b"
style={{ borderColor: '#F0F0F0' }}
>
<td className="px-6 py-4">{board.boardType}</td>
<td className="px-6 py-4">
<span
className="px-2 py-1 rounded text-xs"
style={{ backgroundColor: '#F0F2F5', color: 'rgba(0, 0, 0, 0.85)' }}
>
{board.requiredVersion}
</span>
</td>
<td className="px-6 py-4" style={{ color: 'rgba(0, 0, 0, 0.65)' }}>{board.validationRule}</td>
<td className="px-6 py-4">
<div className="flex items-center gap-3">
<button className="text-sm" style={{ color: '#1890FF' }}></button>
<button className="text-sm" style={{ color: '#1890FF' }}></button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,416 @@
<script setup lang="ts">
import { ref } from 'vue'
import {
Info,
Server,
CheckCircle2,
AlertTriangle,
Layers,
Plus,
Trash2,
GripVertical,
} from 'lucide-vue-next'
interface ChecklistItem {
id: number
text: string
type: 'text' | 'bool'
required: boolean
note: string
}
interface ChecklistData {
[key: string]: ChecklistItem[]
}
const activeTab = ref('GD30')
const stats = [
{ label: '型号总数', value: '12', icon: Server, color: '#1890FF' },
{ label: '在产型号', value: '8', icon: CheckCircle2, color: '#52C41A' },
{ label: '停产型号', value: '3', icon: AlertTriangle, color: '#FAAD14' },
{ label: '关联设备总数', value: '5,234', icon: Layers, color: '#722ED1' },
]
const modelData = [
{
name: 'GD30 高密度电法仪',
code: 'GD30-2024',
authFile: 'auth_gd30_v2.3.lic',
configFile: 'config_gd30_v1.5.json',
firmwareVersion: 'v2.3.5',
deviceCount: 2456,
status: '在产' as const,
},
{
name: 'GT20 瞬变电磁仪',
code: 'GT20-2023',
authFile: 'auth_gt20_v1.8.lic',
configFile: 'config_gt20_v1.2.json',
firmwareVersion: 'v1.8.2',
deviceCount: 1823,
status: '在产' as const,
},
{
name: 'GM10 大地电磁仪',
code: 'GM10-2022',
authFile: 'auth_gm10_v1.5.lic',
configFile: 'config_gm10_v1.0.json',
firmwareVersion: 'v1.5.1',
deviceCount: 955,
status: '停产' as const,
},
]
const checklistData = ref<ChecklistData>({
GD30: [
{ id: 1, text: '主板SN扫码绑定主机', type: 'text', required: true, note: '唯一SN' },
{ id: 2, text: '采集板SN录入', type: 'text', required: true, note: '1/6/12通道' },
{ id: 3, text: '发射板SN录入', type: 'text', required: true, note: '' },
{ id: 4, text: '内置升压模块检查', type: 'bool', required: true, note: '' },
{ id: 5, text: 'GPS/北斗检测', type: 'bool', required: true, note: '授时正常' },
{ id: 6, text: '电池安装与容量检测', type: 'bool', required: true, note: '' },
{ id: 7, text: '输入电压12~48V测试', type: 'bool', required: true, note: '' },
{ id: 8, text: '接收电压精度校验', type: 'bool', required: true, note: '按型号量程' },
{ id: 9, text: '自电补偿±10V', type: 'bool', required: true, note: '' },
{ id: 10, text: '输入阻抗≥100MΩ', type: 'bool', required: true, note: '' },
{ id: 11, text: '恒压/恒流模式', type: 'bool', required: true, note: '' },
{ id: 12, text: '最大发射电流达标', type: 'bool', required: true, note: '6A/10A/10A' },
{ id: 13, text: '脉冲宽度配置', type: 'bool', required: true, note: '' },
{ id: 14, text: '调级输出电压', type: 'bool', required: true, note: '100~600V' },
{ id: 15, text: '系统启动正常', type: 'bool', required: true, note: '' },
{ id: 16, text: '采集APP连接', type: 'bool', required: true, note: '' },
{ id: 17, text: 'Geometa账号配置', type: 'bool', required: true, note: '' },
{ id: 18, text: '授权文件校验', type: 'bool', required: true, note: '' },
{ id: 19, text: 'USB/WiFi/网口/SD', type: 'bool', required: true, note: '' },
{ id: 20, text: 'IP66防护与密封', type: 'bool', required: true, note: '' },
{ id: 21, text: '过流/过压/短路保护', type: 'bool', required: true, note: '' },
{ id: 22, text: '出厂装箱核对', type: 'bool', required: true, note: '' },
],
GT20: [
{ id: 1, text: '主板SN扫码绑定主机', type: 'text', required: true, note: '唯一SN' },
{ id: 2, text: '采集板SN录入', type: 'text', required: true, note: '' },
{ id: 3, text: 'GPS/北斗检测', type: 'bool', required: true, note: '授时正常' },
{ id: 4, text: '系统启动正常', type: 'bool', required: true, note: '' },
{ id: 5, text: '整体功能测试', type: 'bool', required: true, note: '' },
],
GTXD: [
{ id: 1, text: '主板SN扫码绑定主机', type: 'text', required: true, note: '唯一SN' },
{ id: 2, text: '传感器模块连接', type: 'bool', required: true, note: '' },
{ id: 3, text: '接口板安装', type: 'bool', required: true, note: '' },
{ id: 4, text: '线缆整理', type: 'bool', required: true, note: '' },
{ id: 5, text: '系统初始化检测', type: 'bool', required: true, note: '' },
],
})
const editingCell = ref<{ model: string; id: number; field: string } | null>(null)
const updateChecklistItem = (model: string, id: number, field: keyof ChecklistItem, value: string | boolean) => {
checklistData.value = {
...checklistData.value,
[model]: checklistData.value[model].map((item: ChecklistItem) =>
item.id === id ? { ...item, [field]: value } : item
),
}
}
const addChecklistItem = (model: string) => {
const items = checklistData.value[model]
const newId = items.length > 0 ? Math.max(...items.map((i: ChecklistItem) => i.id)) + 1 : 1
checklistData.value = {
...checklistData.value,
[model]: [...items, { id: newId, text: '新检查项', type: 'bool' as const, required: true, note: '' }],
}
}
const deleteChecklistItem = (model: string, id: number) => {
checklistData.value = {
...checklistData.value,
[model]: checklistData.value[model].filter((item: ChecklistItem) => item.id !== id).map((item: ChecklistItem, i: number) => ({ ...item, id: i + 1 })),
}
}
const handleEditBlur = (e: Event, model: string, id: number) => {
updateChecklistItem(model, id, 'text', (e.target as HTMLInputElement).value)
editingCell.value = null
}
const handleEditKeydown = (e: KeyboardEvent) => {
if (e.key === 'Enter') (e.target as HTMLInputElement).blur()
}
const boardVersionData = [
{
boardType: '主板',
requiredVersion: 'v2.3.x',
validationRule: '版本号前缀必须为 v2.3',
status: 'active',
},
{
boardType: '采集板',
requiredVersion: 'v1.8.x',
validationRule: '版本号前缀必须为 v1.8',
status: 'active',
},
{
boardType: '测控板',
requiredVersion: 'v1.5.x',
validationRule: '版本号前缀必须为 v1.5',
status: 'active',
},
]
</script>
<template>
<div class="p-6">
<!-- Page Header -->
<div class="mb-6">
<h2 class="text-2xl font-semibold mb-1">设备型号管理</h2>
<p class="text-sm" :style="{ color: 'rgba(0, 0, 0, 0.45)' }">管理设备型号及相关配置</p>
</div>
<!-- Info Banner -->
<div
class="mb-6 p-4 rounded-lg flex items-start gap-3"
:style="{ backgroundColor: '#E6F7FF', border: '1px solid #91D5FF' }"
>
<Info :size="20" :style="{ color: '#1890FF', flexShrink: 0, marginTop: '2px' }" />
<div :style="{ color: '#0050B3' }">
型号管理是平台核心枢纽每个型号关联授权文件配置文件和固件版本
</div>
</div>
<!-- Stat Cards -->
<div class="grid grid-cols-4 gap-6 mb-6">
<div
v-for="(stat, index) in stats"
:key="index"
class="bg-white p-6 rounded-lg"
:style="{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }"
>
<div class="flex items-center justify-between">
<div>
<div class="text-sm mb-2" :style="{ color: 'rgba(0, 0, 0, 0.65)' }">{{ stat.label }}</div>
<div class="text-3xl font-semibold">{{ stat.value }}</div>
</div>
<div
class="w-12 h-12 rounded-lg flex items-center justify-center"
:style="{ backgroundColor: `${stat.color}15` }"
>
<component :is="stat.icon" :size="24" :style="{ color: stat.color }" />
</div>
</div>
</div>
</div>
<!-- Model Table -->
<div class="bg-white rounded-lg mb-6" :style="{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }">
<div class="p-6 border-b" :style="{ borderColor: '#F0F0F0' }">
<div class="flex items-center justify-between">
<h3 class="text-lg font-semibold">型号列表</h3>
<button
class="px-4 py-2 rounded text-white"
:style="{ backgroundColor: '#1890FF' }"
>
新增型号
</button>
</div>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead :style="{ backgroundColor: '#FAFAFA' }">
<tr>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">型号名称</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">编码</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">授权文件</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">配置文件</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">固件版本</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">设备数</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">状态</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">操作</th>
</tr>
</thead>
<tbody>
<tr
v-for="(model, index) in modelData"
:key="index"
class="border-b"
:style="{ borderColor: '#F0F0F0' }"
>
<td class="px-6 py-4">{{ model.name }}</td>
<td class="px-6 py-4" :style="{ color: 'rgba(0, 0, 0, 0.65)' }">{{ model.code }}</td>
<td class="px-6 py-4">
<button class="text-sm" :style="{ color: '#1890FF' }">{{ model.authFile }}</button>
</td>
<td class="px-6 py-4">
<button class="text-sm" :style="{ color: '#1890FF' }">{{ model.configFile }}</button>
</td>
<td class="px-6 py-4">
<button class="text-sm" :style="{ color: '#1890FF' }">{{ model.firmwareVersion }}</button>
</td>
<td class="px-6 py-4">{{ model.deviceCount.toLocaleString() }}</td>
<td class="px-6 py-4">
<span
class="px-2 py-1 rounded text-xs"
:style="{
backgroundColor: model.status === '在产' ? '#F6FFED' : '#FFFBE6',
color: model.status === '在产' ? '#52C41A' : '#FAAD14',
border: `1px solid ${model.status === '在产' ? '#B7EB8F' : '#FFE58F'}`,
}"
>
{{ model.status }}
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-3">
<button class="text-sm" :style="{ color: '#1890FF' }">编辑</button>
<button class="text-sm" :style="{ color: '#1890FF' }">授权</button>
<button class="text-sm" :style="{ color: '#1890FF' }">配置</button>
<button class="text-sm" :style="{ color: '#1890FF' }">固件</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Assembly Checklist -->
<div class="bg-white rounded-lg mb-6" :style="{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }">
<div class="p-6 border-b" :style="{ borderColor: '#F0F0F0' }">
<h3 class="text-lg font-semibold">装配Checklist模板</h3>
</div>
<!-- Tabs -->
<div class="flex border-b" :style="{ borderColor: '#F0F0F0' }">
<button
v-for="model in Object.keys(checklistData)"
:key="model"
@click="activeTab = model"
class="px-6 py-3 text-sm font-medium transition-colors"
:style="{
color: activeTab === model ? '#1890FF' : 'rgba(0, 0, 0, 0.65)',
borderBottom: activeTab === model ? '2px solid #1890FF' : 'none',
marginBottom: activeTab === model ? '-1px' : '0',
}"
>
{{ model }}
</button>
</div>
<!-- Checklist Content -->
<div class="p-6">
<table class="w-full">
<thead :style="{ backgroundColor: '#FAFAFA' }">
<tr>
<th class="px-3 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)', width: '50px' }">序号</th>
<th class="px-3 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">项目名称</th>
<th class="px-3 py-3 text-center text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)', width: '70px' }">操作</th>
</tr>
</thead>
<tbody>
<tr
v-for="item in checklistData[activeTab]"
:key="item.id"
class="border-b"
:style="{ borderColor: '#F0F0F0' }"
>
<td class="px-3 py-3 text-sm" :style="{ color: 'rgba(0, 0, 0, 0.45)' }">
<div class="flex items-center gap-1">
<GripVertical :size="14" :style="{ color: '#D9D9D9' }" />
{{ item.id }}
</div>
</td>
<td class="px-3 py-3 text-sm">
<input
v-if="editingCell?.model === activeTab && editingCell?.id === item.id && editingCell?.field === 'text'"
autofocus
class="w-full px-2 py-1 border rounded text-sm"
:style="{ borderColor: '#1890FF' }"
:value="item.text"
@blur="handleEditBlur($event, activeTab, item.id)"
@keydown="handleEditKeydown"
/>
<span
v-else
class="cursor-pointer hover:text-blue-500"
@click="editingCell = { model: activeTab, id: item.id, field: 'text' }"
>
{{ item.text }}
</span>
</td>
<td class="px-3 py-3 text-center">
<button
@click="deleteChecklistItem(activeTab, item.id)"
class="text-gray-400 hover:text-red-500 transition-colors"
>
<Trash2 :size="15" />
</button>
</td>
</tr>
</tbody>
</table>
<div class="mt-4 flex items-center justify-between">
<button
@click="addChecklistItem(activeTab)"
class="px-4 py-2 text-sm flex items-center gap-1 rounded transition-colors"
:style="{ color: '#1890FF', border: '1px dashed #1890FF' }"
>
<Plus :size="14" />
添加检查项
</button>
<button
class="px-6 py-2 text-sm rounded text-white"
:style="{ backgroundColor: '#1890FF' }"
>
确认保存
</button>
</div>
</div>
</div>
<!-- Board Version Requirements -->
<div class="bg-white rounded-lg" :style="{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }">
<div class="p-6 border-b" :style="{ borderColor: '#F0F0F0' }">
<h3 class="text-lg font-semibold">板卡版本要求</h3>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead :style="{ backgroundColor: '#FAFAFA' }">
<tr>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">板卡类型</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">要求固件版本</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">校验规则</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">操作</th>
</tr>
</thead>
<tbody>
<tr
v-for="(board, index) in boardVersionData"
:key="index"
class="border-b"
:style="{ borderColor: '#F0F0F0' }"
>
<td class="px-6 py-4">{{ board.boardType }}</td>
<td class="px-6 py-4">
<span
class="px-2 py-1 rounded text-xs"
:style="{ backgroundColor: '#F0F2F5', color: 'rgba(0, 0, 0, 0.85)' }"
>
{{ board.requiredVersion }}
</span>
</td>
<td class="px-6 py-4" :style="{ color: 'rgba(0, 0, 0, 0.65)' }">{{ board.validationRule }}</td>
<td class="px-6 py-4">
<div class="flex items-center gap-3">
<button class="text-sm" :style="{ color: '#1890FF' }">编辑</button>
<button class="text-sm" :style="{ color: '#1890FF' }">测试</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>

View File

@ -1,337 +0,0 @@
import { useState } from "react";
import {
CheckCircle2,
AlertTriangle,
Camera,
Upload,
X,
Check
} from "lucide-react";
export default function DeviceRegistration() {
const [selectedModel, setSelectedModel] = useState("GD30");
const [checklistItems, setChecklistItems] = useState([
{ id: 1, text: "主板安装及固定", completed: true, photos: 3, needPhoto: true, versionCheck: true, versionMatch: true },
{ id: 2, text: "采集板连接", completed: true, photos: 2, needPhoto: true, versionCheck: true, versionMatch: true },
{ id: 3, text: "测控板安装", completed: true, photos: 2, needPhoto: true, versionCheck: true, versionMatch: false },
{ id: 4, text: "电源线连接检查", completed: false, photos: 0, needPhoto: true, versionCheck: false, versionMatch: true },
{ id: 5, text: "外壳密封性检测", completed: false, photos: 0, needPhoto: true, versionCheck: false, versionMatch: true },
]);
const bomData = [
{ code: "MB-2024-001", name: "主控板", sn: "MB20240308001", spec: "GD30-MB-V2.3", calibration: "已校准", quantity: 1 },
{ code: "AC-2024-002", name: "采集板", sn: "AC20240308002", spec: "GD30-AC-V1.8", calibration: "已校准", quantity: 1 },
{ code: "CT-2024-003", name: "测控板", sn: "CT20240308003", spec: "GD30-CT-V1.5", calibration: "已校准", quantity: 1 },
{ code: "PS-2024-004", name: "电源模块", sn: "PS20240308004", spec: "DC24V-5A", calibration: "无需校准", quantity: 1 },
{ code: "CS-2024-005", name: "外壳组件", sn: "-", spec: "AL6061-T6", calibration: "无需校准", quantity: 1 },
];
const completedCount = checklistItems.filter(item => item.completed).length;
const totalCount = checklistItems.length;
const toggleChecklistItem = (id: number) => {
setChecklistItems(items =>
items.map(item =>
item.id === id ? { ...item, completed: !item.completed } : item
)
);
};
return (
<div className="p-6">
{/* Page Header */}
<div className="mb-6">
<h2 className="text-2xl font-semibold mb-1"></h2>
<p className="text-sm" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></p>
</div>
{/* Installation Info Card */}
<div className="bg-white p-6 rounded-lg mb-6" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<h3 className="text-lg font-semibold mb-6"></h3>
<div className="grid grid-cols-3 gap-6">
<div>
<label className="block text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.85)' }}>
<span style={{ color: '#FF4D4F' }}>*</span>
</label>
<select
className="w-full px-3 py-2 border rounded"
style={{ borderColor: '#D9D9D9', backgroundColor: '#fff' }}
value={selectedModel}
onChange={(e) => setSelectedModel(e.target.value)}
>
<option value="GD30">GD30 </option>
<option value="GT20">GT20 </option>
<option value="GTXD">GTXD </option>
</select>
</div>
<div>
<label className="block text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.85)' }}>
SN号 <span style={{ color: '#FF4D4F' }}>*</span>
</label>
<input
type="text"
className="w-full px-3 py-2 border rounded"
style={{ borderColor: '#D9D9D9' }}
placeholder="请输入主机SN号"
defaultValue="GD30-20240308-001"
/>
</div>
<div>
<label className="block text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.85)' }}>
SN号 <span style={{ color: '#FF4D4F' }}>*</span>
</label>
<div className="flex items-center gap-2">
<input
type="text"
className="flex-1 px-3 py-2 border rounded"
style={{ borderColor: '#D9D9D9' }}
placeholder="请输入主板SN号"
defaultValue="MB20240308001"
/>
<span
className="px-2 py-1 rounded text-xs whitespace-nowrap"
style={{ backgroundColor: '#F6FFED', color: '#52C41A', border: '1px solid #B7EB8F' }}
>
</span>
</div>
</div>
<div>
<label className="block text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.85)' }}>
</label>
<div className="flex items-center gap-2 px-3 py-2 border rounded" style={{ borderColor: '#D9D9D9', backgroundColor: '#FAFAFA' }}>
<CheckCircle2 size={16} style={{ color: '#52C41A' }} />
<span style={{ color: '#52C41A' }}></span>
</div>
</div>
<div>
<label className="block text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.85)' }}>
</label>
<input
type="date"
className="w-full px-3 py-2 border rounded"
style={{ borderColor: '#D9D9D9' }}
defaultValue="2024-03-08"
/>
</div>
<div>
<label className="block text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.85)' }}>
</label>
<input
type="text"
className="w-full px-3 py-2 border rounded"
style={{ borderColor: '#D9D9D9', backgroundColor: '#FAFAFA' }}
defaultValue="张工程师"
readOnly
/>
</div>
</div>
</div>
{/* Success Banner */}
<div
className="mb-4 p-4 rounded-lg flex items-start gap-3"
style={{ backgroundColor: '#F6FFED', border: '1px solid #B7EB8F' }}
>
<CheckCircle2 size={20} style={{ color: '#52C41A', flexShrink: 0, marginTop: 2 }} />
<div>
<div style={{ color: '#389E0D', fontWeight: 500 }}>GD30</div>
<div className="text-sm mt-1" style={{ color: '#52C41A' }}>
auth_gd30_v2.3.lic | config_gd30_v1.5.json | v2.3.5
</div>
</div>
</div>
{/* Warning Banner (Optional - shown when model doesn't match) */}
<div
className="mb-6 p-4 rounded-lg flex items-start gap-3"
style={{ backgroundColor: '#FFFBE6', border: '1px solid #FFE58F', display: 'none' }}
>
<AlertTriangle size={20} style={{ color: '#FAAD14', flexShrink: 0, marginTop: 2 }} />
<div>
<div style={{ color: '#D46B08', fontWeight: 500 }}></div>
<div className="text-sm mt-1" style={{ color: '#FAAD14' }}>
</div>
</div>
</div>
{/* BOM List Card */}
<div className="bg-white rounded-lg mb-6" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<div className="p-6 border-b" style={{ borderColor: '#F0F0F0' }}>
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold">BOM清单</h3>
<button
className="px-4 py-2 rounded flex items-center gap-2"
style={{ border: '1px solid #D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }}
>
<Upload size={16} />
</button>
</div>
</div>
<div className="overflow-x-auto">
<table className="w-full">
<thead style={{ backgroundColor: '#FAFAFA' }}>
<tr>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}></th>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}></th>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}>SN号</th>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}></th>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}></th>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}></th>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}></th>
</tr>
</thead>
<tbody>
{bomData.map((item, index) => (
<tr
key={index}
className="border-b"
style={{ borderColor: '#F0F0F0' }}
>
<td className="px-6 py-4" style={{ color: 'rgba(0, 0, 0, 0.65)' }}>{item.code}</td>
<td className="px-6 py-4">{item.name}</td>
<td className="px-6 py-4">{item.sn}</td>
<td className="px-6 py-4" style={{ color: 'rgba(0, 0, 0, 0.65)' }}>{item.spec}</td>
<td className="px-6 py-4">
<span
className="px-2 py-1 rounded text-xs"
style={{
backgroundColor: item.calibration === "已校准" ? '#F6FFED' : '#F0F2F5',
color: item.calibration === "已校准" ? '#52C41A' : 'rgba(0, 0, 0, 0.65)',
border: `1px solid ${item.calibration === "已校准" ? '#B7EB8F' : '#D9D9D9'}`
}}
>
{item.calibration}
</span>
</td>
<td className="px-6 py-4">{item.quantity}</td>
<td className="px-6 py-4">
<div className="flex items-center gap-3">
<button className="text-sm" style={{ color: '#1890FF' }}></button>
<button className="text-sm" style={{ color: '#FF4D4F' }}></button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="p-4 border-t" style={{ borderColor: '#F0F0F0' }}>
<button className="text-sm" style={{ color: '#1890FF' }}>+ </button>
</div>
</div>
{/* Assembly Checklist Card */}
<div className="bg-white rounded-lg mb-6" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<div className="p-6 border-b" style={{ borderColor: '#F0F0F0' }}>
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold">Checklist (GD30 )</h3>
<div className="flex items-center gap-2">
<span className="text-sm" style={{ color: 'rgba(0, 0, 0, 0.65)' }}></span>
<span className="text-sm font-semibold" style={{ color: '#1890FF' }}>
{completedCount}/{totalCount}
</span>
</div>
</div>
</div>
<div className="p-6">
<div className="space-y-3">
{checklistItems.map((item) => (
<div
key={item.id}
className="flex items-center gap-4 p-4 rounded border"
style={{
backgroundColor: item.completed ? '#F6FFED' : '#FAFAFA',
borderColor: item.completed ? '#B7EB8F' : '#F0F0F0'
}}
>
<button
onClick={() => toggleChecklistItem(item.id)}
className="w-6 h-6 rounded border flex items-center justify-center flex-shrink-0 transition-colors"
style={{
borderColor: item.completed ? '#52C41A' : '#D9D9D9',
backgroundColor: item.completed ? '#52C41A' : '#fff'
}}
>
{item.completed && <Check size={16} color="#fff" />}
</button>
<div
className="w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0"
style={{ backgroundColor: item.completed ? '#52C41A' : '#D9D9D9', color: 'white' }}
>
{item.id}
</div>
<div className="flex-1">
<div className="flex items-center gap-2">
<span style={{ color: item.completed ? 'rgba(0, 0, 0, 0.85)' : 'rgba(0, 0, 0, 0.65)' }}>
{item.text}
</span>
{item.versionCheck && !item.versionMatch && (
<span
className="px-2 py-1 rounded text-xs flex items-center gap-1"
style={{ backgroundColor: '#FFF1F0', color: '#FF4D4F', border: '1px solid #FFCCC7' }}
>
<AlertTriangle size={12} />
!
</span>
)}
</div>
</div>
<div className="flex items-center gap-2">
{item.needPhoto && item.completed && item.photos > 0 && (
<span
className="px-3 py-1 rounded text-xs flex items-center gap-1"
style={{ backgroundColor: '#E6F7FF', color: '#1890FF' }}
>
<Camera size={12} />
{item.photos}
</span>
)}
{item.needPhoto && !item.completed && (
<button
className="px-3 py-1 rounded text-xs flex items-center gap-1"
style={{ backgroundColor: '#1890FF', color: '#fff' }}
>
<Camera size={12} />
</button>
)}
</div>
</div>
))}
</div>
</div>
</div>
{/* Action Bar */}
<div
className="flex items-center justify-end gap-3 p-4 bg-white rounded-lg sticky bottom-0"
style={{ boxShadow: '0 -2px 8px rgba(0, 0, 0, 0.05)' }}
>
<button
className="px-6 py-2 rounded"
style={{ border: '1px solid #D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }}
>
</button>
<button
className="px-6 py-2 rounded"
style={{ border: '1px solid #D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }}
>
稿
</button>
<button
className="px-6 py-2 rounded text-white"
style={{ backgroundColor: '#1890FF' }}
>
</button>
</div>
</div>
);
}

View File

@ -0,0 +1,331 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import {
CheckCircle2,
AlertTriangle,
Camera,
Upload,
Check,
} from 'lucide-vue-next'
const selectedModel = ref('GD30')
const checklistItems = ref([
{ id: 1, text: '主板安装及固定', completed: true, photos: 3, needPhoto: true, versionCheck: true, versionMatch: true },
{ id: 2, text: '采集板连接', completed: true, photos: 2, needPhoto: true, versionCheck: true, versionMatch: true },
{ id: 3, text: '测控板安装', completed: true, photos: 2, needPhoto: true, versionCheck: true, versionMatch: false },
{ id: 4, text: '电源线连接检查', completed: false, photos: 0, needPhoto: true, versionCheck: false, versionMatch: true },
{ id: 5, text: '外壳密封性检测', completed: false, photos: 0, needPhoto: true, versionCheck: false, versionMatch: true },
])
const bomData = [
{ code: 'MB-2024-001', name: '主控板', sn: 'MB20240308001', spec: 'GD30-MB-V2.3', calibration: '无需校准', quantity: 1 },
{ code: 'RX-2024-002', name: '采集板', sn: 'RX20240308002', spec: 'GD30-RX-V1.8', calibration: '已校准', quantity: 2 },
{ code: 'MC-2024-003', name: '测控板', sn: 'MC20240308003', spec: 'GD30-MC-V1.5', calibration: '无需校准', quantity: 1 },
{ code: 'TX-2024-003', name: '发射板', sn: 'TX20240308003', spec: 'GD30-TX-V1.5', calibration: '无需校准', quantity: 1 },
{ code: 'BO-2024-004', name: '升压板', sn: 'BO20240308004', spec: 'BP600', calibration: '无需校准', quantity: 1 },
{ code: 'CS-2024-005', name: '外壳机箱', sn: '-', spec: 'AL6061-T6', calibration: '无需校准', quantity: 1 },
]
const completedCount = computed(() => checklistItems.value.filter((item: typeof checklistItems.value[number]) => item.completed).length)
const totalCount = computed(() => checklistItems.value.length)
const toggleChecklistItem = (id: number) => {
checklistItems.value = checklistItems.value.map((item: typeof checklistItems.value[number]) =>
item.id === id ? { ...item, completed: !item.completed } : item
)
}
</script>
<template>
<div class="p-6">
<!-- Page Header -->
<div class="mb-6">
<h2 class="text-2xl font-semibold mb-1">设备登记</h2>
<p class="text-sm" :style="{ color: 'rgba(0, 0, 0, 0.45)' }">登记新设备信息及装配记录</p>
</div>
<!-- Installation Info Card -->
<div class="bg-white p-6 rounded-lg mb-6" :style="{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }">
<h3 class="text-lg font-semibold mb-6">装机信息</h3>
<div class="grid grid-cols-3 gap-6">
<div>
<label class="block text-sm mb-2" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">
设备型号 <span :style="{ color: '#FF4D4F' }">*</span>
</label>
<select
v-model="selectedModel"
class="w-full px-3 py-2 border rounded"
:style="{ borderColor: '#D9D9D9', backgroundColor: '#fff' }"
>
<option value="GD30">GD30 高密度电法仪</option>
<option value="GT20">GT20 瞬变电磁仪</option>
<option value="GTXD">GM10 大地电磁仪</option>
</select>
</div>
<div>
<label class="block text-sm mb-2" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">
主机SN号 <span :style="{ color: '#FF4D4F' }">*</span>
</label>
<input
type="text"
class="w-full px-3 py-2 border rounded"
:style="{ borderColor: '#D9D9D9' }"
placeholder="请输入主机SN号"
value="GD30-20240308-001"
/>
</div>
<div>
<label class="block text-sm mb-2" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">
主板SN号 <span :style="{ color: '#FF4D4F' }">*</span>
</label>
<div class="flex items-center gap-2">
<input
type="text"
class="flex-1 px-3 py-2 border rounded"
:style="{ borderColor: '#D9D9D9' }"
placeholder="请输入主板SN号"
value="MB20240308001"
/>
<span
class="px-2 py-1 rounded text-xs whitespace-nowrap"
:style="{ backgroundColor: '#F6FFED', color: '#52C41A', border: '1px solid #B7EB8F' }"
>
已绑定
</span>
</div>
</div>
<div>
<label class="block text-sm mb-2" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">
装机测试状态
</label>
<div class="flex items-center gap-2 px-3 py-2 border rounded" :style="{ borderColor: '#D9D9D9', backgroundColor: '#FAFAFA' }">
<select>
<option><span :style="{ color: '#52C41A' }">测试通过</span></option>
<option><span :style="{ color: '#c41a1aff' }">测试不通过</span></option>
</select>
</div>
</div>
<div>
<label class="block text-sm mb-2" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">
生产日期
</label>
<input
type="date"
class="w-full px-3 py-2 border rounded"
:style="{ borderColor: '#D9D9D9' }"
value="2024-03-08"
/>
</div>
<div>
<label class="block text-sm mb-2" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">
登记人
</label>
<input
type="text"
class="w-full px-3 py-2 border rounded"
:style="{ borderColor: '#D9D9D9', backgroundColor: '#FAFAFA' }"
value="张工程师"
readonly
/>
</div>
</div>
</div>
<!-- Success Banner -->
<div
class="mb-4 p-4 rounded-lg flex items-start gap-3"
:style="{ backgroundColor: '#F6FFED', border: '1px solid #B7EB8F' }"
>
<CheckCircle2 :size="20" :style="{ color: '#52C41A', flexShrink: 0, marginTop: '2px' }" />
<div>
<div :style="{ color: '#389E0D', fontWeight: 500 }">型号已匹配GD30</div>
<div class="text-sm mt-1" :style="{ color: '#52C41A' }">
授权文件auth_gd30_v2.3.lic | 配置文件config_gd30_v1.5.json | 固件版本v2.3.5
</div>
</div>
</div>
<!-- Warning Banner (Optional - shown when model doesn't match) -->
<div
class="mb-6 p-4 rounded-lg flex items-start gap-3"
:style="{ backgroundColor: '#FFFBE6', border: '1px solid #FFE58F', display: 'none' }"
>
<AlertTriangle :size="20" :style="{ color: '#FAAD14', flexShrink: 0, marginTop: '2px' }" />
<div>
<div :style="{ color: '#D46B08', fontWeight: 500 }">未匹配到型号关联信息</div>
<div class="text-sm mt-1" :style="{ color: '#FAAD14' }">
请先在型号管理中配置该型号的授权文件配置文件和固件版本
</div>
</div>
</div>
<!-- BOM List Card -->
<div class="bg-white rounded-lg mb-6" :style="{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }">
<div class="p-6 border-b" :style="{ borderColor: '#F0F0F0' }">
<div class="flex items-center justify-between">
<h3 class="text-lg font-semibold">设备BOM清单</h3>
<button
class="px-4 py-2 rounded flex items-center gap-2"
:style="{ border: '1px solid #D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }"
>
<Upload :size="16" />
导入
</button>
</div>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead :style="{ backgroundColor: '#FAFAFA' }">
<tr>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">物料编码</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">物料名称</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">SN号</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">规格型号</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">校准状态</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">数量</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">操作</th>
</tr>
</thead>
<tbody>
<tr
v-for="(item, index) in bomData"
:key="index"
class="border-b"
:style="{ borderColor: '#F0F0F0' }"
>
<td class="px-6 py-4" :style="{ color: 'rgba(0, 0, 0, 0.65)' }">{{ item.code }}</td>
<td class="px-6 py-4">{{ item.name }}</td>
<td class="px-6 py-4">{{ item.sn }}</td>
<td class="px-6 py-4" :style="{ color: 'rgba(0, 0, 0, 0.65)' }">{{ item.spec }}</td>
<td class="px-6 py-4">
<span
class="px-2 py-1 rounded text-xs"
:style="{
backgroundColor: item.calibration === '已校准' ? '#F6FFED' : '#F0F2F5',
color: item.calibration === '已校准' ? '#52C41A' : 'rgba(0, 0, 0, 0.65)',
border: `1px solid ${item.calibration === '已校准' ? '#B7EB8F' : '#D9D9D9'}`,
}"
>
{{ item.calibration }}
</span>
</td>
<td class="px-6 py-4">{{ item.quantity }}</td>
<td class="px-6 py-4">
<div class="flex items-center gap-3">
<button class="text-sm" :style="{ color: '#1890FF' }">编辑</button>
<button class="text-sm" :style="{ color: '#FF4D4F' }">删除</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="p-4 border-t" :style="{ borderColor: '#F0F0F0' }">
<button class="text-sm" :style="{ color: '#1890FF' }">+ 添加物料</button>
</div>
</div>
<!-- Assembly Checklist Card -->
<div class="bg-white rounded-lg mb-6" :style="{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }">
<div class="p-6 border-b" :style="{ borderColor: '#F0F0F0' }">
<div class="flex items-center justify-between">
<h3 class="text-lg font-semibold">装配Checklist</h3>
<div class="flex items-center gap-2">
<span class="text-sm" :style="{ color: 'rgba(0, 0, 0, 0.65)' }">完成进度</span>
<span class="text-sm font-semibold" :style="{ color: '#1890FF' }">
{{ completedCount }}/{{ totalCount }}
</span>
</div>
</div>
</div>
<div class="p-6">
<div class="space-y-3">
<div
v-for="item in checklistItems"
:key="item.id"
class="flex items-center gap-4 p-4 rounded border"
:style="{
backgroundColor: item.completed ? '#F6FFED' : '#FAFAFA',
borderColor: item.completed ? '#B7EB8F' : '#F0F0F0',
}"
>
<button
@click="toggleChecklistItem(item.id)"
class="w-6 h-6 rounded border flex items-center justify-center flex-shrink-0 transition-colors"
:style="{
borderColor: item.completed ? '#52C41A' : '#D9D9D9',
backgroundColor: item.completed ? '#52C41A' : '#fff',
}"
>
<Check v-if="item.completed" :size="16" color="#fff" />
</button>
<div
class="w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0"
:style="{ backgroundColor: item.completed ? '#52C41A' : '#D9D9D9', color: 'white' }"
>
{{ item.id }}
</div>
<div class="flex-1">
<div class="flex items-center gap-2">
<span :style="{ color: item.completed ? 'rgba(0, 0, 0, 0.85)' : 'rgba(0, 0, 0, 0.65)' }">
{{ item.text }}
</span>
<span
v-if="item.versionCheck && !item.versionMatch"
class="px-2 py-1 rounded text-xs flex items-center gap-1"
:style="{ backgroundColor: '#FFF1F0', color: '#FF4D4F', border: '1px solid #FFCCC7' }"
>
<AlertTriangle :size="12" />
版本不一致!
</span>
</div>
</div>
<div class="flex items-center gap-2">
<span
v-if="item.needPhoto && item.completed && item.photos > 0"
class="px-3 py-1 rounded text-xs flex items-center gap-1"
:style="{ backgroundColor: '#E6F7FF', color: '#1890FF' }"
>
<Camera :size="12" />
已上传 {{ item.photos }}
</span>
<button
v-if="item.needPhoto && !item.completed"
class="px-3 py-1 rounded text-xs flex items-center gap-1"
:style="{ backgroundColor: '#1890FF', color: '#fff' }"
>
<Camera :size="12" />
拍照上传
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Action Bar -->
<div
class="flex items-center justify-end gap-3 p-4 bg-white rounded-lg sticky bottom-0"
:style="{ boxShadow: '0 -2px 8px rgba(0, 0, 0, 0.05)' }"
>
<button
class="px-6 py-2 rounded"
:style="{ border: '1px solid #D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }"
>
取消
</button>
<button
class="px-6 py-2 rounded"
:style="{ border: '1px solid #D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }"
>
更新
</button>
<button
class="px-6 py-2 rounded text-white"
:style="{ backgroundColor: '#1890FF' }"
>
提交
</button>
</div>
</div>
</template>

View File

@ -0,0 +1,141 @@
<script setup lang="ts">
import { Upload } from 'lucide-vue-next'
import { ref } from 'vue'
interface Firmware {
version: string
releaseDate: string
status: '已发布' | '草稿'
fileSize: string
downloads: number
md5?: string
sha256?: string
model?: string
hardwareRange?: string
upgradeType?: string
signed?: boolean
notes?: string[]
expanded: boolean
}
const firmwares = ref<Firmware[]>([
{
version: 'v2.3.0',
releaseDate: '2024-02-20',
status: '已发布',
fileSize: '12.5MB',
downloads: 1234,
md5: 'a1b2c3d4e5f6...',
sha256: '...',
model: 'GD30',
hardwareRange: 'A1-A3',
upgradeType: '可选',
signed: true,
notes: [
'修复OTA升级流程中的断点续传Bug',
'优化设备心跳上报频率',
'新增配置文件远程下发功能',
'增强安全性,升级前强制验证固件签名',
],
expanded: true,
},
{
version: 'v2.2.0',
releaseDate: '2024-01-15',
status: '已发布',
fileSize: '12.0MB',
downloads: 2456,
expanded: false,
},
{
version: 'v2.1.0',
releaseDate: '2023-12-01',
status: '已发布',
fileSize: '11.5MB',
downloads: 3587,
expanded: false,
},
])
</script>
<template>
<div class="p-6">
<!-- Page Header -->
<div class="mb-6">
<div class="flex items-center justify-between mb-2">
<h2 class="text-2xl font-semibold">固件库</h2>
<div class="flex items-center gap-3">
<button class="px-4 py-2 rounded text-white flex items-center gap-2" style="background-color: #1890FF">
<Upload :size="16" />
上传固件
</button>
<button class="px-4 py-2 rounded flex items-center gap-2" style="border: 1px solid #D9D9D9; color: rgba(0,0,0,0.85)">
发布固件
</button>
</div>
</div>
</div>
<!-- Firmware Version List -->
<div class="bg-white rounded-lg" style="box-shadow: 0 1px 2px rgba(0,0,0,0.05)">
<div class="p-6 border-b" style="border-color: #F0F0F0">
<h3 class="text-base font-medium">固件版本库</h3>
</div>
<div class="divide-y" style="border-color: #F0F0F0">
<div v-for="fw in firmwares" :key="fw.version" class="p-6">
<!-- Header row -->
<div class="flex items-center gap-4 mb-2">
<span class="text-lg font-semibold">版本: {{ fw.version }}</span>
<span class="text-sm" style="color: rgba(0,0,0,0.45)">发布日期: {{ fw.releaseDate }}</span>
<span class="text-sm" style="color: #52C41A">{{ fw.status }}</span>
</div>
<!-- Basic info -->
<div class="text-sm mb-1" style="color: rgba(0,0,0,0.65)">
文件大小: {{ fw.fileSize }}&emsp;下载次数: {{ fw.downloads.toLocaleString() }}
</div>
<!-- Expanded detail -->
<template v-if="fw.expanded && fw.md5">
<div class="text-sm mb-1" style="color: rgba(0,0,0,0.65)">
MD5: {{ fw.md5 }}&emsp;SHA256: {{ fw.sha256 }}
</div>
<div class="text-sm mb-3" style="color: rgba(0,0,0,0.65)">
适用型号: {{ fw.model }}&emsp;硬件版本范围: {{ fw.hardwareRange }}&emsp;升级类型: {{ fw.upgradeType }}
</div>
<!-- Signature -->
<div v-if="fw.signed" class="mb-3">
<span class="text-sm" style="color: #52C41A">数字签名: 已签名</span>
</div>
<!-- Release notes -->
<div v-if="fw.notes && fw.notes.length" class="mb-4">
<div class="text-sm font-medium mb-1" style="color: rgba(0,0,0,0.85)">发布说明:</div>
<ul class="text-sm list-disc pl-5" style="color: rgba(0,0,0,0.65)">
<li v-for="(note, i) in fw.notes" :key="i">{{ note }}</li>
</ul>
</div>
<!-- Actions (expanded) -->
<div class="flex items-center gap-6 pt-2">
<button class="text-sm" style="color: #1890FF">下载</button>
<button class="text-sm" style="color: #1890FF">编辑</button>
<button class="text-sm" style="color: #1890FF">撤回发布</button>
<button class="text-sm" style="color: #1890FF">查看升级任务</button>
</div>
</template>
<!-- Collapsed actions -->
<template v-else>
<div class="flex items-center gap-6 pt-3">
<button class="text-sm" style="color: #1890FF">下载</button>
<button class="text-sm" style="color: #1890FF" @click="fw.expanded = true">查看详情</button>
</div>
</template>
</div>
</div>
</div>
</div>
</template>

View File

@ -0,0 +1,165 @@
<script setup lang="ts">
import { ArrowLeft, Info } from 'lucide-vue-next'
import { useRouter } from 'vue-router'
import { ref, computed } from 'vue'
const router = useRouter()
const selectedModel = ref('GD-10 Supreme')
const expiryType = ref('永久')
const expiryDate = ref('')
interface AuthItem {
name: string
}
const authItems = ref<AuthItem[]>([
{ name: '1D SP' },
{ name: '2D SP' },
{ name: '3D SP' },
{ name: '1D VES' },
{ name: '2D ERT' },
{ name: '3D ERT' },
{ name: '1D IP'},
{ name: '2D IP' },
{ name: '3D IP'},
{ name: '跨孔Cross-Hole' },
{ name: '水上Marine'},
])
const availableItems = computed(() => {
return authItems.value.map(item => {
let available = false
return { ...item, available }
})
})
const checkedItems = ref(new Set<string>())
const toggleItem = (name: string) => {
if (checkedItems.value.has(name)) checkedItems.value.delete(name)
else checkedItems.value.add(name)
}
const selectAll = () => {
availableItems.value.forEach(item => {
if (item.available) checkedItems.value.add(item.name)
})
}
const clearAll = () => {
checkedItems.value.clear()
}
const selectedCount = computed(() => {
return availableItems.value.filter(i => i.available && checkedItems.value.has(i.name)).length
})
const totalAvailable = computed(() => {
return availableItems.value.filter(i => i.available).length
})
</script>
<template>
<div class="p-6">
<!-- Page Header -->
<div class="mb-6">
<div class="flex items-center gap-4 mb-2">
<button @click="router.go(-1)" class="p-2 rounded hover:bg-gray-100 transition-colors" style="color: rgba(0,0,0,0.65)">
<ArrowLeft :size="20" />
</button>
<h2 class="text-2xl font-semibold">选择授权项生成</h2>
</div>
<p class="text-sm ml-12" style="color: rgba(0,0,0,0.45)">选择设备型号和授权功能模块生成授权文件</p>
</div>
<!-- Info Banner -->
<div class="mb-6 p-4 rounded-lg flex items-start gap-3" style="background-color: #E6F7FF; border: 1px solid #91D5FF">
<Info :size="20" style="color: #1890FF; flex-shrink: 0; margin-top: 2px" />
<div style="color: #0050B3">
<div class="text-sm">
不同设备型号支持不同的授权功能模块选择型号后下方表格会显示该型号可用的授权项生成的授权文件将在设备APP激活时自动下载到对应设备
</div>
</div>
</div>
<!-- Model & Expiry Selection -->
<div class="bg-white p-6 rounded-lg mb-6" style="box-shadow: 0 1px 2px rgba(0,0,0,0.05)">
<h3 class="text-lg font-semibold mb-6">基本信息</h3>
<div class="grid grid-cols-3 gap-6">
<div>
<label class="block text-sm mb-2" style="color: rgba(0,0,0,0.85)">设备型号 <span style="color: #FF4D4F">*</span></label>
<select v-model="selectedModel" class="w-full px-3 py-2 border rounded" style="border-color: #D9D9D9; background-color: #fff" @change="clearAll()">
<option>GD-10 Supreme</option>
<option>GD-20 Supreme</option>
<option>GD-30 Supreme</option>
</select>
</div>
<div>
<label class="block text-sm mb-2" style="color: rgba(0,0,0,0.85)">授权有效期</label>
<select v-model="expiryType" class="w-full px-3 py-2 border rounded" style="border-color: #D9D9D9; background-color: #fff">
<option>永久</option>
<option>1</option>
<option>2</option>
<option>自定义</option>
</select>
</div>
<div v-if="expiryType === '自定义'">
<label class="block text-sm mb-2" style="color: rgba(0,0,0,0.85)">到期日期</label>
<input type="date" v-model="expiryDate" class="w-full px-3 py-2 border rounded" style="border-color: #D9D9D9" />
</div>
</div>
</div>
<!-- Authorization Items Table -->
<div class="bg-white rounded-lg mb-6" style="box-shadow: 0 1px 2px rgba(0,0,0,0.05)">
<div class="p-6 border-b flex items-center justify-between" style="border-color: #F0F0F0">
<div class="flex items-center gap-3">
<h3 class="text-lg font-semibold">功能授权项选择</h3>
<span class="text-sm" style="color: rgba(0,0,0,0.45)">已选 {{ selectedCount }} / {{ totalAvailable }} </span>
</div>
<div class="flex items-center gap-3">
<button class="text-sm" style="color: #1890FF" @click="selectAll">全选可用项</button>
<button class="text-sm" style="color: rgba(0,0,0,0.45)" @click="clearAll">清空</button>
</div>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead style="background-color: #FAFAFA">
<tr>
<th class="px-6 py-3 text-left text-sm font-medium" style="color: rgba(0,0,0,0.85); width: 150px">选择</th>
<th class="px-6 py-3 text-left text-sm font-medium" style="color: rgba(0,0,0,0.85)">授权项名称</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in availableItems" :key="index" class="border-b" style="border-color: #F0F0F0">
<td class="px-6 py-3">
<input
type="checkbox"
class="w-4 h-4"
style="accent-color: #1890FF"
:checked="checkedItems.has(item.name)"
:disabled="!item.available"
@change="toggleItem(item.name)"
/>
</td>
<td class="px-6 py-3 text-sm">{{ item.name }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Action Bar -->
<div class="flex items-center justify-end gap-3 p-4 bg-white rounded-lg sticky bottom-0" style="box-shadow: 0 -2px 8px rgba(0,0,0,0.05)">
<button class="px-6 py-2 rounded" style="border: 1px solid #D9D9D9; color: rgba(0,0,0,0.85)" @click="router.go(-1)">取消</button>
<button
class="px-6 py-2 rounded text-white"
:style="{ backgroundColor: selectedCount > 0 ? '#52C41A' : '#D9D9D9' }"
:disabled="selectedCount === 0"
@click="router.push('/licenses')"
>
生成授权文件{{ selectedCount }}
</button>
</div>
</div>
</template>

View File

@ -1,249 +0,0 @@
import { useState } from "react";
import { Plus, Upload, Download, Info } from "lucide-react";
interface License {
sn: string;
licenseId: string;
status: "已激活" | "已生成" | "待激活";
generationDate: string;
expiryDate: string;
}
export default function LicenseManagement() {
const licenses: License[] = [
{
sn: "GD30-2025-000001",
licenseId: "LIC-2025-0001",
status: "已激活",
generationDate: "2025-02-01",
expiryDate: "2026-02-01",
},
{
sn: "GT20-2025-000045",
licenseId: "LIC-2025-0045",
status: "已生成",
generationDate: "2025-02-05",
expiryDate: "2026-02-05",
},
{
sn: "GTXD-2025-000023",
licenseId: "LIC-2025-0023",
status: "待激活",
generationDate: "2025-02-08",
expiryDate: "2026-02-08",
},
];
const getStatusStyle = (status: License["status"]) => {
switch (status) {
case "已激活":
return {
backgroundColor: '#F6FFED',
color: '#52C41A',
border: '1px solid #B7EB8F'
};
case "已生成":
return {
backgroundColor: '#E6F7FF',
color: '#1890FF',
border: '1px solid #91D5FF'
};
case "待激活":
return {
backgroundColor: '#FFFBE6',
color: '#FAAD14',
border: '1px solid #FFE58F'
};
}
};
return (
<div className="p-6">
{/* Page Header */}
<div className="mb-6">
<div className="flex items-center justify-between mb-2">
<h2 className="text-2xl font-semibold"></h2>
<div className="flex items-center gap-3">
<button
className="px-4 py-2 rounded flex items-center gap-2"
style={{ border: '1px solid #D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }}
>
<Upload size={16} />
</button>
<button
className="px-4 py-2 rounded flex items-center gap-2"
style={{ border: '1px solid #D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }}
>
<Download size={16} />
</button>
<button
className="px-4 py-2 rounded text-white flex items-center gap-2"
style={{ backgroundColor: '#1890FF' }}
>
<Plus size={16} />
</button>
</div>
</div>
<p className="text-sm" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></p>
</div>
{/* Info Banner */}
<div
className="mb-6 p-4 rounded-lg flex items-start gap-3"
style={{ backgroundColor: '#E6FFFB', border: '1px solid #87E8DE' }}
>
<Info size={20} style={{ color: '#13C2C2', flexShrink: 0, marginTop: 2 }} />
<div style={{ color: '#006D75' }}>
<div className="font-medium mb-1"></div>
<div className="text-sm">
</div>
</div>
</div>
{/* Filter Card */}
<div className="bg-white p-6 rounded-lg mb-6" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<div className="grid grid-cols-4 gap-4">
<div>
<label className="block text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.65)' }}>
SN号
</label>
<input
type="text"
className="w-full px-3 py-2 border rounded"
style={{ borderColor: '#D9D9D9' }}
placeholder="输入设备SN号搜索"
/>
</div>
<div>
<label className="block text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.65)' }}>
</label>
<select
className="w-full px-3 py-2 border rounded"
style={{ borderColor: '#D9D9D9', backgroundColor: '#fff' }}
>
<option></option>
<option></option>
<option></option>
<option></option>
</select>
</div>
<div>
<label className="block text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.65)' }}>
</label>
<select
className="w-full px-3 py-2 border rounded"
style={{ borderColor: '#D9D9D9', backgroundColor: '#fff' }}
>
<option></option>
<option>GD30 </option>
<option>GT20 </option>
<option>GTXD </option>
</select>
</div>
<div className="flex items-end">
<button
className="w-full px-4 py-2 rounded text-white"
style={{ backgroundColor: '#1890FF' }}
>
</button>
</div>
</div>
</div>
{/* License List */}
<div className="bg-white rounded-lg mb-6" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<div className="overflow-x-auto">
<table className="w-full">
<thead style={{ backgroundColor: '#FAFAFA' }}>
<tr>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}>SN号</th>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}>ID</th>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}></th>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}></th>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}></th>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}></th>
</tr>
</thead>
<tbody>
{licenses.map((license, index) => (
<tr
key={index}
className="border-b"
style={{ borderColor: '#F0F0F0' }}
>
<td className="px-6 py-4">{license.sn}</td>
<td className="px-6 py-4" style={{ color: 'rgba(0, 0, 0, 0.65)' }}>{license.licenseId}</td>
<td className="px-6 py-4">
<span
className="px-2 py-1 rounded text-xs"
style={getStatusStyle(license.status)}
>
{license.status}
</span>
</td>
<td className="px-6 py-4" style={{ color: 'rgba(0, 0, 0, 0.65)' }}>{license.generationDate}</td>
<td className="px-6 py-4" style={{ color: 'rgba(0, 0, 0, 0.65)' }}>{license.expiryDate}</td>
<td className="px-6 py-4">
<div className="flex items-center gap-3">
<button className="text-sm" style={{ color: '#1890FF' }}></button>
<button className="text-sm" style={{ color: '#1890FF' }}></button>
<button className="text-sm" style={{ color: '#1890FF' }}></button>
<button className="text-sm" style={{ color: '#FF4D4F' }}></button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* Pagination */}
<div className="bg-white p-4 rounded-lg flex items-center justify-between" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<div className="text-sm" style={{ color: 'rgba(0, 0, 0, 0.65)' }}>
1-10 / 156
</div>
<div className="flex items-center gap-2">
<button
className="px-3 py-1 rounded border"
style={{ borderColor: '#D9D9D9', color: 'rgba(0, 0, 0, 0.45)' }}
disabled
>
</button>
<button
className="px-3 py-1 rounded"
style={{ backgroundColor: '#1890FF', color: '#fff' }}
>
1
</button>
<button
className="px-3 py-1 rounded border"
style={{ borderColor: '#D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }}
>
2
</button>
<button
className="px-3 py-1 rounded border"
style={{ borderColor: '#D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }}
>
3
</button>
<button
className="px-3 py-1 rounded border"
style={{ borderColor: '#D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }}
>
</button>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,251 @@
<script setup lang="ts">
import { Plus, Upload, Download, Info } from 'lucide-vue-next'
import { useRouter } from 'vue-router'
const router = useRouter()
interface License {
sn: string
licenseId: string
status: '已激活' | '已生成' | '待激活'
generationDate: string
expiryDate: string
}
const licenses: License[] = [
{
sn: 'GD30-2025-000001',
licenseId: 'LIC-2025-0001',
status: '已激活',
generationDate: '2025-02-01',
expiryDate: '2026-02-01',
},
{
sn: 'GT20-2025-000045',
licenseId: 'LIC-2025-0045',
status: '已生成',
generationDate: '2025-02-05',
expiryDate: '2026-02-05',
},
{
sn: 'GTXD-2025-000023',
licenseId: 'LIC-2025-0023',
status: '待激活',
generationDate: '2025-02-08',
expiryDate: '2026-02-08',
},
]
const getStatusStyle = (status: License['status']) => {
switch (status) {
case '已激活':
return {
backgroundColor: '#F6FFED',
color: '#52C41A',
border: '1px solid #B7EB8F',
}
case '已生成':
return {
backgroundColor: '#E6F7FF',
color: '#1890FF',
border: '1px solid #91D5FF',
}
case '待激活':
return {
backgroundColor: '#FFFBE6',
color: '#FAAD14',
border: '1px solid #FFE58F',
}
}
}
</script>
<template>
<div class="p-6">
<!-- Page Header -->
<div class="mb-6">
<div class="flex items-center justify-between mb-2">
<h2 class="text-2xl font-semibold">授权管理</h2>
<div class="flex items-center gap-3">
<button
class="px-4 py-2 rounded flex items-center gap-2"
:style="{ border: '1px solid #D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }"
>
<Upload :size="16" />
批量生成
</button>
<button
class="px-4 py-2 rounded flex items-center gap-2"
:style="{ border: '1px solid #D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }"
>
<Download :size="16" />
导出
</button>
<button
class="px-4 py-2 rounded text-white flex items-center gap-2"
:style="{ backgroundColor: '#1890FF' }"
@click="router.push('/licenses/generate')"
>
<Plus :size="16" />
选择授权项生成
</button>
</div>
</div>
<p class="text-sm" :style="{ color: 'rgba(0, 0, 0, 0.45)' }">管理设备授权文件</p>
</div>
<!-- Info Banner -->
<div
class="mb-6 p-4 rounded-lg flex items-start gap-3"
:style="{ backgroundColor: '#E6FFFB', border: '1px solid #87E8DE' }"
>
<Info :size="20" :style="{ color: '#13C2C2', flexShrink: 0, marginTop: '2px' }" />
<div :style="{ color: '#006D75' }">
<div class="font-medium mb-1">授权说明</div>
<div class="text-sm">
授权文件按型号关联点击选择授权项生成可勾选功能模块和有效期生产装配阶段有配置无授权授权在出厂阶段生成
</div>
</div>
</div>
<!-- Filter Card -->
<div class="bg-white p-6 rounded-lg mb-6" :style="{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }">
<div class="grid grid-cols-4 gap-4">
<div>
<label class="block text-sm mb-2" :style="{ color: 'rgba(0, 0, 0, 0.65)' }">
设备SN号
</label>
<input
type="text"
class="w-full px-3 py-2 border rounded"
:style="{ borderColor: '#D9D9D9' }"
placeholder="输入设备SN号搜索"
/>
</div>
<div>
<label class="block text-sm mb-2" :style="{ color: 'rgba(0, 0, 0, 0.65)' }">
授权状态
</label>
<select
class="w-full px-3 py-2 border rounded"
:style="{ borderColor: '#D9D9D9', backgroundColor: '#fff' }"
>
<option>全部</option>
<option>已激活</option>
<option>已生成</option>
<option>待激活</option>
</select>
</div>
<div>
<label class="block text-sm mb-2" :style="{ color: 'rgba(0, 0, 0, 0.65)' }">
设备型号
</label>
<select
class="w-full px-3 py-2 border rounded"
:style="{ borderColor: '#D9D9D9', backgroundColor: '#fff' }"
>
<option>全部</option>
<option>GD30 高密度电法仪</option>
<option>GT20 瞬变电磁仪</option>
<option>GM10 大地电磁仪</option>
</select>
</div>
<div class="flex items-end">
<button
class="w-full px-4 py-2 rounded text-white"
:style="{ backgroundColor: '#1890FF' }"
>
查询
</button>
</div>
</div>
</div>
<!-- License List -->
<div class="bg-white rounded-lg mb-6" :style="{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }">
<div class="overflow-x-auto">
<table class="w-full">
<thead :style="{ backgroundColor: '#FAFAFA' }">
<tr>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">设备SN号</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">授权ID</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">状态</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">生成日期</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">到期日期</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">操作</th>
</tr>
</thead>
<tbody>
<tr
v-for="(license, index) in licenses"
:key="index"
class="border-b"
:style="{ borderColor: '#F0F0F0' }"
>
<td class="px-6 py-4">{{ license.sn }}</td>
<td class="px-6 py-4" :style="{ color: 'rgba(0, 0, 0, 0.65)' }">{{ license.licenseId }}</td>
<td class="px-6 py-4">
<span
class="px-2 py-1 rounded text-xs"
:style="getStatusStyle(license.status)"
>
{{ license.status }}
</span>
</td>
<td class="px-6 py-4" :style="{ color: 'rgba(0, 0, 0, 0.65)' }">{{ license.generationDate }}</td>
<td class="px-6 py-4" :style="{ color: 'rgba(0, 0, 0, 0.65)' }">{{ license.expiryDate }}</td>
<td class="px-6 py-4">
<div class="flex items-center gap-3">
<button class="text-sm" :style="{ color: '#1890FF' }">详情</button>
<button class="text-sm" :style="{ color: '#1890FF' }">下载</button>
<button class="text-sm" :style="{ color: '#1890FF' }">续期</button>
<button class="text-sm" :style="{ color: '#FF4D4F' }">撤销</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Pagination -->
<div class="bg-white p-4 rounded-lg flex items-center justify-between" :style="{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }">
<div class="text-sm" :style="{ color: 'rgba(0, 0, 0, 0.65)' }">
显示 1-10 / 156
</div>
<div class="flex items-center gap-2">
<button
class="px-3 py-1 rounded border"
:style="{ borderColor: '#D9D9D9', color: 'rgba(0, 0, 0, 0.45)' }"
disabled
>
上一页
</button>
<button
class="px-3 py-1 rounded"
:style="{ backgroundColor: '#1890FF', color: '#fff' }"
>
1
</button>
<button
class="px-3 py-1 rounded border"
:style="{ borderColor: '#D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }"
>
2
</button>
<button
class="px-3 py-1 rounded border"
:style="{ borderColor: '#D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }"
>
3
</button>
<button
class="px-3 py-1 rounded border"
:style="{ borderColor: '#D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }"
>
下一页
</button>
</div>
</div>
</div>
</template>

View File

@ -1,310 +0,0 @@
import { ArrowLeft, Eye } from "lucide-react";
import { useNavigate } from "react-router";
import { useState } from "react";
export default function ParameterConfiguration() {
const navigate = useNavigate();
const [transmissionVoltage, setTransmissionVoltage] = useState("1500V");
const [transmissionCurrent, setTransmissionCurrent] = useState("10A");
const [pulseWidthOptions] = useState([
{ value: "0.25s", checked: true },
{ value: "0.5s", checked: true },
{ value: "1s", checked: true },
{ value: "2s", checked: true },
{ value: "4s", checked: true },
{ value: "8s", checked: true },
{ value: "16s", checked: true },
{ value: "32s", checked: true },
{ value: "64s", checked: true },
]);
const [waveformOptions] = useState([
{ value: "0+0-", checked: true },
{ value: "+0-0", checked: true },
{ value: "+-", checked: true },
]);
const [samplingRateOptions] = useState([
{ value: "50Hz", checked: true },
{ value: "60Hz", checked: true },
{ value: "100Hz", checked: true },
{ value: "1000Hz", checked: true },
]);
return (
<div className="p-6">
{/* Page Header */}
<div className="mb-6">
<div className="flex items-center gap-4 mb-2">
<button
onClick={() => navigate(-1)}
className="p-2 rounded hover:bg-gray-100 transition-colors"
style={{ color: 'rgba(0, 0, 0, 0.65)' }}
>
<ArrowLeft size={20} />
</button>
<h2 className="text-2xl font-semibold"></h2>
</div>
<p className="text-sm ml-12" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></p>
</div>
{/* Model Info Card */}
<div className="bg-white p-6 rounded-lg mb-6" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<h3 className="text-lg font-semibold mb-6"></h3>
<div className="grid grid-cols-3 gap-x-12 gap-y-6">
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div className="font-medium">GD30 </div>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div style={{ color: '#1890FF' }}>v1.2.0</div>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div style={{ color: 'rgba(0, 0, 0, 0.65)' }}>2024-03-01 10:30</div>
</div>
</div>
</div>
{/* Transmission Parameters Card */}
<div className="bg-white p-6 rounded-lg mb-6" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<h3 className="text-lg font-semibold mb-6"></h3>
<div className="grid grid-cols-2 gap-x-12 gap-y-6">
<div>
<label className="block text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.85)' }}>
</label>
<select
className="w-full px-3 py-2 border rounded"
style={{ borderColor: '#D9D9D9', backgroundColor: '#fff' }}
value={transmissionVoltage}
onChange={(e) => setTransmissionVoltage(e.target.value)}
>
<option>500V</option>
<option>800V</option>
<option>1000V</option>
<option>1200V</option>
<option>1500V</option>
</select>
</div>
<div>
<label className="block text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.85)' }}>
</label>
<select
className="w-full px-3 py-2 border rounded"
style={{ borderColor: '#D9D9D9', backgroundColor: '#fff' }}
value={transmissionCurrent}
onChange={(e) => setTransmissionCurrent(e.target.value)}
>
<option>2A</option>
<option>5A</option>
<option>8A</option>
<option>10A</option>
<option>15A</option>
</select>
</div>
<div className="col-span-2">
<label className="block text-sm mb-3" style={{ color: 'rgba(0, 0, 0, 0.85)' }}>
</label>
<div className="flex flex-wrap gap-2">
{pulseWidthOptions.map((option) => (
<label
key={option.value}
className="px-4 py-2 rounded border cursor-pointer transition-colors"
style={{
borderColor: option.checked ? '#1890FF' : '#D9D9D9',
backgroundColor: option.checked ? '#E6F7FF' : '#fff',
color: option.checked ? '#1890FF' : 'rgba(0, 0, 0, 0.65)'
}}
>
<input type="checkbox" className="hidden" checked={option.checked} readOnly />
{option.value}
</label>
))}
</div>
</div>
<div className="col-span-2">
<label className="block text-sm mb-3" style={{ color: 'rgba(0, 0, 0, 0.85)' }}>
</label>
<div className="flex flex-wrap gap-2">
{waveformOptions.map((option) => (
<label
key={option.value}
className="px-4 py-2 rounded border cursor-pointer transition-colors"
style={{
borderColor: option.checked ? '#1890FF' : '#D9D9D9',
backgroundColor: option.checked ? '#E6F7FF' : '#fff',
color: option.checked ? '#1890FF' : 'rgba(0, 0, 0, 0.65)'
}}
>
<input type="checkbox" className="hidden" checked={option.checked} readOnly />
{option.value}
</label>
))}
</div>
</div>
</div>
</div>
{/* Acquisition Parameters Card */}
<div className="bg-white p-6 rounded-lg mb-6" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<h3 className="text-lg font-semibold mb-6"></h3>
<div className="grid grid-cols-2 gap-x-12 gap-y-6">
<div className="col-span-2">
<label className="block text-sm mb-3" style={{ color: 'rgba(0, 0, 0, 0.85)' }}>
</label>
<div className="flex flex-wrap gap-2">
{samplingRateOptions.map((option) => (
<label
key={option.value}
className="px-4 py-2 rounded border cursor-pointer transition-colors"
style={{
borderColor: option.checked ? '#1890FF' : '#D9D9D9',
backgroundColor: option.checked ? '#E6F7FF' : '#fff',
color: option.checked ? '#1890FF' : 'rgba(0, 0, 0, 0.65)'
}}
>
<input type="checkbox" className="hidden" checked={option.checked} readOnly />
{option.value}
</label>
))}
</div>
</div>
<div>
<label className="block text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.85)' }}>
</label>
<input
type="number"
className="w-full px-3 py-2 border rounded"
style={{ borderColor: '#D9D9D9' }}
defaultValue="32"
/>
</div>
<div>
<label className="block text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.85)' }}>
</label>
<div className="flex items-center gap-2">
<input
type="number"
className="flex-1 px-3 py-2 border rounded"
style={{ borderColor: '#D9D9D9' }}
defaultValue="1"
/>
<span style={{ color: 'rgba(0, 0, 0, 0.45)' }}>-</span>
<input
type="number"
className="flex-1 px-3 py-2 border rounded"
style={{ borderColor: '#D9D9D9' }}
defaultValue="256"
/>
</div>
</div>
</div>
</div>
{/* Network Parameters Card */}
<div className="bg-white p-6 rounded-lg mb-6" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<h3 className="text-lg font-semibold mb-6"></h3>
<div className="grid grid-cols-2 gap-x-12 gap-y-6">
<div>
<label className="block text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.85)' }}>
WiFi SSID
</label>
<input
type="text"
className="w-full px-3 py-2 border rounded"
style={{ borderColor: '#D9D9D9' }}
defaultValue="GD30_Device"
/>
</div>
<div>
<label className="block text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.85)' }}>
WiFi密码
</label>
<input
type="password"
className="w-full px-3 py-2 border rounded"
style={{ borderColor: '#D9D9D9' }}
defaultValue="12345678"
/>
</div>
<div>
<label className="block text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.85)' }}>
IP地址
</label>
<input
type="text"
className="w-full px-3 py-2 border rounded"
style={{ borderColor: '#D9D9D9' }}
defaultValue="192.168.1.100"
/>
</div>
<div>
<label className="block text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.85)' }}>
</label>
<input
type="text"
className="w-full px-3 py-2 border rounded"
style={{ borderColor: '#D9D9D9' }}
defaultValue="255.255.255.0"
/>
</div>
<div>
<label className="block text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.85)' }}>
</label>
<input
type="text"
className="w-full px-3 py-2 border rounded"
style={{ borderColor: '#D9D9D9' }}
defaultValue="192.168.1.1"
/>
</div>
<div>
<label className="block text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.85)' }}>
DNS服务器
</label>
<input
type="text"
className="w-full px-3 py-2 border rounded"
style={{ borderColor: '#D9D9D9' }}
defaultValue="8.8.8.8"
/>
</div>
</div>
</div>
{/* Action Bar */}
<div
className="flex items-center justify-end gap-3 p-4 bg-white rounded-lg sticky bottom-0"
style={{ boxShadow: '0 -2px 8px rgba(0, 0, 0, 0.05)' }}
>
<button
className="px-6 py-2 rounded"
style={{ border: '1px solid #D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }}
>
</button>
<button
className="px-6 py-2 rounded flex items-center gap-2"
style={{ border: '1px solid #D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }}
>
<Eye size={16} />
</button>
<button
className="px-6 py-2 rounded text-white"
style={{ backgroundColor: '#1890FF' }}
>
</button>
</div>
</div>
);
}

View File

@ -0,0 +1,429 @@
<script setup lang="ts">
import { ArrowLeft, Eye } from 'lucide-vue-next'
import { useRouter, useRoute } from 'vue-router'
import { ref, computed } from 'vue'
const router = useRouter()
const route = useRoute()
const isNew = computed(() => route.params.configId === 'new')
const selectedModel = ref('GD30')
const configVersion = ref('v1.0.0')
const transmissionVoltage = ref('1500V')
const transmissionCurrent = ref('10A')
const voltageMeasureOptions = ref([
{ value: '±2.5V', checked: true },
{ value: '±80V', checked: true },
{ value: '±600V', checked: false },
])
const supportFullWaveform = ref(true)
const supportDuty50 = ref(true)
const supportDuty100 = ref(true)
const overvoltageProtection = ref(true)
const overvoltageThreshold = ref('1600')
const overcurrentProtection = ref(true)
const overcurrentThreshold = ref('12')
const shortCircuitProtection = ref(true)
const highTempProtection = ref(true)
const highTempThreshold = ref('75')
const pulseWidthOptions = ref([
{ value: '0.25s', checked: true },
{ value: '0.5s', checked: true },
{ value: '1s', checked: true },
{ value: '2s', checked: true },
{ value: '4s', checked: true },
{ value: '8s', checked: true },
{ value: '16s', checked: true },
{ value: '32s', checked: true },
{ value: '64s', checked: true },
])
const waveformOptions = ref([
{ value: '0+0-', checked: true },
{ value: '+0-0', checked: true },
{ value: '+-', checked: true },
])
const samplingRateOptions = ref([
{ value: '50Hz', checked: true },
{ value: '60Hz', checked: true },
{ value: '100Hz', checked: true },
{ value: '1000Hz', checked: true },
])
</script>
<template>
<div class="p-6">
<!-- Page Header -->
<div class="mb-6">
<div class="flex items-center gap-4 mb-2">
<button
@click="router.go(-1)"
class="p-2 rounded hover:bg-gray-100 transition-colors"
style="color: rgba(0, 0, 0, 0.65)"
>
<ArrowLeft :size="20" />
</button>
<h2 class="text-2xl font-semibold">{{ isNew ? '新建配置文件' : '参数配置' }}</h2>
</div>
<p class="text-sm ml-12" style="color: rgba(0, 0, 0, 0.45)">{{ isNew ? '选择设备型号和参数,生成配置文件' : '配置设备型号的详细参数' }}</p>
</div>
<!-- Model Info Card (new mode: editable, edit mode: readonly) -->
<div class="bg-white p-6 rounded-lg mb-6" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<h3 class="text-lg font-semibold mb-6">型号信息</h3>
<div class="grid grid-cols-3 gap-x-12 gap-y-6">
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">适配型号</label>
<select v-if="isNew" v-model="selectedModel" class="w-full px-3 py-2 border rounded" style="border-color: #D9D9D9; background-color: #fff">
<option value="GD30">GD30 高密度电法仪</option>
<option value="GT20">GT20 瞬变电磁仪</option>
<option value="GM10">GM10 大地电磁仪</option>
</select>
<div v-else class="font-medium">GD30 高密度电法仪</div>
</div>
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">配置文件版本</label>
<input v-if="isNew" type="text" v-model="configVersion" class="w-full px-3 py-2 border rounded" style="border-color: #D9D9D9" placeholder="如 v1.0.0" />
<div v-else style="color: #1890FF">v1.2.0</div>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">{{ isNew ? '状态' : '最后更新时间' }}</div>
<span v-if="isNew" class="px-2 py-1 rounded text-xs" style="background-color: #FFFBE6; color: #FAAD14; border: 1px solid #FFE58F">待生成</span>
<div v-else style="color: rgba(0, 0, 0, 0.65)">2024-03-01 10:30</div>
</div>
</div>
</div>
<!-- Transmission Parameters Card -->
<div class="bg-white p-6 rounded-lg mb-6" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<h3 class="text-lg font-semibold mb-6">发射参数</h3>
<div class="grid grid-cols-2 gap-x-12 gap-y-6">
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.85)">
最大发射电压
</label>
<select
class="w-full px-3 py-2 border rounded"
style="border-color: #D9D9D9; background-color: #fff"
v-model="transmissionVoltage"
>
<option>500V</option>
<option>800V</option>
<option>1000V</option>
<option>1200V</option>
<option>1500V</option>
</select>
</div>
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.85)">
最大发射电流
</label>
<select
class="w-full px-3 py-2 border rounded"
style="border-color: #D9D9D9; background-color: #fff"
v-model="transmissionCurrent"
>
<option>2A</option>
<option>5A</option>
<option>8A</option>
<option>10A</option>
<option>15A</option>
</select>
</div>
<div class="col-span-2">
<label class="block text-sm mb-3" style="color: rgba(0, 0, 0, 0.85)">
发射脉宽
</label>
<div class="flex flex-wrap gap-2">
<label
v-for="option in pulseWidthOptions"
:key="option.value"
class="px-4 py-2 rounded border cursor-pointer transition-colors"
:style="{
borderColor: option.checked ? '#1890FF' : '#D9D9D9',
backgroundColor: option.checked ? '#E6F7FF' : '#fff',
color: option.checked ? '#1890FF' : 'rgba(0, 0, 0, 0.65)',
}"
>
<input type="checkbox" class="hidden" :checked="option.checked" readonly />
{{ option.value }}
</label>
</div>
</div>
<div class="col-span-2">
<label class="block text-sm mb-3" style="color: rgba(0, 0, 0, 0.85)">
发射波形
</label>
<div class="flex flex-wrap gap-2">
<label
v-for="option in waveformOptions"
:key="option.value"
class="px-4 py-2 rounded border cursor-pointer transition-colors"
:style="{
borderColor: option.checked ? '#1890FF' : '#D9D9D9',
backgroundColor: option.checked ? '#E6F7FF' : '#fff',
color: option.checked ? '#1890FF' : 'rgba(0, 0, 0, 0.65)',
}"
>
<input type="checkbox" class="hidden" :checked="option.checked" readonly />
{{ option.value }}
</label>
</div>
</div>
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.85)">支持全波形测量</label>
<label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" v-model="supportFullWaveform" class="w-4 h-4" style="accent-color: #1890FF" />
<span style="color: rgba(0, 0, 0, 0.65)">{{ supportFullWaveform ? '是' : '否' }}</span>
</label>
</div>
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.85)">占空比支持</label>
<div class="flex items-center gap-6">
<label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" v-model="supportDuty50" class="w-4 h-4" style="accent-color: #1890FF" />
<span style="color: rgba(0, 0, 0, 0.65)">50% 占空比</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" v-model="supportDuty100" class="w-4 h-4" style="accent-color: #1890FF" />
<span style="color: rgba(0, 0, 0, 0.65)">100% 占空比</span>
</label>
</div>
</div>
</div>
</div>
<!-- Acquisition Parameters Card -->
<div class="bg-white p-6 rounded-lg mb-6" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<h3 class="text-lg font-semibold mb-6">采集参数</h3>
<div class="grid grid-cols-2 gap-x-12 gap-y-6">
<div class="col-span-2">
<label class="block text-sm mb-3" style="color: rgba(0, 0, 0, 0.85)">电压测量范围</label>
<div class="flex flex-wrap gap-2">
<label
v-for="option in voltageMeasureOptions"
:key="option.value"
class="px-4 py-2 rounded border cursor-pointer transition-colors"
:style="{
borderColor: option.checked ? '#1890FF' : '#D9D9D9',
backgroundColor: option.checked ? '#E6F7FF' : '#fff',
color: option.checked ? '#1890FF' : 'rgba(0, 0, 0, 0.65)',
}"
@click="option.checked = !option.checked"
>
{{ option.value }}
</label>
</div>
</div>
<div class="col-span-2">
<label class="block text-sm mb-3" style="color: rgba(0, 0, 0, 0.85)">
采样率
</label>
<div class="flex flex-wrap gap-2">
<label
v-for="option in samplingRateOptions"
:key="option.value"
class="px-4 py-2 rounded border cursor-pointer transition-colors"
:style="{
borderColor: option.checked ? '#1890FF' : '#D9D9D9',
backgroundColor: option.checked ? '#E6F7FF' : '#fff',
color: option.checked ? '#1890FF' : 'rgba(0, 0, 0, 0.65)',
}"
>
<input type="checkbox" class="hidden" :checked="option.checked" readonly />
{{ option.value }}
</label>
</div>
</div>
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.85)">
支持通道数
</label>
<input
type="number"
class="w-full px-3 py-2 border rounded"
style="border-color: #D9D9D9"
value="12"
/>
</div>
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.85)">
迭代次数范围
</label>
<div class="flex items-center gap-2">
<input
type="number"
class="flex-1 px-3 py-2 border rounded"
style="border-color: #D9D9D9"
value="1"
/>
<span style="color: rgba(0, 0, 0, 0.45)">-</span>
<input
type="number"
class="flex-1 px-3 py-2 border rounded"
style="border-color: #D9D9D9"
value="256"
/>
</div>
</div>
</div>
</div>
<!-- Protection Parameters Card -->
<div class="bg-white p-6 rounded-lg mb-6" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<h3 class="text-lg font-semibold mb-6">保护参数</h3>
<div class="grid grid-cols-2 gap-x-12 gap-y-6">
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.85)">过压保护</label>
<div class="flex items-center gap-4">
<label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" v-model="overvoltageProtection" class="w-4 h-4" style="accent-color: #1890FF" />
<span style="color: rgba(0, 0, 0, 0.65)">启用</span>
</label>
<input v-if="overvoltageProtection" type="number" class="w-32 px-3 py-1 border rounded text-sm" style="border-color: #D9D9D9" v-model="overvoltageThreshold" />
<span v-if="overvoltageProtection" class="text-sm" style="color: rgba(0, 0, 0, 0.45)">V</span>
</div>
</div>
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.85)">过流保护</label>
<div class="flex items-center gap-4">
<label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" v-model="overcurrentProtection" class="w-4 h-4" style="accent-color: #1890FF" />
<span style="color: rgba(0, 0, 0, 0.65)">启用</span>
</label>
<input v-if="overcurrentProtection" type="number" class="w-32 px-3 py-1 border rounded text-sm" style="border-color: #D9D9D9" v-model="overcurrentThreshold" />
<span v-if="overcurrentProtection" class="text-sm" style="color: rgba(0, 0, 0, 0.45)">A</span>
</div>
</div>
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.85)">短路保护</label>
<label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" v-model="shortCircuitProtection" class="w-4 h-4" style="accent-color: #1890FF" />
<span style="color: rgba(0, 0, 0, 0.65)">{{ shortCircuitProtection ? '已启用' : '未启用' }}</span>
</label>
</div>
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.85)">高温保护</label>
<div class="flex items-center gap-4">
<label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" v-model="highTempProtection" class="w-4 h-4" style="accent-color: #1890FF" />
<span style="color: rgba(0, 0, 0, 0.65)">启用</span>
</label>
<input v-if="highTempProtection" type="number" class="w-32 px-3 py-1 border rounded text-sm" style="border-color: #D9D9D9" v-model="highTempThreshold" />
<span v-if="highTempProtection" class="text-sm" style="color: rgba(0, 0, 0, 0.45)">°C</span>
</div>
</div>
</div>
</div>
<!-- Network Parameters Card -->
<div class="bg-white p-6 rounded-lg mb-6" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<h3 class="text-lg font-semibold mb-6">网络参数</h3>
<div class="grid grid-cols-2 gap-x-12 gap-y-6">
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.85)">WiFi SSID</label>
<input
type="text"
class="w-full px-3 py-2 border rounded"
style="border-color: #D9D9D9"
value="GD30_Device"
/>
</div>
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.85)">WiFi密码</label>
<input
type="password"
class="w-full px-3 py-2 border rounded"
style="border-color: #D9D9D9"
value="12345678"
/>
</div>
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.85)">默认IP地址</label>
<input
type="text"
class="w-full px-3 py-2 border rounded"
style="border-color: #D9D9D9"
value="192.168.1.100"
/>
</div>
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.85)">子网掩码</label>
<input
type="text"
class="w-full px-3 py-2 border rounded"
style="border-color: #D9D9D9"
value="255.255.255.0"
/>
</div>
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.85)">默认网关</label>
<input
type="text"
class="w-full px-3 py-2 border rounded"
style="border-color: #D9D9D9"
value="192.168.1.1"
/>
</div>
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.85)">DNS服务器</label>
<input
type="text"
class="w-full px-3 py-2 border rounded"
style="border-color: #D9D9D9"
value="8.8.8.8"
/>
</div>
</div>
</div>
<!-- Action Bar -->
<div
class="flex items-center justify-end gap-3 p-4 bg-white rounded-lg sticky bottom-0"
style="box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05)"
>
<button
class="px-6 py-2 rounded"
style="border: 1px solid #D9D9D9; color: rgba(0, 0, 0, 0.85)"
@click="router.go(-1)"
>
取消
</button>
<template v-if="isNew">
<button
class="px-6 py-2 rounded flex items-center gap-2"
style="border: 1px solid #D9D9D9; color: rgba(0, 0, 0, 0.85)"
>
<Eye :size="16" />
预览配置
</button>
<button
class="px-6 py-2 rounded text-white"
style="background-color: #52C41A"
@click="router.push('/config-files')"
>
确认生成配置文件
</button>
</template>
<template v-else>
<button
class="px-6 py-2 rounded"
style="border: 1px solid #D9D9D9; color: rgba(0, 0, 0, 0.85)"
>
重置默认
</button>
<button
class="px-6 py-2 rounded flex items-center gap-2"
style="border: 1px solid #D9D9D9; color: rgba(0, 0, 0, 0.85)"
>
<Eye :size="16" />
预览配置
</button>
<button
class="px-6 py-2 rounded text-white"
style="background-color: #1890FF"
>
下发配置
</button>
</template>
</div>
</div>
</template>

View File

@ -1,10 +0,0 @@
export default function PlaceholderPage({ title }: { title: string }) {
return (
<div className="p-6">
<h2 className="text-2xl font-semibold mb-4">{title}</h2>
<div className="bg-white p-6 rounded-lg" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<p style={{ color: 'rgba(0, 0, 0, 0.45)' }}>...</p>
</div>
</div>
);
}

View File

@ -0,0 +1,12 @@
<template>
<div class="p-6">
<h2 class="text-2xl font-semibold mb-4">{{ title }}</h2>
<div class="bg-white p-6 rounded-lg" style="box-shadow: 0 1px 2px rgba(0,0,0,0.05)">
<p style="color: rgba(0,0,0,0.45)">此页面正在开发中...</p>
</div>
</div>
</template>
<script setup lang="ts">
defineProps<{ title: string }>()
</script>

View File

@ -1,322 +0,0 @@
import { ArrowLeft, Clock, AlertTriangle, ArrowRight, Trash2 } from "lucide-react";
import { useNavigate } from "react-router";
import { useState } from "react";
export default function RepairOrderDetail() {
const navigate = useNavigate();
const [regenerateAuth, setRegenerateAuth] = useState(false);
const [pushFirmware, setPushFirmware] = useState(false);
const processingTimeline = [
{ time: "2024-03-08 09:00", operator: "张工程师", action: "创建工单", description: "客户报修,设备无法开机" },
{ time: "2024-03-08 10:30", operator: "李工程师", action: "故障诊断", description: "初步诊断为主控板故障" },
{ time: "2024-03-08 14:00", operator: "王工程师", action: "板卡更换", description: "更换主控板,测试正常" },
{ time: "2024-03-08 16:00", operator: "李工程师", action: "测试完成", description: "功能测试通过,等待客户确认" },
];
return (
<div className="p-6">
{/* Page Header */}
<div className="mb-6">
<div className="flex items-center gap-4 mb-2">
<button
onClick={() => navigate(-1)}
className="p-2 rounded hover:bg-gray-100 transition-colors"
style={{ color: 'rgba(0, 0, 0, 0.65)' }}
>
<ArrowLeft size={20} />
</button>
<h2 className="text-2xl font-semibold"></h2>
<span className="text-xl" style={{ color: 'rgba(0, 0, 0, 0.45)' }}>WO-2024-0001</span>
</div>
</div>
{/* Work Order Info Card */}
<div className="bg-white p-6 rounded-lg mb-6" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<h3 className="text-lg font-semibold mb-6"></h3>
<div className="grid grid-cols-3 gap-x-12 gap-y-6">
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}>ID</div>
<div>WO-2024-0001</div>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<span
className="inline-block px-2 py-1 rounded text-xs"
style={{ backgroundColor: '#E6F7FF', color: '#1890FF', border: '1px solid #91D5FF' }}
>
</span>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<span
className="inline-block px-2 py-1 rounded text-xs"
style={{ backgroundColor: '#FFF1F0', color: '#FF4D4F', border: '1px solid #FFCCC7' }}
>
</span>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div>2024-03-08 09:00</div>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div></div>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div>2024-03-09 18:00</div>
</div>
</div>
</div>
{/* Device Info Card */}
<div className="bg-white p-6 rounded-lg mb-6" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<h3 className="text-lg font-semibold mb-6"></h3>
<div className="grid grid-cols-3 gap-x-12 gap-y-6">
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}>SN号</div>
<div>GD30-2025-000001</div>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div>GD30 </div>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div>v2.3.5</div>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div></div>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div>010-12345678</div>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div>2025-02-01</div>
</div>
</div>
</div>
{/* Fault Info Card */}
<div className="bg-white p-6 rounded-lg mb-6" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<h3 className="text-lg font-semibold mb-6"></h3>
<div className="grid grid-cols-2 gap-x-12 gap-y-6">
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div></div>
</div>
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div></div>
</div>
<div className="col-span-2">
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div className="p-3 rounded" style={{ backgroundColor: '#FAFAFA', color: 'rgba(0, 0, 0, 0.65)' }}>
</div>
</div>
</div>
</div>
{/* Processing Timeline Card */}
<div className="bg-white p-6 rounded-lg mb-6" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<h3 className="text-lg font-semibold mb-6"></h3>
<div className="relative">
{/* Timeline line */}
<div
className="absolute left-6 top-6 bottom-6 w-0.5"
style={{ backgroundColor: '#F0F0F0' }}
></div>
<div className="space-y-6">
{processingTimeline.map((entry, index) => (
<div key={index} className="flex gap-4">
<div className="flex flex-col items-center flex-shrink-0">
<div
className="w-12 h-12 rounded-full flex items-center justify-center relative z-10"
style={{ backgroundColor: '#E6F7FF', border: '2px solid #1890FF' }}
>
<Clock size={20} style={{ color: '#1890FF' }} />
</div>
</div>
<div className="flex-1 pt-2">
<div className="flex items-center gap-3 mb-2">
<span className="font-medium">{entry.action}</span>
<span className="text-sm" style={{ color: 'rgba(0, 0, 0, 0.45)' }}>{entry.time}</span>
<span className="text-sm" style={{ color: 'rgba(0, 0, 0, 0.45)' }}>{entry.operator}</span>
</div>
<div className="text-sm" style={{ color: 'rgba(0, 0, 0, 0.65)' }}>
{entry.description}
</div>
</div>
</div>
))}
</div>
</div>
</div>
{/* Board Replacement Card */}
<div className="bg-white p-6 rounded-lg mb-6" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<h3 className="text-lg font-semibold mb-6"></h3>
<div className="p-4 rounded-lg" style={{ backgroundColor: '#FAFAFA' }}>
<div className="flex items-center justify-between">
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div className="font-medium"> MB20231215001</div>
</div>
<ArrowRight size={24} style={{ color: '#1890FF' }} />
<div>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div className="font-medium"> MB20240308001</div>
</div>
<div className="text-right">
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div style={{ color: 'rgba(0, 0, 0, 0.65)' }}>2024-03-08 14:00</div>
</div>
<div className="text-right">
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div></div>
</div>
</div>
</div>
</div>
{/* License Processing Card */}
<div className="bg-white p-6 rounded-lg mb-6" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<h3 className="text-lg font-semibold mb-6"></h3>
<div className="space-y-3">
<label className="flex items-center gap-3 cursor-pointer">
<input
type="checkbox"
className="w-5 h-5 rounded"
style={{ accentColor: '#1890FF' }}
checked={regenerateAuth}
onChange={(e) => setRegenerateAuth(e.target.checked)}
/>
<span></span>
</label>
<label className="flex items-center gap-3 cursor-pointer">
<input
type="checkbox"
className="w-5 h-5 rounded"
style={{ accentColor: '#1890FF' }}
checked={pushFirmware}
onChange={(e) => setPushFirmware(e.target.checked)}
/>
<span></span>
</label>
</div>
</div>
{/* Scrap Processing Card */}
<div className="bg-white p-6 rounded-lg mb-6" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<h3 className="text-lg font-semibold mb-6"></h3>
{/* Warning Banner */}
<div
className="mb-6 p-4 rounded-lg flex items-start gap-3"
style={{ backgroundColor: '#FFF1F0', border: '1px solid #FFCCC7' }}
>
<AlertTriangle size={20} style={{ color: '#FF4D4F', flexShrink: 0, marginTop: 2 }} />
<div>
<div className="font-medium" style={{ color: '#CF1322' }}></div>
<div className="text-sm mt-1" style={{ color: '#FF4D4F' }}>
使
</div>
</div>
</div>
<div className="grid grid-cols-2 gap-x-12 gap-y-6 mb-6">
<div>
<label className="block text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.85)' }}>
<span style={{ color: '#FF4D4F' }}>*</span>
</label>
<select
className="w-full px-3 py-2 border rounded"
style={{ borderColor: '#D9D9D9', backgroundColor: '#fff' }}
>
<option></option>
<option></option>
<option></option>
<option></option>
<option></option>
</select>
</div>
<div>
<label className="block text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.85)' }}>
()
</label>
<input
type="number"
className="w-full px-3 py-2 border rounded"
style={{ borderColor: '#D9D9D9' }}
placeholder="输入残值评估金额"
defaultValue="500"
/>
</div>
<div className="col-span-2">
<label className="block text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.85)' }}>
</label>
<div className="p-3 rounded border" style={{ borderColor: '#D9D9D9', backgroundColor: '#FAFAFA' }}>
<div className="flex flex-wrap gap-2">
<span className="px-3 py-1 rounded text-sm" style={{ backgroundColor: '#E6F7FF', color: '#1890FF' }}>
AC20240308002
</span>
<span className="px-3 py-1 rounded text-sm" style={{ backgroundColor: '#E6F7FF', color: '#1890FF' }}>
CT20240308003
</span>
<span className="px-3 py-1 rounded text-sm" style={{ backgroundColor: '#E6F7FF', color: '#1890FF' }}>
PS20240308004
</span>
</div>
</div>
</div>
</div>
{/* Scrap Process Flow */}
<div
className="p-4 rounded-lg flex items-center gap-3"
style={{ backgroundColor: '#E6F7FF', border: '1px solid #91D5FF' }}
>
<div className="text-sm" style={{ color: '#0050B3' }}>
<span className="font-medium"></span>
</div>
</div>
</div>
{/* Action Bar */}
<div
className="flex items-center justify-end gap-3 p-4 bg-white rounded-lg sticky bottom-0"
style={{ boxShadow: '0 -2px 8px rgba(0, 0, 0, 0.05)' }}
>
<button
className="px-6 py-2 rounded"
style={{ border: '1px solid #D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }}
>
</button>
<button
className="px-6 py-2 rounded text-white"
style={{ backgroundColor: '#1890FF' }}
>
</button>
<button
className="px-6 py-2 rounded flex items-center gap-2"
style={{ border: '1px solid #FF4D4F', color: '#FF4D4F' }}
>
<Trash2 size={16} />
</button>
</div>
</div>
);
}

View File

@ -0,0 +1,318 @@
<script setup lang="ts">
import { ArrowLeft, Clock, AlertTriangle, ArrowRight, Trash2 } from 'lucide-vue-next'
import { useRouter } from 'vue-router'
import { ref } from 'vue'
const router = useRouter()
const regenerateAuth = ref(false)
const pushFirmware = ref(false)
const processingTimeline = [
{ time: '2024-03-08 09:00', operator: '张工程师', action: '创建工单', description: '客户报修,设备无法开机' },
{ time: '2024-03-08 10:30', operator: '李工程师', action: '故障诊断', description: '初步诊断为主控板故障' },
{ time: '2024-03-08 14:00', operator: '王工程师', action: '板卡更换', description: '更换主控板,测试正常' },
{ time: '2024-03-08 16:00', operator: '李工程师', action: '测试完成', description: '功能测试通过,等待客户确认' },
]
</script>
<template>
<div class="p-6">
<!-- Page Header -->
<div class="mb-6">
<div class="flex items-center gap-4 mb-2">
<button
@click="router.go(-1)"
class="p-2 rounded hover:bg-gray-100 transition-colors"
style="color: rgba(0, 0, 0, 0.65)"
>
<ArrowLeft :size="20" />
</button>
<h2 class="text-2xl font-semibold">维修工单详情</h2>
<span class="text-xl" style="color: rgba(0, 0, 0, 0.45)">WO-2024-0001</span>
</div>
</div>
<!-- Work Order Info Card -->
<div class="bg-white p-6 rounded-lg mb-6" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<h3 class="text-lg font-semibold mb-6">工单信息</h3>
<div class="grid grid-cols-3 gap-x-12 gap-y-6">
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">工单ID</div>
<div>WO-2024-0001</div>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">工单状态</div>
<span
class="inline-block px-2 py-1 rounded text-xs"
style="background-color: #E6F7FF; color: #1890FF; border: 1px solid #91D5FF"
>
处理中
</span>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">优先级</div>
<span
class="inline-block px-2 py-1 rounded text-xs"
style="background-color: #FFF1F0; color: #FF4D4F; border: 1px solid #FFCCC7"
>
</span>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">创建时间</div>
<div>2024-03-08 09:00</div>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">负责人</div>
<div>李工程师</div>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">预计完成时间</div>
<div>2024-03-09 18:00</div>
</div>
</div>
</div>
<!-- Device Info Card -->
<div class="bg-white p-6 rounded-lg mb-6" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<h3 class="text-lg font-semibold mb-6">设备信息</h3>
<div class="grid grid-cols-3 gap-x-12 gap-y-6">
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">设备SN号</div>
<div>GD30-2025-000001</div>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">设备型号</div>
<div>GD30 地质探测仪</div>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">固件版本</div>
<div>v2.3.5</div>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">客户名称</div>
<div>北京地质研究院</div>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">联系方式</div>
<div>010-12345678</div>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">购买日期</div>
<div>2025-02-01</div>
</div>
</div>
</div>
<!-- Fault Info Card -->
<div class="bg-white p-6 rounded-lg mb-6" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<h3 class="text-lg font-semibold mb-6">故障信息</h3>
<div class="grid grid-cols-2 gap-x-12 gap-y-6">
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">故障类型</div>
<div>硬件故障</div>
</div>
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">故障症状</div>
<div>设备无法开机指示灯不亮</div>
</div>
<div class="col-span-2">
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">故障描述</div>
<div class="p-3 rounded" style="background-color: #FAFAFA; color: rgba(0, 0, 0, 0.65)">
设备在野外作业时突然关机之后无法重新启动检查电源连接正常充电器工作正常初步判断为主控板损坏
</div>
</div>
</div>
</div>
<!-- Processing Timeline Card -->
<div class="bg-white p-6 rounded-lg mb-6" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<h3 class="text-lg font-semibold mb-6">处理记录</h3>
<div class="relative">
<!-- Timeline line -->
<div
class="absolute left-6 top-6 bottom-6 w-0.5"
style="background-color: #F0F0F0"
></div>
<div class="space-y-6">
<div v-for="(entry, index) in processingTimeline" :key="index" class="flex gap-4">
<div class="flex flex-col items-center flex-shrink-0">
<div
class="w-12 h-12 rounded-full flex items-center justify-center relative z-10"
style="background-color: #E6F7FF; border: 2px solid #1890FF"
>
<Clock :size="20" style="color: #1890FF" />
</div>
</div>
<div class="flex-1 pt-2">
<div class="flex items-center gap-3 mb-2">
<span class="font-medium">{{ entry.action }}</span>
<span class="text-sm" style="color: rgba(0, 0, 0, 0.45)">{{ entry.time }}</span>
<span class="text-sm" style="color: rgba(0, 0, 0, 0.45)">操作人{{ entry.operator }}</span>
</div>
<div class="text-sm" style="color: rgba(0, 0, 0, 0.65)">
{{ entry.description }}
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Board Replacement Card -->
<div class="bg-white p-6 rounded-lg mb-6" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<h3 class="text-lg font-semibold mb-6">板卡更换记录</h3>
<div class="p-4 rounded-lg" style="background-color: #FAFAFA">
<div class="flex items-center justify-between">
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">旧板卡</div>
<div class="font-medium">主控板 MB20231215001</div>
</div>
<ArrowRight :size="24" style="color: #1890FF" />
<div>
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">新板卡</div>
<div class="font-medium">主控板 MB20240308001</div>
</div>
<div class="text-right">
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">更换时间</div>
<div style="color: rgba(0, 0, 0, 0.65)">2024-03-08 14:00</div>
</div>
<div class="text-right">
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">操作人</div>
<div>王工程师</div>
</div>
</div>
</div>
</div>
<!-- License Processing Card -->
<div class="bg-white p-6 rounded-lg mb-6" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<h3 class="text-lg font-semibold mb-6">授权处理</h3>
<div class="space-y-3">
<label class="flex items-center gap-3 cursor-pointer">
<input
type="checkbox"
class="w-5 h-5 rounded"
style="accent-color: #1890FF"
v-model="regenerateAuth"
/>
<span>重新生成授权文件板卡更换后需重新绑定授权</span>
</label>
<label class="flex items-center gap-3 cursor-pointer">
<input
type="checkbox"
class="w-5 h-5 rounded"
style="accent-color: #1890FF"
v-model="pushFirmware"
/>
<span>推送适配固件如更换板卡型号不同需推送兼容固件</span>
</label>
</div>
</div>
<!-- Scrap Processing Card -->
<div class="bg-white p-6 rounded-lg mb-6" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<h3 class="text-lg font-semibold mb-6">报废处理</h3>
<!-- Warning Banner -->
<div
class="mb-6 p-4 rounded-lg flex items-start gap-3"
style="background-color: #FFF1F0; border: 1px solid #FFCCC7"
>
<AlertTriangle :size="20" style="color: #FF4D4F; flex-shrink: 0; margin-top: 2px" />
<div>
<div class="font-medium" style="color: #CF1322">警告报废操作不可逆</div>
<div class="text-sm mt-1" style="color: #FF4D4F">
报废后设备将无法恢复使用请谨慎操作
</div>
</div>
</div>
<div class="grid grid-cols-2 gap-x-12 gap-y-6 mb-6">
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.85)">
报废原因 <span style="color: #FF4D4F">*</span>
</label>
<select
class="w-full px-3 py-2 border rounded"
style="border-color: #D9D9D9; background-color: #fff"
>
<option>请选择报废原因</option>
<option>主板损坏无法修复</option>
<option>多个核心部件损坏</option>
<option>维修成本超过设备价值</option>
<option>设备老化严重</option>
</select>
</div>
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.85)">
残值评估 ()
</label>
<input
type="number"
class="w-full px-3 py-2 border rounded"
style="border-color: #D9D9D9"
placeholder="输入残值评估金额"
value="500"
/>
</div>
<div class="col-span-2">
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.85)">
可回收物料
</label>
<div class="p-3 rounded border" style="border-color: #D9D9D9; background-color: #FAFAFA">
<div class="flex flex-wrap gap-2">
<span class="px-3 py-1 rounded text-sm" style="background-color: #E6F7FF; color: #1890FF">
采集板 AC20240308002
</span>
<span class="px-3 py-1 rounded text-sm" style="background-color: #E6F7FF; color: #1890FF">
测控板 CT20240308003
</span>
<span class="px-3 py-1 rounded text-sm" style="background-color: #E6F7FF; color: #1890FF">
电源模块 PS20240308004
</span>
</div>
</div>
</div>
</div>
<!-- Scrap Process Flow -->
<div
class="p-4 rounded-lg flex items-center gap-3"
style="background-color: #E6F7FF; border: 1px solid #91D5FF"
>
<div class="text-sm" style="color: #0050B3">
<span class="font-medium">报废流程</span>
申请报废 创建报废单 报废审批 物料回收 入库
</div>
</div>
</div>
<!-- Action Bar -->
<div
class="flex items-center justify-end gap-3 p-4 bg-white rounded-lg sticky bottom-0"
style="box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05)"
>
<button
class="px-6 py-2 rounded"
style="border: 1px solid #D9D9D9; color: rgba(0, 0, 0, 0.85)"
>
取消
</button>
<button
class="px-6 py-2 rounded text-white"
style="background-color: #1890FF"
>
关闭工单
</button>
<button
class="px-6 py-2 rounded flex items-center gap-2"
style="border: 1px solid #FF4D4F; color: #FF4D4F"
>
<Trash2 :size="16" />
申请报废
</button>
</div>
</div>
</template>

View File

@ -1,272 +0,0 @@
import { Info, Download, Upload, Link as LinkIcon } from "lucide-react";
interface ScrapDevice {
sn: string;
model: string;
scrapDate: string;
status: "待审批" | "已审批" | "已回收";
sourceOrder: string;
reason: string;
}
export default function ScrapManagement() {
const scrapDevices: ScrapDevice[] = [
{
sn: "GD30-2023-001234",
model: "GD30 地质探测仪",
scrapDate: "2024-03-01",
status: "已回收",
sourceOrder: "WO-2024-0001",
reason: "主板损坏无法修复",
},
{
sn: "GT20-2023-000567",
model: "GT20 物探仪",
scrapDate: "2024-03-05",
status: "已审批",
sourceOrder: "WO-2024-0025",
reason: "多个核心部件损坏",
},
{
sn: "GTXD-2023-000890",
model: "GTXD 探测仪",
scrapDate: "2024-03-08",
status: "待审批",
sourceOrder: "WO-2024-0048",
reason: "维修成本超过设备价值",
},
];
const getStatusStyle = (status: ScrapDevice["status"]) => {
switch (status) {
case "已回收":
return {
backgroundColor: '#F6FFED',
color: '#52C41A',
border: '1px solid #B7EB8F'
};
case "已审批":
return {
backgroundColor: '#E6F7FF',
color: '#1890FF',
border: '1px solid #91D5FF'
};
case "待审批":
return {
backgroundColor: '#FFFBE6',
color: '#FAAD14',
border: '1px solid #FFE58F'
};
}
};
return (
<div className="p-6">
{/* Page Header */}
<div className="mb-6">
<div className="flex items-center justify-between mb-2">
<h2 className="text-2xl font-semibold"></h2>
<div className="flex items-center gap-3">
<button
className="px-4 py-2 rounded flex items-center gap-2"
style={{ border: '1px solid #D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }}
>
<Upload size={16} />
</button>
<button
className="px-4 py-2 rounded flex items-center gap-2"
style={{ border: '1px solid #D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }}
>
<Download size={16} />
</button>
</div>
</div>
<p className="text-sm" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></p>
</div>
{/* Info Banner */}
<div
className="mb-6 p-4 rounded-lg flex items-start gap-3"
style={{ backgroundColor: '#E6F7FF', border: '1px solid #91D5FF' }}
>
<Info size={20} style={{ color: '#1890FF', flexShrink: 0, marginTop: 2 }} />
<div style={{ color: '#0050B3' }}>
<div className="font-medium mb-1"></div>
<div className="text-sm">
</div>
</div>
</div>
{/* Stat Cards */}
<div className="grid grid-cols-4 gap-6 mb-6">
<div className="bg-white p-6 rounded-lg" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div className="text-3xl font-semibold" style={{ color: 'rgba(0, 0, 0, 0.85)' }}>156</div>
</div>
<div className="bg-white p-6 rounded-lg" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div className="text-3xl font-semibold" style={{ color: '#FAAD14' }}>12</div>
</div>
<div className="bg-white p-6 rounded-lg" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div className="text-3xl font-semibold" style={{ color: '#1890FF' }}>8</div>
</div>
<div className="bg-white p-6 rounded-lg" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.45)' }}></div>
<div className="text-3xl font-semibold" style={{ color: '#52C41A' }}>136</div>
</div>
</div>
{/* Filter Card */}
<div className="bg-white p-6 rounded-lg mb-6" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<div className="grid grid-cols-4 gap-4">
<div>
<label className="block text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.65)' }}>
SN号
</label>
<input
type="text"
className="w-full px-3 py-2 border rounded"
style={{ borderColor: '#D9D9D9' }}
placeholder="输入设备SN号搜索"
/>
</div>
<div>
<label className="block text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.65)' }}>
</label>
<select
className="w-full px-3 py-2 border rounded"
style={{ borderColor: '#D9D9D9', backgroundColor: '#fff' }}
>
<option></option>
<option></option>
<option></option>
<option></option>
</select>
</div>
<div>
<label className="block text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.65)' }}>
</label>
<input
type="date"
className="w-full px-3 py-2 border rounded"
style={{ borderColor: '#D9D9D9' }}
/>
</div>
<div className="flex items-end">
<button
className="w-full px-4 py-2 rounded text-white"
style={{ backgroundColor: '#1890FF' }}
>
</button>
</div>
</div>
</div>
{/* Scrap Device List */}
<div className="bg-white rounded-lg mb-6" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<div className="p-6 border-b" style={{ borderColor: '#F0F0F0' }}>
<h3 className="text-lg font-semibold"></h3>
</div>
<div className="overflow-x-auto">
<table className="w-full">
<thead style={{ backgroundColor: '#FAFAFA' }}>
<tr>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}>SN号</th>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}></th>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}></th>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}></th>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}></th>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}></th>
<th className="px-6 py-3 text-left text-sm font-medium" style={{ color: 'rgba(0, 0, 0, 0.85)' }}></th>
</tr>
</thead>
<tbody>
{scrapDevices.map((device, index) => (
<tr
key={index}
className="border-b"
style={{ borderColor: '#F0F0F0' }}
>
<td className="px-6 py-4">{device.sn}</td>
<td className="px-6 py-4" style={{ color: 'rgba(0, 0, 0, 0.65)' }}>{device.model}</td>
<td className="px-6 py-4" style={{ color: 'rgba(0, 0, 0, 0.65)' }}>{device.scrapDate}</td>
<td className="px-6 py-4" style={{ color: 'rgba(0, 0, 0, 0.65)' }}>{device.reason}</td>
<td className="px-6 py-4">
<span
className="px-2 py-1 rounded text-xs"
style={getStatusStyle(device.status)}
>
{device.status}
</span>
</td>
<td className="px-6 py-4">
<button className="text-sm flex items-center gap-1" style={{ color: '#1890FF' }}>
<LinkIcon size={14} />
{device.sourceOrder}
</button>
</td>
<td className="px-6 py-4">
<div className="flex items-center gap-3">
<button className="text-sm" style={{ color: '#1890FF' }}></button>
<button className="text-sm" style={{ color: '#1890FF' }}></button>
{device.status === "已审批" && (
<button className="text-sm" style={{ color: '#52C41A' }}></button>
)}
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* Pagination */}
<div className="bg-white p-4 rounded-lg flex items-center justify-between" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
<div className="text-sm" style={{ color: 'rgba(0, 0, 0, 0.65)' }}>
1-10 / 156
</div>
<div className="flex items-center gap-2">
<button
className="px-3 py-1 rounded border"
style={{ borderColor: '#D9D9D9', color: 'rgba(0, 0, 0, 0.45)' }}
disabled
>
</button>
<button
className="px-3 py-1 rounded"
style={{ backgroundColor: '#1890FF', color: '#fff' }}
>
1
</button>
<button
className="px-3 py-1 rounded border"
style={{ borderColor: '#D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }}
>
2
</button>
<button
className="px-3 py-1 rounded border"
style={{ borderColor: '#D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }}
>
3
</button>
<button
className="px-3 py-1 rounded border"
style={{ borderColor: '#D9D9D9', color: 'rgba(0, 0, 0, 0.85)' }}
>
</button>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,238 @@
<script setup lang="ts">
import { Info, Download, Upload, Link as LinkIcon } from 'lucide-vue-next'
interface ScrapDevice {
sn: string
model: string
scrapDate: string
status: '待审批' | '已审批' | '已回收'
sourceOrder: string
reason: string
}
const scrapDevices: ScrapDevice[] = [
{
sn: 'GD30-2023-001234',
model: 'GD30 地质探测仪',
scrapDate: '2024-03-01',
status: '已回收',
sourceOrder: 'WO-2024-0001',
reason: '主板损坏无法修复',
},
{
sn: 'GT20-2023-000567',
model: 'GT20 物探仪',
scrapDate: '2024-03-05',
status: '已审批',
sourceOrder: 'WO-2024-0025',
reason: '多个核心部件损坏',
},
{
sn: 'GTXD-2023-000890',
model: 'GTXD 探测仪',
scrapDate: '2024-03-08',
status: '待审批',
sourceOrder: 'WO-2024-0048',
reason: '维修成本超过设备价值',
},
]
const getStatusStyle = (status: ScrapDevice['status']) => {
switch (status) {
case '已回收':
return {
backgroundColor: '#F6FFED',
color: '#52C41A',
border: '1px solid #B7EB8F',
}
case '已审批':
return {
backgroundColor: '#E6F7FF',
color: '#1890FF',
border: '1px solid #91D5FF',
}
case '待审批':
return {
backgroundColor: '#FFFBE6',
color: '#FAAD14',
border: '1px solid #FFE58F',
}
}
}
</script>
<template>
<div class="p-6">
<!-- Page Header -->
<div class="mb-6">
<div class="flex items-center justify-between mb-2">
<h2 class="text-2xl font-semibold">报废管理</h2>
<div class="flex items-center gap-3">
<button
class="px-4 py-2 rounded flex items-center gap-2"
style="border: 1px solid #D9D9D9; color: rgba(0, 0, 0, 0.85)"
>
<Upload :size="16" />
批量导入
</button>
<button
class="px-4 py-2 rounded flex items-center gap-2"
style="border: 1px solid #D9D9D9; color: rgba(0, 0, 0, 0.85)"
>
<Download :size="16" />
导出
</button>
</div>
</div>
<p class="text-sm" style="color: rgba(0, 0, 0, 0.45)">管理报废设备与物料回收</p>
</div>
<!-- Info Banner -->
<div
class="mb-6 p-4 rounded-lg flex items-start gap-3"
style="background-color: #E6F7FF; border: 1px solid #91D5FF"
>
<Info :size="20" style="color: #1890FF; flex-shrink: 0; margin-top: 2px" />
<div style="color: #0050B3">
<div class="font-medium mb-1">报废流程说明</div>
<div class="text-sm">
报废单由维修工单中申请报废创建关联来源维修工单报废设备需经过审批流程审批通过后进行物料回收和入库
</div>
</div>
</div>
<!-- Stat Cards -->
<div class="grid grid-cols-4 gap-6 mb-6">
<div class="bg-white p-6 rounded-lg" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">报废设备总数</div>
<div class="text-3xl font-semibold" style="color: rgba(0, 0, 0, 0.85)">156</div>
</div>
<div class="bg-white p-6 rounded-lg" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">待审批</div>
<div class="text-3xl font-semibold" style="color: #FAAD14">12</div>
</div>
<div class="bg-white p-6 rounded-lg" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">已审批待回收</div>
<div class="text-3xl font-semibold" style="color: #1890FF">8</div>
</div>
<div class="bg-white p-6 rounded-lg" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.45)">已回收入库</div>
<div class="text-3xl font-semibold" style="color: #52C41A">136</div>
</div>
</div>
<!-- Filter Card -->
<div class="bg-white p-6 rounded-lg mb-6" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<div class="grid grid-cols-4 gap-4">
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.65)">设备SN号</label>
<input
type="text"
class="w-full px-3 py-2 border rounded"
style="border-color: #D9D9D9"
placeholder="输入设备SN号搜索"
/>
</div>
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.65)">报废状态</label>
<select
class="w-full px-3 py-2 border rounded"
style="border-color: #D9D9D9; background-color: #fff"
>
<option>全部</option>
<option>待审批</option>
<option>已审批</option>
<option>已回收</option>
</select>
</div>
<div>
<label class="block text-sm mb-2" style="color: rgba(0, 0, 0, 0.65)">报废日期</label>
<input
type="date"
class="w-full px-3 py-2 border rounded"
style="border-color: #D9D9D9"
/>
</div>
<div class="flex items-end">
<button class="w-full px-4 py-2 rounded text-white" style="background-color: #1890FF">
查询
</button>
</div>
</div>
</div>
<!-- Scrap Device List -->
<div class="bg-white rounded-lg mb-6" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<div class="p-6 border-b" style="border-color: #F0F0F0">
<h3 class="text-lg font-semibold">报废设备列表</h3>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead style="background-color: #FAFAFA">
<tr>
<th class="px-6 py-3 text-left text-sm font-medium" style="color: rgba(0, 0, 0, 0.85)">设备SN号</th>
<th class="px-6 py-3 text-left text-sm font-medium" style="color: rgba(0, 0, 0, 0.85)">型号</th>
<th class="px-6 py-3 text-left text-sm font-medium" style="color: rgba(0, 0, 0, 0.85)">报废日期</th>
<th class="px-6 py-3 text-left text-sm font-medium" style="color: rgba(0, 0, 0, 0.85)">报废原因</th>
<th class="px-6 py-3 text-left text-sm font-medium" style="color: rgba(0, 0, 0, 0.85)">状态</th>
<th class="px-6 py-3 text-left text-sm font-medium" style="color: rgba(0, 0, 0, 0.85)">来源工单</th>
<th class="px-6 py-3 text-left text-sm font-medium" style="color: rgba(0, 0, 0, 0.85)">操作</th>
</tr>
</thead>
<tbody>
<tr
v-for="(device, index) in scrapDevices"
:key="index"
class="border-b"
style="border-color: #F0F0F0"
>
<td class="px-6 py-4">{{ device.sn }}</td>
<td class="px-6 py-4" style="color: rgba(0, 0, 0, 0.65)">{{ device.model }}</td>
<td class="px-6 py-4" style="color: rgba(0, 0, 0, 0.65)">{{ device.scrapDate }}</td>
<td class="px-6 py-4" style="color: rgba(0, 0, 0, 0.65)">{{ device.reason }}</td>
<td class="px-6 py-4">
<span class="px-2 py-1 rounded text-xs" :style="getStatusStyle(device.status)">
{{ device.status }}
</span>
</td>
<td class="px-6 py-4">
<button class="text-sm flex items-center gap-1" style="color: #1890FF">
<LinkIcon :size="14" />
{{ device.sourceOrder }}
</button>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-3">
<button class="text-sm" style="color: #1890FF">查看详情</button>
<button class="text-sm" style="color: #1890FF">物料检测</button>
<button v-if="device.status === '已审批'" class="text-sm" style="color: #52C41A">回收入库</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Pagination -->
<div
class="bg-white p-4 rounded-lg flex items-center justify-between"
style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)"
>
<div class="text-sm" style="color: rgba(0, 0, 0, 0.65)">显示 1-10 / 156 </div>
<div class="flex items-center gap-2">
<button
class="px-3 py-1 rounded border"
style="border-color: #D9D9D9; color: rgba(0, 0, 0, 0.45)"
disabled
>
上一页
</button>
<button class="px-3 py-1 rounded" style="background-color: #1890FF; color: #fff">1</button>
<button class="px-3 py-1 rounded border" style="border-color: #D9D9D9; color: rgba(0, 0, 0, 0.85)">2</button>
<button class="px-3 py-1 rounded border" style="border-color: #D9D9D9; color: rgba(0, 0, 0, 0.85)">3</button>
<button class="px-3 py-1 rounded border" style="border-color: #D9D9D9; color: rgba(0, 0, 0, 0.85)">下一页</button>
</div>
</div>
</div>
</template>

32
src/app/router.ts Normal file
View File

@ -0,0 +1,32 @@
import { createRouter, createWebHistory } from 'vue-router'
import Layout from './Layout.vue'
export const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
component: Layout,
children: [
{ path: '', component: () => import('./pages/Dashboard.vue') },
{ path: 'models', component: () => import('./pages/DeviceModelManagement.vue') },
{ path: 'devices', component: () => import('./pages/DeviceList.vue') },
{ path: 'devices/:id', component: () => import('./pages/DeviceDetail.vue') },
{ path: 'registration', component: () => import('./pages/DeviceRegistration.vue') },
{ path: 'licenses', component: () => import('./pages/LicenseManagement.vue') },
{ path: 'licenses/generate', component: () => import('./pages/LicenseGenerate.vue') },
{ path: 'activation', component: () => import('./pages/ActivationManagement.vue') },
{ path: 'calibration', component: () => import('./pages/CalibrationRecords.vue') },
{ path: 'repair/:orderId', component: () => import('./pages/RepairOrderDetail.vue') },
{ path: 'scrap', component: () => import('./pages/ScrapManagement.vue') },
{ path: 'firmware', component: () => import('./pages/FirmwareLibrary.vue') },
{ path: 'config-files', component: () => import('./pages/ConfigFileManagement.vue') },
{ path: 'config-files/:configId', component: () => import('./pages/ParameterConfiguration.vue') },
{ path: 'reports', component: () => import('./pages/PlaceholderPage.vue'), props: { title: '数据报表' } },
{ path: 'logs', component: () => import('./pages/PlaceholderPage.vue'), props: { title: '操作日志' } },
{ path: 'settings', component: () => import('./pages/PlaceholderPage.vue'), props: { title: '系统设置' } },
{ path: ':pathMatch(.*)*', component: () => import('./pages/PlaceholderPage.vue'), props: { title: '页面未找到' } },
],
},
],
})

View File

@ -1,40 +0,0 @@
import { createBrowserRouter } from "react-router";
import { Layout } from "./Layout";
import Dashboard from "./pages/Dashboard";
import DeviceModelManagement from "./pages/DeviceModelManagement";
import DeviceList from "./pages/DeviceList";
import DeviceRegistration from "./pages/DeviceRegistration";
import DeviceDetail from "./pages/DeviceDetail";
import LicenseManagement from "./pages/LicenseManagement";
import ActivationManagement from "./pages/ActivationManagement";
import CalibrationRecords from "./pages/CalibrationRecords";
import RepairOrderDetail from "./pages/RepairOrderDetail";
import ScrapManagement from "./pages/ScrapManagement";
import ConfigFileManagement from "./pages/ConfigFileManagement";
import ParameterConfiguration from "./pages/ParameterConfiguration";
import PlaceholderPage from "./pages/PlaceholderPage";
export const router = createBrowserRouter([
{
path: "/",
Component: Layout,
children: [
{ index: true, Component: Dashboard },
{ path: "models", Component: DeviceModelManagement },
{ path: "devices", Component: DeviceList },
{ path: "devices/:id", Component: DeviceDetail },
{ path: "registration", Component: DeviceRegistration },
{ path: "licenses", Component: LicenseManagement },
{ path: "activation", Component: ActivationManagement },
{ path: "calibration", Component: CalibrationRecords },
{ path: "repair/:orderId", Component: RepairOrderDetail },
{ path: "scrap", Component: ScrapManagement },
{ path: "config-files", Component: ConfigFileManagement },
{ path: "config-files/:configId", Component: ParameterConfiguration },
{ path: "reports", element: <PlaceholderPage title="数据报表" /> },
{ path: "logs", element: <PlaceholderPage title="操作日志" /> },
{ path: "settings", element: <PlaceholderPage title="系统设置" /> },
{ path: "*", element: <PlaceholderPage title="页面未找到" /> },
],
},
]);

6
src/env.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

6
src/main.ts Normal file
View File

@ -0,0 +1,6 @@
import { createApp } from 'vue'
import App from './app/App.vue'
import { router } from './app/router'
import './styles/index.css'
createApp(App).use(router).mount('#app')

View File

@ -1,7 +0,0 @@
import { createRoot } from "react-dom/client";
import App from "./app/App.tsx";
import "./styles/index.css";
createRoot(document.getElementById("root")!).render(<App />);

View File

@ -1,4 +1,2 @@
@import 'tailwindcss' source(none); @import 'tailwindcss' source(none);
@source '../**/*.{js,ts,jsx,tsx}'; @source '../**/*.{js,ts,vue}';
@import 'tw-animate-css';

View File

@ -1,22 +1,13 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import path from 'path' import path from 'path'
import tailwindcss from '@tailwindcss/vite' import tailwindcss from '@tailwindcss/vite'
import react from '@vitejs/plugin-react' import vue from '@vitejs/plugin-vue'
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [vue(), tailwindcss()],
// The React and Tailwind plugins are both required for Make, even if
// Tailwind is not being actively used do not remove them
react(),
tailwindcss(),
],
resolve: { resolve: {
alias: { alias: {
// Alias @ to the src directory
'@': path.resolve(__dirname, './src'), '@': path.resolve(__dirname, './src'),
}, },
}, },
// File types to support raw imports. Never add .css, .tsx, or .ts files to this.
assetsInclude: ['**/*.svg', '**/*.csv'],
}) })