Revert "fix: refine V3 business detail views"

This reverts commit 107a56cca4.
V3.0
M先生 3 months ago
parent 107a56cca4
commit f4749fb2ca
  1. 16
      src/components/mCountCard/index.vue
  2. 8
      src/components/mCountTo/vue-countTo.vue
  3. 31
      src/components/mSvglineAnimation/index.vue
  4. 2
      src/services/pastureApi.js
  5. 9
      src/services/request.js
  6. 2
      src/services/resourceManagementApi.js
  7. 9
      src/services/yakApi.js
  8. 3
      src/views/admin/OperationLogs.vue
  9. 64
      src/views/forestGrassWet/components/FeatureInfoPanel.vue
  10. 2
      src/views/forestGrassWet/index.vue
  11. 4
      src/views/grassLivestockBalance/BalanceAnalysisWorkbench.vue
  12. 16
      src/views/pastureManagement/index.vue
  13. 5
      src/views/yakManagement/YakOlMap.vue
  14. 62
      src/views/yakManagement/index.vue

@ -37,14 +37,14 @@ const props = defineProps({
info: { info: {
type: Object, type: Object,
default: () => { default: () => {
return { return {
icon: "", icon: "xiaoshoujine",
zh: "", zh: "2023年销售金额",
en: "", en: "Sales amount in 2023",
value: 0, value: 9500,
unit: "", unit: "万元",
decimals: 0, decimals: 0,
} }
}, },
}, },
}) })

