fix: refine screen issue acceptance updates

V2.0
M先生 4 weeks ago
parent 610bfc5319
commit 2c5ae7c42f
  1. 4
      src/config/layers.js
  2. 25
      src/services/homeVideoApi.js
  3. 58
      src/views/forestGrassWet/map.js
  4. 38
      src/views/gdMap/map.js
  5. 211
      src/views/grassLivestockBalance/index.vue
  6. 122
      src/views/home/index.vue
  7. 114
      src/views/pastureManagement/index.vue
  8. 18
      src/views/yakManagement/YakOlMap.vue
  9. 38
      src/views/yakManagement/data.js
  10. 41
      src/views/yakManagement/index.vue

@ -15,11 +15,11 @@ export const SATELLITE_TEXTURE_ZOOM = 9
export const NATIONAL_PARK_ZONING_COLORS = { export const NATIONAL_PARK_ZONING_COLORS = {
core: { core: {
fillColor: "#24F6C4", fillColor: "#24F6C4",
strokeColor: "#B7FFF2", strokeColor: "#FFB15E",
}, },
general: { general: {
fillColor: "#FFE06B", fillColor: "#FFE06B",
strokeColor: "#FFF2A0", strokeColor: "#FF8F3D",
}, },
} }

@ -14,6 +14,10 @@ const HONGYUAN_TOWN_NAMES = [
"麦洼乡", "麦洼乡",
"色地镇", "色地镇",
] ]
const MONITORING_FALLBACK_ROWS = [
{ id: "fallback-qiongxi-pasture", name: "邛溪镇家庭生态牧场监控", number: "CAM-001", statusName: "预览点位" },
{ id: "fallback-wache-grassland", name: "瓦切镇草场监控", number: "CAM-002", statusName: "预览点位" },
]
function joinBase(path) { function joinBase(path) {
if (/^https?:\/\//.test(path)) return path if (/^https?:\/\//.test(path)) return path
@ -139,11 +143,18 @@ function normalizeMonitoring(row, index, total, validTotal) {
} }
export async function getHomeMonitorings(params = {}) { export async function getHomeMonitorings(params = {}) {
const data = await requestJson(joinBase("/api/home/monitorings"), { try {
params, const data = await requestJson(joinBase("/api/home/monitorings"), {
timeout: 18000, params,
}).then(unwrapResponse) timeout: 18000,
const rows = Array.isArray(data) ? data : data?.rows || data?.list || [] }).then(unwrapResponse)
const playableRows = rows.filter(isPlayableMonitoring) const rows = Array.isArray(data) ? data : data?.rows || data?.list || []
return playableRows.map((row, index) => normalizeMonitoring(row, index, rows.length, playableRows.length)) const playableRows = rows.filter(isPlayableMonitoring)
if (playableRows.length) {
return playableRows.map((row, index) => normalizeMonitoring(row, index, rows.length, playableRows.length))
}
} catch (error) {
console.warn("[homeVideo] fallback monitorings used", error)
}
return MONITORING_FALLBACK_ROWS.map((row, index) => normalizeMonitoring(row, index, MONITORING_FALLBACK_ROWS.length, 0))
} }

@ -96,6 +96,11 @@ function toFiniteNumber(value, fallback = 0) {
const number = Number(value) const number = Number(value)
return Number.isFinite(number) ? number : fallback return Number.isFinite(number) ? number : fallback
} }
function clampNumber(value, min, max) {
const number = Number(value)
if (!Number.isFinite(number)) return min
return Math.max(min, Math.min(max, number))
}
function escapeHtml(value) { function escapeHtml(value) {
return String(value ?? "").replace(/[&<>"']/g, (char) => ({ return String(value ?? "").replace(/[&<>"']/g, (char) => ({
"&": "&amp;", "&": "&amp;",
@ -1592,20 +1597,29 @@ export class World extends Mini3d {
const baseStyle = this.getVectorFeatureStyle(layer, feature) const baseStyle = this.getVectorFeatureStyle(layer, feature)
this.drawVectorFeature(context, feature.geometry, { this.drawVectorFeature(context, feature.geometry, {
...baseStyle, ...baseStyle,
fill: "#001D2C", fill: "#351A05",
stroke: "#001D2C", stroke: "#351A05",
polygonAlpha: 0, polygonAlpha: 0,
lineAlpha: 0.58, lineAlpha: 0.52,
lineWidth: 12, lineWidth: 9,
glow: true, glow: true,
}) })
this.drawVectorFeature(context, feature.geometry, { this.drawVectorFeature(context, feature.geometry, {
...baseStyle, ...baseStyle,
fill: "#F8FFFF", fill: "#FFE3AA",
stroke: "#F8FFFF", stroke: "#FFE3AA",
polygonAlpha: 0, polygonAlpha: 0,
lineAlpha: 0.96, lineAlpha: 0.98,
lineWidth: 3.2, lineWidth: 2.8,
glow: false,
})
this.drawVectorFeature(context, feature.geometry, {
...baseStyle,
fill: "#FF8F3D",
stroke: "#FF8F3D",
polygonAlpha: 0,
lineAlpha: 0.9,
lineWidth: 1.45,
glow: false, glow: false,
}) })
}) })
@ -2332,11 +2346,11 @@ export class World extends Mini3d {
return { return {
fill: legend.fillColor || "#30DCFF", fill: legend.fillColor || "#30DCFF",
stroke: legend.strokeColor || "#F2FFFF", stroke: legend.strokeColor || "#F2FFFF",
polygonAlpha: 0.36, polygonAlpha: 0.28,
lineAlpha: 1, lineAlpha: 0.95,
lineWidth: 5.8, lineWidth: 3.2,
pointRadius: 4.5, pointRadius: 4.5,
glow: true, glow: false,
} }
} }
if (layer?.boundaryOnly) { if (layer?.boundaryOnly) {
@ -3916,6 +3930,8 @@ export class World extends Mini3d {
label.setParent(labelGroup) label.setParent(labelGroup)
label.userData = { label.userData = {
...province, ...province,
labelType: "town-label",
baseScale: 0.018,
townName: province.name, townName: province.name,
} }
return label return label
@ -5361,10 +5377,26 @@ export class World extends Mini3d {
geoProjection(args) { geoProjection(args) {
return geoMercator().center(this.geoProjectionCenter).scale(this.geoProjectionScale).translate([0, 0])(args) return geoMercator().center(this.geoProjectionCenter).scale(this.geoProjectionScale).translate([0, 0])(args)
} }
updateTownLabelScale() {
const labels = this.townNameLabels || this.otherLabel || []
const camera = this.camera?.instance
const target = this.camera?.controls?.target
if (!labels.length || !camera || !target) return
const distance = camera.position.distanceTo(target)
const scale = clampNumber(distance * 0.00072, 0.011, 0.026)
const opacity = distance > 45 ? "0.76" : distance < 22 ? "0.94" : "1"
labels.forEach((label) => {
if (label?.userData?.labelType !== "town-label") return
label.scale.set(scale, scale, scale)
const element = label.element?.querySelector?.(".other-label")
if (element) element.style.opacity = opacity
})
}
update(delta) { update(delta) {
super.update(delta) super.update(delta)
this.updateDynamicSatelliteTiles() this.updateDynamicSatelliteTiles()
this.updateTownLabelScale()
this.updateTradeFlowPulses(delta) this.updateTradeFlowPulses(delta)
this.interactionManager && this.interactionManager.update() this.interactionManager && this.interactionManager.update()
} }

@ -71,6 +71,11 @@ function normalizeMapGeoJsonData(data) {
}) })
return parsed return parsed
} }
function clampNumber(value, min, max) {
const number = Number(value)
if (!Number.isFinite(number)) return min
return Math.max(min, Math.min(max, number))
}
export class World extends Mini3d { export class World extends Mini3d {
constructor(canvas, assets, options = {}) { constructor(canvas, assets, options = {}) {
super(canvas) super(canvas)
@ -965,9 +970,14 @@ export class World extends Mini3d {
new Vector3(x, -y, self.depth + 0.85) new Vector3(x, -y, self.depth + 0.85)
) )
label3d.setLabelStyle(label, 0.018, "x") label3d.setLabelStyle(label, 0.018, "x")
label.setParent(labelGroup) label.setParent(labelGroup)
return label label.userData = {
} ...province,
labelType: "town-label",
baseScale: 0.018,
}
return label
}
function labelStyle03(data, label3d, labelGroup) { function labelStyle03(data, label3d, labelGroup) {
let label = label3d.create("", `decoration-label ${data.reflect ? " reflect" : ""}`, false) let label = label3d.create("", `decoration-label ${data.reflect ? " reflect" : ""}`, false)
const [x, y] = self.geoProjection(data.center) const [x, y] = self.geoProjection(data.center)
@ -1322,11 +1332,27 @@ export class World extends Mini3d {
}) })
} }
geoProjection(args) { geoProjection(args) {
return geoMercator().center(this.geoProjectionCenter).scale(this.geoProjectionScale).translate([0, 0])(args) return geoMercator().center(this.geoProjectionCenter).scale(this.geoProjectionScale).translate([0, 0])(args)
} }
updateTownLabelScale() {
const labels = this.otherLabel || []
const camera = this.camera?.instance
const target = this.camera?.controls?.target
if (!labels.length || !camera || !target) return
const distance = camera.position.distanceTo(target)
const scale = clampNumber(distance * 0.00072, 0.011, 0.026)
const opacity = distance > 45 ? "0.76" : distance < 22 ? "0.94" : "1"
labels.forEach((label) => {
if (label?.userData?.labelType !== "town-label") return
label.scale.set(scale, scale, scale)
const element = label.element?.querySelector?.(".other-label")
if (element) element.style.opacity = opacity
})
}
update() { update() {
super.update() super.update()
this.updateTownLabelScale()
this.interactionManager && this.interactionManager.update() this.interactionManager && this.interactionManager.update()
} }
pauseScene() { pauseScene() {

@ -204,6 +204,27 @@
</div> </div>
</div> </div>
</div> </div>
<div class="balance-overload-ranking">
<div class="balance-section-title">
<span>超载率排行</span>
<small>乡镇 Top10</small>
</div>
<div class="balance-rank-list">
<div
v-for="item in overloadRankRows"
:key="`overload-${item.key || item.name}`"
class="balance-rank-row"
:class="getOverloadRankClass(item)"
:style="{ '--rank-progress': `${getOverloadRankProgress(item)}%` }"
>
<span>{{ item.rank }}</span>
<strong>{{ item.name }}</strong>
<div><i></i></div>
<em>{{ formatPercent(item.overloadRate) }}</em>
</div>
</div>
</div>
</div> </div>
</section> </section>
</div> </div>
@ -437,6 +458,36 @@ const workflowRows = computed(() => [
]) ])
const activeAdviceRows = computed(() => analysisDone.value ? splitAdvice(activeBalance.value, activeDerived.value.risk) : []) const activeAdviceRows = computed(() => analysisDone.value ? splitAdvice(activeBalance.value, activeDerived.value.risk) : [])
const overloadRankSourceRows = computed(() =>
townRows.value
.filter((item) => Number.isFinite(Number(item.overloadRate)))
.slice()
.sort((a, b) => Number(b.overloadRate) - Number(a.overloadRate))
.slice(0, 10)
)
const overloadRankMax = computed(() => Math.max(
1,
...overloadRankSourceRows.value.map((item) => Math.max(0, Number(item.overloadRate) || 0)),
))
const overloadRankRows = computed(() =>
overloadRankSourceRows.value.map((item, index) => ({
...item,
rank: index + 1,
}))
)
function getOverloadRankProgress(item) {
const rate = Math.max(0, Number(item?.overloadRate) || 0)
return Math.max(6, Math.min(100, (rate / overloadRankMax.value) * 100))
}
function getOverloadRankClass(item) {
const rate = Number(item?.overloadRate)
if (!Number.isFinite(rate)) return "is-empty"
if (rate >= 2.5) return "is-alert"
if (rate >= 2) return "is-warning"
return "is-stable"
}
function updateScreenViewportHeight() { function updateScreenViewportHeight() {
screenViewportHeight.value = typeof window === "undefined" ? 1080 : window.innerHeight screenViewportHeight.value = typeof window === "undefined" ? 1080 : window.innerHeight
@ -509,8 +560,9 @@ function startAnalysis() {
let requestFailed = false let requestFailed = false
const analysisRequest = refreshActiveBalance().catch((error) => { const analysisRequest = refreshActiveBalance().catch((error) => {
requestFailed = true requestFailed = true
analysisError.value = error?.message || "未查询到该对象的草畜平衡结果" console.warn("[grassLivestockBalance] fallback balance row used", error)
return null analysisError.value = "接口暂不可用,已使用本地兜底口径"
return activeBalance.value
}) })
activeAnalysisRequest = analysisRequest activeAnalysisRequest = analysisRequest
analysisTimer = window.setInterval(() => { analysisTimer = window.setInterval(() => {
@ -530,12 +582,15 @@ function startAnalysis() {
if (destroyed || analysisState.value !== "running" || activeAnalysisRequest !== analysisRequest) return if (destroyed || analysisState.value !== "running" || activeAnalysisRequest !== analysisRequest) return
activeAnalysisRequest = null activeAnalysisRequest = null
clearAnalysisTimer() clearAnalysisTimer()
if (requestFailed || !apiResults.value[activeBalance.value?.key]) { if (!activeBalance.value) {
analysisProgress.value = 0 analysisProgress.value = 0
processStageIndex.value = 0 processStageIndex.value = 0
analysisState.value = "idle" analysisState.value = "idle"
return return
} }
if (!requestFailed && !apiResults.value[activeBalance.value?.key]) {
analysisError.value = "未查询到接口结果,已使用本地兜底口径"
}
analysisProgress.value = 100 analysisProgress.value = 100
processStageIndex.value = analysisProcessRows.value.length - 1 processStageIndex.value = analysisProcessRows.value.length - 1
analysisState.value = "done" analysisState.value = "done"
@ -1997,13 +2052,14 @@ function getProcessStepState(index) {
.balance-detail { .balance-detail {
display: grid; display: grid;
grid-template-columns: minmax(0, 1.26fr) minmax(0, 0.74fr); grid-template-columns: minmax(0, 1.04fr) minmax(0, 0.82fr) 248px;
gap: 15px; gap: 12px;
min-height: 0; min-height: 0;
} }
.balance-process, .balance-process,
.balance-decision { .balance-decision,
.balance-overload-ranking {
position: relative; position: relative;
min-width: 0; min-width: 0;
min-height: 0; min-height: 0;
@ -2183,6 +2239,120 @@ function getProcessStepState(index) {
gap: 12px; gap: 12px;
} }
.balance-overload-ranking {
display: grid;
grid-template-rows: 28px minmax(0, 1fr);
gap: 10px;
}
.balance-rank-list {
position: relative;
z-index: 1;
display: grid;
align-content: start;
gap: 5px;
min-height: 0;
overflow: hidden;
}
.balance-rank-row {
position: relative;
display: grid;
grid-template-columns: 24px 58px minmax(0, 1fr) 50px;
align-items: center;
gap: 7px;
height: 25px;
min-width: 0;
padding: 0 8px 0 6px;
background:
linear-gradient(90deg, rgba(48, 220, 255, 0.08), transparent 62%),
rgba(4, 33, 46, 0.62);
clip-path: polygon(0 0, calc(100% - 8px) 0, 100% 50%, calc(100% - 8px) 100%, 0 100%);
box-sizing: border-box;
&::before {
content: "";
position: absolute;
inset: 0;
border: 1px solid rgba(48, 220, 255, 0.08);
clip-path: inherit;
pointer-events: none;
}
> span {
position: relative;
z-index: 1;
width: 20px;
height: 18px;
color: rgba(126, 251, 246, 0.9);
font-family: D-DIN, Arial, sans-serif;
font-size: 12px;
line-height: 18px;
font-weight: 800;
text-align: center;
background: rgba(126, 251, 246, 0.08);
}
strong {
position: relative;
z-index: 1;
min-width: 0;
overflow: hidden;
color: rgba(237, 253, 255, 0.9);
font-size: 12px;
line-height: 18px;
font-weight: 800;
text-overflow: ellipsis;
white-space: nowrap;
}
div {
position: relative;
z-index: 1;
height: 5px;
overflow: hidden;
background: rgba(126, 251, 246, 0.08);
i {
display: block;
width: var(--rank-progress);
height: 100%;
background: linear-gradient(90deg, rgba(126, 251, 246, 0.35), #7efbf6);
box-shadow: 0 0 10px rgba(126, 251, 246, 0.22);
}
}
em {
position: relative;
z-index: 1;
color: #7efbf6;
font-family: D-DIN, Arial, sans-serif;
font-size: 12px;
font-style: normal;
font-weight: 800;
text-align: right;
white-space: nowrap;
}
&.is-warning div i {
background: linear-gradient(90deg, rgba(255, 215, 108, 0.32), #ffd76c);
box-shadow: 0 0 10px rgba(255, 215, 108, 0.2);
}
&.is-warning em {
color: #ffd76c;
}
&.is-alert div i {
background: linear-gradient(90deg, rgba(255, 143, 107, 0.3), #ff8f6b);
box-shadow: 0 0 10px rgba(255, 143, 107, 0.24);
}
&.is-alert em {
color: #ffb15e;
}
}
.balance-conclusion { .balance-conclusion {
position: relative; position: relative;
z-index: 1; z-index: 1;
@ -2542,7 +2712,8 @@ function getProcessStepState(index) {
.balance-dashboard, .balance-dashboard,
.balance-compare, .balance-compare,
.balance-process, .balance-process,
.balance-decision { .balance-decision,
.balance-overload-ranking {
background: linear-gradient(180deg, rgba(7, 48, 62, 0.9), rgba(3, 28, 42, 0.9)); background: linear-gradient(180deg, rgba(7, 48, 62, 0.9), rgba(3, 28, 42, 0.9));
clip-path: polygon(0 0, calc(100% - 16px) 0, 100% 16px, 100% 100%, 0 100%); clip-path: polygon(0 0, calc(100% - 16px) 0, 100% 16px, 100% 100%, 0 100%);
@ -2607,7 +2778,8 @@ function getProcessStepState(index) {
} }
.balance-process, .balance-process,
.balance-decision { .balance-decision,
.balance-overload-ranking {
padding: 14px 16px; padding: 14px 16px;
} }
@ -2712,12 +2884,13 @@ function getProcessStepState(index) {
} }
.balance-sheet.is-ready .balance-detail { .balance-sheet.is-ready .balance-detail {
grid-template-columns: minmax(0, 1.18fr) minmax(360px, 0.82fr); grid-template-columns: minmax(0, 1fr) minmax(318px, 0.78fr) 238px;
gap: 14px; gap: 12px;
} }
.balance-sheet.is-ready .balance-process, .balance-sheet.is-ready .balance-process,
.balance-sheet.is-ready .balance-decision { .balance-sheet.is-ready .balance-decision,
.balance-sheet.is-ready .balance-overload-ranking {
overflow: hidden; overflow: hidden;
padding: 13px 15px; padding: 13px 15px;
} }
@ -2732,6 +2905,11 @@ function getProcessStepState(index) {
gap: 10px; gap: 10px;
} }
.balance-sheet.is-ready .balance-overload-ranking {
grid-template-rows: 26px minmax(0, 1fr);
gap: 8px;
}
.balance-sheet.is-ready .balance-section-title { .balance-sheet.is-ready .balance-section-title {
height: 26px; height: 26px;
} }
@ -2807,4 +2985,15 @@ function getProcessStepState(index) {
line-height: 18px; line-height: 18px;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
} }
.balance-sheet.is-ready .balance-rank-list {
gap: 4px;
}
.balance-sheet.is-ready .balance-rank-row {
grid-template-columns: 22px 54px minmax(0, 1fr) 48px;
gap: 6px;
height: 23px;
padding-right: 7px;
}
</style> </style>

@ -138,20 +138,27 @@
:disabled="homeVideoRows.length <= 1" :disabled="homeVideoRows.length <= 1"
@click="showPrevVideo" @click="showPrevVideo"
></button> ></button>
<div <div class="home-video-grid" :class="{ 'is-single': visibleHomeVideos.length <= 1 }">
:key="panel.live.key" <div
:ref="setHomeVideoScreenRef" v-for="item in visibleHomeVideos"
class="home-video-screen" :key="item.key"
:class="{ 'is-playing': videoPlaying, 'is-loading': videoLoading }" :ref="(el) => setHomeVideoItemRef(el, item)"
:style="videoScreenStyle(panel.live)" class="home-video-screen"
@click="playCurrentVideo" :class="{
> 'is-active': item.isActive,
<div :id="videoElementId" class="home-video-player"></div> 'is-playing': videoPlaying && item.isActive,
<div v-if="!videoPlaying" class="home-video-scan"></div> 'is-loading': videoLoading && item.isActive,
<div v-if="!videoPlaying && panel.live.hdPlayUrl" class="home-video-play"></div> }"
<div v-if="videoLoading" class="home-video-loading">加载中</div> :style="videoScreenStyle(item.live, item.isActive)"
<div v-if="videoError" class="home-video-error">{{ videoError }}</div> @click="playHomeVideoAt(item.index)"
<div class="home-video-point-name">{{ panel.live.title }}</div> >
<div v-if="item.isActive" :id="videoElementId" class="home-video-player"></div>
<div v-if="!videoPlaying || !item.isActive" class="home-video-scan"></div>
<div v-if="(!videoPlaying || !item.isActive) && item.live.hdPlayUrl" class="home-video-play"></div>
<div v-if="videoLoading && item.isActive" class="home-video-loading">加载中</div>
<div v-if="videoError && item.isActive" class="home-video-error">{{ videoError }}</div>
<div class="home-video-point-name">{{ item.live.title }}</div>
</div>
</div> </div>
<button <button
class="home-video-nav home-video-next" class="home-video-nav home-video-next"
@ -237,8 +244,10 @@ import { chartTooltip, withChartDefaults } from "@/utils/chartTheme"
import { import {
buildForecastSeries, buildForecastSeries,
estimatedStock, estimatedStock,
formatForecastDayLabel,
forecastPeriods, forecastPeriods,
getForecastPeriod, getForecastPeriod,
getForecastMonthCount,
structureRows, structureRows,
} from "@/views/yakManagement/data" } from "@/views/yakManagement/data"
import { balanceTownRows } from "@/views/grassLivestockBalance/data" import { balanceTownRows } from "@/views/grassLivestockBalance/data"
@ -417,6 +426,7 @@ const activeVideoIndex = computed(() => {
return (chartMotionTick.value + activeVideoManualIndex.value) % length return (chartMotionTick.value + activeVideoManualIndex.value) % length
}) })
const currentHomeVideo = computed(() => homeVideoRows.value[activeVideoIndex.value] || emptyHomeVideo) const currentHomeVideo = computed(() => homeVideoRows.value[activeVideoIndex.value] || emptyHomeVideo)
const visibleHomeVideos = computed(() => buildVideoPreviewRows(homeVideoRows.value, activeVideoIndex.value, emptyHomeVideo))
const leftPanels = computed(() => [ const leftPanels = computed(() => [
pickTopicPanel("grassland", "grassland-town"), pickTopicPanel("grassland", "grassland-town"),
@ -833,6 +843,28 @@ function setHomeVideoScreenRef(el) {
homeVideoScreenRef.value = el || null homeVideoScreenRef.value = el || null
} }
function setHomeVideoItemRef(el, item) {
if (item?.isActive) homeVideoScreenRef.value = el || null
}
function buildVideoPreviewRows(rows = [], activeIndex = 0, emptyRow = emptyHomeVideo) {
const sourceRows = Array.isArray(rows) ? rows : []
if (!sourceRows.length) {
return [{ key: `empty-${emptyRow.key || "video"}`, index: 0, live: emptyRow, isActive: true }]
}
const count = Math.min(2, sourceRows.length)
return Array.from({ length: count }, (_, offset) => {
const index = (activeIndex + offset + sourceRows.length) % sourceRows.length
const live = sourceRows[index] || emptyRow
return {
key: `${live.key || live.title || "video"}-${index}`,
index,
live,
isActive: index === activeIndex,
}
})
}
function startChartMotion() { function startChartMotion() {
if (!props.active || destroyed) return if (!props.active || destroyed) return
stopChartMotion() stopChartMotion()
@ -877,9 +909,9 @@ async function loadHomeMonitorings() {
} }
} }
function videoScreenStyle(live) { function videoScreenStyle(live, isActive = true) {
const image = live?.image || liveSceneImage const image = live?.image || liveSceneImage
return videoPlaying.value return videoPlaying.value && isActive
? undefined ? undefined
: { : {
backgroundImage: `linear-gradient(180deg, rgba(0, 16, 26, 0.08), rgba(0, 16, 26, 0.32)), url(${image})`, backgroundImage: `linear-gradient(180deg, rgba(0, 16, 26, 0.08), rgba(0, 16, 26, 0.32)), url(${image})`,
@ -903,6 +935,21 @@ function stepVideo(step) {
chartMotionTick.value = 0 chartMotionTick.value = 0
} }
function playHomeVideoAt(index) {
const length = homeVideoRows.value.length
if (!length) {
void playCurrentVideo()
return
}
const targetIndex = ((Number(index) || 0) + length) % length
if (targetIndex !== activeVideoIndex.value || videoPlayer || videoLoading.value) {
stopVideoPlayer({ resetError: true })
}
activeVideoManualIndex.value = targetIndex
chartMotionTick.value = 0
void nextTick(playCurrentVideo)
}
function homeBalanceGaugeProgress(item) { function homeBalanceGaugeProgress(item) {
const value = Math.max(0, Number(item?.value || 0)) const value = Math.max(0, Number(item?.value || 0))
const maxValue = Math.max(5, ...homeBalanceRows.value.map((row) => Math.max(0, Number(row.value || 0)))) const maxValue = Math.max(5, ...homeBalanceRows.value.map((row) => Math.max(0, Number(row.value || 0))))
@ -1337,14 +1384,13 @@ function getForecastGranularity(key) {
function buildForecastSeriesByGranularity(period, granularityKey = "month") { function buildForecastSeriesByGranularity(period, granularityKey = "month") {
if (granularityKey === "month") return buildForecastSeries(period) if (granularityKey === "month") return buildForecastSeries(period)
const dayCount = Math.max(1, Math.round(Number(period.months || 0) * 30)) const monthCount = getForecastMonthCount(period)
const dayCount = Math.max(1, Math.round(Math.max(1, monthCount - 1) * 30))
const stepDays = granularityKey === "week" ? 7 : 1 const stepDays = granularityKey === "week" ? 7 : 1
const count = Math.ceil(dayCount / stepDays) + 1 const count = Math.ceil(dayCount / stepDays) + 1
const labels = Array.from({ length: count }, (_, index) => { const labels = Array.from({ length: count }, (_, index) => {
const day = Math.min(dayCount, index * stepDays) const day = Math.min(dayCount, index * stepDays)
if (index === 0) return "2026-06" return formatForecastDayLabel(day)
if (day >= dayCount) return period.label
return granularityKey === "week" ? `${index}` : `${day}`
}) })
const stock = labels.map((_, index) => { const stock = labels.map((_, index) => {
const day = Math.min(dayCount, index * stepDays) const day = Math.min(dayCount, index * stepDays)
@ -2114,20 +2160,32 @@ function handleOutsideFeaturePointerDown(event) {
position: relative; position: relative;
display: block; display: block;
height: 100%; height: 100%;
padding: 2px 18px; padding: 2px 24px;
box-sizing: border-box; box-sizing: border-box;
pointer-events: auto; pointer-events: auto;
} }
.home-video-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
height: 100%;
min-height: 0;
}
.home-video-grid.is-single {
grid-template-columns: minmax(0, 1fr);
}
.home-video-screen { .home-video-screen {
position: relative; position: relative;
width: 88%; width: 100%;
height: 100%; height: 100%;
margin: 0 auto; margin: 0;
min-width: 0; min-width: 0;
overflow: hidden; overflow: hidden;
cursor: pointer; cursor: pointer;
border: 1px solid rgba(48, 220, 255, 0.34); border: 1px solid rgba(48, 220, 255, 0.25);
background-color: #020d14; background-color: #020d14;
background-position: center; background-position: center;
background-size: cover; background-size: cover;
@ -2137,6 +2195,18 @@ function handleOutsideFeaturePointerDown(event) {
0 0 16px rgba(18, 145, 190, 0.14); 0 0 16px rgba(18, 145, 190, 0.14);
} }
.home-video-screen.is-active {
border-color: rgba(126, 251, 246, 0.66);
box-shadow:
inset 0 0 0 1px rgba(10, 255, 214, 0.12),
inset 0 0 26px rgba(48, 220, 255, 0.18),
0 0 18px rgba(18, 202, 204, 0.18);
}
.home-video-screen:not(.is-active) {
opacity: 0.88;
}
.home-video-screen.is-playing { .home-video-screen.is-playing {
background-image: none !important; background-image: none !important;
} }
@ -2187,11 +2257,11 @@ function handleOutsideFeaturePointerDown(event) {
} }
.home-video-prev { .home-video-prev {
left: 25px; left: 18px;
} }
.home-video-next { .home-video-next {
right: 25px; right: 18px;
} }
.home-video-screen::before { .home-video-screen::before {

@ -39,20 +39,27 @@
> >
</button> </button>
<div <div class="pasture-video-grid" :class="{ 'is-single': visiblePastureVideos.length <= 1 }">
:key="currentPastureVideo.key" <div
:ref="setPastureVideoScreenRef" v-for="item in visiblePastureVideos"
class="pasture-video-screen" :key="item.key"
:class="{ 'is-playing': pastureVideoPlaying, 'is-loading': pastureVideoLoading }" :ref="(el) => setPastureVideoItemRef(el, item)"
:style="pastureVideoScreenStyle(currentPastureVideo)" class="pasture-video-screen"
@click="playCurrentPastureVideo" :class="{
> 'is-active': item.isActive,
<div :id="pastureVideoElementId" class="pasture-video-player"></div> 'is-playing': pastureVideoPlaying && item.isActive,
<div v-if="!pastureVideoPlaying" class="pasture-video-scan"></div> 'is-loading': pastureVideoLoading && item.isActive,
<div v-if="!pastureVideoPlaying && currentPastureVideo.hdPlayUrl" class="pasture-video-play"></div> }"
<div v-if="pastureVideoLoading" class="pasture-video-loading">加载中</div> :style="pastureVideoScreenStyle(item.live, item.isActive)"
<div v-if="pastureVideoError" class="pasture-video-error">{{ pastureVideoError }}</div> @click="playPastureVideoAt(item.index)"
<div class="pasture-video-point-name">{{ currentPastureVideo.title }}</div> >
<div v-if="item.isActive" :id="pastureVideoElementId" class="pasture-video-player"></div>
<div v-if="!pastureVideoPlaying || !item.isActive" class="pasture-video-scan"></div>
<div v-if="(!pastureVideoPlaying || !item.isActive) && item.live.hdPlayUrl" class="pasture-video-play"></div>
<div v-if="pastureVideoLoading && item.isActive" class="pasture-video-loading">加载中</div>
<div v-if="pastureVideoError && item.isActive" class="pasture-video-error">{{ pastureVideoError }}</div>
<div class="pasture-video-point-name">{{ item.live.title }}</div>
</div>
</div> </div>
<button <button
class="pasture-video-nav pasture-video-next" class="pasture-video-nav pasture-video-next"
@ -190,7 +197,7 @@ const pastureVideoElementId = `pasture-video-player-${Math.random().toString(36)
const pastureVideoLoadTimeoutMs = 12000 const pastureVideoLoadTimeoutMs = 12000
const pastureMapTileUrlTemplate = "https://qkl-map.oss-cn-chengdu.aliyuncs.com/hy-result/{z}/{x}/{y}.png" const pastureMapTileUrlTemplate = "https://qkl-map.oss-cn-chengdu.aliyuncs.com/hy-result/{z}/{x}/{y}.png"
const pastureOlCenter = [103.07984, 33.029058] const pastureOlCenter = [103.07984, 33.029058]
const pastureBoundarySldBody = `<?xml version="1.0" encoding="UTF-8"?><StyledLayerDescriptor version="1.0.0" xmlns="http://www.opengis.net/sld" xmlns:ogc="http://www.opengis.net/ogc" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><NamedLayer><Name>t_grassland_result_caodi</Name><UserStyle><Title>pasture boundary</Title><FeatureTypeStyle><Rule><PolygonSymbolizer><Fill><CssParameter name="fill">#FFE35A</CssParameter><CssParameter name="fill-opacity">0</CssParameter></Fill><Stroke><CssParameter name="stroke">#FFE35A</CssParameter><CssParameter name="stroke-width">2.4</CssParameter><CssParameter name="stroke-opacity">0.98</CssParameter></Stroke></PolygonSymbolizer></Rule></FeatureTypeStyle></UserStyle></NamedLayer></StyledLayerDescriptor>` const pastureBoundarySldBody = `<?xml version="1.0" encoding="UTF-8"?><StyledLayerDescriptor version="1.0.0" xmlns="http://www.opengis.net/sld" xmlns:ogc="http://www.opengis.net/ogc" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><NamedLayer><Name>t_grassland_result_caodi</Name><UserStyle><Title>pasture boundary</Title><FeatureTypeStyle><Rule><PolygonSymbolizer><Fill><CssParameter name="fill">#FFE35A</CssParameter><CssParameter name="fill-opacity">0</CssParameter></Fill><Stroke><CssParameter name="stroke">#FFE35A</CssParameter><CssParameter name="stroke-width">1.2</CssParameter><CssParameter name="stroke-opacity">0.92</CssParameter></Stroke></PolygonSymbolizer></Rule></FeatureTypeStyle></UserStyle></NamedLayer></StyledLayerDescriptor>`
const pastureYakRecognitionLayer = { const pastureYakRecognitionLayer = {
key: pastureYakLayerKey, key: pastureYakLayerKey,
sourceType: "wms", sourceType: "wms",
@ -506,6 +513,7 @@ const activePastureVideoIndex = computed(() => {
return activePastureVideoManualIndex.value % length return activePastureVideoManualIndex.value % length
}) })
const currentPastureVideo = computed(() => pastureVideoRows.value[activePastureVideoIndex.value] || emptyPastureVideo) const currentPastureVideo = computed(() => pastureVideoRows.value[activePastureVideoIndex.value] || emptyPastureVideo)
const visiblePastureVideos = computed(() => buildPastureVideoPreviewRows(pastureVideoRows.value, activePastureVideoIndex.value, emptyPastureVideo))
function normalizeVisualRows(rows = []) { function normalizeVisualRows(rows = []) {
return (Array.isArray(rows) ? rows : []) return (Array.isArray(rows) ? rows : [])
@ -596,9 +604,31 @@ function setPastureVideoScreenRef(el) {
pastureVideoScreenRef.value = el || null pastureVideoScreenRef.value = el || null
} }
function pastureVideoScreenStyle(live) { function setPastureVideoItemRef(el, item) {
if (item?.isActive) pastureVideoScreenRef.value = el || null
}
function buildPastureVideoPreviewRows(rows = [], activeIndex = 0, emptyRow = emptyPastureVideo) {
const sourceRows = Array.isArray(rows) ? rows : []
if (!sourceRows.length) {
return [{ key: `empty-${emptyRow.key || "video"}`, index: 0, live: emptyRow, isActive: true }]
}
const count = Math.min(2, sourceRows.length)
return Array.from({ length: count }, (_, offset) => {
const index = (activeIndex + offset + sourceRows.length) % sourceRows.length
const live = sourceRows[index] || emptyRow
return {
key: `${live.key || live.title || "video"}-${index}`,
index,
live,
isActive: index === activeIndex,
}
})
}
function pastureVideoScreenStyle(live, isActive = true) {
const image = live?.image || liveSceneImage const image = live?.image || liveSceneImage
return pastureVideoPlaying.value return pastureVideoPlaying.value && isActive
? undefined ? undefined
: { : {
backgroundImage: `linear-gradient(180deg, rgba(0, 16, 26, 0.08), rgba(0, 16, 26, 0.32)), url(${image})`, backgroundImage: `linear-gradient(180deg, rgba(0, 16, 26, 0.08), rgba(0, 16, 26, 0.32)), url(${image})`,
@ -620,6 +650,20 @@ function stepPastureVideo(step) {
activePastureVideoManualIndex.value = (activePastureVideoIndex.value + step + length) % length activePastureVideoManualIndex.value = (activePastureVideoIndex.value + step + length) % length
} }
function playPastureVideoAt(index) {
const length = pastureVideoRows.value.length
if (!length) {
void playCurrentPastureVideo()
return
}
const targetIndex = ((Number(index) || 0) + length) % length
if (targetIndex !== activePastureVideoIndex.value || pastureVideoPlayer || pastureVideoLoading.value) {
stopPastureVideoPlayer({ resetError: true })
}
activePastureVideoManualIndex.value = targetIndex
void nextTick(playCurrentPastureVideo)
}
async function playCurrentPastureVideo() { async function playCurrentPastureVideo() {
const live = currentPastureVideo.value const live = currentPastureVideo.value
if (!live?.hdPlayUrl || !live?.token) { if (!live?.hdPlayUrl || !live?.token) {
@ -2233,20 +2277,32 @@ function isLngLatInPolygon(point, polygon) {
position: relative; position: relative;
display: block; display: block;
height: 100%; height: 100%;
padding: 2px 18px; padding: 2px 24px;
box-sizing: border-box; box-sizing: border-box;
pointer-events: auto; pointer-events: auto;
} }
.pasture-video-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
height: 100%;
min-height: 0;
}
.pasture-video-grid.is-single {
grid-template-columns: minmax(0, 1fr);
}
.pasture-video-screen { .pasture-video-screen {
position: relative; position: relative;
width: 88%; width: 100%;
height: 100%; height: 100%;
min-width: 0; min-width: 0;
margin: 0 auto; margin: 0;
overflow: hidden; overflow: hidden;
cursor: pointer; cursor: pointer;
border: 1px solid rgba(48, 220, 255, 0.34); border: 1px solid rgba(48, 220, 255, 0.25);
background-color: #020d14; background-color: #020d14;
background-position: center; background-position: center;
background-size: cover; background-size: cover;
@ -2256,6 +2312,18 @@ function isLngLatInPolygon(point, polygon) {
0 0 16px rgba(18, 145, 190, 0.14); 0 0 16px rgba(18, 145, 190, 0.14);
} }
.pasture-video-screen.is-active {
border-color: rgba(126, 251, 246, 0.66);
box-shadow:
inset 0 0 0 1px rgba(10, 255, 214, 0.12),
inset 0 0 26px rgba(48, 220, 255, 0.18),
0 0 18px rgba(18, 202, 204, 0.18);
}
.pasture-video-screen:not(.is-active) {
opacity: 0.88;
}
.pasture-video-screen.is-playing { .pasture-video-screen.is-playing {
background-image: none !important; background-image: none !important;
} }
@ -2310,11 +2378,11 @@ function isLngLatInPolygon(point, polygon) {
} }
.pasture-video-prev { .pasture-video-prev {
left: 25px; left: 18px;
} }
.pasture-video-next { .pasture-video-next {
right: 25px; right: 18px;
} }
.pasture-video-screen::before { .pasture-video-screen::before {

@ -865,18 +865,22 @@ function clearPastureFocus() {
function createTownLabelStyle(feature, resolution) { function createTownLabelStyle(feature, resolution) {
const name = feature.get("name") || "" const name = feature.get("name") || ""
const compact = resolution > 150 const viewResolution = Number(resolution) || 0
const hidden = resolution > 420 const compact = viewResolution > 180
const close = viewResolution < 55
const hidden = viewResolution > 520
if (hidden) return labelPointStyle if (hidden) return labelPointStyle
const fontSize = compact ? 12 : 13 const fontSize = close ? 14 : compact ? 11 : 12.5
const minWidthPadding = compact ? [5, 10, 5, 10] : [6, 12, 6, 12] const minWidthPadding = close ? [6, 12, 6, 12] : compact ? [4, 8, 4, 8] : [5, 10, 5, 10]
const triangleRadius = close ? 5 : compact ? 3.8 : 4.4
const offsetY = close ? -20 : compact ? -15 : -17
return [ return [
new Style({ new Style({
image: new RegularShape({ image: new RegularShape({
points: 3, points: 3,
radius: compact ? 4 : 5, radius: triangleRadius,
rotation: Math.PI, rotation: Math.PI,
displacement: [0, 14], displacement: [0, close ? 16 : compact ? 12 : 14],
fill: new Fill({ color: "rgba(126, 251, 246, 0.98)" }), fill: new Fill({ color: "rgba(126, 251, 246, 0.98)" }),
stroke: new Stroke({ color: "rgba(4, 28, 40, 0.95)", width: 1.2 }), stroke: new Stroke({ color: "rgba(4, 28, 40, 0.95)", width: 1.2 }),
}), }),
@ -890,7 +894,7 @@ function createTownLabelStyle(feature, resolution) {
backgroundFill: new Fill({ color: "rgba(3, 21, 33, 0.9)" }), backgroundFill: new Fill({ color: "rgba(3, 21, 33, 0.9)" }),
backgroundStroke: new Stroke({ color: "rgba(126, 251, 246, 0.58)", width: 1.2 }), backgroundStroke: new Stroke({ color: "rgba(126, 251, 246, 0.58)", width: 1.2 }),
padding: minWidthPadding, padding: minWidthPadding,
offsetY: -17, offsetY,
}), }),
}), }),
] ]

@ -120,6 +120,9 @@ export const forecastFactorRows = [
{ name: "幼畜存活", value: 73 }, { name: "幼畜存活", value: 73 },
] ]
const FORECAST_START_YEAR = 2026
const FORECAST_START_MONTH = 6
export function getForecastPeriod(key) { export function getForecastPeriod(key) {
return forecastPeriods.find((item) => item.key === key) || forecastPeriods[1] return forecastPeriods.find((item) => item.key === key) || forecastPeriods[1]
} }
@ -153,22 +156,43 @@ export function getTownForecastRows(period) {
} }
export function buildForecastSeries(period) { export function buildForecastSeries(period) {
const count = period.months + 1 const count = getForecastMonthCount(period)
const labels = Array.from({ length: count }, (_, index) => { const labels = Array.from({ length: count }, (_, index) => {
if (index === 0) return "2026-06" return formatForecastMonthLabel(index)
if (index === period.months) return period.label
return `+${index}`
}) })
const stock = labels.map((_, index) => { const stock = labels.map((_, index) => {
const ratio = index / period.months const ratio = getForecastStepRatio(index, count)
const seasonal = Math.sin(index / 2.6) * Math.sin(Math.PI * ratio) * Math.min(360, period.months * 18) const seasonal = Math.sin(index / 2.6) * Math.sin(Math.PI * ratio) * Math.min(360, period.months * 18)
return Math.round(estimatedStock + (period.stock - estimatedStock) * ratio + seasonal) return Math.round(estimatedStock + (period.stock - estimatedStock) * ratio + seasonal)
}) })
const births = labels.map((_, index) => Math.round((period.births / period.months) * index)) const births = labels.map((_, index) => Math.round(period.births * getForecastStepRatio(index, count)))
const outflow = labels.map((_, index) => Math.round((period.outflow / period.months) * index)) const outflow = labels.map((_, index) => Math.round(period.outflow * getForecastStepRatio(index, count)))
return { labels, stock, births, outflow } return { labels, stock, births, outflow }
} }
export function getForecastMonthCount(period = {}) {
return Math.max(1, Math.round(Number(period.months || 1)))
}
export function getForecastStepRatio(index, count) {
if (count <= 1) return 1
return Math.min(1, Math.max(0, index / (count - 1)))
}
export function formatForecastMonthLabel(monthOffset = 0) {
const date = new Date(Date.UTC(FORECAST_START_YEAR, FORECAST_START_MONTH - 1 + monthOffset, 1))
return `${String(date.getUTCFullYear()).slice(-2)}-${String(date.getUTCMonth() + 1).padStart(2, "0")}`
}
export function formatForecastDayLabel(dayOffset = 0) {
const date = new Date(Date.UTC(FORECAST_START_YEAR, FORECAST_START_MONTH - 1, 1 + dayOffset))
return [
String(date.getUTCFullYear()).slice(-2),
String(date.getUTCMonth() + 1).padStart(2, "0"),
String(date.getUTCDate()).padStart(2, "0"),
].join("-")
}
function withStockCounts(rows, total) { function withStockCounts(rows, total) {
const baseRows = rows.map((item, index) => { const baseRows = rows.map((item, index) => {
const exact = (total * item.ratio) / 100 const exact = (total * item.ratio) / 100

@ -727,8 +727,10 @@ import {
canonicalTownName, canonicalTownName,
estimatedStock, estimatedStock,
expertCorrection, expertCorrection,
formatForecastDayLabel,
forecastFactorRows, forecastFactorRows,
forecastPeriods, forecastPeriods,
getForecastMonthCount,
genderAgeRows, genderAgeRows,
getForecastPeriod, getForecastPeriod,
getForecastStructureRows, getForecastStructureRows,
@ -1830,6 +1832,12 @@ const forecastTownOption = computed(() => buildHorizontalBarOption(forecastTownR
const forecastStructureOption = computed(() => { const forecastStructureOption = computed(() => {
const rows = forecastStructureRows.value const rows = forecastStructureRows.value
const legendRich = rows.reduce((acc, item, index) => {
const color = item.color || palette[index % palette.length]
acc[`name${index}`] = { color, fontSize: 10, fontWeight: 800 }
acc[`percent${index}`] = { color, fontSize: 10, fontFamily: "D-DIN, Arial, sans-serif", fontWeight: 800 }
return acc
}, {})
return { return {
color: palette, color: palette,
tooltip: chartTooltip((params) => { tooltip: chartTooltip((params) => {
@ -1844,10 +1852,11 @@ const forecastStructureOption = computed(() => {
itemHeight: 8, itemHeight: 8,
itemGap: 6, itemGap: 6,
formatter(name) { formatter(name) {
const row = rows.find((item) => item.name === name) const index = rows.findIndex((item) => item.name === name)
return row ? `${name} ${formatPercent(row.ratio)}` : name const row = rows[index]
return row ? `{name${index}|${name}} {percent${index}|${formatPercent(row.ratio)}}` : name
}, },
textStyle: { color: "rgba(222, 250, 255, 0.8)", fontSize: 10 }, textStyle: { color: "rgba(222, 250, 255, 0.8)", fontSize: 10, rich: legendRich },
}, },
series: [ series: [
{ {
@ -4095,7 +4104,6 @@ function emitModuleStats() {
items: [ items: [
{ zh: "流通总量", value: tradeMetricValue("headCount"), unit: "头" }, { zh: "流通总量", value: tradeMetricValue("headCount"), unit: "头" },
{ zh: "出证记录", value: tradeMetricValue("recordCount"), unit: "条" }, { zh: "出证记录", value: tradeMetricValue("recordCount"), unit: "条" },
{ zh: "交易销售", value: tradeMetricValue("saleHeadCount"), unit: "头" },
{ zh: "运输车辆", value: tradeMetricValue("vehicleCount"), unit: "辆" }, { zh: "运输车辆", value: tradeMetricValue("vehicleCount"), unit: "辆" },
], ],
}) })
@ -4245,14 +4253,13 @@ function getForecastGranularity(key) {
function buildForecastSeriesByGranularity(period, granularityKey = "month") { function buildForecastSeriesByGranularity(period, granularityKey = "month") {
if (granularityKey === "month") return buildForecastSeries(period) if (granularityKey === "month") return buildForecastSeries(period)
const dayCount = Math.max(1, Math.round(Number(period.months || 0) * 30)) const monthCount = getForecastMonthCount(period)
const dayCount = Math.max(1, Math.round(Math.max(1, monthCount - 1) * 30))
const stepDays = granularityKey === "week" ? 7 : 1 const stepDays = granularityKey === "week" ? 7 : 1
const count = Math.ceil(dayCount / stepDays) + 1 const count = Math.ceil(dayCount / stepDays) + 1
const labels = Array.from({ length: count }, (_, index) => { const labels = Array.from({ length: count }, (_, index) => {
const day = Math.min(dayCount, index * stepDays) const day = Math.min(dayCount, index * stepDays)
if (index === 0) return "2026-06" return formatForecastDayLabel(day)
if (day >= dayCount) return period.label
return granularityKey === "week" ? `${index}` : `${day}`
}) })
const stock = labels.map((_, index) => { const stock = labels.map((_, index) => {
const day = Math.min(dayCount, index * stepDays) const day = Math.min(dayCount, index * stepDays)
@ -5153,16 +5160,17 @@ function formatSignedWan(value) {
gap: 4px; gap: 4px;
min-width: 0; min-width: 0;
overflow: hidden; overflow: hidden;
color: rgba(255, 255, 255, 0.9); color: var(--row-color);
font-size: 13px; font-size: 13px;
line-height: 15px; line-height: 15px;
font-weight: 800; font-weight: 800;
text-shadow: 0 0 9px color-mix(in srgb, var(--row-color) 42%, transparent);
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
small { small {
flex: none; flex: none;
color: rgba(126, 251, 246, 0.72); color: color-mix(in srgb, var(--row-color) 78%, rgba(255, 255, 255, 0.65));
font-family: D-DIN, Arial, sans-serif; font-family: D-DIN, Arial, sans-serif;
font-size: 10px; font-size: 10px;
font-weight: 800; font-weight: 800;
@ -5173,12 +5181,13 @@ function formatSignedWan(value) {
strong { strong {
position: relative; position: relative;
z-index: 1; z-index: 1;
color: #ffffff; color: var(--row-color);
font-family: D-DIN, Arial, sans-serif; font-family: D-DIN, Arial, sans-serif;
font-size: 18px; font-size: 18px;
font-weight: 800; font-weight: 800;
line-height: 20px; line-height: 20px;
text-align: right; text-align: right;
text-shadow: 0 0 10px color-mix(in srgb, var(--row-color) 42%, transparent);
white-space: nowrap; white-space: nowrap;
} }
@ -5192,7 +5201,7 @@ function formatSignedWan(value) {
} }
&.is-active { &.is-active {
color: #ffffff; color: var(--row-color);
border-color: rgba(48, 220, 255, 0.22); border-color: rgba(48, 220, 255, 0.22);
background: linear-gradient(90deg, rgba(48, 220, 255, 0.18), rgba(48, 220, 255, 0.04)); background: linear-gradient(90deg, rgba(48, 220, 255, 0.18), rgba(48, 220, 255, 0.04));
} }
@ -5275,10 +5284,11 @@ function formatSignedWan(value) {
align-items: center; align-items: center;
gap: 8px; gap: 8px;
overflow: hidden; overflow: hidden;
color: rgba(242, 254, 255, 0.88); color: var(--structure-color);
font-size: 13px; font-size: 13px;
line-height: 17px; line-height: 17px;
font-weight: 800; font-weight: 800;
text-shadow: 0 0 9px color-mix(in srgb, var(--structure-color) 38%, transparent);
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
@ -5295,12 +5305,13 @@ function formatSignedWan(value) {
strong { strong {
grid-column: 3; grid-column: 3;
grid-row: 1; grid-row: 1;
color: #ffffff; color: var(--structure-color);
font-family: D-DIN, Arial, sans-serif; font-family: D-DIN, Arial, sans-serif;
font-size: 14px; font-size: 14px;
line-height: 15px; line-height: 15px;
font-weight: 800; font-weight: 800;
text-align: right; text-align: right;
text-shadow: 0 0 10px color-mix(in srgb, var(--structure-color) 38%, transparent);
white-space: nowrap; white-space: nowrap;
em { em {
@ -5336,7 +5347,7 @@ function formatSignedWan(value) {
small { small {
grid-column: 3; grid-column: 3;
grid-row: 2; grid-row: 2;
color: rgba(226, 252, 255, 0.78); color: color-mix(in srgb, var(--structure-color) 76%, rgba(255, 255, 255, 0.72));
font-family: D-DIN, Arial, sans-serif; font-family: D-DIN, Arial, sans-serif;
font-size: 10px; font-size: 10px;
line-height: 11px; line-height: 11px;

Loading…
Cancel
Save