16 changed files with 2314 additions and 3 deletions
@ -0,0 +1,497 @@ |
|||||||
|
// Extend the Array class
|
||||||
|
Array.prototype.max = function () { |
||||||
|
return Math.max.apply(null, this); |
||||||
|
}; |
||||||
|
Array.prototype.min = function () { |
||||||
|
return Math.min.apply(null, this); |
||||||
|
}; |
||||||
|
Array.prototype.mean = function () { |
||||||
|
var i, sum; |
||||||
|
for (i = 0, sum = 0; i < this.length; i++) |
||||||
|
sum += this[i]; |
||||||
|
return sum / this.length; |
||||||
|
}; |
||||||
|
Array.prototype.pip = function (x, y) { |
||||||
|
var i, j, c = false; |
||||||
|
for (i = 0, j = this.length - 1; i < this.length; j = i++) { |
||||||
|
if (((this[i][1] > y) != (this[j][1] > y)) && |
||||||
|
(x < (this[j][0] - this[i][0]) * (y - this[i][1]) / (this[j][1] - this[i][1]) + this[i][0])) { |
||||||
|
c = !c; |
||||||
|
} |
||||||
|
} |
||||||
|
return c; |
||||||
|
} |
||||||
|
|
||||||
|
var kriging = function () { |
||||||
|
var kriging = {}; |
||||||
|
|
||||||
|
var createArrayWithValues = function (value, n) { |
||||||
|
var array = []; |
||||||
|
for (var i = 0; i < n; i++) { |
||||||
|
array.push(value); |
||||||
|
} |
||||||
|
return array; |
||||||
|
}; |
||||||
|
|
||||||
|
// Matrix algebra
|
||||||
|
var kriging_matrix_diag = function (c, n) { |
||||||
|
var Z = createArrayWithValues(0, n * n); |
||||||
|
for (i = 0; i < n; i++) Z[i * n + i] = c; |
||||||
|
return Z; |
||||||
|
}; |
||||||
|
var kriging_matrix_transpose = function (X, n, m) { |
||||||
|
var i, j, Z = Array(m * n); |
||||||
|
for (i = 0; i < n; i++) |
||||||
|
for (j = 0; j < m; j++) |
||||||
|
Z[j * n + i] = X[i * m + j]; |
||||||
|
return Z; |
||||||
|
}; |
||||||
|
var kriging_matrix_scale = function (X, c, n, m) { |
||||||
|
var i, j; |
||||||
|
for (i = 0; i < n; i++) |
||||||
|
for (j = 0; j < m; j++) |
||||||
|
X[i * m + j] *= c; |
||||||
|
}; |
||||||
|
var kriging_matrix_add = function (X, Y, n, m) { |
||||||
|
var i, j, Z = Array(n * m); |
||||||
|
for (i = 0; i < n; i++) |
||||||
|
for (j = 0; j < m; j++) |
||||||
|
Z[i * m + j] = X[i * m + j] + Y[i * m + j]; |
||||||
|
return Z; |
||||||
|
}; |
||||||
|
// Naive matrix multiplication
|
||||||
|
var kriging_matrix_chol = function (X, n) { |
||||||
|
var i, j, k, sum, p = Array(n); |
||||||
|
for (i = 0; i < n; i++) p[i] = X[i * n + i]; |
||||||
|
for (i = 0; i < n; i++) { |
||||||
|
for (j = 0; j < i; j++) |
||||||
|
p[i] -= X[i * n + j] * X[i * n + j]; |
||||||
|
if (p[i] <= 0) return false; |
||||||
|
p[i] = Math.sqrt(p[i]); |
||||||
|
for (j = i + 1; j < n; j++) { |
||||||
|
for (k = 0; k < i; k++) |
||||||
|
X[j * n + i] -= X[j * n + k] * X[i * n + k]; |
||||||
|
X[j * n + i] /= p[i]; |
||||||
|
} |
||||||
|
} |
||||||
|
for (i = 0; i < n; i++) X[i * n + i] = p[i]; |
||||||
|
return true; |
||||||
|
}; |
||||||
|
// Cholesky decomposition
|
||||||
|
kriging_matrix_chol = function (X, n) { |
||||||
|
var i, j, k, sum, p = Array(n); |
||||||
|
for (i = 0; i < n; i++) p[i] = X[i * n + i]; |
||||||
|
for (i = 0; i < n; i++) { |
||||||
|
for (j = 0; j < i; j++) |
||||||
|
p[i] -= X[i * n + j] * X[i * n + j]; |
||||||
|
if (p[i] <= 0) return false; |
||||||
|
p[i] = Math.sqrt(p[i]); |
||||||
|
for (j = i + 1; j < n; j++) { |
||||||
|
for (k = 0; k < i; k++) |
||||||
|
X[j * n + i] -= X[j * n + k] * X[i * n + k]; |
||||||
|
X[j * n + i] /= p[i]; |
||||||
|
} |
||||||
|
} |
||||||
|
for (i = 0; i < n; i++) X[i * n + i] = p[i]; |
||||||
|
return true; |
||||||
|
}; |
||||||
|
// Inversion of cholesky decomposition
|
||||||
|
var kriging_matrix_chol2inv = function (X, n) { |
||||||
|
var i, j, k, sum; |
||||||
|
for (i = 0; i < n; i++) { |
||||||
|
X[i * n + i] = 1 / X[i * n + i]; |
||||||
|
for (j = i + 1; j < n; j++) { |
||||||
|
sum = 0; |
||||||
|
for (k = i; k < j; k++) |
||||||
|
sum -= X[j * n + k] * X[k * n + i]; |
||||||
|
X[j * n + i] = sum / X[j * n + j]; |
||||||
|
} |
||||||
|
} |
||||||
|
for (i = 0; i < n; i++) |
||||||
|
for (j = i + 1; j < n; j++) |
||||||
|
X[i * n + j] = 0; |
||||||
|
for (i = 0; i < n; i++) { |
||||||
|
X[i * n + i] *= X[i * n + i]; |
||||||
|
for (k = i + 1; k < n; k++) |
||||||
|
X[i * n + i] += X[k * n + i] * X[k * n + i]; |
||||||
|
for (j = i + 1; j < n; j++) |
||||||
|
for (k = j; k < n; k++) |
||||||
|
X[i * n + j] += X[k * n + i] * X[k * n + j]; |
||||||
|
} |
||||||
|
for (i = 0; i < n; i++) |
||||||
|
for (j = 0; j < i; j++) |
||||||
|
X[i * n + j] = X[j * n + i]; |
||||||
|
|
||||||
|
}; |
||||||
|
// Inversion via gauss-jordan elimination
|
||||||
|
var kriging_matrix_solve = function (X, n) { |
||||||
|
var m = n; |
||||||
|
var b = Array(n * n); |
||||||
|
var indxc = Array(n); |
||||||
|
var indxr = Array(n); |
||||||
|
var ipiv = Array(n); |
||||||
|
var i, icol, irow, j, k, l, ll; |
||||||
|
var big, dum, pivinv, temp; |
||||||
|
|
||||||
|
for (i = 0; i < n; i++) |
||||||
|
for (j = 0; j < n; j++) { |
||||||
|
if (i == j) b[i * n + j] = 1; |
||||||
|
else b[i * n + j] = 0; |
||||||
|
} |
||||||
|
for (j = 0; j < n; j++) ipiv[j] = 0; |
||||||
|
for (i = 0; i < n; i++) { |
||||||
|
big = 0; |
||||||
|
for (j = 0; j < n; j++) { |
||||||
|
if (ipiv[j] != 1) { |
||||||
|
for (k = 0; k < n; k++) { |
||||||
|
if (ipiv[k] == 0) { |
||||||
|
if (Math.abs(X[j * n + k]) >= big) { |
||||||
|
big = Math.abs(X[j * n + k]); |
||||||
|
irow = j; |
||||||
|
icol = k; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
++(ipiv[icol]); |
||||||
|
|
||||||
|
if (irow != icol) { |
||||||
|
for (l = 0; l < n; l++) { |
||||||
|
temp = X[irow * n + l]; |
||||||
|
X[irow * n + l] = X[icol * n + l]; |
||||||
|
X[icol * n + l] = temp; |
||||||
|
} |
||||||
|
for (l = 0; l < m; l++) { |
||||||
|
temp = b[irow * n + l]; |
||||||
|
b[irow * n + l] = b[icol * n + l]; |
||||||
|
b[icol * n + l] = temp; |
||||||
|
} |
||||||
|
} |
||||||
|
indxr[i] = irow; |
||||||
|
indxc[i] = icol; |
||||||
|
|
||||||
|
if (X[icol * n + icol] == 0) return false; // Singular
|
||||||
|
|
||||||
|
pivinv = 1 / X[icol * n + icol]; |
||||||
|
X[icol * n + icol] = 1; |
||||||
|
for (l = 0; l < n; l++) X[icol * n + l] *= pivinv; |
||||||
|
for (l = 0; l < m; l++) b[icol * n + l] *= pivinv; |
||||||
|
|
||||||
|
for (ll = 0; ll < n; ll++) { |
||||||
|
if (ll != icol) { |
||||||
|
dum = X[ll * n + icol]; |
||||||
|
X[ll * n + icol] = 0; |
||||||
|
for (l = 0; l < n; l++) X[ll * n + l] -= X[icol * n + l] * dum; |
||||||
|
for (l = 0; l < m; l++) b[ll * n + l] -= b[icol * n + l] * dum; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
for (l = (n - 1); l >= 0; l--) |
||||||
|
if (indxr[l] != indxc[l]) { |
||||||
|
for (k = 0; k < n; k++) { |
||||||
|
temp = X[k * n + indxr[l]]; |
||||||
|
X[k * n + indxr[l]] = X[k * n + indxc[l]]; |
||||||
|
X[k * n + indxc[l]] = temp; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
// Variogram models
|
||||||
|
var kriging_variogram_gaussian = function (h, nugget, range, sill, A) { |
||||||
|
return nugget + ((sill - nugget) / range) * |
||||||
|
(1.0 - Math.exp(-(1.0 / A) * Math.pow(h / range, 2))); |
||||||
|
}; |
||||||
|
var kriging_variogram_exponential = function (h, nugget, range, sill, A) { |
||||||
|
return nugget + ((sill - nugget) / range) * |
||||||
|
(1.0 - Math.exp(-(1.0 / A) * (h / range))); |
||||||
|
}; |
||||||
|
var kriging_variogram_spherical = function (h, nugget, range, sill, A) { |
||||||
|
if (h > range) return nugget + (sill - nugget) / range; |
||||||
|
return nugget + ((sill - nugget) / range) * |
||||||
|
(1.5 * (h / range) - 0.5 * Math.pow(h / range, 3)); |
||||||
|
}; |
||||||
|
|
||||||
|
// Train using gaussian processes with bayesian priors
|
||||||
|
kriging.train = function (t, x, y, model, sigma2, alpha) { |
||||||
|
var variogram = { |
||||||
|
t: t, |
||||||
|
x: x, |
||||||
|
y: y, |
||||||
|
nugget: 0.0, |
||||||
|
range: 0.0, |
||||||
|
sill: 0.0, |
||||||
|
A: 1 / 3, |
||||||
|
n: 0 |
||||||
|
}; |
||||||
|
switch (model) { |
||||||
|
case "gaussian": |
||||||
|
variogram.model = kriging_variogram_gaussian; |
||||||
|
break; |
||||||
|
case "exponential": |
||||||
|
variogram.model = kriging_variogram_exponential; |
||||||
|
break; |
||||||
|
case "spherical": |
||||||
|
variogram.model = kriging_variogram_spherical; |
||||||
|
break; |
||||||
|
}; |
||||||
|
|
||||||
|
// Lag distance/semivariance
|
||||||
|
var i, j, k, l, n = t.length; |
||||||
|
var distance = Array((n * n - n) / 2); |
||||||
|
for (i = 0, k = 0; i < n; i++) |
||||||
|
for (j = 0; j < i; j++, k++) { |
||||||
|
distance[k] = Array(2); |
||||||
|
distance[k][0] = Math.pow( |
||||||
|
Math.pow(x[i] - x[j], 2) + |
||||||
|
Math.pow(y[i] - y[j], 2), 0.5); |
||||||
|
distance[k][1] = Math.abs(t[i] - t[j]); |
||||||
|
} |
||||||
|
distance.sort(function (a, b) { return a[0] - b[0]; }); |
||||||
|
variogram.range = distance[(n * n - n) / 2 - 1][0]; |
||||||
|
|
||||||
|
// Bin lag distance
|
||||||
|
var lags = ((n * n - n) / 2) > 30 ? 30 : (n * n - n) / 2; |
||||||
|
var tolerance = variogram.range / lags; |
||||||
|
var lag = createArrayWithValues(0, lags); |
||||||
|
var semi = createArrayWithValues(0, lags); |
||||||
|
if (lags < 30) { |
||||||
|
for (l = 0; l < lags; l++) { |
||||||
|
lag[l] = distance[l][0]; |
||||||
|
semi[l] = distance[l][1]; |
||||||
|
} |
||||||
|
} |
||||||
|
else { |
||||||
|
for (i = 0, j = 0, k = 0, l = 0; i < lags && j < ((n * n - n) / 2); i++, k = 0) { |
||||||
|
while (distance[j][0] <= ((i + 1) * tolerance)) { |
||||||
|
lag[l] += distance[j][0]; |
||||||
|
semi[l] += distance[j][1]; |
||||||
|
j++; k++; |
||||||
|
if (j >= ((n * n - n) / 2)) break; |
||||||
|
} |
||||||
|
if (k > 0) { |
||||||
|
lag[l] /= k; |
||||||
|
semi[l] /= k; |
||||||
|
l++; |
||||||
|
} |
||||||
|
} |
||||||
|
if (l < 2) return variogram; // Error: Not enough points
|
||||||
|
} |
||||||
|
|
||||||
|
// Feature transformation
|
||||||
|
n = l; |
||||||
|
variogram.range = lag[n - 1] - lag[0]; |
||||||
|
var X = createArrayWithValues(1, 2 * n); |
||||||
|
var Y = Array(n); |
||||||
|
var A = variogram.A; |
||||||
|
for (i = 0; i < n; i++) { |
||||||
|
switch (model) { |
||||||
|
case "gaussian": |
||||||
|
X[i * 2 + 1] = 1.0 - Math.exp(-(1.0 / A) * Math.pow(lag[i] / variogram.range, 2)); |
||||||
|
break; |
||||||
|
case "exponential": |
||||||
|
X[i * 2 + 1] = 1.0 - Math.exp(-(1.0 / A) * lag[i] / variogram.range); |
||||||
|
break; |
||||||
|
case "spherical": |
||||||
|
X[i * 2 + 1] = 1.5 * (lag[i] / variogram.range) - |
||||||
|
0.5 * Math.pow(lag[i] / variogram.range, 3); |
||||||
|
break; |
||||||
|
}; |
||||||
|
Y[i] = semi[i]; |
||||||
|
} |
||||||
|
|
||||||
|
// Least squares
|
||||||
|
var Xt = kriging_matrix_transpose(X, n, 2); |
||||||
|
var Z = kriging_matrix_multiply(Xt, X, 2, n, 2); |
||||||
|
Z = kriging_matrix_add(Z, kriging_matrix_diag(1 / alpha, 2), 2, 2); |
||||||
|
var cloneZ = Z.slice(0); |
||||||
|
if (kriging_matrix_chol(Z, 2)) |
||||||
|
kriging_matrix_chol2inv(Z, 2); |
||||||
|
else { |
||||||
|
kriging_matrix_solve(cloneZ, 2); |
||||||
|
Z = cloneZ; |
||||||
|
} |
||||||
|
var W = kriging_matrix_multiply(kriging_matrix_multiply(Z, Xt, 2, 2, n), Y, 2, n, 1); |
||||||
|
|
||||||
|
// Variogram parameters
|
||||||
|
variogram.nugget = W[0]; |
||||||
|
variogram.sill = W[1] * variogram.range + variogram.nugget; |
||||||
|
variogram.n = x.length; |
||||||
|
|
||||||
|
// Gram matrix with prior
|
||||||
|
n = x.length; |
||||||
|
var K = Array(n * n); |
||||||
|
for (i = 0; i < n; i++) { |
||||||
|
for (j = 0; j < i; j++) { |
||||||
|
K[i * n + j] = variogram.model(Math.pow(Math.pow(x[i] - x[j], 2) + |
||||||
|
Math.pow(y[i] - y[j], 2), 0.5), |
||||||
|
variogram.nugget, |
||||||
|
variogram.range, |
||||||
|
variogram.sill, |
||||||
|
variogram.A); |
||||||
|
K[j * n + i] = K[i * n + j]; |
||||||
|
} |
||||||
|
K[i * n + i] = variogram.model(0, variogram.nugget, |
||||||
|
variogram.range, |
||||||
|
variogram.sill, |
||||||
|
variogram.A); |
||||||
|
} |
||||||
|
|
||||||
|
// Inverse penalized Gram matrix projected to target vector
|
||||||
|
var C = kriging_matrix_add(K, kriging_matrix_diag(sigma2, n), n, n); |
||||||
|
var cloneC = C.slice(0); |
||||||
|
if (kriging_matrix_chol(C, n)) |
||||||
|
kriging_matrix_chol2inv(C, n); |
||||||
|
else { |
||||||
|
kriging_matrix_solve(cloneC, n); |
||||||
|
C = cloneC; |
||||||
|
} |
||||||
|
|
||||||
|
// Copy unprojected inverted matrix as K
|
||||||
|
var K = C.slice(0); |
||||||
|
var M = kriging_matrix_multiply(C, t, n, n, 1); |
||||||
|
variogram.K = K; |
||||||
|
variogram.M = M; |
||||||
|
|
||||||
|
return variogram; |
||||||
|
}; |
||||||
|
|
||||||
|
// Model prediction
|
||||||
|
kriging.predict = function (x, y, variogram) { |
||||||
|
var i, k = Array(variogram.n); |
||||||
|
for (i = 0; i < variogram.n; i++) |
||||||
|
k[i] = variogram.model(Math.pow(Math.pow(x - variogram.x[i], 2) + |
||||||
|
Math.pow(y - variogram.y[i], 2), 0.5), |
||||||
|
variogram.nugget, variogram.range, |
||||||
|
variogram.sill, variogram.A); |
||||||
|
return kriging_matrix_multiply(k, variogram.M, 1, variogram.n, 1)[0]; |
||||||
|
}; |
||||||
|
kriging.variance = function (x, y, variogram) { |
||||||
|
var i, k = Array(variogram.n); |
||||||
|
for (i = 0; i < variogram.n; i++) |
||||||
|
k[i] = variogram.model(Math.pow(Math.pow(x - variogram.x[i], 2) + |
||||||
|
Math.pow(y - variogram.y[i], 2), 0.5), |
||||||
|
variogram.nugget, variogram.range, |
||||||
|
variogram.sill, variogram.A); |
||||||
|
return variogram.model(0, variogram.nugget, variogram.range, |
||||||
|
variogram.sill, variogram.A) + |
||||||
|
kriging_matrix_multiply(kriging_matrix_multiply(k, variogram.K, |
||||||
|
1, variogram.n, variogram.n), |
||||||
|
k, 1, variogram.n, 1)[0]; |
||||||
|
}; |
||||||
|
|
||||||
|
// Gridded matrices or contour paths
|
||||||
|
kriging.grid = function (polygons, variogram, width) { |
||||||
|
var i, j, k, n = polygons.length; |
||||||
|
if (n == 0) return; |
||||||
|
|
||||||
|
// Boundaries of polygons space
|
||||||
|
var xlim = [polygons[0][0][0], polygons[0][0][0]]; |
||||||
|
var ylim = [polygons[0][0][1], polygons[0][0][1]]; |
||||||
|
for (i = 0; i < n; i++) // Polygons
|
||||||
|
for (j = 0; j < polygons[i].length; j++) { // Vertices
|
||||||
|
if (polygons[i][j][0] < xlim[0]) |
||||||
|
xlim[0] = polygons[i][j][0]; |
||||||
|
if (polygons[i][j][0] > xlim[1]) |
||||||
|
xlim[1] = polygons[i][j][0]; |
||||||
|
if (polygons[i][j][1] < ylim[0]) |
||||||
|
ylim[0] = polygons[i][j][1]; |
||||||
|
if (polygons[i][j][1] > ylim[1]) |
||||||
|
ylim[1] = polygons[i][j][1]; |
||||||
|
} |
||||||
|
|
||||||
|
// Alloc for O(n^2) space
|
||||||
|
var xtarget, ytarget; |
||||||
|
var a = Array(2), b = Array(2); |
||||||
|
var lxlim = Array(2); // Local dimensions
|
||||||
|
var lylim = Array(2); // Local dimensions
|
||||||
|
var x = Math.ceil((xlim[1] - xlim[0]) / width); |
||||||
|
var y = Math.ceil((ylim[1] - ylim[0]) / width); |
||||||
|
|
||||||
|
var A = Array(x + 1); |
||||||
|
for (i = 0; i <= x; i++) A[i] = Array(y + 1); |
||||||
|
for (i = 0; i < n; i++) { |
||||||
|
// Range for polygons[i]
|
||||||
|
lxlim[0] = polygons[i][0][0]; |
||||||
|
lxlim[1] = lxlim[0]; |
||||||
|
lylim[0] = polygons[i][0][1]; |
||||||
|
lylim[1] = lylim[0]; |
||||||
|
for (j = 1; j < polygons[i].length; j++) { // Vertices
|
||||||
|
if (polygons[i][j][0] < lxlim[0]) |
||||||
|
lxlim[0] = polygons[i][j][0]; |
||||||
|
if (polygons[i][j][0] > lxlim[1]) |
||||||
|
lxlim[1] = polygons[i][j][0]; |
||||||
|
if (polygons[i][j][1] < lylim[0]) |
||||||
|
lylim[0] = polygons[i][j][1]; |
||||||
|
if (polygons[i][j][1] > lylim[1]) |
||||||
|
lylim[1] = polygons[i][j][1]; |
||||||
|
} |
||||||
|
|
||||||
|
// Loop through polygon subspace
|
||||||
|
a[0] = Math.floor(((lxlim[0] - ((lxlim[0] - xlim[0]) % width)) - xlim[0]) / width); |
||||||
|
a[1] = Math.ceil(((lxlim[1] - ((lxlim[1] - xlim[1]) % width)) - xlim[0]) / width); |
||||||
|
b[0] = Math.floor(((lylim[0] - ((lylim[0] - ylim[0]) % width)) - ylim[0]) / width); |
||||||
|
b[1] = Math.ceil(((lylim[1] - ((lylim[1] - ylim[1]) % width)) - ylim[0]) / width); |
||||||
|
for (j = a[0]; j <= a[1]; j++) |
||||||
|
for (k = b[0]; k <= b[1]; k++) { |
||||||
|
xtarget = xlim[0] + j * width; |
||||||
|
ytarget = ylim[0] + k * width; |
||||||
|
if (polygons[i].pip(xtarget, ytarget)) |
||||||
|
A[j][k] = kriging.predict(xtarget, |
||||||
|
ytarget, |
||||||
|
variogram); |
||||||
|
} |
||||||
|
} |
||||||
|
A.xlim = xlim; |
||||||
|
A.ylim = ylim; |
||||||
|
A.zlim = [variogram.t.min(), variogram.t.max()]; |
||||||
|
A.width = width; |
||||||
|
return A; |
||||||
|
}; |
||||||
|
kriging.contour = function (value, polygons, variogram) { |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
// Plotting on the DOM
|
||||||
|
kriging.plot = function (canvas, grid, xlim, ylim, colors) { |
||||||
|
// Clear screen
|
||||||
|
var ctx = canvas.getContext("2d"); |
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height); |
||||||
|
|
||||||
|
// Starting boundaries
|
||||||
|
var range = [xlim[1] - xlim[0], ylim[1] - ylim[0], grid.zlim[1] - grid.zlim[0]]; |
||||||
|
var i, j, x, y, z; |
||||||
|
var n = grid.length; |
||||||
|
var m = grid[0].length; |
||||||
|
var wx = Math.ceil(grid.width * canvas.width / (xlim[1] - xlim[0])); |
||||||
|
var wy = Math.ceil(grid.width * canvas.height / (ylim[1] - ylim[0])); |
||||||
|
for (i = 0; i < n; i++) |
||||||
|
for (j = 0; j < m; j++) { |
||||||
|
if (grid[i][j] == undefined) continue; |
||||||
|
x = canvas.width * (i * grid.width + grid.xlim[0] - xlim[0]) / range[0]; |
||||||
|
y = canvas.height * (1 - (j * grid.width + grid.ylim[0] - ylim[0]) / range[1]); |
||||||
|
z = (grid[i][j] - grid.zlim[0]) / range[2]; |
||||||
|
if (z < 0.0) z = 0.0; |
||||||
|
if (z > 1.0) z = 1.0; |
||||||
|
|
||||||
|
ctx.fillStyle = colors[Math.floor((colors.length - 1) * z)]; |
||||||
|
ctx.fillRect(Math.round(x - wx / 2), Math.round(y - wy / 2), wx, wy); |
||||||
|
} |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
return kriging; |
||||||
|
}(); |
||||||
|
|
||||||
|
// 修改为支持两种模块系统的写法
|
||||||
|
if (typeof module !== 'undefined' && module.exports) { |
||||||
|
module.exports = kriging; |
||||||
|
} else if (typeof define === 'function' && define.amd) { |
||||||
|
define(function () { return kriging; }); |
||||||
|
} |
||||||
|
|
||||||
|
// 添加 ES6 默认导出
|
||||||
|
export default kriging; |
||||||
@ -0,0 +1,321 @@ |
|||||||
|
<template> |
||||||
|
<div class="app-container app-container-bg"> |
||||||
|
<el-card class="first-card" ref='firstCard' shadow="always"> |
||||||
|
<el-form :model="queryParams" ref="queryForm" :inline="true" @submit.native.prevent> |
||||||
|
<el-form-item label="测站名称" prop="stnmId"> |
||||||
|
<ESelectSingle ref="eSelectSingle" :stationType="stationType" @stationChange="handleStationChange" @loadingChange="handleStationLoading" /> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="开始时间"> |
||||||
|
<el-date-picker v-model="queryParams.startTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss" placeholder="选择开始时间" :disabled-date="disabledStartDate"> |
||||||
|
</el-date-picker> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="结束时间"> |
||||||
|
<el-date-picker v-model="queryParams.endTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="选择结束时间" :disabled-date="disabledEndDate"> |
||||||
|
</el-date-picker> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item> |
||||||
|
<el-button type="primary" icon="Search" @click="handleQuery">查询</el-button> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item class="classification"> |
||||||
|
<div style="margin: -6px 0 0 0">设备故障: <span class="red">{{gzCount}}</span></div> |
||||||
|
<div style="margin: -5px 0 0 0">数据异常: <span class="red">{{ycCount}}</span></div> |
||||||
|
</el-form-item> |
||||||
|
</el-form> |
||||||
|
</el-card> |
||||||
|
<splitpanes horizontal class="el-card-p card-shadow carder-border mt10 pad10 default-theme container-box" :push-other-panes="false"> |
||||||
|
<pane size="70" ref="firstPane" class="mr10"> |
||||||
|
<splitpanes :horizontal="device === 'mobile'" class=" default-theme" :push-other-panes="false"> |
||||||
|
<pane :size="100 - firstSize" class="mr10"> |
||||||
|
<div v-table-height='{bottom:0}' style="width:100%;"> |
||||||
|
<TimeBarChart v-loading="echartsLoading" :legendData='legendData' :xAxisData="xAxisData" :seriesData="seriesData" :textTitle="textTitle" :unit="unit" :echartType="echartType" /> |
||||||
|
</div> |
||||||
|
</pane> |
||||||
|
<pane :size="firstSize" :min-size="SPLITPANES_CONFIG.MIN_SIZE" :max-size="SPLITPANES_CONFIG.MAX_SIZE" ref="firstPane"> |
||||||
|
<div style="display:flex;align-items: center;" class="mb20"> |
||||||
|
<div class="ml10">主设备:<span>{{zsbStcd}}</span></div> |
||||||
|
</div> |
||||||
|
<el-table min-height="500" v-loading="rightLoading" :data="tableData1" border style="height:88%;overflow: auto;"> |
||||||
|
<el-table-column prop="tm" label="时间" width="180" :align="alignment"> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column prop="value" label="数值" :align="alignment"></el-table-column> |
||||||
|
</el-table> |
||||||
|
</pane> |
||||||
|
</splitpanes> |
||||||
|
</pane> |
||||||
|
<pane size="30"> |
||||||
|
<el-table v-table-height v-loading="loading" :data="alarmList" border> |
||||||
|
<el-table-column label="测站类型" :align="alignment" prop="type" /> |
||||||
|
<el-table-column label="预警类型" :align="alignment" prop="alarmType" /> |
||||||
|
<el-table-column label="预警级别" :align="alignment" prop="alarmLevel" /> |
||||||
|
<el-table-column label="预警内容" :align="alignment" prop="alarmContent" width="600" /> |
||||||
|
<el-table-column label="预警时间" :align="alignment" prop="alarmTime" /> |
||||||
|
<el-table-column label="预警时长(时)" :align="alignment" prop="alarmTotalLength" /> |
||||||
|
<el-table-column label="预警状态" :align="alignment" prop="flag"> |
||||||
|
<template #default="scope"> |
||||||
|
<span v-if="scope.row.flag == 0 " class="red">预警中</span> |
||||||
|
<span v-if="scope.row.flag == 1 " class="green">预警结束</span> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column label="是否处理" :align="alignment" prop="isDeal"> |
||||||
|
<template #default="scope"> |
||||||
|
<span v-if="scope.row.isDeal == 'n' " class="red">否</span> |
||||||
|
<span v-if="scope.row.isDeal == 'y' " class="green">是</span> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
</el-table> |
||||||
|
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" /> |
||||||
|
</pane> |
||||||
|
</splitpanes> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
<script setup> |
||||||
|
import { ref, reactive, watch, toRaw, computed, onMounted } from 'vue' |
||||||
|
import dayjs from 'dayjs'; |
||||||
|
import ESelectSingle from '@/components/ESelectSingle/index.vue' |
||||||
|
import TimeBarChart from '@/components/ChartsTimeBar/index.vue' |
||||||
|
|
||||||
|
import { |
||||||
|
Splitpanes, |
||||||
|
Pane |
||||||
|
} from 'splitpanes' |
||||||
|
import 'splitpanes/dist/splitpanes.css' |
||||||
|
import useAppStore from '@/store/modules/app' |
||||||
|
const device = computed(() => useAppStore().device); |
||||||
|
|
||||||
|
|
||||||
|
const props = defineProps({ |
||||||
|
stationType: { |
||||||
|
type: String, |
||||||
|
default: 'A' |
||||||
|
}, |
||||||
|
requestSuffix: { |
||||||
|
type: String, |
||||||
|
default: '/historydata' |
||||||
|
}, |
||||||
|
fixed: { |
||||||
|
type: Number, |
||||||
|
default: 1 |
||||||
|
} |
||||||
|
|
||||||
|
}) |
||||||
|
|
||||||
|
const { |
||||||
|
proxy |
||||||
|
} = getCurrentInstance() |
||||||
|
const requestPrefix = '/history/data' |
||||||
|
const alignment = 'center' |
||||||
|
|
||||||
|
const textTitle = computed(() => { |
||||||
|
switch (props.stationType) { |
||||||
|
case 'A': |
||||||
|
return '雨量过程线'; |
||||||
|
case 'B': |
||||||
|
return '水位过程线'; |
||||||
|
case 'C': |
||||||
|
return '水位过程线'; |
||||||
|
case 'D': |
||||||
|
return '潮位过程线'; |
||||||
|
case 'E': |
||||||
|
return '流量过程线'; |
||||||
|
} |
||||||
|
}); |
||||||
|
const unit = computed(() => { |
||||||
|
switch (props.stationType) { |
||||||
|
case 'A': |
||||||
|
return 'mm'; |
||||||
|
case 'B': |
||||||
|
return 'm'; |
||||||
|
case 'C': |
||||||
|
return 'm'; |
||||||
|
case 'D': |
||||||
|
return 'm'; |
||||||
|
case 'E': |
||||||
|
return 'm³/s'; |
||||||
|
} |
||||||
|
}); |
||||||
|
const echartType = computed(() => { |
||||||
|
switch (props.stationType) { |
||||||
|
case 'A': |
||||||
|
return 'bar'; |
||||||
|
case 'B': |
||||||
|
return 'line'; |
||||||
|
case 'C': |
||||||
|
return 'line'; |
||||||
|
case 'D': |
||||||
|
return 'line'; |
||||||
|
case 'E': |
||||||
|
return 'line'; |
||||||
|
} |
||||||
|
}); |
||||||
|
const firstSize = ref(proxy.SPLITPANES_CONFIG.DEFAULT_SIZE) |
||||||
|
|
||||||
|
const eSelectSingle = ref(null) |
||||||
|
|
||||||
|
const handleStationLoading = (loadingState) => { |
||||||
|
loading.value = loadingState; |
||||||
|
} |
||||||
|
// 新增处理站点变化的函数 |
||||||
|
const handleStationChange = async (stnmId) => { |
||||||
|
queryParams.stnmId = stnmId |
||||||
|
if (stnmId == undefined) { |
||||||
|
proxy.$modal.msgWarning("请选择站点后查询"); |
||||||
|
return |
||||||
|
} else { |
||||||
|
try { |
||||||
|
await Promise.all([ |
||||||
|
getList(), |
||||||
|
drawTable1(), |
||||||
|
getEchartsData() |
||||||
|
]); |
||||||
|
} catch (error) { |
||||||
|
console.error('请求执行出错:', error); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const loading = ref(false); |
||||||
|
const tableData = ref([]); |
||||||
|
|
||||||
|
const queryParams = reactive({ |
||||||
|
stnmId: "", |
||||||
|
startTime: dayjs().subtract(1, 'day').format('YYYY-MM-DD HH:mm:ss'), |
||||||
|
endTime: dayjs().format('YYYY-MM-DD HH:mm:ss'), |
||||||
|
pageNum: 1, |
||||||
|
pageSize: 10, |
||||||
|
stationType: props.stationType |
||||||
|
|
||||||
|
}) |
||||||
|
const gzCount = ref(0) |
||||||
|
const ycCount = ref(0) |
||||||
|
const total = ref(0) |
||||||
|
const alarmList = ref([]) |
||||||
|
const getList = async () => { |
||||||
|
loading.value = true; |
||||||
|
try { |
||||||
|
const res = await proxy.axiosGet('/alarm/alarm/queryByStnmIdAndTm', queryParams) |
||||||
|
if (res.code == 0) { |
||||||
|
let data = res.data |
||||||
|
total.value = res.count |
||||||
|
gzCount.value = res.gzCount |
||||||
|
ycCount.value = res.ycCount |
||||||
|
for (var i = 0; i < data.length; i++) { |
||||||
|
data[i].value = data[i].value.toFixed(proxy.fixed) |
||||||
|
} |
||||||
|
alarmList.value = data |
||||||
|
} |
||||||
|
} catch (error) { |
||||||
|
} finally { |
||||||
|
loading.value = false |
||||||
|
} |
||||||
|
}; |
||||||
|
const tableData1 = ref([]); |
||||||
|
const rightLoading = ref(false); |
||||||
|
// 修改 drawTable1 函数,保存原始数据 |
||||||
|
const drawTable1 = async () => { |
||||||
|
let params = getParams(); |
||||||
|
rightLoading.value = true; |
||||||
|
try { |
||||||
|
let url = requestPrefix + '/originaldata'; |
||||||
|
let res = await proxy.axiosPost2(url, params); |
||||||
|
if (res.code === 0) { |
||||||
|
let data = res.data; |
||||||
|
tableData1.value = data; |
||||||
|
// 保存原始数据副本 |
||||||
|
originalTableData1.value = JSON.parse(JSON.stringify(data)); |
||||||
|
isDataModified.value = false; // 重置修改状态 |
||||||
|
} |
||||||
|
} catch (error) { |
||||||
|
console.error(error); |
||||||
|
} finally { |
||||||
|
rightLoading.value = false; |
||||||
|
} |
||||||
|
}; |
||||||
|
// 获取echarts数据 |
||||||
|
const zsbStcd = ref('') |
||||||
|
const legendData = ref([]) |
||||||
|
const xAxisData = ref([]) |
||||||
|
const seriesData = ref([]) |
||||||
|
const echartsLoading = ref(false) |
||||||
|
const getEchartsData = async () => { |
||||||
|
let params = getParams() |
||||||
|
echartsLoading.value = true |
||||||
|
let url = requestPrefix + props.requestSuffix |
||||||
|
try { |
||||||
|
let res = await proxy.axiosPost2(url, params); |
||||||
|
if (res.code === 0) { |
||||||
|
zsbStcd.value = res.data.zstcd |
||||||
|
legendData.value = res.data.legend |
||||||
|
// 提取每个series中data的第一个元素并格式化时间 |
||||||
|
if (res.data.series && res.data.series.length > 0) { |
||||||
|
// 假设第一个series的data包含完整的时间点 |
||||||
|
xAxisData.value = res.data.series[0].data.map(item => { |
||||||
|
// 如果item是对象且包含时间字段 |
||||||
|
if (typeof item === 'object' && item !== null) { |
||||||
|
// 假设时间字段可能是tm, time, or the first element |
||||||
|
const timeValue = item.tm || item.time || item[0]; |
||||||
|
return dayjs(timeValue).format('YYYY-MM-DD HH:mm'); |
||||||
|
} else { |
||||||
|
// 如果item直接就是时间值 |
||||||
|
return dayjs(item).format('YYYY-MM-DD HH:mm'); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
seriesData.value = res.data.series |
||||||
|
console.log(res, '======获取echarts数据', res.data, xAxisData) |
||||||
|
} |
||||||
|
} catch (error) { |
||||||
|
|
||||||
|
} finally { |
||||||
|
echartsLoading.value = false |
||||||
|
} |
||||||
|
}; |
||||||
|
// 查询按钮 |
||||||
|
const handleQuery = async () => { |
||||||
|
try { |
||||||
|
await Promise.all([ |
||||||
|
getList(), |
||||||
|
drawTable1(), |
||||||
|
getEchartsData() |
||||||
|
]); |
||||||
|
} catch (error) { |
||||||
|
console.error('请求执行出错:', error); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
|
||||||
|
}) |
||||||
|
const getParams = () => { |
||||||
|
let params = { |
||||||
|
stnmId: queryParams.stnmId, |
||||||
|
startTime: queryParams.startTime, |
||||||
|
endTime: queryParams.endTime, |
||||||
|
stationType: props.stationType |
||||||
|
} |
||||||
|
return params |
||||||
|
} |
||||||
|
|
||||||
|
// 禁用开始时间选择器中大于当前时间和结束时间的日期 |
||||||
|
const disabledStartDate = (time) => { |
||||||
|
const endTime = queryParams.endTime ? dayjs(queryParams.endTime) : null; |
||||||
|
return dayjs(time).isAfter(dayjs()) || |
||||||
|
(endTime && dayjs(time).isAfter(endTime)); |
||||||
|
}; |
||||||
|
|
||||||
|
// 禁用结束时间选择器中大于当前时间和小于开始时间的日期 |
||||||
|
const disabledEndDate = (time) => { |
||||||
|
const startTime = queryParams.startTime ? dayjs(queryParams.startTime) : null; |
||||||
|
return dayjs(time).isAfter(dayjs()) || |
||||||
|
(startTime && dayjs(time).isBefore(startTime)); |
||||||
|
}; |
||||||
|
</script> |
||||||
|
<style scoped lang="scss"> |
||||||
|
.classification :deep(.el-form-item__content) { |
||||||
|
flex-direction: column; |
||||||
|
} |
||||||
|
|
||||||
|
.splitpanes__splitter { |
||||||
|
margin-bottom: 20px |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,345 @@ |
|||||||
|
<template> |
||||||
|
<div class="app-container app-container-bg"> |
||||||
|
<el-card class="first-card" ref='firstCard' shadow="always"> |
||||||
|
<el-form :model="queryParams" ref="queryForm" :inline="true" @submit.native.prevent> |
||||||
|
<el-form-item label="选择站点"> |
||||||
|
<el-tree-select filterable :props="{ value: 'stnmId', label: 'name' }" v-model="queryParams.stnmId" style="width: 240px" :data="treeSelectOptions" node-key="id" /> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="开始时间"> |
||||||
|
<el-date-picker :clearable="false" v-model="queryParams.startTime" type="datetime" value-format="MM-DD HH:mm" format="YYYY-MM-DD HH:mm:ss" placeholder="选择开始时间" :disabled-date="disabledStartDate"> |
||||||
|
</el-date-picker> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="结束时间"> |
||||||
|
<el-date-picker :clearable="false" v-model="queryParams.endTime" type="datetime" value-format="MM-DD HH:mm" format="YYYY-MM-DD HH:mm:ss" placeholder="选择结束时间" :disabled-date="disabledEndDate"> |
||||||
|
</el-date-picker> |
||||||
|
</el-form-item> |
||||||
|
<br /> |
||||||
|
<el-form-item label="年份区间"> |
||||||
|
<el-date-picker :clearable="false" class="picker-year" style="width:180px" v-model="yearstart" type="year" value-format="YYYY" placeholder="选择开始年" @change="changeYearStart"> |
||||||
|
</el-date-picker> |
||||||
|
<span class="pr10 pl10"> -</span> |
||||||
|
<el-date-picker :clearable="false" class="picker-year" style="width:180px" v-model="yearend" type="year" value-format="YYYY" placeholder="选择结束年" @change="changeYearEnd"> |
||||||
|
</el-date-picker> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item> |
||||||
|
<el-button type="primary" icon="Search" @click="handleQuery">查询</el-button> |
||||||
|
<!-- <el-button type="warning" icon="Download" @click="handleExport">下载</el-button> --> |
||||||
|
</el-form-item> |
||||||
|
</el-form> |
||||||
|
</el-card> |
||||||
|
|
||||||
|
<div class="el-card-p card-shadow carder-border mt10 pad10" style="max-width: 100%; overflow-x: hidden;padding-top:0" v-loading="loading"> |
||||||
|
<div class="main-table-header sticky-header"> |
||||||
|
<div class="table-title"> |
||||||
|
<span class="pr10" v-if="stnm!==''">{{stnm}}</span> |
||||||
|
<span v-else>{{tableTitle}}</span> |
||||||
|
</div> |
||||||
|
<div class="table-time mb5"> |
||||||
|
<div> |
||||||
|
<span>年份: </span><span id="title1">{{queryParams.years }}</span> |
||||||
|
</div> |
||||||
|
<div> |
||||||
|
<span>单位: </span><span id="title2">{{stationType=='E'?'m³/s':'m'}}</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<el-table v-table-height border :data="tableData" :span-method="arraySpanMethod" style="width: 100%;"> |
||||||
|
<el-table-column prop="year" label="年份" :align="alignment"> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column v-if="stationType == 'D'" prop="nvalue" label="当前潮位" :align="alignment"> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column v-if="stationType != 'D' && stationType != 'E'" prop="nvalue" label="当前水位" :align="alignment"> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column v-if="stationType == 'E'" prop="nvalue" label="当前流量" :align="alignment"> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column v-if="stationType == 'D'" prop="value" label="平均潮位" :align="alignment"> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column v-if="stationType != 'D' && stationType != 'E'" prop="value" label="平均水位" :align="alignment"> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column v-if="stationType == 'E'" prop="value" label="平均流量" :align="alignment"> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column label="最高潮位" :align="alignment" v-if="stationType == 'D'"> |
||||||
|
<el-table-column prop="maxvalue" label="潮位" :align="alignment"> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column label="时间" :align="alignment" width="180"> |
||||||
|
<template #default="scope"> |
||||||
|
<span>{{ parseTime(scope.row.maxtm) }}</span> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column label="最高水位" :align="alignment" v-if="stationType != 'D' && stationType != 'E'"> |
||||||
|
<el-table-column prop="maxvalue" label="水位" :align="alignment"> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column label="时间" :align="alignment" width="180"> |
||||||
|
<template #default="scope"> |
||||||
|
<span>{{ parseTime(scope.row.maxtm) }}</span> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column label="最高流量" :align="alignment" v-if="stationType == 'E'"> |
||||||
|
<el-table-column prop="maxvalue" label="流量" :align="alignment"> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column label="时间" :align="alignment" width="180"> |
||||||
|
<template #default="scope"> |
||||||
|
<span>{{ parseTime(scope.row.maxtm) }}</span> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column label="最低潮位" :align="alignment" v-if="stationType == 'D'"> |
||||||
|
<el-table-column prop="minvalue" label="潮位" :align="alignment"> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column label="时间" :align="alignment" width="180"> |
||||||
|
<template #default="scope"> |
||||||
|
<span>{{ parseTime(scope.row.mintm) }}</span> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column label="最低水位" :align="alignment" v-if="stationType != 'D' && stationType != 'E'"> |
||||||
|
<el-table-column prop="minvalue" label="水位" :align="alignment"> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column label="时间" :align="alignment" width="180"> |
||||||
|
<template #default="scope"> |
||||||
|
<span>{{ parseTime(scope.row.mintm) }}</span> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column label="最低流量" :align="alignment" v-if="stationType == 'E'"> |
||||||
|
<el-table-column prop="minvalue" label="流量" :align="alignment"> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column label="时间" :align="alignment" width="180"> |
||||||
|
<template #default="scope"> |
||||||
|
<span>{{ parseTime(scope.row.mintm) }}</span> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
</el-table-column> |
||||||
|
</el-table> |
||||||
|
</div> |
||||||
|
|
||||||
|
</div> |
||||||
|
</template> |
||||||
|
<script setup> |
||||||
|
import dayjs from 'dayjs'; |
||||||
|
import singleStation from '@/components/SingleStation/index.vue' |
||||||
|
import { ref, reactive, onMounted } from 'vue'; |
||||||
|
|
||||||
|
const props = defineProps({ |
||||||
|
tableTitle: { |
||||||
|
type: String, |
||||||
|
default: '表格标题' |
||||||
|
}, |
||||||
|
stationType: { |
||||||
|
type: String, |
||||||
|
default: 'A' |
||||||
|
}, |
||||||
|
requestPrefix: { |
||||||
|
type: String, |
||||||
|
default: "" |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance() |
||||||
|
const alignment = 'center' |
||||||
|
const type = computed(() => { |
||||||
|
switch (props.stationType) { |
||||||
|
case 'A': |
||||||
|
return 'rain'; |
||||||
|
case 'B': |
||||||
|
return 'river'; |
||||||
|
case 'C': |
||||||
|
return 'rsvr'; |
||||||
|
case 'D': |
||||||
|
return 'tide'; |
||||||
|
case 'E': |
||||||
|
return 'q_qflow'; |
||||||
|
} |
||||||
|
}); |
||||||
|
const tablename = computed(() => { |
||||||
|
switch (props.stationType) { |
||||||
|
case 'D': |
||||||
|
return '潮位过程表'; |
||||||
|
case 'E': |
||||||
|
return '流量过程表'; |
||||||
|
default: |
||||||
|
return '水位过程表'; |
||||||
|
} |
||||||
|
}); |
||||||
|
const loading = ref(false); |
||||||
|
const tableData = ref([]); |
||||||
|
const yearstart = ref(dayjs().subtract(1, 'year').format('YYYY')) |
||||||
|
const yearend = ref(dayjs().format('YYYY')) |
||||||
|
const queryParams = reactive({ |
||||||
|
startTime: dayjs().subtract(30, 'day').format('MM-DD 08:00'), |
||||||
|
endTime: dayjs().format('MM-DD 08:00'), |
||||||
|
years: `${yearstart.value}-${yearend.value}`, |
||||||
|
stnmIds: '', |
||||||
|
type: type.value |
||||||
|
}); |
||||||
|
|
||||||
|
let total = ref(0) |
||||||
|
let dataCount = ref(0) |
||||||
|
const stnm = ref('') |
||||||
|
const getList = async () => { |
||||||
|
loading.value = true; |
||||||
|
let url = `/report/${props.requestPrefix}` |
||||||
|
try { |
||||||
|
const res = await proxy.axiosGet(url, queryParams) |
||||||
|
if (res.code == 0) { |
||||||
|
let data = res.data.list |
||||||
|
let { avg, max, min, list } = res.data |
||||||
|
tableData.value = data; |
||||||
|
if (data && data.length > 0) { |
||||||
|
stnm.value = `${data[0].stnm}${queryParams.startTime} ~ ${queryParams.endTime}${tablename.value}` |
||||||
|
} |
||||||
|
console.log(avg[0], avg[1], avg[2], avg[3]) |
||||||
|
tableData.value.push({ |
||||||
|
year: "平均", |
||||||
|
nvalue: (avg[0] == 0 ? "" : avg[0]), |
||||||
|
value: (avg[1] == 0 ? "" : avg[1]), |
||||||
|
maxvalue: (avg[2] == 0 ? "" : avg[2]), |
||||||
|
minvalue: (avg[3] == 0 ? "" : avg[3]), |
||||||
|
}); |
||||||
|
tableData.value.push({ |
||||||
|
year: "最高", |
||||||
|
nvalue: (typeof (max[0].nvalue) == "undefined" ? "" : max[0].nvalue), |
||||||
|
value: (typeof (max[1].value) == "undefined" ? "" : max[1].value), |
||||||
|
maxvalue: (typeof (max[3].maxvalue) == "undefined" ? "" : max[3].maxvalue), |
||||||
|
minvalue: (typeof (max[4].minvalue) == "undefined" ? "" : max[4].minvalue), |
||||||
|
}); |
||||||
|
tableData.value.push({ |
||||||
|
year: "时间", |
||||||
|
nvalue: (typeof (max[0].year) == "undefined" ? "" : max[0].year), |
||||||
|
value: (typeof (max[1].year) == "undefined" ? "" : max[1].year), |
||||||
|
maxvalue: (proxy.parseTime(typeof (max[3].maxtm)) == "undefined" ? "" : proxy.parseTime(max[3].maxtm)), |
||||||
|
minvalue: (proxy.parseTime(typeof (max[4].mintm)) == "undefined" ? "" : proxy.parseTime(max[4].mintm)), |
||||||
|
}); |
||||||
|
tableData.value.push({ |
||||||
|
year: "最低", |
||||||
|
nvalue: (typeof (min[0].nvalue) == "undefined" ? "" : min[0].nvalue), |
||||||
|
value: (typeof (min[1].value) == "undefined" ? "" : min[1].value), |
||||||
|
maxvalue: (typeof (min[2].maxvalue) == "undefined" ? "" : min[2].maxvalue), |
||||||
|
minvalue: (typeof (min[3].minvalue) == "undefined" ? "" : min[3].minvalue), |
||||||
|
}); |
||||||
|
tableData.value.push({ |
||||||
|
year: "时间", |
||||||
|
nvalue: (typeof (min[0].year) == "undefined" ? "" : min[0].year), |
||||||
|
value: (typeof (min[1].year) == "undefined" ? "" : min[1].year), |
||||||
|
maxvalue: (proxy.parseTime(typeof (min[2].maxtm)) == "undefined" ? "" : proxy.parseTime(min[2].maxtm)), |
||||||
|
minvalue: (proxy.parseTime(typeof (min[3].mintm)) == "undefined" ? "" : proxy.parseTime(min[3].mintm)), |
||||||
|
}); |
||||||
|
dataCount.value = tableData.value.length; |
||||||
|
} |
||||||
|
} catch (error) { |
||||||
|
console.error(error) |
||||||
|
} finally { |
||||||
|
// 确保在请求完成后隐藏loading |
||||||
|
loading.value = false |
||||||
|
} |
||||||
|
} |
||||||
|
const changeYearStart = (val) => { |
||||||
|
yearstart.value = val |
||||||
|
queryParams.years = `${yearstart.value}-${yearend.value}` |
||||||
|
getList() |
||||||
|
} |
||||||
|
const changeYearEnd = (val) => { |
||||||
|
yearend.value = val |
||||||
|
queryParams.years = `${yearstart.value}-${yearend.value}` |
||||||
|
getList() |
||||||
|
} |
||||||
|
|
||||||
|
// 优化后的获取上一行月份的方法 |
||||||
|
const getPrevRow = (scope, field, type) => { |
||||||
|
const rowIndex = scope.$index; |
||||||
|
const data = scope.store.states.data; |
||||||
|
|
||||||
|
// 如果是第一行,返回null |
||||||
|
if (rowIndex === 0) return null; |
||||||
|
|
||||||
|
// 找到上一个非空行的月份 |
||||||
|
let prevRowIndex = rowIndex - 1; |
||||||
|
while (prevRowIndex >= 0 && (!data.value[prevRowIndex][field] || data.value[prevRowIndex][field] === '')) { |
||||||
|
prevRowIndex--; |
||||||
|
} |
||||||
|
if (type == '1') { |
||||||
|
field = 'month' |
||||||
|
} |
||||||
|
let result = prevRowIndex >= 0 ? data.value[prevRowIndex][field] : null; |
||||||
|
// 如果找到有效的上一行数据,返回其月份;否则返回null |
||||||
|
return result |
||||||
|
}; |
||||||
|
|
||||||
|
const treeSelectOptions = ref([]) |
||||||
|
const getTreeStation = async () => { |
||||||
|
loading.value = true; |
||||||
|
try { |
||||||
|
let res = await proxy.axiosGet('/basic/stype/getTreeStation2/' + proxy.stationType); |
||||||
|
if (res.code == 0) { |
||||||
|
treeSelectOptions.value = res.data |
||||||
|
// 设置默认值为第一个叶子节点的stnmId |
||||||
|
if (treeSelectOptions.value.length > 0) { |
||||||
|
const firstLeafNode = findFirstLeafNode(treeSelectOptions.value); |
||||||
|
if (firstLeafNode) { |
||||||
|
queryParams.stnmId = firstLeafNode.stnmId; |
||||||
|
nextTick(() => { |
||||||
|
getList() |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} catch (error) { |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
// 查找树结构中的第一个叶子节点 |
||||||
|
const findFirstLeafNode = (nodes) => { |
||||||
|
for (let node of nodes) { |
||||||
|
// 如果节点有子节点,则递归查找 |
||||||
|
if (node.children && node.children.length > 0) { |
||||||
|
const leaf = findFirstLeafNode(node.children); |
||||||
|
if (leaf) { |
||||||
|
return leaf; |
||||||
|
} |
||||||
|
} |
||||||
|
// 如果节点没有子节点,则为叶子节点 |
||||||
|
else if (node.stnmId) { |
||||||
|
return node; |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
// 搜索按钮操作 |
||||||
|
const handleQuery = () => { |
||||||
|
getList() |
||||||
|
|
||||||
|
} |
||||||
|
// 下载按钮操作 |
||||||
|
const handleExport = () => { } |
||||||
|
onMounted(() => { |
||||||
|
getTreeStation() |
||||||
|
}) |
||||||
|
const arraySpanMethod = ({ row, column, rowIndex, columnIndex }) => { |
||||||
|
if (dataCount.value > 4) { |
||||||
|
if (rowIndex > dataCount.value - 6) { |
||||||
|
if (columnIndex === 3 || columnIndex === 5) { |
||||||
|
return [1, 2]; |
||||||
|
} else if (columnIndex === 4 || columnIndex === 6) { |
||||||
|
return [0, 0]; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
<style scoped lang="scss"> |
||||||
|
.no-border-table :deep(.el-table__body-wrapper .el-table__cell) { |
||||||
|
border-bottom: 0 !important; |
||||||
|
} |
||||||
|
|
||||||
|
.no-border-table :deep(.el-table__body-wrapper .el-table__cell) { |
||||||
|
padding: 2px 0 !important; |
||||||
|
} |
||||||
|
|
||||||
|
.no-border-table :deep(.el-table__body-wrapper .el-table__row:nth-child(6n) .el-table__cell) { |
||||||
|
padding: 10px 0 !important; |
||||||
|
background-color: #f9f9f9; |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,173 @@ |
|||||||
|
<template> |
||||||
|
<div class="app-container app-container-bg"> |
||||||
|
<el-card class="first-card" ref='firstCard' shadow="always"> |
||||||
|
<el-form :model="queryParams" ref="queryForm" :inline="true" @submit.native.prevent> |
||||||
|
<el-form-item label="开始时间"> |
||||||
|
<el-date-picker v-model="queryParams.startTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss" placeholder="选择开始时间" :disabled-date="disabledStartDate"> |
||||||
|
</el-date-picker> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="结束时间"> |
||||||
|
<el-date-picker v-model="queryParams.endTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss" placeholder="选择结束时间" :disabled-date="disabledEndDate"> |
||||||
|
</el-date-picker> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item> |
||||||
|
<el-button type="primary" icon="Search" @click="handleQuery">查询</el-button> |
||||||
|
</el-form-item> |
||||||
|
</el-form> |
||||||
|
</el-card> |
||||||
|
<splitpanes :horizontal="device === 'mobile'" class="el-card-p card-shadow carder-border mt10 pad10 default-theme container-box" :push-other-panes="false"> |
||||||
|
<pane :size="firstSize" :min-size="SPLITPANES_CONFIG.MIN_SIZE" :max-size="SPLITPANES_CONFIG.MAX_SIZE" ref="firstPane" class="mr10"> |
||||||
|
<e-tree ref="eTreeRef" :stationType="stationType" @stationChange="handleStationChange" @loadingChange="handleStationLoading"></e-tree> |
||||||
|
</pane> |
||||||
|
<pane :size="100 - firstSize" style="height: 100%;"> |
||||||
|
<div class="main-table-header"> |
||||||
|
<div class="table-title">{{tableTitle}}</div> |
||||||
|
</div> |
||||||
|
<el-table v-table-height v-loading="loading" :data="tableData" border> |
||||||
|
<el-table-column v-if="rsvrsdFlag" prop="typename" label="类型" :align="alignment"> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column v-if="!rsvrsdFlag" prop="area" label="区域" :align="alignment"> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column prop="stnm" label="站名" :align="alignment" width="200"> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column prop="minvalue" label="当前水位" :align="alignment"> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column prop="value" label="平均水位" :align="alignment"> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column label="最高水位" :align="alignment"> |
||||||
|
<el-table-column prop="maxvalue" label="水位" :align="alignment" width="80"> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column label="时间" :align="alignment" width="180" prop="maxtm"></el-table-column> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column label="最低水位" :align="alignment"> |
||||||
|
<el-table-column prop="minvalue" label="水位" :align="alignment" width="80"> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column label="时间" :align="alignment" width="180" prop="mintm"></el-table-column> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column prop="lsmaxvalue" label="历史最高" :align="alignment"> |
||||||
|
</el-table-column> |
||||||
|
</el-table> |
||||||
|
</pane> |
||||||
|
</splitpanes> |
||||||
|
|
||||||
|
</div> |
||||||
|
</template> |
||||||
|
<script setup> |
||||||
|
import dayjs from 'dayjs'; |
||||||
|
import { |
||||||
|
Splitpanes, |
||||||
|
Pane |
||||||
|
} from 'splitpanes' |
||||||
|
import 'splitpanes/dist/splitpanes.css' |
||||||
|
import ETree from '@/components/ETree/index.vue' |
||||||
|
import useAppStore from '@/store/modules/app' |
||||||
|
const device = computed(() => useAppStore().device); |
||||||
|
|
||||||
|
const props = defineProps({ |
||||||
|
stationType: { |
||||||
|
type: String, |
||||||
|
default: 'A' |
||||||
|
}, |
||||||
|
requestPrefix: { |
||||||
|
type: String, |
||||||
|
default: "" |
||||||
|
} |
||||||
|
}) |
||||||
|
const { |
||||||
|
proxy |
||||||
|
} = getCurrentInstance() |
||||||
|
const type = computed(() => { |
||||||
|
switch (props.stationType) { |
||||||
|
case 'A': |
||||||
|
return 'rain'; |
||||||
|
case 'B': |
||||||
|
return 'river'; |
||||||
|
case 'C': |
||||||
|
return 'rsvr'; |
||||||
|
case 'D': |
||||||
|
return 'tide'; |
||||||
|
case 'E': |
||||||
|
return 'q_qflow'; |
||||||
|
} |
||||||
|
}); |
||||||
|
const alignment = 'center' |
||||||
|
const firstSize = ref(proxy.SPLITPANES_CONFIG.DEFAULT_SIZE) |
||||||
|
const eTreeRef = ref(null) |
||||||
|
let stnmIdsList = [] |
||||||
|
const handleStationLoading = (loadingState) => { |
||||||
|
loading.value = loadingState; |
||||||
|
} |
||||||
|
// 新增处理站点变化的函数 |
||||||
|
const handleStationChange = (stnmIds) => { |
||||||
|
stnmIdsList = stnmIds |
||||||
|
queryParams.stnmIds = stnmIds.join(',') |
||||||
|
if (stnmIdsList.length == 0) { |
||||||
|
proxy.$modal.msgWarning("请选择站点后查询"); |
||||||
|
return |
||||||
|
} else if (stnmIdsList.length > 50) { |
||||||
|
proxy.$modal.msg("站点最多可选择50个"); |
||||||
|
return |
||||||
|
} else { |
||||||
|
getList() |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
const queryParams = reactive({ |
||||||
|
startTime: dayjs().subtract(30, 'day').format('YYYY-MM-DD 08:00:00'), |
||||||
|
endTime: dayjs().format('YYYY-MM-DD 08:00:00'), |
||||||
|
type: type.value, |
||||||
|
stnmIds: '', |
||||||
|
}); |
||||||
|
|
||||||
|
// 禁用开始时间选择器中大于当前时间和结束时间的日期 |
||||||
|
const disabledStartDate = (time) => { |
||||||
|
const endTime = queryParams.endTime ? dayjs(queryParams.endTime) : null; |
||||||
|
return dayjs(time).isAfter(dayjs()) || |
||||||
|
(endTime && dayjs(time).isAfter(endTime)); |
||||||
|
}; |
||||||
|
|
||||||
|
// 禁用结束时间选择器中大于当前时间和小于开始时间的日期 |
||||||
|
const disabledEndDate = (time) => { |
||||||
|
const startTime = queryParams.startTime ? dayjs(queryParams.startTime) : null; |
||||||
|
return dayjs(time).isAfter(dayjs()) || |
||||||
|
(startTime && dayjs(time).isBefore(startTime)); |
||||||
|
}; |
||||||
|
|
||||||
|
const tableTitle = '水位时段统计(m)' |
||||||
|
// 根据stationType判断是否为河道 |
||||||
|
const rsvrsdFlag = computed(() => { |
||||||
|
return props.stationType == 'C' |
||||||
|
}) |
||||||
|
const tableData = ref([]) |
||||||
|
const loading = ref(false) |
||||||
|
|
||||||
|
// 修改 getList 函数来处理动态列和数据 |
||||||
|
const getList = async () => { |
||||||
|
loading.value = true; |
||||||
|
let url = `/report/${props.requestPrefix}` |
||||||
|
try { |
||||||
|
let res = await proxy.axiosPost2(url, queryParams) |
||||||
|
if (res.code == 0) { |
||||||
|
let data = res.data |
||||||
|
data.map(item => { |
||||||
|
item.maxtm = dayjs(item.maxtm).format('YYYY-MM-DD HH:mm:ss') |
||||||
|
item.mintm = dayjs(item.mintm).format('YYYY-MM-DD HH:mm:ss') |
||||||
|
return item |
||||||
|
}) |
||||||
|
tableData.value = data |
||||||
|
} |
||||||
|
} catch (error) { |
||||||
|
tableData.value = []; |
||||||
|
} finally { |
||||||
|
loading.value = false |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 查询 |
||||||
|
const handleQuery = async () => { |
||||||
|
getList() |
||||||
|
} |
||||||
|
|
||||||
|
</script> |
||||||
|
<style lang="scss" scoped> |
||||||
|
</style> |
||||||
@ -0,0 +1,245 @@ |
|||||||
|
<template> |
||||||
|
<div class="app-container app-container-bg"> |
||||||
|
<el-card class="first-card" ref='firstCard' shadow="always"> |
||||||
|
<el-form :model="queryParams" ref="queryForm" :inline="true" @submit.native.prevent> |
||||||
|
<el-form-item label="时间维度"> |
||||||
|
<el-select v-model="queryParams.dataType" placeholder="请选择时间维度" class="w150" @change="changeDataType"> |
||||||
|
<el-option v-for="item in dataTypes" :key="item.value" :label="item.label" :value="item.value"> |
||||||
|
</el-option> |
||||||
|
</el-select> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="开始时间"> |
||||||
|
<el-date-picker v-model="queryParams.startTime" type="datetime" format="YYYY-MM-DD HH:mm:ss" placeholder="选择开始时间" :disabled-date="disabledStartDate" @change="(val)=>changeTime(val,'start')"> |
||||||
|
</el-date-picker> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="结束时间"> |
||||||
|
<el-date-picker v-model="queryParams.endTime" type="datetime" format="YYYY-MM-DD HH:mm:ss" placeholder="选择结束时间" :disabled-date="disabledEndDate" @change="(val)=>changeTime(val,'end')"> |
||||||
|
</el-date-picker> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item> |
||||||
|
<el-button type="primary" icon="Search" @click="handleQuery">查询</el-button> |
||||||
|
</el-form-item> |
||||||
|
</el-form> |
||||||
|
</el-card> |
||||||
|
<splitpanes :horizontal="device === 'mobile'" class="el-card-p card-shadow carder-border mt10 pad10 default-theme container-box" :push-other-panes="false"> |
||||||
|
<pane :size="firstSize" :min-size="SPLITPANES_CONFIG.MIN_SIZE" :max-size="SPLITPANES_CONFIG.MAX_SIZE" ref="firstPane" class="mr10"> |
||||||
|
<e-tree ref="eTreeRef" :stationType="stationType" @stationChange="handleStationChange"></e-tree> |
||||||
|
</pane> |
||||||
|
<pane :size="100 - firstSize"> |
||||||
|
<splitpanes horizontal class="default-theme" :push-other-panes="false"> |
||||||
|
<pane size="50"> |
||||||
|
<TimeBarChart v-loading="echartsLoading" :legendData='legendData' :xAxisData="xAxisData" :seriesData="seriesData" :echartType="echartType" :grid="grid" :BarFormatter="false" :unit="unit" /> |
||||||
|
</pane> |
||||||
|
<pane size="50" class="mr10"> |
||||||
|
<div class="main-table-header"> |
||||||
|
<div class="table-title">{{tableTitle}}</div> |
||||||
|
<div class="table-time mb5"> |
||||||
|
<div> |
||||||
|
<span>时间: </span><span id="title1">{{ tableTime}}</span> |
||||||
|
</div> |
||||||
|
<div> |
||||||
|
<span>单位: </span><span id="title2">mm</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<el-table v-loading="loading" ref="myTable" :data="tableData" v-table-height="{bottom:90}" border> |
||||||
|
<el-table-column :align="alignment" :width="item.label == '时间' ? '180': '' " :prop="item.prop" :label="item.label" v-for="(item, index) in tableHead" :key="index"></el-table-column> |
||||||
|
</el-table> |
||||||
|
</pane> |
||||||
|
</splitpanes> |
||||||
|
</pane> |
||||||
|
</splitpanes> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
<script setup> |
||||||
|
import dayjs from 'dayjs'; |
||||||
|
import { |
||||||
|
Splitpanes, |
||||||
|
Pane |
||||||
|
} from 'splitpanes' |
||||||
|
import 'splitpanes/dist/splitpanes.css' |
||||||
|
import BarChart from '@/components/ChartsBar/index.vue' |
||||||
|
import TimeBarChart from '@/components/ChartsTimeBar/index.vue' |
||||||
|
import ETree from '@/components/ETree/index.vue' |
||||||
|
|
||||||
|
import useAppStore from '@/store/modules/app' |
||||||
|
const device = computed(() => useAppStore().device); |
||||||
|
|
||||||
|
const props = defineProps({ |
||||||
|
stationType: { |
||||||
|
type: String, |
||||||
|
default: 'A' |
||||||
|
}, |
||||||
|
tableTitle: { |
||||||
|
type: String, |
||||||
|
default: '表格标题' |
||||||
|
}, |
||||||
|
requestPrefix: { |
||||||
|
type: String, |
||||||
|
default: '' |
||||||
|
}, |
||||||
|
requestPrefixTable: { |
||||||
|
type: String, |
||||||
|
default: '' |
||||||
|
} |
||||||
|
}) |
||||||
|
const { proxy } = getCurrentInstance() |
||||||
|
const alignment = 'center' |
||||||
|
let unit = ref('m') |
||||||
|
const eTreeRef = ref(null) |
||||||
|
let stnmIdsList = [] |
||||||
|
// 新增处理站点变化的函数 |
||||||
|
const handleStationChange = (stnmIds) => { |
||||||
|
stnmIdsList = stnmIds |
||||||
|
queryParams.stnmIds = stnmIds.join(',') |
||||||
|
if (stnmIdsList.length == 0) { |
||||||
|
proxy.$modal.msgWarning("请选择站点后查询"); |
||||||
|
return |
||||||
|
} else if (stnmIdsList.length > 50) { |
||||||
|
proxy.$modal.msg("站点最多可选择50个"); |
||||||
|
return |
||||||
|
} else { |
||||||
|
getList() |
||||||
|
getEchartsData() |
||||||
|
} |
||||||
|
} |
||||||
|
const queryParams = reactive({ |
||||||
|
startTime: dayjs().subtract(7, 'day').format('YYYY-MM-DD 08:00:00'), |
||||||
|
endTime: dayjs().format('YYYY-MM-DD 08:00:00'), |
||||||
|
dataType: '0', |
||||||
|
}); |
||||||
|
|
||||||
|
// 禁用开始时间选择器中大于当前时间和结束时间的日期 |
||||||
|
const disabledStartDate = (time) => { |
||||||
|
const endTime = queryParams.endTime ? dayjs(queryParams.endTime) : null; |
||||||
|
return dayjs(time).isAfter(dayjs()) || |
||||||
|
(endTime && dayjs(time).isAfter(endTime)); |
||||||
|
}; |
||||||
|
|
||||||
|
// 禁用结束时间选择器中大于当前时间和小于开始时间的日期 |
||||||
|
const disabledEndDate = (time) => { |
||||||
|
const startTime = queryParams.startTime ? dayjs(queryParams.startTime) : null; |
||||||
|
return dayjs(time).isAfter(dayjs()) || |
||||||
|
(startTime && dayjs(time).isBefore(startTime)); |
||||||
|
}; |
||||||
|
const tableTime = ref(queryParams.startTime.substring(0, 16) + " 至 " + queryParams.endTime.substring(0, 16)) |
||||||
|
const timeType = ref('day') |
||||||
|
// 改变时间纬度 |
||||||
|
const changeDataType = (val) => { |
||||||
|
queryParams.dataType = val |
||||||
|
if (val == 0) { |
||||||
|
tableTime.value = queryParams.startTime.substring(0, 16) + " 至 " + queryParams.endTime.substring(0, 16); |
||||||
|
} else { |
||||||
|
tableTime.value = queryParams.startTime.substring(0, 10) + " 至 " + queryParams.endTime.substring(0, 10); |
||||||
|
} |
||||||
|
getList() |
||||||
|
getEchartsData() |
||||||
|
} |
||||||
|
// 改变时间 |
||||||
|
const changeTime = (val, type = 'start') => { |
||||||
|
if (type == 'start') { |
||||||
|
queryParams.startTime = dayjs(val).format('YYYY-MM-DD HH:mm:ss') |
||||||
|
} else { |
||||||
|
queryParams.endTime = dayjs(val).format('YYYY-MM-DD HH:mm:ss') |
||||||
|
} |
||||||
|
} |
||||||
|
const firstSize = ref(proxy.SPLITPANES_CONFIG.DEFAULT_SIZE) |
||||||
|
const loading = ref(false) |
||||||
|
const tableData = ref([]) |
||||||
|
const tableHead = ref([]) |
||||||
|
const getList = async () => { |
||||||
|
loading.value = true; |
||||||
|
try { |
||||||
|
let res = await proxy.axiosPost2(props.requestPrefixTable, queryParams) |
||||||
|
if (res.code == 0) { |
||||||
|
tableHead.value = handleTableHead(res.data) |
||||||
|
let data = res.data.slice(1) |
||||||
|
if (queryParams.dataType == 1) { |
||||||
|
data.map(item => { item.tm = item.tm.slice(0, 10) }) |
||||||
|
} else { |
||||||
|
data.map(item => { item.tm = item.tm.slice(0, 16) }) |
||||||
|
} |
||||||
|
tableData.value = data |
||||||
|
} |
||||||
|
} catch (error) { |
||||||
|
tableHead.value = []; |
||||||
|
tableData.value = []; |
||||||
|
} finally { |
||||||
|
loading.value = false |
||||||
|
} |
||||||
|
} |
||||||
|
// 假设 res.data 是从接口返回的数据 |
||||||
|
const handleTableHead = (data) => { |
||||||
|
if (!data || data.length === 0) return []; |
||||||
|
|
||||||
|
const firstRow = data[0]; // 第一条数据,包含所有字段名 |
||||||
|
const head = []; |
||||||
|
|
||||||
|
// 添加 "时间" 列 |
||||||
|
head.push({ prop: 'tm', label: '时间' }); |
||||||
|
|
||||||
|
// 遍历 firstRow 的 key-value,提取站点名称作为列名 |
||||||
|
Object.keys(firstRow).forEach(key => { |
||||||
|
if (key !== 'avg' && key !== 'tm') { |
||||||
|
head.push({ prop: key, label: firstRow[key] }); |
||||||
|
} |
||||||
|
}); |
||||||
|
return head; |
||||||
|
}; |
||||||
|
const legendData = ref([]) |
||||||
|
const xAxisData = ref([]) |
||||||
|
const seriesData = ref([]) |
||||||
|
const echartsLoading = ref(false) |
||||||
|
const getEchartsData = async () => { |
||||||
|
echartsLoading.value = true |
||||||
|
try { |
||||||
|
let res = await proxy.axiosPost2(props.requestPrefix, queryParams); |
||||||
|
if (res.code === 0) { |
||||||
|
legendData.value = res.data.legend |
||||||
|
// 提取每个series中data的第一个元素并格式化时间 |
||||||
|
if (res.data.series && res.data.series.length > 0) { |
||||||
|
// 假设第一个series的data包含完整的时间点 |
||||||
|
xAxisData.value = res.data.series[0].data.map(item => { |
||||||
|
// 如果item是对象且包含时间字段 |
||||||
|
if (typeof item === 'object' && item !== null) { |
||||||
|
// 假设时间字段可能是tm, time, or the first element |
||||||
|
const timeValue = item.tm || item.time || item[0]; |
||||||
|
return dayjs(timeValue).format('YYYY-MM-DD HH:mm'); |
||||||
|
} else { |
||||||
|
// 如果item直接就是时间值 |
||||||
|
return dayjs(item).format('YYYY-MM-DD HH:mm'); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
seriesData.value = res.data.series |
||||||
|
} |
||||||
|
} catch (error) { |
||||||
|
|
||||||
|
} finally { |
||||||
|
echartsLoading.value = false |
||||||
|
} |
||||||
|
}; |
||||||
|
const echartType = ref('line') |
||||||
|
const handleQuery = () => { |
||||||
|
getList() |
||||||
|
getEchartsData() |
||||||
|
} |
||||||
|
const dataTypes = ref([ |
||||||
|
{ label: '5分钟', value: '-1' }, |
||||||
|
{ label: '小时', value: '0' }, |
||||||
|
{ label: '日', value: '1' } |
||||||
|
]) |
||||||
|
const grid = { |
||||||
|
left: "5%", |
||||||
|
right: "5%", |
||||||
|
bottom: "10%", |
||||||
|
top: "10%", |
||||||
|
containLabel: true, |
||||||
|
} |
||||||
|
</script> |
||||||
|
<style lang="scss" scoped> |
||||||
|
:deep(.el-tabs), |
||||||
|
:deep(.el-tabs .el-tabs__content) { |
||||||
|
height: 100%; |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,283 @@ |
|||||||
|
<template> |
||||||
|
<div class="app-container app-container-bg"> |
||||||
|
<el-card class="first-card" ref='firstCard' shadow="always"> |
||||||
|
<el-form :model="queryParams" ref="queryForm" :inline="true" @submit.native.prevent> |
||||||
|
<el-form-item label="年份区间"> |
||||||
|
<el-date-picker class="picker-year" style="width:180px" v-model="queryParams.startTime" type="year" value-format="YYYY" placeholder="选择开始年" @change="handleQuery"> |
||||||
|
</el-date-picker> |
||||||
|
<span class="pr10 pl10"> - </span> |
||||||
|
<el-date-picker class="picker-year" style="width:180px" v-model="queryParams.endTime" type="year" value-format="YYYY" placeholder="选择结束年" @change="handleQuery"> |
||||||
|
</el-date-picker> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item> |
||||||
|
<el-button type="primary" icon="Search" @click="handleQuery">查询</el-button> |
||||||
|
</el-form-item> |
||||||
|
<br /> |
||||||
|
<el-form-item label="报表排列方式(按站点)"> |
||||||
|
<el-radio-group v-model="direction" @change="getList"> |
||||||
|
<el-radio label="zx">纵向</el-radio> |
||||||
|
<el-radio label="hx">横向</el-radio> |
||||||
|
</el-radio-group> |
||||||
|
</el-form-item> |
||||||
|
</el-form> |
||||||
|
</el-card> |
||||||
|
<splitpanes :horizontal="device === 'mobile'" class="el-card-p card-shadow carder-border mt10 pad10 default-theme container-box" :push-other-panes="false"> |
||||||
|
<pane :size="firstSize" :min-size="SPLITPANES_CONFIG.MIN_SIZE" :max-size="SPLITPANES_CONFIG.MAX_SIZE" ref="firstPane" class="mr10"> |
||||||
|
<e-tree ref="eTreeRef" :stationType="stationType" @stationChange="handleStationChange"></e-tree> |
||||||
|
</pane> |
||||||
|
<pane :size="100 - firstSize"> |
||||||
|
<div class="main-table-header"> |
||||||
|
<div class="table-title">{{tableTitle}}</div> |
||||||
|
</div> |
||||||
|
<el-table v-if="direction=='zx'" v-loading="loading" ref="myTable" :data="tableData" v-table-height="{bottom:90}" border :span-method="arraySpanMethod"> |
||||||
|
<el-table-column prop="station" label="站点" :align="alignment" :rowspan="getRowspan"></el-table-column> |
||||||
|
<el-table-column prop="type" label="" :align="alignment"></el-table-column> |
||||||
|
<el-table-column :align="alignment" :prop="item.prop" :label="item.label" v-for="(item, index) in tableColumns.filter(col => col.prop !== 'station' && col.prop !== 'type')" :key="index"></el-table-column> |
||||||
|
</el-table> |
||||||
|
<el-table v-if="direction=='hx'" v-table-height v-loading="loading" :data="tableData" border :span-method="arraySpanMethod"> |
||||||
|
<el-table-column label="年份" prop="time" :align="alignment"></el-table-column> |
||||||
|
<el-table-column :align="alignment" v-for="(column, index) in tableColumns" :key="column.prop" :prop="column.prop" :label="column.label" :min-width="120"> |
||||||
|
<el-table-column label="平均水位" :prop="'avg'+index" :align="alignment"></el-table-column> |
||||||
|
<el-table-column label="最高水位" :prop="'maxValue'+index" :align="alignment"></el-table-column> |
||||||
|
<el-table-column label="时间" :prop="'maxtm'+index" :align="alignment"></el-table-column> |
||||||
|
<el-table-column label="最低水位" :prop="'minValue'+index" :align="alignment"></el-table-column> |
||||||
|
<el-table-column label="时间" :prop="'mintm'+index" :align="alignment"></el-table-column> |
||||||
|
</el-table-column> |
||||||
|
</el-table> |
||||||
|
|
||||||
|
</pane> |
||||||
|
</splitpanes> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
<script setup> |
||||||
|
import dayjs from 'dayjs'; |
||||||
|
import { |
||||||
|
Splitpanes, |
||||||
|
Pane |
||||||
|
} from 'splitpanes' |
||||||
|
import 'splitpanes/dist/splitpanes.css' |
||||||
|
import ETree from '@/components/ETree/index.vue' |
||||||
|
|
||||||
|
import useAppStore from '@/store/modules/app' |
||||||
|
const device = computed(() => useAppStore().device); |
||||||
|
|
||||||
|
const props = defineProps({ |
||||||
|
// 类型 |
||||||
|
stationType: { |
||||||
|
type: String, |
||||||
|
default: 'A' |
||||||
|
}, |
||||||
|
// 请求接口 |
||||||
|
requestPrefix: { |
||||||
|
type: String, |
||||||
|
default: '' |
||||||
|
}, |
||||||
|
// 数据库表名 |
||||||
|
tableName: { |
||||||
|
type: String, |
||||||
|
default: "" |
||||||
|
}, |
||||||
|
}) |
||||||
|
const { proxy } = getCurrentInstance() |
||||||
|
const alignment = 'center' |
||||||
|
let direction = ref('zx') |
||||||
|
const eTreeRef = ref(null) |
||||||
|
const tableTitle = '水位年特征值表(m)' |
||||||
|
let stnmIdsList = [] |
||||||
|
// 新增处理站点变化的函数 |
||||||
|
const handleStationChange = (stnmIds) => { |
||||||
|
stnmIdsList = stnmIds |
||||||
|
queryParams.stnmIds = stnmIds.join(',') |
||||||
|
if (stnmIdsList.length == 0) { |
||||||
|
proxy.$modal.msgWarning("请选择站点后查询"); |
||||||
|
return |
||||||
|
} else if (stnmIdsList.length > 50) { |
||||||
|
proxy.$modal.msg("站点最多可选择50个"); |
||||||
|
return |
||||||
|
} else { |
||||||
|
getList() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const queryParams = reactive({ |
||||||
|
startTime: dayjs().subtract(1, 'year').format('YYYY'), |
||||||
|
endTime: dayjs().format('YYYY'), |
||||||
|
tableName: props.tableName, |
||||||
|
valueType: "all" |
||||||
|
}); |
||||||
|
|
||||||
|
|
||||||
|
const firstSize = ref(proxy.SPLITPANES_CONFIG.DEFAULT_SIZE) |
||||||
|
const loading = ref(false) |
||||||
|
const tableColumns = ref([]) |
||||||
|
const tableData = ref([]) |
||||||
|
const getList = async () => { |
||||||
|
loading.value = true; |
||||||
|
let url = `/report/${props.requestPrefix}` |
||||||
|
try { |
||||||
|
let res = await proxy.axiosPost2(url, queryParams) |
||||||
|
if (res.code == 0) { |
||||||
|
if (direction.value == 'zx') { |
||||||
|
getZXTableData(res) |
||||||
|
} else { |
||||||
|
getHXTableData(res) |
||||||
|
} |
||||||
|
} |
||||||
|
} catch (error) { |
||||||
|
tableColumns.value = []; |
||||||
|
tableData.value = []; |
||||||
|
} finally { |
||||||
|
loading.value = false |
||||||
|
} |
||||||
|
} |
||||||
|
// 修复后的 getZXTableData 方法 |
||||||
|
const getZXTableData = (res) => { |
||||||
|
let { year, list, name } = res.data; |
||||||
|
let columns = [ |
||||||
|
{ |
||||||
|
prop: "station", |
||||||
|
label: "站点" |
||||||
|
}, |
||||||
|
{ |
||||||
|
prop: "type", |
||||||
|
label: "" |
||||||
|
} |
||||||
|
] |
||||||
|
|
||||||
|
// 添加年份列 |
||||||
|
year.forEach(item => { |
||||||
|
columns.push({ |
||||||
|
prop: item, |
||||||
|
label: item |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
tableColumns.value = columns |
||||||
|
|
||||||
|
// 处理数据部分 |
||||||
|
let processedData = []; |
||||||
|
|
||||||
|
list.forEach((item, index) => { |
||||||
|
// 平均水位行 |
||||||
|
const row1 = { |
||||||
|
station: name[index], |
||||||
|
type: "平均水位" |
||||||
|
}; |
||||||
|
|
||||||
|
year.forEach((y, yIndex) => { |
||||||
|
row1[y] = item[yIndex] && item[yIndex].value !== undefined ? item[yIndex].value : '-'; |
||||||
|
}); |
||||||
|
|
||||||
|
// 最高水位行 |
||||||
|
const row2 = { |
||||||
|
station: name[index], |
||||||
|
type: "最高水位" |
||||||
|
}; |
||||||
|
|
||||||
|
year.forEach((y, yIndex) => { |
||||||
|
row2[y] = item[yIndex] && item[yIndex].maxValue !== undefined ? item[yIndex].maxValue : '-'; |
||||||
|
}); |
||||||
|
|
||||||
|
// 最高水位时间行 |
||||||
|
const row3 = { |
||||||
|
station: name[index], |
||||||
|
type: "时间" |
||||||
|
}; |
||||||
|
|
||||||
|
year.forEach((y, yIndex) => { |
||||||
|
row3[y] = item[yIndex] && item[yIndex].maxtm !== undefined ? |
||||||
|
proxy.parseTime(item[yIndex].maxtm, '{y}-{m}-{d}') : '-'; |
||||||
|
}); |
||||||
|
|
||||||
|
// 最低水位行 |
||||||
|
const row4 = { |
||||||
|
station: name[index], |
||||||
|
type: "最低水位" |
||||||
|
}; |
||||||
|
|
||||||
|
year.forEach((y, yIndex) => { |
||||||
|
row4[y] = item[yIndex] && item[yIndex].minValue !== undefined ? item[yIndex].minValue : '-'; |
||||||
|
}); |
||||||
|
|
||||||
|
// 最低水位时间行 |
||||||
|
const row5 = { |
||||||
|
station: name[index], |
||||||
|
type: "时间" |
||||||
|
}; |
||||||
|
|
||||||
|
year.forEach((y, yIndex) => { |
||||||
|
row5[y] = item[yIndex] && item[yIndex].mintm !== undefined ? |
||||||
|
proxy.parseTime(item[yIndex].mintm, '{y}-{m}-{d}') : '-'; |
||||||
|
}); |
||||||
|
|
||||||
|
processedData.push(row1); |
||||||
|
processedData.push(row2); |
||||||
|
processedData.push(row3); |
||||||
|
processedData.push(row4); |
||||||
|
processedData.push(row5); |
||||||
|
}); |
||||||
|
|
||||||
|
// 关键:确保将处理好的数据赋值给 tableData |
||||||
|
tableData.value = processedData; |
||||||
|
} |
||||||
|
const getHXTableData = (res) => { |
||||||
|
const { year, list, name, } = res.data; |
||||||
|
|
||||||
|
if (!year || !list || !name) { |
||||||
|
tableData.value = []; |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
const columns = name.map(stationName => ({ |
||||||
|
prop: stationName, |
||||||
|
label: stationName |
||||||
|
})); |
||||||
|
|
||||||
|
tableColumns.value = columns; |
||||||
|
const processedData = []; |
||||||
|
|
||||||
|
// 添加年份行 |
||||||
|
year.forEach(y => { |
||||||
|
const row = { time: y }; |
||||||
|
name.forEach((stationName, idx) => { |
||||||
|
const stationIndex = name.indexOf(stationName); |
||||||
|
const stationList = list[stationIndex]; |
||||||
|
if (!stationList || stationList.length === 0) { |
||||||
|
row[stationName] = { avg: "-", maxValue: '-', maxtm: '-', minValue: '-', mintm: '-' }; |
||||||
|
return; |
||||||
|
} |
||||||
|
const valueItem = stationList[0]; // 假设只有一个记录 |
||||||
|
row['avg' + idx] = valueItem.value ? valueItem.value : '-' |
||||||
|
row['maxValue' + idx] = valueItem.maxValue ? valueItem.maxValue : '-' |
||||||
|
row['maxtm' + idx] = valueItem.maxtm ? valueItem.maxtm : '-' |
||||||
|
row['minValue' + idx] = valueItem.minValue ? valueItem.minValue : '-' |
||||||
|
row['mintm' + idx] = valueItem.mintm ? valueItem.mintm : '-' |
||||||
|
}); |
||||||
|
processedData.push(row); |
||||||
|
}); |
||||||
|
tableData.value = processedData; |
||||||
|
}; |
||||||
|
|
||||||
|
// 行列合并 |
||||||
|
|
||||||
|
const arraySpanMethod = ({ row, column, rowIndex, columnIndex }) => { |
||||||
|
if (direction.value == 'zx') { |
||||||
|
if (columnIndex === 0) { |
||||||
|
// 每两行合并一次 |
||||||
|
if (rowIndex % 5 === 0) { |
||||||
|
// 偶数行显示,奇数行隐藏 |
||||||
|
return [5, 1]; |
||||||
|
} else { |
||||||
|
// 隐藏奇数行 |
||||||
|
return [0, 0]; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return [1, 1]; |
||||||
|
} |
||||||
|
|
||||||
|
const handleQuery = () => { |
||||||
|
getList() |
||||||
|
} |
||||||
|
|
||||||
|
</script> |
||||||
@ -0,0 +1,86 @@ |
|||||||
|
<template> |
||||||
|
<div class="report-rain"> |
||||||
|
<div class="tjfx-menu"> |
||||||
|
<el-select v-model="menu" placeholder="请选择"> |
||||||
|
<el-option v-for="dict in tjfxMenus" :key="dict.value" :label="dict.label" :value="dict.value" /> |
||||||
|
</el-select> |
||||||
|
</div> |
||||||
|
<!-- 添加加载状态和错误提示 --> |
||||||
|
<el-empty v-if="componentError" class="error-message" description="组件加载失败,请刷新页面重试" /> |
||||||
|
<component :is="currentComponent" :tableTitle="tableTitle"></component> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import { ref, computed, defineAsyncComponent } from 'vue' |
||||||
|
|
||||||
|
|
||||||
|
// 定义菜单选项 |
||||||
|
const tjfxMenus = [ |
||||||
|
{ |
||||||
|
value: '1', |
||||||
|
label: '时段过程' |
||||||
|
}, |
||||||
|
{ |
||||||
|
value: '2', |
||||||
|
label: '年特征值' |
||||||
|
}, { |
||||||
|
value: '3', |
||||||
|
label: '时段统计' |
||||||
|
}, { |
||||||
|
value: '4', |
||||||
|
label: '历史同期对比' |
||||||
|
} |
||||||
|
] |
||||||
|
|
||||||
|
// 当前选中的菜单 |
||||||
|
const menu = ref(tjfxMenus.length > 0 ? tjfxMenus[0].value : '') |
||||||
|
|
||||||
|
// 动态组件映射 |
||||||
|
const componentMap = { |
||||||
|
'1': defineAsyncComponent(() => import('@/views/statistic/rsvr/sdrsvr.vue')), |
||||||
|
'2': defineAsyncComponent(() => import('@/views/statistic/rsvr/rsvrtzz.vue')), |
||||||
|
'3': defineAsyncComponent(() => import('@/views/statistic/rsvr/rsvrsd.vue')), |
||||||
|
'4': defineAsyncComponent(() => import('@/views/statistic/rsvr/rsvrls.vue')) |
||||||
|
} |
||||||
|
// 添加错误处理 |
||||||
|
const componentError = ref(false) |
||||||
|
|
||||||
|
onErrorCaptured((error) => { |
||||||
|
componentError.value = true |
||||||
|
return true |
||||||
|
}) |
||||||
|
|
||||||
|
// 计算当前应该显示的组件 |
||||||
|
const currentComponent = computed(() => { |
||||||
|
return componentMap[menu.value] |
||||||
|
}) |
||||||
|
// 原代码有误,tjfxMenus 中的对象没有 name 属性 |
||||||
|
const tableTitle = computed(() => { |
||||||
|
const currentItem = tjfxMenus.find(item => item.value === menu.value); |
||||||
|
return currentItem ? currentItem.label : ''; |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<style lang="scss"> |
||||||
|
.tjfx-menu { |
||||||
|
position: absolute; |
||||||
|
right: 25px; |
||||||
|
top: 30px; |
||||||
|
z-index: 999999; |
||||||
|
|
||||||
|
.el-input__wrapper { |
||||||
|
background: #10163A; |
||||||
|
} |
||||||
|
|
||||||
|
.el-input__inner { |
||||||
|
background: #10163A; |
||||||
|
color: #fff; |
||||||
|
border-color: #10163A; |
||||||
|
} |
||||||
|
|
||||||
|
.el-select .el-input .el-select__caret { |
||||||
|
color: #fff !important; |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
<template> |
||||||
|
<history-yoy :stationType="stationType" :fixed="fixed" :requestPrefix="requestPrefix" :tableTitle="title"></history-yoy> |
||||||
|
</template> |
||||||
|
<script setup> |
||||||
|
import HistoryYoy from '@/components/HistoryYOY/index.vue' |
||||||
|
const stationType = ref('C') |
||||||
|
const fixed = ref(1) |
||||||
|
const requestPrefix = ref('history') |
||||||
|
const title = '水库水位历史同期对比' |
||||||
|
</script> |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
<template> |
||||||
|
<time-count :stationType="stationType" :fixed="fixed" :requestPrefix="requestPrefix" :tableTitle="title"></time-count> |
||||||
|
</template> |
||||||
|
<script setup> |
||||||
|
import TimeCount from '@/components/TimeCount/index.vue' |
||||||
|
const stationType = ref('C') |
||||||
|
const fixed = ref(1) |
||||||
|
const requestPrefix = ref('sddata') |
||||||
|
const title = '水库水位时段统计(m)' |
||||||
|
</script> |
||||||
@ -0,0 +1,307 @@ |
|||||||
|
<template> |
||||||
|
<div class="app-container app-container-bg"> |
||||||
|
<el-card class="first-card" ref='firstCard' shadow="always"> |
||||||
|
<el-form :model="queryParams" ref="queryForm" :inline="true" @submit.native.prevent> |
||||||
|
<el-form-item label="开始时间"> |
||||||
|
<el-date-picker v-model="queryParams.startTime" type="datetime" value-format="MM-DD" format="YYYY-MM-DD HH:mm:ss" placeholder="选择开始时间" :disabled-date="disabledStartDate"> |
||||||
|
</el-date-picker> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="结束时间"> |
||||||
|
<el-date-picker v-model="queryParams.endTime" type="datetime" value-format="MM-DD" format="YYYY-MM-DD HH:mm:ss" placeholder="选择结束时间" :disabled-date="disabledEndDate"> |
||||||
|
</el-date-picker> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="年份区间"> |
||||||
|
<el-date-picker class="picker-year" style="width:120px" v-model="yearstart" type="year" value-format="YYYY" placeholder="选择开始年"> |
||||||
|
</el-date-picker> |
||||||
|
<span class="pr10 pl10"> - </span> |
||||||
|
<el-date-picker class="picker-year" style="width:120px" v-model="yearend" type="year" value-format="YYYY" placeholder="选择结束年"> |
||||||
|
</el-date-picker> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item> |
||||||
|
<el-button type="primary" icon="Search" @click="handleQuery">查询</el-button> |
||||||
|
</el-form-item> |
||||||
|
<br /> |
||||||
|
<el-form-item label="报表排列方式(按站点)"> |
||||||
|
<el-radio-group v-model="direction" @change="getList"> |
||||||
|
<el-radio label="zx">纵向</el-radio> |
||||||
|
<el-radio label="hx">横向</el-radio> |
||||||
|
</el-radio-group> |
||||||
|
</el-form-item> |
||||||
|
</el-form> |
||||||
|
</el-card> |
||||||
|
<splitpanes :horizontal="device === 'mobile'" class="el-card-p card-shadow carder-border mt10 pad10 default-theme container-box" :push-other-panes="false"> |
||||||
|
<pane :size="firstSize" :min-size="SPLITPANES_CONFIG.MIN_SIZE" :max-size="SPLITPANES_CONFIG.MAX_SIZE" ref="firstPane" class="mr10"> |
||||||
|
<e-tree ref="eTreeRef" :stationType="stationType" @stationChange="handleStationChange" @loadingChange="handleStationLoading"></e-tree> |
||||||
|
</pane> |
||||||
|
<pane :size="100 - firstSize" style="height: 100%;"> |
||||||
|
<div class="main-table-header"> |
||||||
|
<div class="table-title">{{tableTitle}}</div> |
||||||
|
</div> |
||||||
|
<el-table v-table-height v-loading="loading" :data="tableData" border :span-method="arraySpanMethod"> |
||||||
|
<el-table-column :align="alignment" v-for="column in tableColumns" :key="column.prop" :prop="column.prop" :label="column.label" :min-width="column.width || 120"> |
||||||
|
</el-table-column> |
||||||
|
</el-table> |
||||||
|
</pane> |
||||||
|
</splitpanes> |
||||||
|
|
||||||
|
</div> |
||||||
|
</template> |
||||||
|
<script setup> |
||||||
|
import dayjs from 'dayjs'; |
||||||
|
import { |
||||||
|
Splitpanes, |
||||||
|
Pane |
||||||
|
} from 'splitpanes' |
||||||
|
import 'splitpanes/dist/splitpanes.css' |
||||||
|
import ETree from '@/components/ETree/index.vue' |
||||||
|
import useAppStore from '@/store/modules/app' |
||||||
|
const device = computed(() => useAppStore().device); |
||||||
|
|
||||||
|
const props = defineProps({ |
||||||
|
}) |
||||||
|
const { |
||||||
|
proxy |
||||||
|
} = getCurrentInstance() |
||||||
|
const { update_type_options } = proxy.useDict("update_type_options") |
||||||
|
const stationType = 'A' |
||||||
|
const alignment = 'center' |
||||||
|
const firstSize = ref(proxy.SPLITPANES_CONFIG.DEFAULT_SIZE) |
||||||
|
const direction = ref('zx') |
||||||
|
const eTreeRef = ref(null) |
||||||
|
let stnmIdsList = [] |
||||||
|
const handleStationLoading = (loadingState) => { |
||||||
|
loading.value = loadingState; |
||||||
|
} |
||||||
|
// 新增处理站点变化的函数 |
||||||
|
const handleStationChange = (stnmIds) => { |
||||||
|
stnmIdsList = stnmIds |
||||||
|
queryParams.stnmIds = stnmIds.join(',') |
||||||
|
if (stnmIdsList.length == 0) { |
||||||
|
proxy.$modal.msgWarning("请选择站点后查询"); |
||||||
|
return |
||||||
|
} else if (stnmIdsList.length > 50) { |
||||||
|
proxy.$modal.msg("站点最多可选择50个"); |
||||||
|
return |
||||||
|
} else { |
||||||
|
getList() |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
const yearstart = ref(dayjs().subtract(1, 'year').format('YYYY')) |
||||||
|
const yearend = ref(dayjs().format('YYYY')) |
||||||
|
const queryParams = reactive({ |
||||||
|
startTime: dayjs().subtract(7, 'day').format('MM-DD'), |
||||||
|
endTime: dayjs().format('MM-DD'), |
||||||
|
years: `${yearstart.value}-${yearend.value}`, |
||||||
|
stnmIds: '', |
||||||
|
}); |
||||||
|
|
||||||
|
// 禁用开始时间选择器中大于当前时间和结束时间的日期 |
||||||
|
const disabledStartDate = (time) => { |
||||||
|
const endTime = queryParams.endTime ? dayjs(queryParams.endTime) : null; |
||||||
|
return dayjs(time).isAfter(dayjs()) || |
||||||
|
(endTime && dayjs(time).isAfter(endTime)); |
||||||
|
}; |
||||||
|
|
||||||
|
// 禁用结束时间选择器中大于当前时间和小于开始时间的日期 |
||||||
|
const disabledEndDate = (time) => { |
||||||
|
const startTime = queryParams.startTime ? dayjs(queryParams.startTime) : null; |
||||||
|
return dayjs(time).isAfter(dayjs()) || |
||||||
|
(startTime && dayjs(time).isBefore(startTime)); |
||||||
|
}; |
||||||
|
|
||||||
|
const tableTitle = computed(() => { |
||||||
|
const startFormatted = dayjs(queryParams.startTime, 'MM-DD').format('MM月DD日'); |
||||||
|
const endFormatted = dayjs(queryParams.endTime, 'MM-DD').format('MM月DD日'); |
||||||
|
return `${startFormatted}~${endFormatted}历史同期降水量表(mm)`; |
||||||
|
}); |
||||||
|
const tableColumns = ref([]) |
||||||
|
const tableData = ref([]) |
||||||
|
const loading = ref(false) |
||||||
|
|
||||||
|
// 修改 getList 函数来处理动态列和数据 |
||||||
|
const getList = async () => { |
||||||
|
loading.value = true; |
||||||
|
try { |
||||||
|
let res = await proxy.axiosPost2('/report/rainhistorycount', queryParams) |
||||||
|
if (res.code == 0) { |
||||||
|
if (direction.value == 'zx') { |
||||||
|
getZXTableData(res) |
||||||
|
} else { |
||||||
|
getHXTableData(res) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
} catch (error) { |
||||||
|
tableColumns.value = []; |
||||||
|
tableData.value = []; |
||||||
|
} finally { |
||||||
|
loading.value = false |
||||||
|
} |
||||||
|
} |
||||||
|
const getZXTableData = (res) => { |
||||||
|
let { year, list, name, max, min } = res.data; |
||||||
|
let columns = [ |
||||||
|
{ |
||||||
|
prop: "station", |
||||||
|
label: "站点" |
||||||
|
}, |
||||||
|
{ |
||||||
|
prop: "type", |
||||||
|
label: "" |
||||||
|
} |
||||||
|
] |
||||||
|
// 添加站点列 |
||||||
|
year.forEach(item => { |
||||||
|
columns.push({ |
||||||
|
prop: item, |
||||||
|
label: item |
||||||
|
}); |
||||||
|
}); |
||||||
|
tableColumns.value = columns |
||||||
|
// 处理数据部分 - 构造两行合并的数据结构 |
||||||
|
const processedData = []; |
||||||
|
|
||||||
|
list.forEach((item, index) => { |
||||||
|
// 第一行:显示站点名称和"雨量" |
||||||
|
const row1 = { |
||||||
|
station: name[index], |
||||||
|
type: "平均水位" |
||||||
|
}; |
||||||
|
year.forEach(y => { |
||||||
|
row1[y] = item[0].value ? item[0].value.toFixed(1) : '-'; |
||||||
|
}); |
||||||
|
// const row2 = { |
||||||
|
// station: name[index], |
||||||
|
// type: "最高水位" |
||||||
|
// }; |
||||||
|
// year.forEach(y => { |
||||||
|
// row2[y] = item[0].value ? item[0].value.toFixed(1) : '-'; |
||||||
|
// }); |
||||||
|
// const row3 = { |
||||||
|
// station: name[index], |
||||||
|
// type: "时间" |
||||||
|
// }; |
||||||
|
// year.forEach(y => { |
||||||
|
// row3[y] = item[0].tm ? item[0].tm : '-'; |
||||||
|
// }); |
||||||
|
// const row4 = { |
||||||
|
// station: name[index], |
||||||
|
// type: "最高水位" |
||||||
|
// }; |
||||||
|
// year.forEach(y => { |
||||||
|
// row4[y] = item[0].value ? item[0].value.toFixed(1) : '-'; |
||||||
|
// }); |
||||||
|
// const row5 = { |
||||||
|
// station: name[index], |
||||||
|
// type: "时间" |
||||||
|
// }; |
||||||
|
// year.forEach(y => { |
||||||
|
// row5[y] = item[0].tm ? item[0].tm : '-'; |
||||||
|
// }); |
||||||
|
processedData.push(row1); |
||||||
|
// processedData.push(row2); |
||||||
|
// processedData.push(row3); |
||||||
|
// processedData.push(row4); |
||||||
|
// processedData.push(row5); |
||||||
|
}); |
||||||
|
console.log(processedData, 'processedData') |
||||||
|
tableData.value = processedData; |
||||||
|
} |
||||||
|
const getHXTableData = (res) => { |
||||||
|
const { year, list, name, max, min } = res.data; |
||||||
|
|
||||||
|
if (!year || !list || !name || !max || !min) { |
||||||
|
console.error('数据缺失:', { year, list, name, max, min }); |
||||||
|
tableData.value = []; |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
const columns = name.map(stationName => ({ |
||||||
|
prop: stationName, |
||||||
|
label: stationName |
||||||
|
})); |
||||||
|
|
||||||
|
tableColumns.value = columns; |
||||||
|
console.log(columns, 'tableColumns') |
||||||
|
const processedData = []; |
||||||
|
|
||||||
|
// 添加年份行 |
||||||
|
year.forEach(y => { |
||||||
|
const row = { time: y }; |
||||||
|
name.forEach((stationName, idx) => { |
||||||
|
const stationIndex = name.indexOf(stationName); |
||||||
|
const stationList = list[stationIndex]; |
||||||
|
if (!stationList || stationList.length === 0) { |
||||||
|
row[stationName] = { rain: '-', tm: '-' }; |
||||||
|
return; |
||||||
|
} |
||||||
|
const valueItem = stationList[0]; // 假设只有一个记录 |
||||||
|
row['rain' + idx] = valueItem.value ? valueItem.value.toFixed(1) : '-' |
||||||
|
row['tm' + idx] = valueItem.tm ? valueItem.tm.slice(5) : '-' |
||||||
|
}); |
||||||
|
processedData.push(row); |
||||||
|
}); |
||||||
|
// 添加最大值行 |
||||||
|
processedData.push({ |
||||||
|
time: '最大值', |
||||||
|
...name.reduce((acc, stationName, index) => { |
||||||
|
acc['rain' + index] = max[index]?.value ? max[index]?.value.toFixed(1) : '-'; |
||||||
|
acc['tm' + index] = max[index]?.tm ? max[index].tm : '-'; |
||||||
|
return acc; |
||||||
|
}, {}) |
||||||
|
}); |
||||||
|
|
||||||
|
// 添加最小值行 |
||||||
|
processedData.push({ |
||||||
|
time: '最小值', |
||||||
|
...name.reduce((acc, stationName, index) => { |
||||||
|
acc['rain' + index] = min[index]?.value ? min[index]?.value.toFixed(1) : '-'; |
||||||
|
acc['tm' + index] = min[index]?.tm ? min[index].tm : '-'; |
||||||
|
return acc; |
||||||
|
}, {}) |
||||||
|
}); |
||||||
|
|
||||||
|
tableData.value = processedData; |
||||||
|
}; |
||||||
|
|
||||||
|
// 查询 |
||||||
|
const handleQuery = async () => { |
||||||
|
getList() |
||||||
|
} |
||||||
|
// 行列合并 |
||||||
|
|
||||||
|
const arraySpanMethod = ({ row, column, rowIndex, columnIndex }) => { |
||||||
|
// 获取总列数 |
||||||
|
const columnCount = tableColumns.value.length; |
||||||
|
|
||||||
|
// 如果是最后一列(最大值/最小值列),不进行合并 |
||||||
|
if (columnIndex === columnCount - 1 || columnIndex === columnCount - 2) { |
||||||
|
return [1, 1]; |
||||||
|
} |
||||||
|
|
||||||
|
// 第一列(站点名称列)的合并逻辑 |
||||||
|
if (columnIndex === 0) { |
||||||
|
// 每两行合并一次 |
||||||
|
if (rowIndex % 2 === 0) { |
||||||
|
// 偶数行显示,奇数行隐藏 |
||||||
|
return [2, 1]; |
||||||
|
} else { |
||||||
|
// 隐藏奇数行 |
||||||
|
return [0, 0]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 其他数据列的合并逻辑 |
||||||
|
if (rowIndex % 2 === 0) { |
||||||
|
// 偶数行显示,奇数行隐藏 |
||||||
|
return [2, 1]; |
||||||
|
} else { |
||||||
|
// 隐藏奇数行 |
||||||
|
return [0, 0]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
</script> |
||||||
|
<style lang="scss" scoped> |
||||||
|
</style> |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
<template> |
||||||
|
<year-eigenvalue :stationType="stationType" :fixed="fixed" :requestPrefix="requestPrefix" :tableTitle="title" :tableName="tableName"></year-eigenvalue> |
||||||
|
</template> |
||||||
|
<script setup> |
||||||
|
import YearEigenvalue from '@/components/YearEigenvalue/index.vue' |
||||||
|
const stationType = ref('C') |
||||||
|
const fixed = ref(1) |
||||||
|
const requestPrefix = ref('tzzdatahistory') |
||||||
|
let title = '水位年特征值表(m)' |
||||||
|
let tableName=ref('arch_char_rsvr') |
||||||
|
</script> |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
<template> |
||||||
|
<time-process :stationType="stationType" :fixed="fixed" :requestPrefix="requestPrefix" :requestPrefixTable="requestPrefixTable" :tableTitle="title"></time-process> |
||||||
|
</template> |
||||||
|
<script setup> |
||||||
|
import TimeProcess from '@/components/TimeProcess/index.vue' |
||||||
|
const stationType = ref('C') |
||||||
|
const fixed = ref(1) |
||||||
|
const requestPrefix = ref('/ycrsvrdata/chartdata') |
||||||
|
const requestPrefixTable = ref('/ycrsvrdata/gettabledataopen') |
||||||
|
const title = '水位时段过程' |
||||||
|
</script> |
||||||
Loading…
Reference in new issue