@ -12,10 +12,10 @@ export default {
required: false, required: false,
default: 0, default: 0,
}, },
endVal: { endVal: {
type: Number, type: Number,
required: false, required: false,
default: 0, default: 2017,
}, },
duration: { duration: {
type: Number, type: Number,

@ -37,11 +37,9 @@
* @property {Number} begin 路径开始的位置 * @property {Number} begin 路径开始的位置
* @property {Number} dir 路径移动方向 [0,1]-正向 [1,0]-反向 * @property {Number} dir 路径移动方向 [0,1]-正向 [1,0]-反向
* @property {Number} strokeWidth 路径的粗细 * @property {Number} strokeWidth 路径的粗细
* @property {String} color 路径的颜色 * @property {String} color 路径的颜色
*/ */
let svgLineUid = 0 export default {
export default {
name: "SvglineAnimation", name: "SvglineAnimation",
props: { props: {
width: { width: {
@ -80,15 +78,20 @@ export default {
type: Number, type: Number,
default: 4, default: 4,
}, },
}, },
data() { data() {
const uid = ++svgLineUid let uid = 1
return { return {
maskId: "svgline-" + uid, maskId: "svgline-" + uid,
radialGradientId: "radialGradient-" + uid, radialGradientId: "radialGradient-" + uid,
} }
}, },
} mounted() {
let uid = this._.uid
this.maskId = "svgline-" + uid
this.radialGradientId = "radialGradient-" + uid
},
}
</script> </script>
<style> <style>

@ -66,7 +66,7 @@ export function getPastureList() {
export async function getPastureProfile() { export async function getPastureProfile() {
const [dashboard, list] = await Promise.all([ const [dashboard, list] = await Promise.all([
getV3("/screen2/pasture-management/dashboard"), getV3("/screen2/pasture-management/dashboard", { year: 2024 }),
getPastureList(), getPastureList(),
]) ])

@ -15,15 +15,9 @@ function appendParams(url, params = {}) {
} }
export async function requestJson(url, options = {}) { export async function requestJson(url, options = {}) {
const { params, timeout = DEFAULT_TIMEOUT, signal: externalSignal, ...fetchOptions } = options const { params, timeout = DEFAULT_TIMEOUT, ...fetchOptions } = options
const controller = new AbortController() const controller = new AbortController()
let timedOut = false let timedOut = false
const abortFromExternalSignal = () => controller.abort()
if (externalSignal?.aborted) {
controller.abort()
} else {
externalSignal?.addEventListener?.("abort", abortFromExternalSignal, { once: true })
}
const timer = window.setTimeout(() => { const timer = window.setTimeout(() => {
timedOut = true timedOut = true
controller.abort() controller.abort()
@ -66,7 +60,6 @@ export async function requestJson(url, options = {}) {
throw error throw error
} finally { } finally {
window.clearTimeout(timer) window.clearTimeout(timer)
externalSignal?.removeEventListener?.("abort", abortFromExternalSignal)
} }
} }

@ -6,7 +6,7 @@ export async function getResourceManagementOptions() {
return normalizeResponseData(await requestJson(`${BASE_URL}/options`)) || {} return normalizeResponseData(await requestJson(`${BASE_URL}/options`)) || {}
} }
export async function getResourceDashboard(topicKey, year) { export async function getResourceDashboard(topicKey, year = 2023) {
return normalizeResponseData(await requestJson(`${BASE_URL}/dashboard`, { return normalizeResponseData(await requestJson(`${BASE_URL}/dashboard`, {
params: { topic: topicKey, year }, params: { topic: topicKey, year },
})) || createEmptyDashboard(topicKey) })) || createEmptyDashboard(topicKey)

@ -10,8 +10,9 @@ function joinBase(base, path) {
function unwrapResponse(response) { function unwrapResponse(response) {
const failed = const failed =
response?.success === false || response?.success === false &&
(response?.code !== undefined && ![0, 200, "0", "200"].includes(response.code)) response?.code !== undefined &&
![0, 200, "0", "200"].includes(response.code)
if (failed) { if (failed) {
throw new Error(response?.msg || response?.message || "接口请求失败") throw new Error(response?.msg || response?.message || "接口请求失败")
@ -116,10 +117,6 @@ export function getYakEpidemicAnalysis(params = {}) {
return get(OPEN_API_BASE, "/screen2/yak-management/epidemic/analysis", params).then((data) => data || {}) return get(OPEN_API_BASE, "/screen2/yak-management/epidemic/analysis", params).then((data) => data || {})
} }
export function getYakEpidemicOptions() {
return get(OPEN_API_BASE, "/screen2/yak-management/epidemic/options").then((data) => data || {})
}
export function getYakEpidemicRecords(params = {}) { export function getYakEpidemicRecords(params = {}) {
return get(OPEN_API_BASE, "/screen2/yak-management/epidemic/records", params).then((data) => data || {}) return get(OPEN_API_BASE, "/screen2/yak-management/epidemic/records", params).then((data) => data || {})
} }

@ -46,7 +46,7 @@
</template> </template>
<script setup> <script setup>
import { reactive, ref } from "vue" import { reactive, ref, watch } from "vue"
import { message } from "ant-design-vue" import { message } from "ant-design-vue"
import { ReloadOutlined, SearchOutlined } from "@ant-design/icons-vue" import { ReloadOutlined, SearchOutlined } from "@ant-design/icons-vue"
import { fetchOperationLogs } from "@/services/admin" import { fetchOperationLogs } from "@/services/admin"
@ -63,6 +63,7 @@ const columns = [
const query = reactive({ title: "", operName: "", businessType: undefined, status: undefined }) const query = reactive({ title: "", operName: "", businessType: undefined, status: undefined })
const dateRange = ref([]), rows = ref([]), loading = ref(false), detailOpen = ref(false), detail = ref(null) const dateRange = ref([]), rows = ref([]), loading = ref(false), detailOpen = ref(false), detail = ref(null)
const pagination = reactive({ current: 1, pageSize: 20, total: 0, showSizeChanger: true, showQuickJumper: true, showTotal: (total) => `共 ${total} 条` }) const pagination = reactive({ current: 1, pageSize: 20, total: 0, showSizeChanger: true, showQuickJumper: true, showTotal: (total) => `共 ${total} 条` })
watch(dateRange, () => {}, { deep: true })
async function loadRows() { loading.value = true; try { const result = await fetchOperationLogs({ ...query, beginTime: dateRange.value?.[0], endTime: dateRange.value?.[1], pageNum: pagination.current, pageSize: pagination.pageSize }); rows.value = result.rows || []; pagination.total = Number(result.total || 0) } catch (error) { message.error(error.message) } finally { loading.value = false } } async function loadRows() { loading.value = true; try { const result = await fetchOperationLogs({ ...query, beginTime: dateRange.value?.[0], endTime: dateRange.value?.[1], pageNum: pagination.current, pageSize: pagination.pageSize }); rows.value = result.rows || []; pagination.total = Number(result.total || 0) } catch (error) { message.error(error.message) } finally { loading.value = false } }
function search() { pagination.current = 1; loadRows() } function search() { pagination.current = 1; loadRows() }

@ -228,7 +228,7 @@ const topicBusinessFields = {
[["management_control_requirement"], "管控要求"], [["management_control_requirement"], "管控要求"],
[["reserve_level", "natural_protected_area_level"], "保护地级别"], [["reserve_level", "natural_protected_area_level"], "保护地级别"],
[["reserve_type", "natural_protected_area_type"], "保护地类型"], [["reserve_type", "natural_protected_area_type"], "保护地类型"],
[["area_mu", "area_m2"], "图斑面积"], [["area_source_mj", "area_mu", "area_m2"], "图斑面积"],
[["original_area_m2"], "原始面积"], [["original_area_m2"], "原始面积"],
[["adjusted_area_m2"], "调整面积"], [["adjusted_area_m2"], "调整面积"],
[["remarks"], "备注"], [["remarks"], "备注"],
@ -527,6 +527,7 @@ const fieldLabels = {
redline_type: "红线类型", redline_type: "红线类型",
ecosystem_service_type: "生态系统服务类型", ecosystem_service_type: "生态系统服务类型",
management_control_requirement: "管控要求", management_control_requirement: "管控要求",
area_source_mj: "图斑面积",
area_m2: "图斑面积", area_m2: "图斑面积",
original_area_m2: "原始面积", original_area_m2: "原始面积",
adjusted_area_m2: "调整面积", adjusted_area_m2: "调整面积",
@ -669,6 +670,7 @@ const patchAreaFieldKeys = new Set([
"source_area_mu", "source_area_mu",
"area_m2", "area_m2",
"acreage", "acreage",
"area_source_mj",
"original_area_m2", "original_area_m2",
"adjusted_area_m2", "adjusted_area_m2",
"mj", "mj",
@ -683,6 +685,7 @@ const patchAreaFieldKeys = new Set([
const townshipNamesByLength = [...hongyuanTownshipDisplayOrder].sort((a, b) => b.length - a.length) const townshipNamesByLength = [...hongyuanTownshipDisplayOrder].sort((a, b) => b.length - a.length)
const propsData = computed(() => props.feature?.properties || {}) const propsData = computed(() => props.feature?.properties || {})
const featureGeometry = computed(() => props.feature?.feature?.geometry || props.feature?.geometry || null)
const externalDisplayRows = computed(() => const externalDisplayRows = computed(() =>
(Array.isArray(props.rows) ? props.rows : []) (Array.isArray(props.rows) ? props.rows : [])
.map((row, index) => ({ .map((row, index) => ({
@ -742,6 +745,10 @@ const displayRows = computed(() => {
used.add(row.sourceKey) used.add(row.sourceKey)
} }
}) })
const areaLabel = getTopicAreaLabel(activeTopicKey.value)
if (!isArtificialGrassland.value && !isEngineeringSuitabilityFeature.value && !rows.some((row) => row.label === areaLabel || isGenericPatchAreaLabel(row.label))) {
addStaticRow(rows, used, areaLabel, formatGeometryArea(featureGeometry.value), "geometry-area")
}
if (useCompactGrassland) return rows.slice(0, 10) if (useCompactGrassland) return rows.slice(0, 10)
Object.entries(propsData.value).forEach(([key, value]) => { Object.entries(propsData.value).forEach(([key, value]) => {
if (used.has(key) || shouldHideFallbackField(key, value)) return if (used.has(key) || shouldHideFallbackField(key, value)) return
@ -858,6 +865,17 @@ function getRowPriority(row, index) {
return 150 + index return 150 + index
} }
function addStaticRow(rows, used, label, value, sourceKey) {
if (!hasValue(value) || rows.some((row) => row.label === label || row.value === value)) return
rows.push({
key: `static-${sourceKey}`,
sourceKey,
label,
value,
})
used.add(sourceKey)
}
function resolveDisplayLabel(label, keys = []) { function resolveDisplayLabel(label, keys = []) {
if (activeTopicKey.value === "ecological" && isEcologicalScopeFeature.value && isEcologicalAreaLabel(label, keys)) { if (activeTopicKey.value === "ecological" && isEcologicalScopeFeature.value && isEcologicalAreaLabel(label, keys)) {
return "图斑面积" return "图斑面积"
@ -921,6 +939,7 @@ function formatValue(key, value) {
if (patchAreaFieldKeys.has(key) && number === 0) return "" if (patchAreaFieldKeys.has(key) && number === 0) return ""
if (number === 0 && zeroHiddenKeys.has(key)) return "" if (number === 0 && zeroHiddenKeys.has(key)) return ""
const formatted = Number.isInteger(number) ? `${number}` : `${Number(number.toFixed(2))}` const formatted = Number.isInteger(number) ? `${number}` : `${Number(number.toFixed(2))}`
if (key === "area_source_mj") return `${formatAreaWanMu(number * 15)} 万亩`
if (activeTopicKey.value === "ecological" && isEcologicalScopeFeature.value && ["area_mu", "acreage", "mj"].includes(key)) return `${formatAreaWanMu(number)} 万亩` if (activeTopicKey.value === "ecological" && isEcologicalScopeFeature.value && ["area_mu", "acreage", "mj"].includes(key)) return `${formatAreaWanMu(number)} 万亩`
if (activeTopicKey.value === "ecological" && isEcologicalScopeFeature.value && ["area_m2", "original_area_m2", "adjusted_area_m2"].includes(key)) return `${formatAreaWanMu(number / 666.6667)} 万亩` if (activeTopicKey.value === "ecological" && isEcologicalScopeFeature.value && ["area_m2", "original_area_m2", "adjusted_area_m2"].includes(key)) return `${formatAreaWanMu(number / 666.6667)} 万亩`
if (["xbmj", "tbdlmj", "geomj", "area_square_meter"].includes(key)) return `${formatAreaMu(number / 666.6667)} 亩` if (["xbmj", "tbdlmj", "geomj", "area_square_meter"].includes(key)) return `${formatAreaMu(number / 666.6667)} 亩`
@ -1080,6 +1099,49 @@ function isUnmappedCodeField(key, value) {
].includes(key) ].includes(key)
} }
function formatGeometryArea(geometry) {
const squareMeters = getGeometryAreaSquareMeters(geometry)
if (!squareMeters) return ""
const mu = squareMeters / 666.6667
if (activeTopicKey.value === "ecological" && isEcologicalScopeFeature.value) return `${formatAreaWanMu(mu)} 万亩`
return `${formatAreaMu(mu)} 亩`
}
function getGeometryAreaSquareMeters(geometry) {
if (!geometry?.coordinates) return 0
if (geometry.type === "Polygon") return getPolygonAreaSquareMeters(geometry.coordinates)
if (geometry.type === "MultiPolygon") {
return geometry.coordinates.reduce((sum, polygon) => sum + getPolygonAreaSquareMeters(polygon), 0)
}
return 0
}
function getPolygonAreaSquareMeters(rings = []) {
if (!rings.length) return 0
const [outer, ...holes] = rings
const outerArea = Math.abs(getRingAreaSquareMeters(outer))
const holesArea = holes.reduce((sum, ring) => sum + Math.abs(getRingAreaSquareMeters(ring)), 0)
return Math.max(0, outerArea - holesArea)
}
function getRingAreaSquareMeters(ring = []) {
if (ring.length < 3) return 0
const averageLat = ring.reduce((sum, point) => sum + Number(point?.[1] || 0), 0) / ring.length
const metersPerDegree = 111320
const xScale = metersPerDegree * Math.cos((averageLat * Math.PI) / 180)
let area = 0
for (let index = 0; index < ring.length; index++) {
const current = ring[index]
const next = ring[(index + 1) % ring.length]
const x1 = Number(current?.[0] || 0) * xScale
const y1 = Number(current?.[1] || 0) * metersPerDegree
const x2 = Number(next?.[0] || 0) * xScale
const y2 = Number(next?.[1] || 0) * metersPerDegree
area += x1 * y2 - x2 * y1
}
return area / 2
}
function normalizeFieldLabel(key) { function normalizeFieldLabel(key) {
return String(key) return String(key)
.replace(/_/g, " ") .replace(/_/g, " ")

@ -636,7 +636,7 @@ async function loadBusinessStats(topicKey, layerKey = activeLayerKey.value) {
coreLoading.value = true coreLoading.value = true
coreStatus.value = "loading" coreStatus.value = "loading"
try { try {
const result = await getResourceDashboard(topicKey) const result = await getResourceDashboard(topicKey, 2023)
if (destroyed || requestId !== businessRequestId) return if (destroyed || requestId !== businessRequestId) return
if (topicKey !== activeTopicKey.value) return if (topicKey !== activeTopicKey.value) return
if (topicKey !== "woodland" && (businessLayerKey || "") !== (activeLayerKey.value || "")) return if (topicKey !== "woodland" && (businessLayerKey || "") !== (activeLayerKey.value || "")) return

@ -242,13 +242,13 @@ const verdictLabel = computed(() => {
return text === "基本实现草畜平衡" ? "基本平衡" : text return text === "基本实现草畜平衡" ? "基本平衡" : text
}) })
const adviceText = computed(() => props.balance?.adviceRows?.[0] || props.balance?.advice || "保持动态监测,结合牧草产量及时调整载畜规模。") const adviceText = computed(() => props.balance?.adviceRows?.[0] || props.balance?.advice || "保持动态监测,结合牧草产量及时调整载畜规模。")
const childStatusLabel = computed(() => props.balance?.areaLevel === "village" ? "类型分析" : "下级区域") const childStatusLabel = computed(() => props.balance?.areaLevel === "village" ? "空间明细" : "下级区域")
const childStatusValue = computed(() => { const childStatusValue = computed(() => {
if (!props.balance) return "--" if (!props.balance) return "--"
if (props.balance.areaLevel === "village") { if (props.balance.areaLevel === "village") {
if (props.analysisState === "idle") return "待分析" if (props.analysisState === "idle") return "待分析"
if (props.analysisState === "running" && props.progress < 40) return "核验中" if (props.analysisState === "running" && props.progress < 40) return "核验中"
return detailAvailable.value ? "已完成" : "待归集" return detailAvailable.value ? "图斑已归属" : "待归属"
} }
const suffix = props.balance.areaLevel === "county" ? "个乡镇" : "个村" const suffix = props.balance.areaLevel === "county" ? "个乡镇" : "个村"
return `${Number(props.balance.childCount) || 0}${suffix}` return `${Number(props.balance.childCount) || 0}${suffix}`

@ -1214,7 +1214,9 @@ function handlePastureFeatureInfo(payload = {}) {
if (!pickedFeature || !layerKey.startsWith("pasture-focus")) return if (!pickedFeature || !layerKey.startsWith("pasture-focus")) return
const sourceProperties = pickedFeature.properties || pickedFeature.feature?.properties || {} const sourceProperties = pickedFeature.properties || pickedFeature.feature?.properties || {}
const recognizedYakCount = pickValue(sourceProperties, ["recognizedYakCount", "recognized_yak_count"]) const recognizedYakCount = pickValue(sourceProperties, ["recognizedYakCount", "recognized_yak_count"])
const acreage = pickValue(sourceProperties, ["acreage", "areaMu", "area_mu", "grasslandArea", "grassland_area"])
const grasslandMuPerYak = pickValue(sourceProperties, ["grasslandMuPerYak", "grassland_mu_per_yak"]) const grasslandMuPerYak = pickValue(sourceProperties, ["grasslandMuPerYak", "grassland_mu_per_yak"])
?? calculateGrasslandMuPerYak(acreage, recognizedYakCount)
const properties = { const properties = {
...sourceProperties, ...sourceProperties,
recognizedYakCount, recognizedYakCount,
@ -1232,6 +1234,13 @@ function handlePastureFeatureInfo(payload = {}) {
grasslandFeatureLoading.value = false grasslandFeatureLoading.value = false
} }
function calculateGrasslandMuPerYak(acreage, recognizedYakCount) {
const area = Number(acreage)
const yakCount = Number(recognizedYakCount)
if (!Number.isFinite(area) || !Number.isFinite(yakCount) || yakCount <= 0) return null
return Number((area / yakCount).toFixed(2))
}
function formatPastureDetailValue(value, unit, fractionDigits) { function formatPastureDetailValue(value, unit, fractionDigits) {
if (value === undefined || value === null || value === "") return "" if (value === undefined || value === null || value === "") return ""
const number = Number(value) const number = Number(value)
@ -1505,11 +1514,14 @@ function normalizeOnlineRows(rows = []) {
const registered = Number(pickValue(item, ["registeredMuhuCount", "registered_muhu_count", "registeredCount"])) const registered = Number(pickValue(item, ["registeredMuhuCount", "registered_muhu_count", "registeredCount"]))
const online = Number(pickValue(item, ["muhuCount", "muhu_count", "onlineCount"])) const online = Number(pickValue(item, ["muhuCount", "muhu_count", "onlineCount"]))
const rawValue = pickValue(item, ["onlinePercent", "uplineRate", "upline_rate", "onlineRate", "value", "percent", "rate"]) const rawValue = pickValue(item, ["onlinePercent", "uplineRate", "upline_rate", "onlineRate", "value", "percent", "rate"])
if (!name || rawValue === undefined || rawValue === null || rawValue === "") return null const value = rawValue !== undefined && rawValue !== null && rawValue !== ""
? rawValue
: registered > 0 ? (online / registered) * 100 : undefined
if (!name || value === undefined || value === null || value === "") return null
return { return {
key: item.code || item.areaCode || item.area_code || name, key: item.code || item.areaCode || item.area_code || name,
name, name,
value: Number(String(rawValue).replace("%", "")) || 0, value: Number(String(value).replace("%", "")) || 0,
unit: "%", unit: "%",
extra: Number.isFinite(online) && Number.isFinite(registered) && registered > 0 ? `${online}/${registered}户` : item.extra, extra: Number.isFinite(online) && Number.isFinite(registered) && registered > 0 ? `${online}/${registered}户` : item.extra,
} }

@ -50,7 +50,6 @@ import RegularShape from "ol/style/RegularShape.js"
import Stroke from "ol/style/Stroke.js" import Stroke from "ol/style/Stroke.js"
import Style from "ol/style/Style.js" import Style from "ol/style/Style.js"
import Text from "ol/style/Text.js" import Text from "ol/style/Text.js"
import { HONGYUAN_COUNTY_CENTER } from "@/config/layers"
import { hongyuanTownshipLabelPoints, hongyuanTownshipsGeoJson } from "@/config/townshipBoundaries" import { hongyuanTownshipLabelPoints, hongyuanTownshipsGeoJson } from "@/config/townshipBoundaries"
const props = defineProps({ const props = defineProps({
@ -64,7 +63,7 @@ const props = defineProps({
}, },
center: { center: {
type: Array, type: Array,
default: () => [...HONGYUAN_COUNTY_CENTER], default: () => [103.07984, 33.029058],
}, },
minZoom: { minZoom: {
type: Number, type: Number,
@ -116,7 +115,7 @@ const props = defineProps({
const emit = defineEmits(["ready", "tile-status", "layer-status", "map-click"]) const emit = defineEmits(["ready", "tile-status", "layer-status", "map-click"])
const HONGYUAN_CENTER = [...HONGYUAN_COUNTY_CENTER] const HONGYUAN_CENTER = [103.07984, 33.029058]
const INITIAL_ZOOM = 21 const INITIAL_ZOOM = 21
const TOWN_BOUNDARY_GLOW_Z_INDEX = 232 const TOWN_BOUNDARY_GLOW_Z_INDEX = 232
const TOWN_BOUNDARY_LINE_Z_INDEX = 233 const TOWN_BOUNDARY_LINE_Z_INDEX = 233

@ -213,8 +213,7 @@
</m-card> </m-card>
<m-card class="yak-side-card left-card" title="预测因子雷达" :width="sideCardWidth" :height="sideCardHeight"> <m-card class="yak-side-card left-card" title="预测因子雷达" :width="sideCardWidth" :height="sideCardHeight">
<v-chart v-if="hasForecastFactorData" class="yak-chart" :option="forecastFactorOption" :autoresize="true" /> <v-chart class="yak-chart" :option="forecastFactorOption" :autoresize="true" />
<div v-else class="yak-chart-empty">暂无预测因子数据</div>
</m-card> </m-card>
</template> </template>
@ -757,7 +756,6 @@ import {
import { import {
getYakEpidemicAnalysis, getYakEpidemicAnalysis,
getYakEpidemicDetail, getYakEpidemicDetail,
getYakEpidemicOptions,
getYakEpidemicRecords, getYakEpidemicRecords,
getYakEpidemicStats, getYakEpidemicStats,
getYakEpidemicVillageStats, getYakEpidemicVillageStats,
@ -845,7 +843,7 @@ const viewTopics = [
const activeView = ref(resolveYakViewKey(props.viewKey)) const activeView = ref(resolveYakViewKey(props.viewKey))
const activeYakLayerKeys = ref([]) const activeYakLayerKeys = ref([])
const activePeriodKey = ref("") const activePeriodKey = ref("2027")
const activeForecastGranularity = ref("month") const activeForecastGranularity = ref("month")
const activeFoundationPage = ref(0) const activeFoundationPage = ref(0)
const apiTownRows = ref([]) const apiTownRows = ref([])
@ -853,8 +851,8 @@ const apiStructureRows = ref([])
const yakProfileRevision = ref(0) const yakProfileRevision = ref(0)
const structureLoading = ref(false) const structureLoading = ref(false)
const structureError = ref("") const structureError = ref("")
const epidemicYears = ref([]) const epidemicYears = [2025, 2026]
const activeEpidemicYear = ref(null) const activeEpidemicYear = ref(2026)
const epidemicAnalysis = ref({ metrics: {} }) const epidemicAnalysis = ref({ metrics: {} })
const epidemicStats = ref({ rows: [], year: null }) const epidemicStats = ref({ rows: [], year: null })
const epidemicVillageStats = ref({ rows: [] }) const epidemicVillageStats = ref({ rows: [] })
@ -1980,11 +1978,6 @@ const forecastFactorOption = computed(() => {
} }
}) })
const hasForecastFactorData = computed(() => {
yakProfileRevision.value
return forecastFactorRows.length >= 3
})
const forecastTownOption = computed(() => buildHorizontalBarOption(forecastTownRows.value, "forecast", "头")) const forecastTownOption = computed(() => buildHorizontalBarOption(forecastTownRows.value, "forecast", "头"))
const forecastStructureOption = computed(() => { const forecastStructureOption = computed(() => {
@ -3048,8 +3041,8 @@ function normalizeEpidemicTownName(name) {
function resolveEpidemicYear(year) { function resolveEpidemicYear(year) {
const numericYear = Number(year) const numericYear = Number(year)
if (Number.isFinite(numericYear) && epidemicYears.value.includes(numericYear)) return numericYear if (Number.isFinite(numericYear) && epidemicYears.includes(numericYear)) return numericYear
return epidemicYears.value[epidemicYears.value.length - 1] ?? null return epidemicYears[epidemicYears.length - 1]
} }
function getEpidemicYearRange(year = activeEpidemicYear.value) { function getEpidemicYearRange(year = activeEpidemicYear.value) {
@ -3685,12 +3678,6 @@ async function loadRemoteRows() {
]) ])
if (profileResult.status === "fulfilled") { if (profileResult.status === "fulfilled") {
configureYakManagementProfile(profileResult.value) configureYakManagementProfile(profileResult.value)
const defaultPeriodKey = String(profileResult.value?.defaultPeriod || "")
const currentPeriodExists = forecastPeriods.some((item) => item.key === activePeriodKey.value)
const defaultPeriodExists = forecastPeriods.some((item) => item.key === defaultPeriodKey)
if (!currentPeriodExists) {
activePeriodKey.value = defaultPeriodExists ? defaultPeriodKey : forecastPeriods[0]?.key || ""
}
yakProfileRevision.value += 1 yakProfileRevision.value += 1
} else { } else {
structureError.value = "牦牛管理基础数据接口异常" structureError.value = "牦牛管理基础数据接口异常"
@ -3878,30 +3865,11 @@ function locateTradeRecord(row) {
} }
async function loadEpidemicData(preferredYear = activeEpidemicYear.value) { async function loadEpidemicData(preferredYear = activeEpidemicYear.value) {
epidemicLoading.value = true
epidemicError.value = ""
if (!epidemicYears.value.length) {
try {
const options = await getYakEpidemicOptions()
epidemicYears.value = normalizeTradeYears(options?.years)
const latestYear = Number(options?.latestYear ?? options?.latest_year)
activeEpidemicYear.value = epidemicYears.value.includes(latestYear)
? latestYear
: epidemicYears.value[epidemicYears.value.length - 1] ?? null
} catch (error) {
epidemicError.value = "防疫年份接口异常"
epidemicLoading.value = false
return
}
}
const targetYear = resolveEpidemicYear(preferredYear) const targetYear = resolveEpidemicYear(preferredYear)
if (!Number.isFinite(targetYear)) {
epidemicError.value = "暂无可用防疫年份"
epidemicLoading.value = false
return
}
const yearRange = getEpidemicYearRange(targetYear) const yearRange = getEpidemicYearRange(targetYear)
activeEpidemicYear.value = targetYear activeEpidemicYear.value = targetYear
epidemicLoading.value = true
epidemicError.value = ""
const [analysisResult, recordsResult, statsResult, villageStatsResult] = await Promise.allSettled([ const [analysisResult, recordsResult, statsResult, villageStatsResult] = await Promise.allSettled([
getYakEpidemicAnalysis(yearRange), getYakEpidemicAnalysis(yearRange),
getYakEpidemicRecords({ getYakEpidemicRecords({
@ -4339,8 +4307,9 @@ function buildForecastKLineRows(series) {
const close = Number(series.stock?.[index] || open) const close = Number(series.stock?.[index] || open)
const birth = Math.max(0, Number(series.births?.[index] || 0) - Number(series.births?.[Math.max(index - 1, 0)] || 0)) const birth = Math.max(0, Number(series.births?.[index] || 0) - Number(series.births?.[Math.max(index - 1, 0)] || 0))
const outflow = Math.max(0, Number(series.outflow?.[index] || 0) - Number(series.outflow?.[Math.max(index - 1, 0)] || 0)) const outflow = Math.max(0, Number(series.outflow?.[index] || 0) - Number(series.outflow?.[Math.max(index - 1, 0)] || 0))
const high = Math.max(open, close) const wickPadding = Math.max(120, Math.round((birth + outflow) * 0.18))
const low = Math.min(open, close) const high = Math.max(open, close) + Math.max(wickPadding, Math.round(birth * 0.32))
const low = Math.max(0, Math.min(open, close) - Math.max(wickPadding, Math.round(outflow * 0.32)))
return { return {
label, label,
open, open,
@ -4935,15 +4904,6 @@ function formatSignedWan(value) {
box-sizing: border-box; box-sizing: border-box;
} }
.yak-chart-empty {
display: grid;
place-items: center;
width: 100%;
height: 100%;
color: rgba(180, 235, 242, 0.68);
font-size: 12px;
}
.forecast-trend-card { .forecast-trend-card {
position: relative; position: relative;
width: 100%; width: 100%;

Loading…
Cancel
Save