main
commit
b503d19ef6
@ -0,0 +1,7 @@ |
|||||||
|
target/ |
||||||
|
.idea/ |
||||||
|
*.iml |
||||||
|
.classpath |
||||||
|
.project |
||||||
|
.settings/ |
||||||
|
.DS_Store |
||||||
@ -0,0 +1,14 @@ |
|||||||
|
{ |
||||||
|
"properties" : { }, |
||||||
|
"id" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1", |
||||||
|
"name" : "大屏数据", |
||||||
|
"type" : "api", |
||||||
|
"parentId" : "0", |
||||||
|
"path" : "/dashboard", |
||||||
|
"createTime" : 1780876800000, |
||||||
|
"updateTime" : null, |
||||||
|
"createBy" : "admin", |
||||||
|
"updateBy" : null, |
||||||
|
"paths" : [ ], |
||||||
|
"options" : [ ] |
||||||
|
} |
||||||
@ -0,0 +1,78 @@ |
|||||||
|
{ |
||||||
|
"properties" : { }, |
||||||
|
"id" : "6f7a8490b1c2d3e4f5a678904ab", |
||||||
|
"script" : null, |
||||||
|
"groupId" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1", |
||||||
|
"name" : "交易中心实时服务信息", |
||||||
|
"createTime" : 1780877300000, |
||||||
|
"updateTime" : null, |
||||||
|
"lock" : null, |
||||||
|
"createBy" : "admin", |
||||||
|
"updateBy" : "admin", |
||||||
|
"path" : "/exchange-service-info", |
||||||
|
"method" : "GET", |
||||||
|
"parameters" : [ ], |
||||||
|
"options" : [ ], |
||||||
|
"requestBody" : "", |
||||||
|
"headers" : [ ], |
||||||
|
"paths" : [ ], |
||||||
|
"responseBody" : null, |
||||||
|
"description" : "交易中心实时服务信息:牦牛供应/待售/已售、剩余车位、进场车辆、供应商数量(只读)。牦牛=yak_trade_ear_tag_inventory;车位=yak_car_parking_zone;车辆=yak_car_record+yak_trade_entry_record;供应商=yak_sn_customer(SELLER)。", |
||||||
|
"requestBodyDefinition" : null, |
||||||
|
"responseBodyDefinition" : null |
||||||
|
} |
||||||
|
================================ |
||||||
|
// 只读查询,不修改任何数据 |
||||||
|
|
||||||
|
var sql = """ |
||||||
|
SELECT |
||||||
|
(SELECT COUNT(*) |
||||||
|
FROM yak_trade_ear_tag_inventory) AS total_supply, |
||||||
|
(SELECT COUNT(*) |
||||||
|
FROM yak_trade_ear_tag_inventory |
||||||
|
WHERE status IN ('AVAILABLE', 'LOCKED')) AS for_sale_yaks, |
||||||
|
(SELECT COUNT(*) |
||||||
|
FROM yak_trade_ear_tag_inventory |
||||||
|
WHERE status = 'SOLD') AS sold_yaks, |
||||||
|
(SELECT COALESCE(SUM(total_capacity - current_count), 0) |
||||||
|
FROM yak_car_parking_zone |
||||||
|
WHERE status = 'NORMAL') AS remaining_parking, |
||||||
|
(SELECT COUNT(*) |
||||||
|
FROM ( |
||||||
|
SELECT plate_no AS vehicle_no |
||||||
|
FROM yak_car_record |
||||||
|
WHERE del_flag = '0' |
||||||
|
AND entry_time >= CURRENT_DATE |
||||||
|
UNION |
||||||
|
SELECT vehicle_no |
||||||
|
FROM yak_trade_entry_record |
||||||
|
WHERE entered_at >= CURRENT_DATE |
||||||
|
) v) AS entering_vehicles, |
||||||
|
(SELECT COUNT(*) |
||||||
|
FROM yak_sn_customer |
||||||
|
WHERE del_flag = '0' |
||||||
|
AND customer_type = 'SELLER') AS supplier_count |
||||||
|
""" |
||||||
|
|
||||||
|
var rows = db.select(sql) |
||||||
|
var row = rows && rows.length > 0 ? rows[0] : null |
||||||
|
|
||||||
|
if (!row) { |
||||||
|
return { |
||||||
|
totalSupply: 0, |
||||||
|
forSaleYaks: 0, |
||||||
|
soldYaks: 0, |
||||||
|
remainingParking: 0, |
||||||
|
enteringVehicles: 0, |
||||||
|
supplierCount: 0 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
totalSupply: row.totalSupply ? row.totalSupply : 0, |
||||||
|
forSaleYaks: row.forSaleYaks ? row.forSaleYaks : 0, |
||||||
|
soldYaks: row.soldYaks ? row.soldYaks : 0, |
||||||
|
remainingParking: row.remainingParking ? row.remainingParking : 0, |
||||||
|
enteringVehicles: row.enteringVehicles ? row.enteringVehicles : 0, |
||||||
|
supplierCount: row.supplierCount ? row.supplierCount : 0 |
||||||
|
} |
||||||
@ -0,0 +1,125 @@ |
|||||||
|
{ |
||||||
|
"properties" : { }, |
||||||
|
"id" : "3c4d5e6f7a8490b1c2d3e4f5a678901", |
||||||
|
"script" : null, |
||||||
|
"groupId" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1", |
||||||
|
"name" : "交易所牦牛成交数据", |
||||||
|
"createTime" : 1780877000000, |
||||||
|
"updateTime" : 1780817847278, |
||||||
|
"lock" : null, |
||||||
|
"createBy" : "admin", |
||||||
|
"updateBy" : "admin", |
||||||
|
"path" : "/yak-trading-data", |
||||||
|
"method" : "GET", |
||||||
|
"parameters" : [ { |
||||||
|
"name" : "period", |
||||||
|
"value" : null, |
||||||
|
"description" : "时间维度:day(近6日)/week(近6周)/month(近6月),不传则返回全部", |
||||||
|
"required" : false, |
||||||
|
"dataType" : "String", |
||||||
|
"type" : null, |
||||||
|
"defaultValue" : null, |
||||||
|
"validateType" : null, |
||||||
|
"error" : null, |
||||||
|
"expression" : null, |
||||||
|
"children" : null |
||||||
|
} ], |
||||||
|
"options" : [ ], |
||||||
|
"requestBody" : "", |
||||||
|
"headers" : [ ], |
||||||
|
"paths" : [ ], |
||||||
|
"responseBody" : "{\n \"code\": -1,\n \"message\": \"系统内部出现错误\",\n \"data\": null,\n \"timestamp\": 1780817836665,\n \"executeTime\": 4\n}", |
||||||
|
"description" : "交易所牦牛成交数据:近6个时间段的牦牛交易数量(头)与成交订单数量(单)趋势,数据源 yak_sn_order 已完成订单(只读查询)", |
||||||
|
"requestBodyDefinition" : null, |
||||||
|
"responseBodyDefinition" : null |
||||||
|
} |
||||||
|
================================ |
||||||
|
|
||||||
|
var buildSeries = (rows) => { |
||||||
|
var labels = [] |
||||||
|
var yakTradingVolume = [] |
||||||
|
var orderCount = [] |
||||||
|
|
||||||
|
for (row in rows) { |
||||||
|
labels.push(row.label) |
||||||
|
yakTradingVolume.push(row.yakVolume ? row.yakVolume : 0) |
||||||
|
orderCount.push(row.orderCount ? row.orderCount : 0) |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
labels: labels, |
||||||
|
yakTradingVolume: yakTradingVolume, |
||||||
|
orderCount: orderCount |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var daySql = """ |
||||||
|
SELECT |
||||||
|
TO_CHAR(d.bucket_date, 'FMMM/FMDD') AS label, |
||||||
|
COALESCE(SUM(o.quantity), 0) AS yak_volume, |
||||||
|
COUNT(o.id) AS order_count |
||||||
|
FROM ( |
||||||
|
SELECT (CURRENT_DATE - offs)::date AS bucket_date, offs |
||||||
|
FROM generate_series(5, 0, -1) AS offs |
||||||
|
) d |
||||||
|
LEFT JOIN yak_sn_order o |
||||||
|
ON o.del_flag = '0' |
||||||
|
AND o.status = 'COMPLETED' |
||||||
|
AND o.transaction_time >= d.bucket_date |
||||||
|
AND o.transaction_time < d.bucket_date + INTERVAL '1 day' |
||||||
|
GROUP BY d.bucket_date, d.offs |
||||||
|
ORDER BY d.offs DESC |
||||||
|
""" |
||||||
|
|
||||||
|
var weekSql = """ |
||||||
|
SELECT |
||||||
|
TO_CHAR(d.anchor_date, 'FMMM/FMDD') || '-' || TO_CHAR(d.anchor_date + 6, 'FMMM/FMDD') AS label, |
||||||
|
COALESCE(SUM(o.quantity), 0) AS yak_volume, |
||||||
|
COUNT(o.id) AS order_count |
||||||
|
FROM ( |
||||||
|
SELECT (CURRENT_DATE - offs * 7)::date AS anchor_date, offs |
||||||
|
FROM generate_series(5, 0, -1) AS offs |
||||||
|
) d |
||||||
|
LEFT JOIN yak_sn_order o |
||||||
|
ON o.del_flag = '0' |
||||||
|
AND o.status = 'COMPLETED' |
||||||
|
AND o.transaction_time >= d.anchor_date |
||||||
|
AND o.transaction_time < d.anchor_date + INTERVAL '7 days' |
||||||
|
GROUP BY d.anchor_date, d.offs |
||||||
|
ORDER BY d.offs DESC |
||||||
|
""" |
||||||
|
|
||||||
|
var monthSql = """ |
||||||
|
SELECT |
||||||
|
TO_CHAR(d.month_start, 'YYYY/FMMM') AS label, |
||||||
|
COALESCE(SUM(o.quantity), 0) AS yak_volume, |
||||||
|
COUNT(o.id) AS order_count |
||||||
|
FROM ( |
||||||
|
SELECT (date_trunc('month', CURRENT_DATE) - (offs || ' months')::interval)::date AS month_start, offs |
||||||
|
FROM generate_series(5, 0, -1) AS offs |
||||||
|
) d |
||||||
|
LEFT JOIN yak_sn_order o |
||||||
|
ON o.del_flag = '0' |
||||||
|
AND o.status = 'COMPLETED' |
||||||
|
AND o.transaction_time >= d.month_start |
||||||
|
AND o.transaction_time < d.month_start + INTERVAL '1 month' |
||||||
|
GROUP BY d.month_start, d.offs |
||||||
|
ORDER BY d.offs DESC |
||||||
|
""" |
||||||
|
|
||||||
|
var dayRows = db.select(daySql) |
||||||
|
var weekRows = db.select(weekSql) |
||||||
|
var monthRows = db.select(monthSql) |
||||||
|
|
||||||
|
var result = { |
||||||
|
day: buildSeries(dayRows), |
||||||
|
week: buildSeries(weekRows), |
||||||
|
month: buildSeries(monthRows) |
||||||
|
} |
||||||
|
|
||||||
|
var p = period |
||||||
|
if (p && result[p]) { |
||||||
|
return result[p] |
||||||
|
} |
||||||
|
|
||||||
|
return result |
||||||
@ -0,0 +1,217 @@ |
|||||||
|
{ |
||||||
|
"properties" : { }, |
||||||
|
"id" : "9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4", |
||||||
|
"script" : null, |
||||||
|
"groupId" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1", |
||||||
|
"name" : "地图迁徙数据", |
||||||
|
"createTime" : 1780882000000, |
||||||
|
"updateTime" : 1781360000000, |
||||||
|
"lock" : null, |
||||||
|
"createBy" : "admin", |
||||||
|
"updateBy" : "admin", |
||||||
|
"path" : "/map-trading-network", |
||||||
|
"method" : "GET", |
||||||
|
"parameters" : [ ], |
||||||
|
"options" : [ ], |
||||||
|
"requestBody" : "", |
||||||
|
"headers" : [ ], |
||||||
|
"paths" : [ ], |
||||||
|
"responseBody" : null, |
||||||
|
"description" : "中央地图迁徙数据:销售网络/源地供应/红原本地出栏,数据源 yak_sn_order + yak_sn_customer(只读)。枢纽名称需与系统配置 mapHub.name 一致。", |
||||||
|
"requestBodyDefinition" : null, |
||||||
|
"responseBodyDefinition" : null |
||||||
|
} |
||||||
|
================================ |
||||||
|
// 只读统计已完成订单;hubName 需与系统配置 mapHub.name 保持一致 |
||||||
|
|
||||||
|
var hubName = '红原县' |
||||||
|
|
||||||
|
var getRowText = (row, camelKey, snakeKey) => { |
||||||
|
if (row[camelKey]) { |
||||||
|
return '' + row[camelKey] |
||||||
|
} |
||||||
|
if (row[snakeKey]) { |
||||||
|
return '' + row[snakeKey] |
||||||
|
} |
||||||
|
return '' |
||||||
|
} |
||||||
|
|
||||||
|
var getRowNumber = (row, key) => { |
||||||
|
var v = row[key] |
||||||
|
if (v == null || v === '') { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
return v |
||||||
|
} |
||||||
|
|
||||||
|
var normalizeCityKey = (name) => { |
||||||
|
var text = '' + (name == null ? '' : name) |
||||||
|
if (text.trim() == '') { |
||||||
|
return '未知' |
||||||
|
} |
||||||
|
|
||||||
|
if (text.indexOf('北京') >= 0) { return '北京' } |
||||||
|
if (text.indexOf('上海') >= 0) { return '上海' } |
||||||
|
if (text.indexOf('天津') >= 0) { return '天津' } |
||||||
|
if (text.indexOf('重庆') >= 0) { return '重庆' } |
||||||
|
if (text.indexOf('成都') >= 0) { return '成都' } |
||||||
|
if (text.indexOf('拉萨') >= 0) { return '拉萨' } |
||||||
|
if (text.indexOf('西宁') >= 0) { return '西宁' } |
||||||
|
if (text.indexOf('兰州') >= 0) { return '兰州' } |
||||||
|
if (text.indexOf('西安') >= 0) { return '西安' } |
||||||
|
if (text.indexOf('昆明') >= 0) { return '昆明' } |
||||||
|
if (text.indexOf('贵阳') >= 0) { return '贵阳' } |
||||||
|
if (text.indexOf('广州') >= 0) { return '广州' } |
||||||
|
if (text.indexOf('深圳') >= 0) { return '深圳' } |
||||||
|
if (text.indexOf('康定') >= 0) { return '康定' } |
||||||
|
if (text.indexOf('香格里拉') >= 0) { return '香格里拉' } |
||||||
|
if (text.indexOf('合作') >= 0) { return '合作' } |
||||||
|
if (text.indexOf('甘南') >= 0) { return '甘南藏族自治州' } |
||||||
|
if (text.indexOf('玉树') >= 0) { return '玉树' } |
||||||
|
if (text.indexOf('甘孜') >= 0) { return '甘孜藏族自治州' } |
||||||
|
if (text.indexOf('阿坝') >= 0) { return '阿坝县' } |
||||||
|
if (text.indexOf('马尔康') >= 0) { return '马尔康' } |
||||||
|
if (text.indexOf('理塘') >= 0) { return '理塘' } |
||||||
|
if (text.indexOf('那曲') >= 0) { return '那曲' } |
||||||
|
if (text.indexOf('果洛') >= 0) { return '果洛' } |
||||||
|
if (text.indexOf('红原') >= 0) { return '红原县' } |
||||||
|
|
||||||
|
return text |
||||||
|
} |
||||||
|
|
||||||
|
var buildDirectionalFlows = (rows, direction) => { |
||||||
|
var flows = [] |
||||||
|
var valueMap = {} |
||||||
|
var descMap = {} |
||||||
|
|
||||||
|
if (!rows) { |
||||||
|
return flows |
||||||
|
} |
||||||
|
|
||||||
|
for (row in rows) { |
||||||
|
var rawName = getRowText(row, 'placeName', 'place_name') |
||||||
|
var value = getRowNumber(row, 'value') |
||||||
|
var sample = getRowText(row, 'samplePlace', 'sample_place') |
||||||
|
if (sample == '') { |
||||||
|
sample = rawName |
||||||
|
} |
||||||
|
|
||||||
|
if (value > 0) { |
||||||
|
var cityKey = direction == 'local' ? rawName : normalizeCityKey(rawName) |
||||||
|
if (cityKey != '未知') { |
||||||
|
if (valueMap[cityKey] == null) { |
||||||
|
valueMap[cityKey] = value |
||||||
|
descMap[cityKey] = sample |
||||||
|
if (direction == 'outflow') { |
||||||
|
flows.push({ |
||||||
|
from: hubName, |
||||||
|
to: cityKey, |
||||||
|
value: value, |
||||||
|
description: sample |
||||||
|
}) |
||||||
|
} else { |
||||||
|
flows.push({ |
||||||
|
from: cityKey, |
||||||
|
to: hubName, |
||||||
|
value: value, |
||||||
|
description: sample |
||||||
|
}) |
||||||
|
} |
||||||
|
} else { |
||||||
|
valueMap[cityKey] = valueMap[cityKey] + value |
||||||
|
var descText = '' + descMap[cityKey] |
||||||
|
if (descText.indexOf(sample) < 0 && sample != '') { |
||||||
|
descMap[cityKey] = descText + ';' + sample |
||||||
|
} |
||||||
|
for (flow in flows) { |
||||||
|
var flowCity = direction == 'outflow' ? flow.to : flow.from |
||||||
|
if (flowCity == cityKey) { |
||||||
|
flow.value = valueMap[cityKey] |
||||||
|
flow.description = descMap[cityKey] |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return flows |
||||||
|
} |
||||||
|
|
||||||
|
var outflowSql = """ |
||||||
|
SELECT |
||||||
|
COALESCE(NULLIF(TRIM(c.region_name), ''), NULLIF(TRIM(o.destination_place), ''), '未知') AS place_name, |
||||||
|
COALESCE(SUM(o.quantity), 0) AS value, |
||||||
|
MAX(COALESCE(NULLIF(TRIM(c.region_name), ''), NULLIF(TRIM(o.destination_place), ''), '未知')) AS sample_place |
||||||
|
FROM yak_sn_order o |
||||||
|
LEFT JOIN yak_sn_customer c ON c.id = o.buyer_id AND c.del_flag = '0' |
||||||
|
WHERE o.del_flag = '0' |
||||||
|
AND o.status = 'COMPLETED' |
||||||
|
GROUP BY COALESCE(NULLIF(TRIM(c.region_name), ''), NULLIF(TRIM(o.destination_place), ''), '未知') |
||||||
|
HAVING COALESCE(SUM(o.quantity), 0) > 0 |
||||||
|
ORDER BY value DESC, place_name |
||||||
|
""" |
||||||
|
|
||||||
|
var inflowSql = """ |
||||||
|
SELECT |
||||||
|
COALESCE(NULLIF(TRIM(c.region_name), ''), NULLIF(TRIM(o.origin_place), ''), '未知') AS place_name, |
||||||
|
COALESCE(SUM(o.quantity), 0) AS value, |
||||||
|
MAX(COALESCE(NULLIF(TRIM(c.region_name), ''), NULLIF(TRIM(o.origin_place), ''), '未知')) AS sample_place |
||||||
|
FROM yak_sn_order o |
||||||
|
LEFT JOIN yak_sn_customer c ON c.id = o.seller_id AND c.del_flag = '0' |
||||||
|
WHERE o.del_flag = '0' |
||||||
|
AND o.status = 'COMPLETED' |
||||||
|
GROUP BY COALESCE(NULLIF(TRIM(c.region_name), ''), NULLIF(TRIM(o.origin_place), ''), '未知') |
||||||
|
HAVING COALESCE(SUM(o.quantity), 0) > 0 |
||||||
|
ORDER BY value DESC, place_name |
||||||
|
""" |
||||||
|
|
||||||
|
var localSql = """ |
||||||
|
SELECT |
||||||
|
COALESCE( |
||||||
|
NULLIF((regexp_match(o.origin_place, '([^省市区县]+(?:镇|乡|街道))'))[1], ''), |
||||||
|
NULLIF(TRIM(o.origin_place), ''), |
||||||
|
'未知' |
||||||
|
) AS place_name, |
||||||
|
COALESCE(SUM(o.quantity), 0) AS value, |
||||||
|
MAX(o.origin_place) AS sample_place |
||||||
|
FROM yak_sn_order o |
||||||
|
WHERE o.del_flag = '0' |
||||||
|
AND o.status = 'COMPLETED' |
||||||
|
AND position('红原' in o.origin_place) > 0 |
||||||
|
GROUP BY COALESCE( |
||||||
|
NULLIF((regexp_match(o.origin_place, '([^省市区县]+(?:镇|乡|街道))'))[1], ''), |
||||||
|
NULLIF(TRIM(o.origin_place), ''), |
||||||
|
'未知' |
||||||
|
) |
||||||
|
HAVING COALESCE(SUM(o.quantity), 0) > 0 |
||||||
|
ORDER BY value DESC, place_name |
||||||
|
""" |
||||||
|
|
||||||
|
var outflowRows = db.select(outflowSql) |
||||||
|
var inflowRows = db.select(inflowSql) |
||||||
|
var localRows = db.select(localSql) |
||||||
|
|
||||||
|
var outflowFlows = buildDirectionalFlows(outflowRows, 'outflow') |
||||||
|
var inflowFlows = buildDirectionalFlows(inflowRows, 'inflow') |
||||||
|
var localFlows = buildDirectionalFlows(localRows, 'local') |
||||||
|
|
||||||
|
return { |
||||||
|
tradingModes: { |
||||||
|
outflow: { |
||||||
|
title: '销售网络分布图', |
||||||
|
description: '从' + hubName + '向全国各地输出牦牛的流向分布', |
||||||
|
flows: outflowFlows |
||||||
|
}, |
||||||
|
inflow: { |
||||||
|
title: '源地供应分布图', |
||||||
|
description: '全国各地向' + hubName + '供应牦牛的来源分布', |
||||||
|
flows: inflowFlows |
||||||
|
}, |
||||||
|
local: { |
||||||
|
title: '红原出栏分布图', |
||||||
|
description: hubName + '各乡镇牦牛出栏分布情况', |
||||||
|
flows: localFlows |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,116 @@ |
|||||||
|
{ |
||||||
|
"properties" : { }, |
||||||
|
"id" : "2b3c4d5e6f748590a1b2c3d4e5f6789", |
||||||
|
"script" : null, |
||||||
|
"groupId" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1", |
||||||
|
"name" : "实时交易统计", |
||||||
|
"createTime" : 1780876800000, |
||||||
|
"updateTime" : 1780803706100, |
||||||
|
"lock" : null, |
||||||
|
"createBy" : "admin", |
||||||
|
"updateBy" : "admin", |
||||||
|
"path" : "/real-time-stats", |
||||||
|
"method" : "GET", |
||||||
|
"parameters" : [ { |
||||||
|
"name" : "dimension", |
||||||
|
"value" : null, |
||||||
|
"description" : "时间维度:day(当天)/week(近一周)/month(近一月)/year(当年),不传则返回全部", |
||||||
|
"required" : false, |
||||||
|
"dataType" : "String", |
||||||
|
"type" : null, |
||||||
|
"defaultValue" : null, |
||||||
|
"validateType" : null, |
||||||
|
"error" : null, |
||||||
|
"expression" : null, |
||||||
|
"children" : null |
||||||
|
} ], |
||||||
|
"options" : [ ], |
||||||
|
"requestBody" : "", |
||||||
|
"headers" : [ ], |
||||||
|
"paths" : [ ], |
||||||
|
"responseBody" : null, |
||||||
|
"description" : "实时交易统计:牦牛交易总量、订单交易总量、销售商户数量、采购商户数量,按当天/近一周/近一月/当年统计(只读查询)", |
||||||
|
"requestBodyDefinition" : null, |
||||||
|
"responseBodyDefinition" : null |
||||||
|
} |
||||||
|
================================ |
||||||
|
// 只读统计已完成订单,不修改任何数据 |
||||||
|
var sql = """ |
||||||
|
SELECT |
||||||
|
dim, |
||||||
|
COALESCE(SUM(quantity), 0) AS yak_total_volume, |
||||||
|
COUNT(*) AS order_total_volume, |
||||||
|
COUNT(DISTINCT seller_id) AS seller_count, |
||||||
|
COUNT(DISTINCT buyer_id) AS buyer_count |
||||||
|
FROM ( |
||||||
|
SELECT 'day' AS dim, quantity, seller_id, buyer_id |
||||||
|
FROM yak_sn_order |
||||||
|
WHERE del_flag = '0' |
||||||
|
AND status = 'COMPLETED' |
||||||
|
AND transaction_time >= CURRENT_DATE |
||||||
|
AND transaction_time < CURRENT_DATE + INTERVAL '1 day' |
||||||
|
|
||||||
|
UNION ALL |
||||||
|
|
||||||
|
SELECT 'week' AS dim, quantity, seller_id, buyer_id |
||||||
|
FROM yak_sn_order |
||||||
|
WHERE del_flag = '0' |
||||||
|
AND status = 'COMPLETED' |
||||||
|
AND transaction_time >= CURRENT_DATE - INTERVAL '6 days' |
||||||
|
AND transaction_time < CURRENT_DATE + INTERVAL '1 day' |
||||||
|
|
||||||
|
UNION ALL |
||||||
|
|
||||||
|
SELECT 'month' AS dim, quantity, seller_id, buyer_id |
||||||
|
FROM yak_sn_order |
||||||
|
WHERE del_flag = '0' |
||||||
|
AND status = 'COMPLETED' |
||||||
|
AND transaction_time >= CURRENT_DATE - INTERVAL '29 days' |
||||||
|
AND transaction_time < CURRENT_DATE + INTERVAL '1 day' |
||||||
|
|
||||||
|
UNION ALL |
||||||
|
|
||||||
|
SELECT 'year' AS dim, quantity, seller_id, buyer_id |
||||||
|
FROM yak_sn_order |
||||||
|
WHERE del_flag = '0' |
||||||
|
AND status = 'COMPLETED' |
||||||
|
AND transaction_time >= DATE_TRUNC('year', CURRENT_DATE) |
||||||
|
AND transaction_time < CURRENT_DATE + INTERVAL '1 day' |
||||||
|
) t |
||||||
|
GROUP BY dim |
||||||
|
ORDER BY dim |
||||||
|
""" |
||||||
|
|
||||||
|
var rows = db.select(sql) |
||||||
|
|
||||||
|
var createEmptyStats = () => { |
||||||
|
return { |
||||||
|
yakTotalVolume: 0, |
||||||
|
orderTotalVolume: 0, |
||||||
|
sellerCount: 0, |
||||||
|
buyerCount: 0 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var result = { |
||||||
|
day: createEmptyStats(), |
||||||
|
week: createEmptyStats(), |
||||||
|
month: createEmptyStats(), |
||||||
|
year: createEmptyStats() |
||||||
|
} |
||||||
|
|
||||||
|
for (row in rows) { |
||||||
|
result[row.dim] = { |
||||||
|
yakTotalVolume: row.yakTotalVolume, |
||||||
|
orderTotalVolume: row.orderTotalVolume, |
||||||
|
sellerCount: row.sellerCount, |
||||||
|
buyerCount: row.buyerCount |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var dim = dimension |
||||||
|
if (dim && result[dim]) { |
||||||
|
return result[dim] |
||||||
|
} |
||||||
|
|
||||||
|
return result |
||||||
@ -0,0 +1,171 @@ |
|||||||
|
{ |
||||||
|
"properties" : { }, |
||||||
|
"id" : "c3d4e5f678901234567890abcdef0123", |
||||||
|
"script" : null, |
||||||
|
"groupId" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1", |
||||||
|
"name" : "市场实时监控", |
||||||
|
"createTime" : 1781199600000, |
||||||
|
"updateTime" : null, |
||||||
|
"lock" : null, |
||||||
|
"createBy" : "admin", |
||||||
|
"updateBy" : "admin", |
||||||
|
"path" : "/market-realtime-monitor", |
||||||
|
"method" : "GET", |
||||||
|
"parameters" : [ ], |
||||||
|
"options" : [ ], |
||||||
|
"requestBody" : "", |
||||||
|
"headers" : [ ], |
||||||
|
"paths" : [ ], |
||||||
|
"responseBody" : null, |
||||||
|
"description" : "市场实时监控:视频监控设备列表,数据源 iot_device_video + iot_device_video_data 最新一条(只读)。", |
||||||
|
"requestBodyDefinition" : null, |
||||||
|
"responseBodyDefinition" : null |
||||||
|
} |
||||||
|
================================ |
||||||
|
// 只读查询,不修改任何数据 |
||||||
|
|
||||||
|
var sql = """ |
||||||
|
SELECT |
||||||
|
v.id, |
||||||
|
v.name, |
||||||
|
v.number, |
||||||
|
v.location, |
||||||
|
v.address, |
||||||
|
v.status, |
||||||
|
v.play_url, |
||||||
|
v.hd_play_url, |
||||||
|
v.preview_img_url, |
||||||
|
v.img, |
||||||
|
v.channel_number, |
||||||
|
v."index" AS sort_index, |
||||||
|
v.last_capture_time, |
||||||
|
d.stream_url AS latest_stream_url, |
||||||
|
d.snapshot_url AS latest_snapshot_url, |
||||||
|
d.online_status AS latest_online_status, |
||||||
|
d.stream_status AS latest_stream_status, |
||||||
|
d.fault_code AS latest_fault_code |
||||||
|
FROM iot_device_video v |
||||||
|
LEFT JOIN LATERAL ( |
||||||
|
SELECT |
||||||
|
stream_url, |
||||||
|
snapshot_url, |
||||||
|
online_status, |
||||||
|
stream_status, |
||||||
|
fault_code |
||||||
|
FROM iot_device_video_data |
||||||
|
WHERE device_id = v.id |
||||||
|
ORDER BY collect_time DESC NULLS LAST |
||||||
|
LIMIT 1 |
||||||
|
) d ON true |
||||||
|
WHERE COALESCE(v.del_flag, '0') = '0' |
||||||
|
AND COALESCE(v.is_show, true) = true |
||||||
|
ORDER BY v."index" NULLS LAST, v.name |
||||||
|
""" |
||||||
|
|
||||||
|
var rows = db.select(sql) |
||||||
|
var cameras = [] |
||||||
|
|
||||||
|
var pickText = (row, camelKey, snakeKey) => { |
||||||
|
if (!row) { |
||||||
|
return '' |
||||||
|
} |
||||||
|
if (row[camelKey]) { |
||||||
|
return row[camelKey] |
||||||
|
} |
||||||
|
if (row[snakeKey]) { |
||||||
|
return row[snakeKey] |
||||||
|
} |
||||||
|
return '' |
||||||
|
} |
||||||
|
|
||||||
|
var toUpperText = (text) => { |
||||||
|
if (!text) { |
||||||
|
return '' |
||||||
|
} |
||||||
|
return ('' + text).toUpperCase() |
||||||
|
} |
||||||
|
|
||||||
|
var mapStatus = (row) => { |
||||||
|
var deviceStatus = pickText(row, 'status', 'status') |
||||||
|
var onlineStatus = pickText(row, 'latestOnlineStatus', 'latest_online_status') |
||||||
|
var faultCode = pickText(row, 'latestFaultCode', 'latest_fault_code') |
||||||
|
var deviceUpper = toUpperText(deviceStatus) |
||||||
|
|
||||||
|
if (deviceUpper === 'OFFLINE') { |
||||||
|
return 'offline' |
||||||
|
} |
||||||
|
if (deviceUpper === 'ERROR' || deviceUpper === 'FAULT') { |
||||||
|
return 'error' |
||||||
|
} |
||||||
|
if (faultCode) { |
||||||
|
return 'error' |
||||||
|
} |
||||||
|
if (onlineStatus) { |
||||||
|
var onlineUpper = toUpperText(onlineStatus) |
||||||
|
if (onlineUpper === 'OFFLINE') { |
||||||
|
return 'offline' |
||||||
|
} |
||||||
|
if (onlineUpper === 'ERROR' || onlineUpper === 'FAULT') { |
||||||
|
return 'error' |
||||||
|
} |
||||||
|
} |
||||||
|
return 'online' |
||||||
|
} |
||||||
|
|
||||||
|
var pickStreamUrl = (row) => { |
||||||
|
var hd = pickText(row, 'hdPlayUrl', 'hd_play_url') |
||||||
|
if (hd) { |
||||||
|
return hd |
||||||
|
} |
||||||
|
var play = pickText(row, 'playUrl', 'play_url') |
||||||
|
if (play) { |
||||||
|
return play |
||||||
|
} |
||||||
|
return pickText(row, 'latestStreamUrl', 'latest_stream_url') |
||||||
|
} |
||||||
|
|
||||||
|
var pickHdStreamUrl = (row) => { |
||||||
|
return pickText(row, 'hdPlayUrl', 'hd_play_url') |
||||||
|
} |
||||||
|
|
||||||
|
var pickPlayUrl = (row) => { |
||||||
|
return pickText(row, 'playUrl', 'play_url') |
||||||
|
} |
||||||
|
|
||||||
|
var pickPreviewUrl = (row) => { |
||||||
|
var preview = pickText(row, 'previewImgUrl', 'preview_img_url') |
||||||
|
if (preview) { |
||||||
|
return preview |
||||||
|
} |
||||||
|
var img = pickText(row, 'img', 'img') |
||||||
|
if (img) { |
||||||
|
return img |
||||||
|
} |
||||||
|
return pickText(row, 'latestSnapshotUrl', 'latest_snapshot_url') |
||||||
|
} |
||||||
|
|
||||||
|
if (rows && rows.length > 0) { |
||||||
|
for (row in rows) { |
||||||
|
cameras.push({ |
||||||
|
id: pickText(row, 'id', 'id'), |
||||||
|
name: pickText(row, 'name', 'name'), |
||||||
|
number: pickText(row, 'number', 'number'), |
||||||
|
location: pickText(row, 'location', 'location'), |
||||||
|
address: pickText(row, 'address', 'address'), |
||||||
|
channelNumber: row.channelNumber != null ? row.channelNumber : row.channel_number, |
||||||
|
sortIndex: row.sortIndex != null ? row.sortIndex : row.sort_index, |
||||||
|
status: mapStatus(row), |
||||||
|
resolution: '1920x1080', |
||||||
|
preview: pickPreviewUrl(row), |
||||||
|
hdStreamUrl: pickHdStreamUrl(row), |
||||||
|
playUrl: pickPlayUrl(row), |
||||||
|
streamUrl: pickStreamUrl(row) |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
pageSize: 2, |
||||||
|
autoPlayInterval: 10000, |
||||||
|
cameras: cameras |
||||||
|
} |
||||||
@ -0,0 +1,77 @@ |
|||||||
|
{ |
||||||
|
"properties" : { }, |
||||||
|
"id" : "d4e5f678901234567890abcdef012345", |
||||||
|
"script" : null, |
||||||
|
"groupId" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1", |
||||||
|
"name" : "市场实时监控播放配置", |
||||||
|
"createTime" : 1781203200000, |
||||||
|
"updateTime" : null, |
||||||
|
"lock" : null, |
||||||
|
"createBy" : "admin", |
||||||
|
"updateBy" : "admin", |
||||||
|
"path" : "/market-realtime-player-config", |
||||||
|
"method" : "GET", |
||||||
|
"parameters" : [ ], |
||||||
|
"options" : [ ], |
||||||
|
"requestBody" : "", |
||||||
|
"headers" : [ ], |
||||||
|
"paths" : [ ], |
||||||
|
"responseBody" : null, |
||||||
|
"description" : "市场实时监控播放配置:直播流格式识别规则与播放器参数(HLS/FLV/MP4 等)。", |
||||||
|
"requestBodyDefinition" : null, |
||||||
|
"responseBodyDefinition" : null |
||||||
|
} |
||||||
|
================================ |
||||||
|
// 直播流地址格式配置,供前端 hls.js / flv.js 自动选型 |
||||||
|
|
||||||
|
return { |
||||||
|
defaultProtocol: 'hls', |
||||||
|
urlPriority: ['hdStreamUrl', 'streamUrl', 'playUrl'], |
||||||
|
formatRules: [ |
||||||
|
{ |
||||||
|
type: 'hls', |
||||||
|
match: '.m3u8', |
||||||
|
library: 'hls.js', |
||||||
|
description: 'HLS 直播(萤石 open.ys7.com 等)' |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: 'flv', |
||||||
|
match: '.flv', |
||||||
|
library: 'flv.js', |
||||||
|
description: 'HTTP-FLV 直播(萤石 rtmp*open.ys7.com 等)' |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: 'mp4', |
||||||
|
match: '.mp4', |
||||||
|
library: 'native', |
||||||
|
description: 'MP4 点播/回放' |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: 'webm', |
||||||
|
match: '.webm', |
||||||
|
library: 'native', |
||||||
|
description: 'WebM 点播' |
||||||
|
} |
||||||
|
], |
||||||
|
playerOptions: { |
||||||
|
hls: { |
||||||
|
enableWorker: true, |
||||||
|
lowLatencyMode: true |
||||||
|
}, |
||||||
|
flv: { |
||||||
|
isLive: true, |
||||||
|
hasAudio: true, |
||||||
|
hasVideo: true |
||||||
|
}, |
||||||
|
flvMedia: { |
||||||
|
enableWorker: true, |
||||||
|
enableStashBuffer: false |
||||||
|
} |
||||||
|
}, |
||||||
|
ys7Templates: { |
||||||
|
hdHls: 'https://open.ys7.com/v3/openlive/{deviceSerial}_{channelNo}_1.m3u8', |
||||||
|
hls: 'https://open.ys7.com/v3/openlive/{deviceSerial}_{channelNo}_2.m3u8', |
||||||
|
hdFlv: 'https://rtmp12open.ys7.com:9188/v3/openlive/{deviceSerial}_{channelNo}_1.flv', |
||||||
|
flv: 'https://rtmp12open.ys7.com:9188/v3/openlive/{deviceSerial}_{channelNo}_2.flv' |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,95 @@ |
|||||||
|
{ |
||||||
|
"properties" : { }, |
||||||
|
"id" : "b2c3d4e5f678901234567890abcdef01", |
||||||
|
"script" : null, |
||||||
|
"groupId" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1", |
||||||
|
"name" : "市场环境监控", |
||||||
|
"createTime" : 1781196000000, |
||||||
|
"updateTime" : null, |
||||||
|
"lock" : null, |
||||||
|
"createBy" : "admin", |
||||||
|
"updateBy" : "admin", |
||||||
|
"path" : "/market-environment", |
||||||
|
"method" : "GET", |
||||||
|
"parameters" : [ ], |
||||||
|
"options" : [ ], |
||||||
|
"requestBody" : "", |
||||||
|
"headers" : [ ], |
||||||
|
"paths" : [ ], |
||||||
|
"responseBody" : null, |
||||||
|
"description" : "市场环境监控:温湿度(iot_device_env_data)+ 气象(iot_device_weather_data)最新一条,只读。", |
||||||
|
"requestBodyDefinition" : null, |
||||||
|
"responseBodyDefinition" : null |
||||||
|
} |
||||||
|
================================ |
||||||
|
// 只读查询,不修改任何数据 |
||||||
|
|
||||||
|
var envSql = """ |
||||||
|
SELECT |
||||||
|
temperature, |
||||||
|
humidity, |
||||||
|
to_char(collect_time, 'YYYY-MM-DD HH24:MI:SS') AS collect_time |
||||||
|
FROM iot_device_env_data |
||||||
|
ORDER BY collect_time DESC NULLS LAST |
||||||
|
LIMIT 1 |
||||||
|
""" |
||||||
|
|
||||||
|
var weatherSql = """ |
||||||
|
SELECT |
||||||
|
air_pressure, |
||||||
|
pm25, |
||||||
|
pm10, |
||||||
|
uv_index, |
||||||
|
rainfall, |
||||||
|
wind_speed, |
||||||
|
wind_direction, |
||||||
|
to_char(collect_time, 'YYYY-MM-DD HH24:MI:SS') AS collect_time |
||||||
|
FROM iot_device_weather_data |
||||||
|
ORDER BY collect_time DESC NULLS LAST |
||||||
|
LIMIT 1 |
||||||
|
""" |
||||||
|
|
||||||
|
var envRows = db.select(envSql) |
||||||
|
var weatherRows = db.select(weatherSql) |
||||||
|
var env = envRows && envRows.length > 0 ? envRows[0] : null |
||||||
|
var weather = weatherRows && weatherRows.length > 0 ? weatherRows[0] : null |
||||||
|
|
||||||
|
var pickNum = (row, camelKey, snakeKey) => { |
||||||
|
if (!row) { |
||||||
|
return null |
||||||
|
} |
||||||
|
if (row[camelKey] != null) { |
||||||
|
return row[camelKey] |
||||||
|
} |
||||||
|
if (row[snakeKey] != null) { |
||||||
|
return row[snakeKey] |
||||||
|
} |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
var pickText = (row, camelKey, snakeKey) => { |
||||||
|
if (!row) { |
||||||
|
return '' |
||||||
|
} |
||||||
|
if (row[camelKey]) { |
||||||
|
return row[camelKey] |
||||||
|
} |
||||||
|
if (row[snakeKey]) { |
||||||
|
return row[snakeKey] |
||||||
|
} |
||||||
|
return '' |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
temperature: pickNum(env, 'temperature', 'temperature'), |
||||||
|
humidity: pickNum(env, 'humidity', 'humidity'), |
||||||
|
envCollectTime: pickText(env, 'collectTime', 'collect_time'), |
||||||
|
airPressure: pickNum(weather, 'airPressure', 'air_pressure'), |
||||||
|
pm25: pickNum(weather, 'pm25', 'pm25'), |
||||||
|
pm10: pickNum(weather, 'pm10', 'pm10'), |
||||||
|
uvIndex: pickNum(weather, 'uvIndex', 'uv_index'), |
||||||
|
rainfall: pickNum(weather, 'rainfall', 'rainfall'), |
||||||
|
windSpeed: pickNum(weather, 'windSpeed', 'wind_speed'), |
||||||
|
windDirection: pickNum(weather, 'windDirection', 'wind_direction'), |
||||||
|
weatherCollectTime: pickText(weather, 'collectTime', 'collect_time') |
||||||
|
} |
||||||
@ -0,0 +1,116 @@ |
|||||||
|
{ |
||||||
|
"properties" : { }, |
||||||
|
"id" : "5e6f7a8490b1c2d3e4f5a678903", |
||||||
|
"script" : null, |
||||||
|
"groupId" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1", |
||||||
|
"name" : "活牛鲜肉价格趋势", |
||||||
|
"createTime" : 1780877200000, |
||||||
|
"updateTime" : null, |
||||||
|
"lock" : null, |
||||||
|
"createBy" : "admin", |
||||||
|
"updateBy" : "admin", |
||||||
|
"path" : "/price-trend", |
||||||
|
"method" : "GET", |
||||||
|
"parameters" : [ ], |
||||||
|
"options" : [ ], |
||||||
|
"requestBody" : "", |
||||||
|
"headers" : [ ], |
||||||
|
"paths" : [ ], |
||||||
|
"responseBody" : null, |
||||||
|
"description" : "活牛/鲜肉价格趋势:近8个月市场采集均价。数据源 yak_trade_market_price,活牛=LIVE_YAK,鲜肉=FRESH_MEAT,直接使用 price 字段及 unit 单位,不做重量折算。", |
||||||
|
"requestBodyDefinition" : null, |
||||||
|
"responseBodyDefinition" : null |
||||||
|
} |
||||||
|
================================ |
||||||
|
// 只读查询,不修改任何数据 |
||||||
|
// 数据源:yak_trade_market_price(市场采集价格,已含 price + unit) |
||||||
|
// price_type: LIVE_YAK=活牛, FRESH_MEAT=鲜肉/牛肉 |
||||||
|
|
||||||
|
var monthBucketSql = """ |
||||||
|
SELECT |
||||||
|
d.month_start, |
||||||
|
d.offs, |
||||||
|
TO_CHAR(d.month_start, 'FMMM') || '月' AS label |
||||||
|
FROM ( |
||||||
|
SELECT |
||||||
|
(date_trunc('month', CURRENT_DATE) - (offs || ' months')::interval)::date AS month_start, |
||||||
|
offs |
||||||
|
FROM generate_series(7, 0, -1) AS offs |
||||||
|
) d |
||||||
|
ORDER BY d.offs DESC |
||||||
|
""" |
||||||
|
|
||||||
|
var livePriceSql = """ |
||||||
|
SELECT |
||||||
|
date_trunc('month', p.price_date)::date AS month_start, |
||||||
|
ROUND(AVG(p.price)::numeric, 2) AS live_cattle_price |
||||||
|
FROM yak_trade_market_price p |
||||||
|
WHERE p.price_type = 'LIVE_YAK' |
||||||
|
AND p.price_date >= date_trunc('month', CURRENT_DATE) - INTERVAL '7 months' |
||||||
|
GROUP BY date_trunc('month', p.price_date)::date |
||||||
|
""" |
||||||
|
|
||||||
|
var beefPriceSql = """ |
||||||
|
SELECT |
||||||
|
date_trunc('month', p.price_date)::date AS month_start, |
||||||
|
ROUND(AVG(p.price)::numeric, 2) AS beef_price |
||||||
|
FROM yak_trade_market_price p |
||||||
|
WHERE p.price_type = 'FRESH_MEAT' |
||||||
|
AND p.price_date >= date_trunc('month', CURRENT_DATE) - INTERVAL '7 months' |
||||||
|
GROUP BY date_trunc('month', p.price_date)::date |
||||||
|
""" |
||||||
|
|
||||||
|
var unitSql = """ |
||||||
|
SELECT |
||||||
|
MAX(CASE WHEN price_type = 'LIVE_YAK' THEN unit END) AS live_unit, |
||||||
|
MAX(CASE WHEN price_type = 'FRESH_MEAT' THEN unit END) AS beef_unit |
||||||
|
FROM yak_trade_market_price |
||||||
|
WHERE price_type IN ('LIVE_YAK', 'FRESH_MEAT') |
||||||
|
""" |
||||||
|
|
||||||
|
var buckets = db.select(monthBucketSql) |
||||||
|
var liveRows = db.select(livePriceSql) |
||||||
|
var beefRows = db.select(beefPriceSql) |
||||||
|
var unitRows = db.select(unitSql) |
||||||
|
|
||||||
|
var liveMap = {} |
||||||
|
var beefMap = {} |
||||||
|
|
||||||
|
for (row in liveRows) { |
||||||
|
liveMap[row.monthStart] = row.liveCattlePrice |
||||||
|
} |
||||||
|
|
||||||
|
for (row in beefRows) { |
||||||
|
beefMap[row.monthStart] = row.beefPrice |
||||||
|
} |
||||||
|
|
||||||
|
var liveUnit = '元/公斤' |
||||||
|
var beefUnit = '元/公斤' |
||||||
|
if (unitRows && unitRows.length > 0) { |
||||||
|
if (unitRows[0].liveUnit) { |
||||||
|
liveUnit = unitRows[0].liveUnit |
||||||
|
} |
||||||
|
if (unitRows[0].beefUnit) { |
||||||
|
beefUnit = unitRows[0].beefUnit |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var labels = [] |
||||||
|
var liveCattlePrice = [] |
||||||
|
var beefPrice = [] |
||||||
|
|
||||||
|
for (bucket in buckets) { |
||||||
|
var monthKey = bucket.monthStart |
||||||
|
labels.push(bucket.label) |
||||||
|
liveCattlePrice.push(liveMap[monthKey] != null ? liveMap[monthKey] : null) |
||||||
|
beefPrice.push(beefMap[monthKey] != null ? beefMap[monthKey] : null) |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
labels: labels, |
||||||
|
liveCattlePrice: liveCattlePrice, |
||||||
|
beefPrice: beefPrice, |
||||||
|
unit: liveUnit, |
||||||
|
liveUnit: liveUnit, |
||||||
|
beefUnit: beefUnit |
||||||
|
} |
||||||
@ -0,0 +1,173 @@ |
|||||||
|
{ |
||||||
|
"properties" : { }, |
||||||
|
"id" : "a1b2c3d4e5f6478990abcdef12345678", |
||||||
|
"script" : null, |
||||||
|
"groupId" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1", |
||||||
|
"name" : "牦牛供应实时信息", |
||||||
|
"createTime" : 1780882000000, |
||||||
|
"updateTime" : null, |
||||||
|
"lock" : null, |
||||||
|
"createBy" : "admin", |
||||||
|
"updateBy" : "admin", |
||||||
|
"path" : "/yak-supply-realtime-info", |
||||||
|
"method" : "GET", |
||||||
|
"parameters" : [ { |
||||||
|
"name" : "pageNo", |
||||||
|
"value" : "1", |
||||||
|
"description" : "页码,从 1 开始(勿用 page,与 magic-api 内置分页参数冲突)", |
||||||
|
"required" : false, |
||||||
|
"dataType" : "Integer", |
||||||
|
"type" : null, |
||||||
|
"defaultValue" : "1", |
||||||
|
"validateType" : null, |
||||||
|
"error" : null, |
||||||
|
"expression" : null, |
||||||
|
"children" : null |
||||||
|
}, { |
||||||
|
"name" : "pageSize", |
||||||
|
"value" : "5", |
||||||
|
"description" : "每页条数", |
||||||
|
"required" : false, |
||||||
|
"dataType" : "Integer", |
||||||
|
"type" : null, |
||||||
|
"defaultValue" : "5", |
||||||
|
"validateType" : null, |
||||||
|
"error" : null, |
||||||
|
"expression" : null, |
||||||
|
"children" : null |
||||||
|
} ], |
||||||
|
"options" : [ ], |
||||||
|
"requestBody" : "", |
||||||
|
"headers" : [ ], |
||||||
|
"paths" : [ ], |
||||||
|
"responseBody" : null, |
||||||
|
"description" : "牦牛供应实时信息:卖家进场登记列表与汇总,数据源 yak_trade_entry_record + 耳标库存(只读)。", |
||||||
|
"requestBodyDefinition" : null, |
||||||
|
"responseBodyDefinition" : null |
||||||
|
} |
||||||
|
================================ |
||||||
|
// 只读查询,不修改任何数据 |
||||||
|
|
||||||
|
// page 为 magic-api 内置分页变量,页码参数使用 pageNo |
||||||
|
var pageNum = 1 |
||||||
|
var sizeNum = 5 |
||||||
|
if (pageNo) { |
||||||
|
pageNum = pageNo |
||||||
|
} |
||||||
|
if (pageSize) { |
||||||
|
sizeNum = pageSize |
||||||
|
} |
||||||
|
if (pageNum < 1) { |
||||||
|
pageNum = 1 |
||||||
|
} |
||||||
|
if (sizeNum < 1) { |
||||||
|
sizeNum = 5 |
||||||
|
} |
||||||
|
if (sizeNum > 50) { |
||||||
|
sizeNum = 50 |
||||||
|
} |
||||||
|
var offset = (pageNum - 1) * sizeNum |
||||||
|
|
||||||
|
var summarySql = """ |
||||||
|
SELECT |
||||||
|
COUNT(*) FILTER (WHERE entered_at >= CURRENT_DATE) AS today_entries, |
||||||
|
COALESCE(SUM(yak_count), 0) AS total_yaks, |
||||||
|
COUNT(*) FILTER (WHERE sold_count > 0 AND sold_count < yak_count) AS trading_count, |
||||||
|
COUNT(*) AS total_records |
||||||
|
FROM ( |
||||||
|
SELECT |
||||||
|
e.entered_at, |
||||||
|
COALESCE(NULLIF(inv.yak_count, 0), et.yak_count, 0) AS yak_count, |
||||||
|
COALESCE(inv.sold_count, 0) AS sold_count |
||||||
|
FROM yak_trade_entry_record e |
||||||
|
LEFT JOIN ( |
||||||
|
SELECT |
||||||
|
seller_entry_record_id, |
||||||
|
COUNT(*) AS yak_count, |
||||||
|
COUNT(*) FILTER (WHERE status = 'SOLD') AS sold_count |
||||||
|
FROM yak_trade_ear_tag_inventory |
||||||
|
GROUP BY seller_entry_record_id |
||||||
|
) inv ON inv.seller_entry_record_id = e.id |
||||||
|
LEFT JOIN ( |
||||||
|
SELECT entry_record_id, COUNT(*) AS yak_count |
||||||
|
FROM yak_trade_entry_record_ear_tag |
||||||
|
GROUP BY entry_record_id |
||||||
|
) et ON et.entry_record_id = e.id |
||||||
|
WHERE e.entry_type = 'SELLER' |
||||||
|
) t |
||||||
|
""" |
||||||
|
|
||||||
|
var listSql = """ |
||||||
|
SELECT |
||||||
|
e.id, |
||||||
|
e.name, |
||||||
|
e.vehicle_no, |
||||||
|
e.phone, |
||||||
|
COALESCE(c.region_name, c.address, '') AS origin, |
||||||
|
COALESCE(e.quarantine_certificate_no, '') AS quarantine_no, |
||||||
|
to_char(e.entered_at, 'YYYY-MM-DD HH24:MI') AS entry_time, |
||||||
|
COALESCE(NULLIF(inv.yak_count, 0), et.yak_count, 0) AS yak_count, |
||||||
|
COALESCE(inv.sold_count, 0) AS sold_count, |
||||||
|
CASE |
||||||
|
WHEN COALESCE(NULLIF(inv.yak_count, 0), et.yak_count, 0) = 0 THEN 0 |
||||||
|
ELSE ROUND( |
||||||
|
COALESCE(inv.sold_count, 0) * 100.0 |
||||||
|
/ COALESCE(NULLIF(inv.yak_count, 0), et.yak_count, 0) |
||||||
|
)::int |
||||||
|
END AS progress |
||||||
|
FROM yak_trade_entry_record e |
||||||
|
LEFT JOIN ( |
||||||
|
SELECT |
||||||
|
seller_entry_record_id, |
||||||
|
COUNT(*) AS yak_count, |
||||||
|
COUNT(*) FILTER (WHERE status = 'SOLD') AS sold_count |
||||||
|
FROM yak_trade_ear_tag_inventory |
||||||
|
GROUP BY seller_entry_record_id |
||||||
|
) inv ON inv.seller_entry_record_id = e.id |
||||||
|
LEFT JOIN ( |
||||||
|
SELECT entry_record_id, COUNT(*) AS yak_count |
||||||
|
FROM yak_trade_entry_record_ear_tag |
||||||
|
GROUP BY entry_record_id |
||||||
|
) et ON et.entry_record_id = e.id |
||||||
|
LEFT JOIN yak_sn_customer c |
||||||
|
ON c.id = e.party_id |
||||||
|
AND c.del_flag = '0' |
||||||
|
WHERE e.entry_type = 'SELLER' |
||||||
|
ORDER BY e.entered_at DESC |
||||||
|
LIMIT #{sizeNum} OFFSET #{offset} |
||||||
|
""" |
||||||
|
|
||||||
|
var summaryRows = db.select(summarySql) |
||||||
|
var summaryRow = summaryRows && summaryRows.length > 0 ? summaryRows[0] : null |
||||||
|
var rows = db.select(listSql) |
||||||
|
var list = [] |
||||||
|
|
||||||
|
for (row in rows) { |
||||||
|
var yakCount = row.yakCount ? row.yakCount : (row.yak_count ? row.yak_count : 0) |
||||||
|
var soldCount = row.soldCount ? row.soldCount : (row.sold_count ? row.sold_count : 0) |
||||||
|
var progress = row.progress ? row.progress : 0 |
||||||
|
|
||||||
|
list.push({ |
||||||
|
id: row.id, |
||||||
|
name: row.name ? row.name : '', |
||||||
|
licensePlate: row.vehicleNo ? row.vehicleNo : (row.vehicle_no ? row.vehicle_no : ''), |
||||||
|
yakCount: yakCount, |
||||||
|
contact: row.phone ? row.phone : '', |
||||||
|
origin: row.origin ? row.origin : '', |
||||||
|
quarantineNo: row.quarantineNo ? row.quarantineNo : (row.quarantine_no ? row.quarantine_no : ''), |
||||||
|
entryTime: row.entryTime ? row.entryTime : (row.entry_time ? row.entry_time : ''), |
||||||
|
progress: progress, |
||||||
|
tradedCount: soldCount, |
||||||
|
pendingCount: yakCount - soldCount |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
summary: { |
||||||
|
todayEntries: summaryRow && summaryRow.todayEntries ? summaryRow.todayEntries : (summaryRow && summaryRow.today_entries ? summaryRow.today_entries : 0), |
||||||
|
totalYaks: summaryRow && summaryRow.totalYaks ? summaryRow.totalYaks : (summaryRow && summaryRow.total_yaks ? summaryRow.total_yaks : 0), |
||||||
|
tradingCount: summaryRow && summaryRow.tradingCount ? summaryRow.tradingCount : (summaryRow && summaryRow.trading_count ? summaryRow.trading_count : 0) |
||||||
|
}, |
||||||
|
total: summaryRow && summaryRow.totalRecords ? summaryRow.totalRecords : (summaryRow && summaryRow.total_records ? summaryRow.total_records : 0), |
||||||
|
list: list |
||||||
|
} |
||||||
@ -0,0 +1,344 @@ |
|||||||
|
{ |
||||||
|
"properties" : { }, |
||||||
|
"id" : "b2c3d4e5f6a7488990abcdef123456789", |
||||||
|
"script" : null, |
||||||
|
"groupId" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1", |
||||||
|
"name" : "牦牛供应详情", |
||||||
|
"createTime" : 1780882100000, |
||||||
|
"updateTime" : null, |
||||||
|
"lock" : null, |
||||||
|
"createBy" : "admin", |
||||||
|
"updateBy" : "admin", |
||||||
|
"path" : "/yak-supply-detail", |
||||||
|
"method" : "GET", |
||||||
|
"parameters" : [ { |
||||||
|
"name" : "id", |
||||||
|
"value" : null, |
||||||
|
"description" : "进场登记 ID(yak_trade_entry_record.id)", |
||||||
|
"required" : true, |
||||||
|
"dataType" : "String", |
||||||
|
"type" : null, |
||||||
|
"defaultValue" : null, |
||||||
|
"validateType" : null, |
||||||
|
"error" : null, |
||||||
|
"expression" : null, |
||||||
|
"children" : null |
||||||
|
} ], |
||||||
|
"options" : [ ], |
||||||
|
"requestBody" : "", |
||||||
|
"headers" : [ ], |
||||||
|
"paths" : [ ], |
||||||
|
"responseBody" : null, |
||||||
|
"description" : "牦牛供应单条详情:yak_trade_entry_record + 耳标库存 + 订单/称重 + 证件图(只读)。", |
||||||
|
"requestBodyDefinition" : null, |
||||||
|
"responseBodyDefinition" : null |
||||||
|
} |
||||||
|
================================ |
||||||
|
// 只读查询,不修改任何数据 |
||||||
|
|
||||||
|
if (!id) { |
||||||
|
exit 400, '缺少参数 id' |
||||||
|
} |
||||||
|
|
||||||
|
var pickUrl = (value) => { |
||||||
|
if (!value) { |
||||||
|
return '' |
||||||
|
} |
||||||
|
var text = (value + '').trim() |
||||||
|
if (!text) { |
||||||
|
return '' |
||||||
|
} |
||||||
|
if (text.indexOf('data:') === 0) { |
||||||
|
return text |
||||||
|
} |
||||||
|
if (text.indexOf('http://localhost:8080') === 0) { |
||||||
|
return text.substring('http://localhost:8080'.length) |
||||||
|
} |
||||||
|
if (text.indexOf('http://127.0.0.1:8080') === 0) { |
||||||
|
return text.substring('http://127.0.0.1:8080'.length) |
||||||
|
} |
||||||
|
if (text.indexOf('http') === 0 || text.indexOf('/') === 0) { |
||||||
|
return text |
||||||
|
} |
||||||
|
if (text.indexOf('profile/') === 0) { |
||||||
|
return '/' + text |
||||||
|
} |
||||||
|
if (text.match(/^\d{4}\/\d{2}\/\d{2}\//)) { |
||||||
|
return '/profile/' + text |
||||||
|
} |
||||||
|
return '' |
||||||
|
} |
||||||
|
|
||||||
|
var baseSql = """ |
||||||
|
SELECT |
||||||
|
e.id, |
||||||
|
e.name, |
||||||
|
e.vehicle_no, |
||||||
|
e.phone, |
||||||
|
e.party_id, |
||||||
|
COALESCE(c.region_name, c.address, '') AS origin, |
||||||
|
COALESCE(e.quarantine_certificate_no, '') AS quarantine_no, |
||||||
|
to_char(e.entered_at, 'YYYY-MM-DD HH24:MI') AS entry_time, |
||||||
|
COALESCE(NULLIF(inv.yak_count, 0), et.yak_count, 0) AS yak_count, |
||||||
|
COALESCE(inv.sold_count, 0) AS sold_count, |
||||||
|
e.entry_photo_id, |
||||||
|
cert.certificate_image_file_id, |
||||||
|
cr.entry_image AS car_entry_image, |
||||||
|
entry_oss.url AS entry_oss_url, |
||||||
|
cert_oss.url AS cert_oss_url, |
||||||
|
COALESCE(ord.order_count, 0) AS order_count |
||||||
|
FROM yak_trade_entry_record e |
||||||
|
LEFT JOIN ( |
||||||
|
SELECT |
||||||
|
seller_entry_record_id, |
||||||
|
COUNT(*) AS yak_count, |
||||||
|
COUNT(*) FILTER (WHERE status = 'SOLD') AS sold_count |
||||||
|
FROM yak_trade_ear_tag_inventory |
||||||
|
GROUP BY seller_entry_record_id |
||||||
|
) inv ON inv.seller_entry_record_id = e.id |
||||||
|
LEFT JOIN ( |
||||||
|
SELECT entry_record_id, COUNT(*) AS yak_count |
||||||
|
FROM yak_trade_entry_record_ear_tag |
||||||
|
GROUP BY entry_record_id |
||||||
|
) et ON et.entry_record_id = e.id |
||||||
|
LEFT JOIN yak_sn_customer c |
||||||
|
ON c.id = e.party_id |
||||||
|
AND c.del_flag = '0' |
||||||
|
LEFT JOIN LATERAL ( |
||||||
|
SELECT certificate_image_file_id |
||||||
|
FROM yak_trade_entry_record_ear_tag t |
||||||
|
WHERE t.entry_record_id = e.id |
||||||
|
AND t.certificate_image_file_id IS NOT NULL |
||||||
|
AND TRIM(t.certificate_image_file_id) <> '' |
||||||
|
ORDER BY t.create_time |
||||||
|
LIMIT 1 |
||||||
|
) cert ON TRUE |
||||||
|
LEFT JOIN LATERAL ( |
||||||
|
SELECT entry_image |
||||||
|
FROM yak_car_record cr |
||||||
|
WHERE cr.del_flag = '0' |
||||||
|
AND cr.plate_no = e.vehicle_no |
||||||
|
ORDER BY cr.entry_time DESC NULLS LAST |
||||||
|
LIMIT 1 |
||||||
|
) cr ON TRUE |
||||||
|
LEFT JOIN yak_trade_quarantine_certificate qc |
||||||
|
ON qc.certificate_no = e.quarantine_certificate_no |
||||||
|
LEFT JOIN LATERAL ( |
||||||
|
SELECT o.url |
||||||
|
FROM sys_oss o |
||||||
|
WHERE e.entry_photo_id IS NOT NULL |
||||||
|
AND TRIM(e.entry_photo_id) <> '' |
||||||
|
AND ( |
||||||
|
o.oss_id::text = e.entry_photo_id |
||||||
|
OR o.ext1 = e.entry_photo_id |
||||||
|
OR o.file_name ILIKE '%' || e.entry_photo_id || '%' |
||||||
|
OR o.original_name ILIKE '%' || e.entry_photo_id || '%' |
||||||
|
OR o.url ILIKE '%' || e.entry_photo_id || '%' |
||||||
|
) |
||||||
|
ORDER BY |
||||||
|
CASE |
||||||
|
WHEN o.oss_id::text = e.entry_photo_id THEN 0 |
||||||
|
WHEN o.ext1 = e.entry_photo_id THEN 1 |
||||||
|
ELSE 2 |
||||||
|
END |
||||||
|
LIMIT 1 |
||||||
|
) entry_oss ON TRUE |
||||||
|
LEFT JOIN LATERAL ( |
||||||
|
SELECT o.url |
||||||
|
FROM sys_oss o |
||||||
|
WHERE ( |
||||||
|
cert.certificate_image_file_id IS NOT NULL |
||||||
|
AND TRIM(cert.certificate_image_file_id) <> '' |
||||||
|
AND ( |
||||||
|
o.oss_id::text = cert.certificate_image_file_id |
||||||
|
OR o.ext1 = cert.certificate_image_file_id |
||||||
|
OR o.file_name ILIKE '%' || cert.certificate_image_file_id || '%' |
||||||
|
OR o.original_name ILIKE '%' || cert.certificate_image_file_id || '%' |
||||||
|
OR o.url ILIKE '%' || cert.certificate_image_file_id || '%' |
||||||
|
) |
||||||
|
) |
||||||
|
OR ( |
||||||
|
qc.image_file_id IS NOT NULL |
||||||
|
AND TRIM(qc.image_file_id) <> '' |
||||||
|
AND ( |
||||||
|
o.oss_id::text = qc.image_file_id |
||||||
|
OR o.ext1 = qc.image_file_id |
||||||
|
OR o.file_name ILIKE '%' || qc.image_file_id || '%' |
||||||
|
OR o.original_name ILIKE '%' || qc.image_file_id || '%' |
||||||
|
OR o.url ILIKE '%' || qc.image_file_id || '%' |
||||||
|
) |
||||||
|
) |
||||||
|
ORDER BY |
||||||
|
CASE |
||||||
|
WHEN cert.certificate_image_file_id IS NOT NULL |
||||||
|
AND o.oss_id::text = cert.certificate_image_file_id THEN 0 |
||||||
|
WHEN qc.image_file_id IS NOT NULL |
||||||
|
AND o.oss_id::text = qc.image_file_id THEN 1 |
||||||
|
ELSE 2 |
||||||
|
END |
||||||
|
LIMIT 1 |
||||||
|
) cert_oss ON TRUE |
||||||
|
LEFT JOIN ( |
||||||
|
SELECT |
||||||
|
inv.seller_entry_record_id, |
||||||
|
COUNT(DISTINCT o.id) AS order_count |
||||||
|
FROM yak_trade_ear_tag_inventory inv |
||||||
|
JOIN yak_trade_order_item i ON i.ear_tag_no = inv.ear_tag_no |
||||||
|
JOIN yak_trade_order o ON o.id = i.order_id |
||||||
|
WHERE o.status <> 'CANCELLED' |
||||||
|
GROUP BY inv.seller_entry_record_id |
||||||
|
) ord ON ord.seller_entry_record_id = e.id |
||||||
|
WHERE e.id = #{id} |
||||||
|
AND e.entry_type = 'SELLER' |
||||||
|
""" |
||||||
|
|
||||||
|
var orderSql = """ |
||||||
|
SELECT |
||||||
|
o.id, |
||||||
|
COALESCE(bc.customer_name, be.name, o.buyer_id, '') AS buyer, |
||||||
|
COALESCE(sc.customer_name, e.name, o.seller_id, '') AS seller, |
||||||
|
to_char(o.created_at, 'YYYY-MM-DD HH24:MI') AS trade_time, |
||||||
|
COALESCE(SUM(COALESCE(i.quantity, 1)), 0) AS quantity, |
||||||
|
COALESCE(SUM(i.weight), 0) AS weight, |
||||||
|
MAX(w.photo_id) AS weight_photo_id, |
||||||
|
MAX(wo.url) AS weight_photo_url |
||||||
|
FROM yak_trade_entry_record e |
||||||
|
JOIN yak_trade_ear_tag_inventory inv |
||||||
|
ON inv.seller_entry_record_id = e.id |
||||||
|
JOIN yak_trade_order_item i |
||||||
|
ON i.ear_tag_no = inv.ear_tag_no |
||||||
|
JOIN yak_trade_order o |
||||||
|
ON o.id = i.order_id |
||||||
|
LEFT JOIN yak_sn_customer bc |
||||||
|
ON bc.id = o.buyer_id |
||||||
|
AND bc.del_flag = '0' |
||||||
|
LEFT JOIN yak_sn_customer sc |
||||||
|
ON sc.id = o.seller_id |
||||||
|
AND sc.del_flag = '0' |
||||||
|
LEFT JOIN yak_trade_entry_record be |
||||||
|
ON be.party_id = o.buyer_id |
||||||
|
AND be.entry_type = 'BUYER' |
||||||
|
LEFT JOIN yak_trade_weighing_record w |
||||||
|
ON w.id = i.weighing_record_id |
||||||
|
LEFT JOIN LATERAL ( |
||||||
|
SELECT o.url |
||||||
|
FROM sys_oss o |
||||||
|
WHERE w.photo_id IS NOT NULL |
||||||
|
AND TRIM(w.photo_id) <> '' |
||||||
|
AND ( |
||||||
|
o.oss_id::text = w.photo_id |
||||||
|
OR o.ext1 = w.photo_id |
||||||
|
OR o.file_name ILIKE '%' || w.photo_id || '%' |
||||||
|
OR o.original_name ILIKE '%' || w.photo_id || '%' |
||||||
|
OR o.url ILIKE '%' || w.photo_id || '%' |
||||||
|
) |
||||||
|
ORDER BY |
||||||
|
CASE |
||||||
|
WHEN o.oss_id::text = w.photo_id THEN 0 |
||||||
|
WHEN o.ext1 = w.photo_id THEN 1 |
||||||
|
ELSE 2 |
||||||
|
END |
||||||
|
LIMIT 1 |
||||||
|
) wo ON TRUE |
||||||
|
WHERE e.id = #{id} |
||||||
|
AND o.status <> 'CANCELLED' |
||||||
|
GROUP BY o.id, bc.customer_name, be.name, sc.customer_name, e.name, o.buyer_id, o.seller_id, o.created_at |
||||||
|
ORDER BY o.created_at DESC |
||||||
|
""" |
||||||
|
|
||||||
|
var resolveOssUrl = (ossUrl, rawId) => { |
||||||
|
var url = pickUrl(ossUrl) |
||||||
|
if (url) { |
||||||
|
return url |
||||||
|
} |
||||||
|
if (!rawId) { |
||||||
|
return '' |
||||||
|
} |
||||||
|
var idText = (rawId + '').trim().replace(/'/g, "''") |
||||||
|
if (!idText) { |
||||||
|
return '' |
||||||
|
} |
||||||
|
var lookupSql = """ |
||||||
|
SELECT url FROM sys_oss |
||||||
|
WHERE oss_id::text = '""" + idText + """' |
||||||
|
OR ext1 = '""" + idText + """' |
||||||
|
OR file_name ILIKE '%""" + idText + """%' |
||||||
|
OR original_name ILIKE '%""" + idText + """%' |
||||||
|
OR url ILIKE '%""" + idText + """%' |
||||||
|
ORDER BY |
||||||
|
CASE |
||||||
|
WHEN oss_id::text = '""" + idText + """' THEN 0 |
||||||
|
WHEN ext1 = '""" + idText + """' THEN 1 |
||||||
|
ELSE 2 |
||||||
|
END |
||||||
|
LIMIT 1 |
||||||
|
""" |
||||||
|
var rows = db.select(lookupSql) |
||||||
|
if (rows && rows.length > 0) { |
||||||
|
return pickUrl(rows[0].url) |
||||||
|
} |
||||||
|
return pickUrl(idText) |
||||||
|
} |
||||||
|
|
||||||
|
var baseRows = db.select(baseSql) |
||||||
|
if (!baseRows || baseRows.length == 0) { |
||||||
|
exit 404, '未找到供应记录' |
||||||
|
} |
||||||
|
|
||||||
|
var base = baseRows[0] |
||||||
|
var yakCount = base.yakCount ? base.yakCount : (base.yak_count ? base.yak_count : 0) |
||||||
|
var soldCount = base.soldCount ? base.soldCount : (base.sold_count ? base.sold_count : 0) |
||||||
|
var orderCount = base.orderCount ? base.orderCount : (base.order_count ? base.order_count : 0) |
||||||
|
var progress = 0 |
||||||
|
if (yakCount > 0) { |
||||||
|
progress = Math.round(soldCount * 100 / yakCount) |
||||||
|
} |
||||||
|
|
||||||
|
var entryPhotoId = base.entryPhotoId ? base.entryPhotoId : (base.entry_photo_id ? base.entry_photo_id : '') |
||||||
|
var certFileId = base.certificateImageFileId ? base.certificateImageFileId : (base.certificate_image_file_id ? base.certificate_image_file_id : '') |
||||||
|
|
||||||
|
var entryPhoto = resolveOssUrl(base.entryOssUrl ? base.entryOssUrl : base.entry_oss_url, entryPhotoId) |
||||||
|
if (!entryPhoto) { |
||||||
|
entryPhoto = pickUrl(base.carEntryImage ? base.carEntryImage : base.car_entry_image) |
||||||
|
} |
||||||
|
|
||||||
|
var quarantineCert = resolveOssUrl(base.certOssUrl ? base.certOssUrl : base.cert_oss_url, certFileId) |
||||||
|
|
||||||
|
var orderRows = db.select(orderSql) |
||||||
|
var orders = [] |
||||||
|
|
||||||
|
for (order in orderRows) { |
||||||
|
var weightPhotoId = order.weightPhotoId ? order.weightPhotoId : (order.weight_photo_id ? order.weight_photo_id : '') |
||||||
|
var weightPhoto = resolveOssUrl(order.weightPhotoUrl ? order.weightPhotoUrl : order.weight_photo_url, weightPhotoId) |
||||||
|
|
||||||
|
orders.push({ |
||||||
|
id: order.id, |
||||||
|
buyer: order.buyer ? order.buyer : '', |
||||||
|
seller: order.seller ? order.seller : '', |
||||||
|
tradeTime: order.tradeTime ? order.tradeTime : (order.trade_time ? order.trade_time : ''), |
||||||
|
quantity: order.quantity ? order.quantity : 0, |
||||||
|
weight: order.weight ? order.weight : 0, |
||||||
|
weightPhoto: weightPhoto, |
||||||
|
weightPhotoId: weightPhotoId |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
id: base.id, |
||||||
|
name: base.name ? base.name : '', |
||||||
|
licensePlate: base.vehicleNo ? base.vehicleNo : (base.vehicle_no ? base.vehicle_no : ''), |
||||||
|
yakCount: yakCount, |
||||||
|
contact: base.phone ? base.phone : '', |
||||||
|
origin: base.origin ? base.origin : '', |
||||||
|
quarantineNo: base.quarantineNo ? base.quarantineNo : (base.quarantine_no ? base.quarantine_no : ''), |
||||||
|
entryTime: base.entryTime ? base.entryTime : (base.entry_time ? base.entry_time : ''), |
||||||
|
progress: progress, |
||||||
|
tradedCount: soldCount, |
||||||
|
pendingCount: yakCount - soldCount, |
||||||
|
orderCount: orderCount, |
||||||
|
entryPhoto: entryPhoto, |
||||||
|
entryPhotoId: entryPhotoId, |
||||||
|
quarantineCert: quarantineCert, |
||||||
|
quarantineCertId: certFileId, |
||||||
|
orders: orders |
||||||
|
} |
||||||
@ -0,0 +1,73 @@ |
|||||||
|
{ |
||||||
|
"properties" : { }, |
||||||
|
"id" : "7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2", |
||||||
|
"script" : null, |
||||||
|
"groupId" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1", |
||||||
|
"name" : "牦牛销售类型统计", |
||||||
|
"createTime" : 1780880400000, |
||||||
|
"updateTime" : null, |
||||||
|
"lock" : null, |
||||||
|
"createBy" : "admin", |
||||||
|
"updateBy" : "admin", |
||||||
|
"path" : "/yak-sales-type-stats", |
||||||
|
"method" : "GET", |
||||||
|
"parameters" : [ ], |
||||||
|
"options" : [ ], |
||||||
|
"requestBody" : "", |
||||||
|
"headers" : [ ], |
||||||
|
"paths" : [ ], |
||||||
|
"responseBody" : null, |
||||||
|
"description" : "牦牛销售类型统计:按订单 purpose 归类为屠宰/养殖/其他,统计已完成订单牦牛头数与占比(只读)。", |
||||||
|
"requestBodyDefinition" : null, |
||||||
|
"responseBodyDefinition" : null |
||||||
|
} |
||||||
|
================================ |
||||||
|
// 只读查询,不修改任何数据 |
||||||
|
// 数据源 yak_sn_order.purpose,按用途关键词归类 |
||||||
|
|
||||||
|
var sql = """ |
||||||
|
SELECT category AS name, COALESCE(SUM(quantity), 0) AS value |
||||||
|
FROM ( |
||||||
|
SELECT |
||||||
|
o.quantity, |
||||||
|
CASE |
||||||
|
WHEN TRIM(COALESCE(o.purpose, '')) ~* '(屠宰|餐饮|肉食|宰杀)' THEN '屠宰用途' |
||||||
|
WHEN TRIM(COALESCE(o.purpose, '')) ~* '(养殖|繁育|种|批发|转售)' THEN '养殖用途' |
||||||
|
ELSE '其他用途' |
||||||
|
END AS category |
||||||
|
FROM yak_sn_order o |
||||||
|
WHERE o.del_flag = '0' |
||||||
|
AND o.status = 'COMPLETED' |
||||||
|
) t |
||||||
|
GROUP BY category |
||||||
|
""" |
||||||
|
|
||||||
|
var rows = db.select(sql) |
||||||
|
var categories = ['屠宰用途', '养殖用途', '其他用途'] |
||||||
|
var valueMap = {} |
||||||
|
|
||||||
|
for (row in rows) { |
||||||
|
valueMap[row.name] = row.value ? row.value : 0 |
||||||
|
} |
||||||
|
|
||||||
|
var total = 0 |
||||||
|
for (cat in categories) { |
||||||
|
var count = valueMap[cat] ? valueMap[cat] : 0 |
||||||
|
total = total + count |
||||||
|
} |
||||||
|
|
||||||
|
var result = [] |
||||||
|
for (cat in categories) { |
||||||
|
var count = valueMap[cat] ? valueMap[cat] : 0 |
||||||
|
var percent = 0 |
||||||
|
if (total > 0) { |
||||||
|
percent = (count * 100.0) / total |
||||||
|
} |
||||||
|
result.push({ |
||||||
|
name: cat, |
||||||
|
value: count, |
||||||
|
percent: percent |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
return result |
||||||
@ -0,0 +1,74 @@ |
|||||||
|
{ |
||||||
|
"properties" : { }, |
||||||
|
"id" : "a3c4d5e6f7a8490b1c2d3e4f5a67890", |
||||||
|
"script" : null, |
||||||
|
"groupId" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1", |
||||||
|
"name" : "系统配置", |
||||||
|
"createTime" : 1780876900000, |
||||||
|
"updateTime" : 1780882000000, |
||||||
|
"lock" : null, |
||||||
|
"createBy" : "admin", |
||||||
|
"updateBy" : "admin", |
||||||
|
"path" : "/system-config", |
||||||
|
"method" : "GET", |
||||||
|
"parameters" : [ ], |
||||||
|
"options" : [ ], |
||||||
|
"requestBody" : "", |
||||||
|
"headers" : [ ], |
||||||
|
"paths" : [ ], |
||||||
|
"responseBody" : null, |
||||||
|
"description" : "大屏系统配置:标题、标题背景、地图枢纽节点(红原)及坐标映射等", |
||||||
|
"requestBodyDefinition" : null, |
||||||
|
"responseBodyDefinition" : null |
||||||
|
} |
||||||
|
================================ |
||||||
|
// 默认配置(可在 magic-api 编辑器中直接修改) |
||||||
|
// mapHub.name 需与「地图迁徙数据」接口中的 hubName 保持一致 |
||||||
|
|
||||||
|
var config = { |
||||||
|
title: '/images/标题.png', |
||||||
|
titleBackground: '/images/标题背景.png', |
||||||
|
mapHub: { |
||||||
|
name: '红原县', |
||||||
|
coordinates: [102.568685, 32.826358], |
||||||
|
description: '四川省阿坝州红原县,中国重要的牦牛养殖基地', |
||||||
|
localOriginKeyword: '红原' |
||||||
|
}, |
||||||
|
mapFlowLevels: { |
||||||
|
high: { threshold: 80, color: '#E6A23C', description: '主要流向' }, |
||||||
|
medium: { threshold: 40, color: '#409EFF', description: '重要流向' }, |
||||||
|
low: { threshold: 0, color: '#67C23A', description: '一般流向' } |
||||||
|
}, |
||||||
|
mapGeoCoordMap: { |
||||||
|
'红原县': [102.568685, 32.826358], |
||||||
|
'邛溪镇': [102.515, 32.7855], |
||||||
|
'龙日镇': [102.484, 32.3605], |
||||||
|
'麦洼乡': [102.918, 32.9156], |
||||||
|
'阿木乡': [102.789, 32.9154], |
||||||
|
'刷经寺镇': [102.661, 31.9177], |
||||||
|
'瓦切镇': [102.669, 33.2578], |
||||||
|
'查尔玛乡': [101.921, 32.5029], |
||||||
|
'江茸乡': [102.412, 32.3022], |
||||||
|
'安曲镇': [102.222, 32.6709], |
||||||
|
'色地镇': [102.999, 32.865], |
||||||
|
'成都': [103.958004, 30.772708], |
||||||
|
'重庆': [106.54, 29.59], |
||||||
|
'北京': [116.46, 39.92], |
||||||
|
'上海': [121.48, 31.22], |
||||||
|
'天津': [117.20, 39.13], |
||||||
|
'拉萨': [91.420246, 29.878931], |
||||||
|
'西宁': [101.74113, 36.641678], |
||||||
|
'兰州': [103.249708, 35.956623], |
||||||
|
'西安': [108.95, 34.27], |
||||||
|
'昆明': [102.72, 25.05], |
||||||
|
'贵阳': [106.71, 26.57], |
||||||
|
'康定': [101.956, 30.057], |
||||||
|
'香格里拉': [99.708, 27.825], |
||||||
|
'合作': [102.911, 34.986], |
||||||
|
'甘南藏族自治州': [103.3049, 34.644479], |
||||||
|
'玉树': [97.008, 33.004], |
||||||
|
'甘孜藏族自治州': [101.226006, 30.470387] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return config |
||||||
@ -0,0 +1,157 @@ |
|||||||
|
{ |
||||||
|
"properties" : { }, |
||||||
|
"id" : "4d5e6f7a8490b1c2d3e4f5a678902", |
||||||
|
"script" : null, |
||||||
|
"groupId" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1", |
||||||
|
"name" : "综合销售统计", |
||||||
|
"createTime" : 1780877100000, |
||||||
|
"updateTime" : null, |
||||||
|
"lock" : null, |
||||||
|
"createBy" : "admin", |
||||||
|
"updateBy" : "admin", |
||||||
|
"path" : "/comprehensive-sales-stats", |
||||||
|
"method" : "GET", |
||||||
|
"parameters" : [ { |
||||||
|
"name" : "type", |
||||||
|
"value" : null, |
||||||
|
"description" : "统计类型:monthlySales(本月销售)/overallSalesDistribution(总体销售)/purchaseRegionDistribution(采购地区),不传则返回全部", |
||||||
|
"required" : false, |
||||||
|
"dataType" : "String", |
||||||
|
"type" : null, |
||||||
|
"defaultValue" : null, |
||||||
|
"validateType" : null, |
||||||
|
"error" : null, |
||||||
|
"expression" : null, |
||||||
|
"children" : null |
||||||
|
} ], |
||||||
|
"options" : [ ], |
||||||
|
"requestBody" : "", |
||||||
|
"headers" : [ ], |
||||||
|
"paths" : [ ], |
||||||
|
"responseBody" : null, |
||||||
|
"description" : "综合销售统计:本月销售/总体销售/采购地区三维饼图数据,数据源 yak_sn_order 已完成订单(只读)", |
||||||
|
"requestBodyDefinition" : null, |
||||||
|
"responseBodyDefinition" : null |
||||||
|
} |
||||||
|
================================ |
||||||
|
// 数据源 yak_sn_order + yak_sn_customer |
||||||
|
// value = 牦牛交易头数 quantity |
||||||
|
|
||||||
|
var mapRows = (rows) => { |
||||||
|
var total = 0 |
||||||
|
var result = [] |
||||||
|
|
||||||
|
for (row in rows) { |
||||||
|
total = total + (row.value ? row.value : 0) |
||||||
|
} |
||||||
|
|
||||||
|
for (row in rows) { |
||||||
|
var value = row.value ? row.value : 0 |
||||||
|
var percent = 0 |
||||||
|
if (total > 0) { |
||||||
|
percent = (value * 100) / total |
||||||
|
} |
||||||
|
result.push({ |
||||||
|
name: row.name, |
||||||
|
value: value, |
||||||
|
percent: percent |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
return result |
||||||
|
} |
||||||
|
|
||||||
|
var monthlySql = """ |
||||||
|
SELECT town AS name, COALESCE(SUM(quantity), 0) AS value |
||||||
|
FROM ( |
||||||
|
SELECT |
||||||
|
o.quantity, |
||||||
|
COALESCE( |
||||||
|
NULLIF((regexp_match(o.origin_place, '([^省市区县]+(?:镇|乡|街道))'))[1], ''), |
||||||
|
NULLIF(TRIM(o.origin_place), ''), |
||||||
|
'未知地区' |
||||||
|
) AS town |
||||||
|
FROM yak_sn_order o |
||||||
|
CROSS JOIN ( |
||||||
|
SELECT CASE |
||||||
|
WHEN EXISTS ( |
||||||
|
SELECT 1 FROM yak_sn_order x |
||||||
|
WHERE x.del_flag = '0' |
||||||
|
AND x.status = 'COMPLETED' |
||||||
|
AND x.transaction_time >= date_trunc('month', CURRENT_DATE) |
||||||
|
AND x.transaction_time < date_trunc('month', CURRENT_DATE) + INTERVAL '1 month' |
||||||
|
) THEN date_trunc('month', CURRENT_DATE) |
||||||
|
ELSE date_trunc('month', ( |
||||||
|
SELECT MAX(transaction_time) FROM yak_sn_order |
||||||
|
WHERE del_flag = '0' AND status = 'COMPLETED' |
||||||
|
)) |
||||||
|
END AS month_start |
||||||
|
) m |
||||||
|
WHERE o.del_flag = '0' |
||||||
|
AND o.status = 'COMPLETED' |
||||||
|
AND o.transaction_time >= m.month_start |
||||||
|
AND o.transaction_time < m.month_start + INTERVAL '1 month' |
||||||
|
) t |
||||||
|
GROUP BY town |
||||||
|
ORDER BY value DESC |
||||||
|
LIMIT 5 |
||||||
|
""" |
||||||
|
|
||||||
|
var overallSql = """ |
||||||
|
SELECT town AS name, COALESCE(SUM(quantity), 0) AS value |
||||||
|
FROM ( |
||||||
|
SELECT |
||||||
|
o.quantity, |
||||||
|
COALESCE( |
||||||
|
NULLIF((regexp_match(o.origin_place, '([^省市区县]+(?:镇|乡|街道))'))[1], ''), |
||||||
|
NULLIF(TRIM(o.origin_place), ''), |
||||||
|
'未知地区' |
||||||
|
) AS town |
||||||
|
FROM yak_sn_order o |
||||||
|
WHERE o.del_flag = '0' |
||||||
|
AND o.status = 'COMPLETED' |
||||||
|
) t |
||||||
|
GROUP BY town |
||||||
|
ORDER BY value DESC |
||||||
|
LIMIT 5 |
||||||
|
""" |
||||||
|
|
||||||
|
var purchaseSql = """ |
||||||
|
SELECT region AS name, COALESCE(SUM(quantity), 0) AS value |
||||||
|
FROM ( |
||||||
|
SELECT |
||||||
|
o.quantity, |
||||||
|
COALESCE( |
||||||
|
NULLIF(TRIM(b.region_name), ''), |
||||||
|
NULLIF((regexp_match(o.destination_place, '([^省市区县]+(?:市|州|盟|县|区))'))[1], ''), |
||||||
|
NULLIF(TRIM(o.destination_place), ''), |
||||||
|
'未知地区' |
||||||
|
) AS region |
||||||
|
FROM yak_sn_order o |
||||||
|
LEFT JOIN yak_sn_customer b |
||||||
|
ON b.id = o.buyer_id |
||||||
|
AND b.del_flag = '0' |
||||||
|
WHERE o.del_flag = '0' |
||||||
|
AND o.status = 'COMPLETED' |
||||||
|
) t |
||||||
|
GROUP BY region |
||||||
|
ORDER BY value DESC |
||||||
|
LIMIT 5 |
||||||
|
""" |
||||||
|
|
||||||
|
var monthlyRows = db.select(monthlySql) |
||||||
|
var overallRows = db.select(overallSql) |
||||||
|
var purchaseRows = db.select(purchaseSql) |
||||||
|
|
||||||
|
var result = { |
||||||
|
monthlySales: mapRows(monthlyRows), |
||||||
|
overallSalesDistribution: mapRows(overallRows), |
||||||
|
purchaseRegionDistribution: mapRows(purchaseRows) |
||||||
|
} |
||||||
|
|
||||||
|
var t = type |
||||||
|
if (t && result[t]) { |
||||||
|
return result[t] |
||||||
|
} |
||||||
|
|
||||||
|
return result |
||||||
@ -0,0 +1,55 @@ |
|||||||
|
{ |
||||||
|
"properties" : { }, |
||||||
|
"id" : "8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3", |
||||||
|
"script" : null, |
||||||
|
"groupId" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1", |
||||||
|
"name" : "采购商户来源分析", |
||||||
|
"createTime" : 1780881000000, |
||||||
|
"updateTime" : null, |
||||||
|
"lock" : null, |
||||||
|
"createBy" : "admin", |
||||||
|
"updateBy" : "admin", |
||||||
|
"path" : "/buyer-source-analysis", |
||||||
|
"method" : "GET", |
||||||
|
"parameters" : [ ], |
||||||
|
"options" : [ ], |
||||||
|
"requestBody" : "", |
||||||
|
"headers" : [ ], |
||||||
|
"paths" : [ ], |
||||||
|
"responseBody" : null, |
||||||
|
"description" : "采购商户来源分析:按地区统计采购商户(BUYER)数量 Top9,数据源 yak_sn_customer(只读)。", |
||||||
|
"requestBodyDefinition" : null, |
||||||
|
"responseBodyDefinition" : null |
||||||
|
} |
||||||
|
================================ |
||||||
|
// 只读查询,不修改任何数据 |
||||||
|
|
||||||
|
var sql = """ |
||||||
|
SELECT |
||||||
|
region_name AS name, |
||||||
|
region_name AS full_name, |
||||||
|
COUNT(*) AS value, |
||||||
|
region_name || '采购商户' AS description |
||||||
|
FROM yak_sn_customer c |
||||||
|
WHERE c.del_flag = '0' |
||||||
|
AND c.customer_type = 'BUYER' |
||||||
|
AND c.region_name IS NOT NULL |
||||||
|
AND TRIM(c.region_name) <> '' |
||||||
|
GROUP BY region_name |
||||||
|
ORDER BY value DESC, region_name |
||||||
|
LIMIT 9 |
||||||
|
""" |
||||||
|
|
||||||
|
var rows = db.select(sql) |
||||||
|
var result = [] |
||||||
|
|
||||||
|
for (row in rows) { |
||||||
|
result.push({ |
||||||
|
name: row.name, |
||||||
|
value: row.value ? row.value : 0, |
||||||
|
fullName: row.fullName ? row.fullName : (row.full_name ? row.full_name : ''), |
||||||
|
description: row.description ? row.description : '采购商户' |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
return result |
||||||
@ -0,0 +1,14 @@ |
|||||||
|
{ |
||||||
|
"properties" : { }, |
||||||
|
"id" : "d9addaf6c9904669b19434d023e8e55e", |
||||||
|
"name" : "测试分组", |
||||||
|
"type" : "api", |
||||||
|
"parentId" : "0", |
||||||
|
"path" : "/test", |
||||||
|
"createTime" : 1780801394664, |
||||||
|
"updateTime" : null, |
||||||
|
"createBy" : "admin", |
||||||
|
"updateBy" : null, |
||||||
|
"paths" : [ ], |
||||||
|
"options" : [ ] |
||||||
|
} |
||||||
@ -0,0 +1,25 @@ |
|||||||
|
{ |
||||||
|
"properties" : { }, |
||||||
|
"id" : "df98822a60294a7ea789d1f81f1e0e85", |
||||||
|
"script" : null, |
||||||
|
"groupId" : "d9addaf6c9904669b19434d023e8e55e", |
||||||
|
"name" : "测试接口", |
||||||
|
"createTime" : 1780801413284, |
||||||
|
"updateTime" : null, |
||||||
|
"lock" : null, |
||||||
|
"createBy" : "admin", |
||||||
|
"updateBy" : null, |
||||||
|
"path" : "/api1", |
||||||
|
"method" : "GET", |
||||||
|
"parameters" : [ ], |
||||||
|
"options" : [ ], |
||||||
|
"requestBody" : null, |
||||||
|
"headers" : [ ], |
||||||
|
"paths" : [ ], |
||||||
|
"responseBody" : null, |
||||||
|
"description" : null, |
||||||
|
"requestBodyDefinition" : null, |
||||||
|
"responseBodyDefinition" : null |
||||||
|
} |
||||||
|
================================ |
||||||
|
return 'Hello magic-api' |
||||||
@ -0,0 +1,234 @@ |
|||||||
|
大屏 Magic-API 接口速查 |
||||||
|
======================================== |
||||||
|
分组:/dashboard |
||||||
|
前端代理:/api/dashboard/* |
||||||
|
源文件:livestock-trading-backend/data/magic-api/api/大屏数据/*.ms |
||||||
|
说明:均为只读;复杂逻辑见各 .ms 文件。修改 .ms 后需重启后端。 |
||||||
|
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------- |
||||||
|
1. 系统配置 |
||||||
|
-------------------------------------------------------------------------------- |
||||||
|
GET /dashboard/system-config |
||||||
|
文件:系统配置.ms | 前端:Dashboard.vue、ChinaMap.vue、systemConfig.js |
||||||
|
|
||||||
|
业务:大屏静态配置(标题图、地图枢纽红原、城市坐标、流向等级配色等)。 |
||||||
|
|
||||||
|
数据表:无(脚本内硬编码,可在 magic-api 或 .ms 中改) |
||||||
|
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------- |
||||||
|
2. 实时交易统计 |
||||||
|
-------------------------------------------------------------------------------- |
||||||
|
GET /dashboard/real-time-stats |
||||||
|
文件:实时交易统计.ms | 前端:RealTimeStats.vue |
||||||
|
|
||||||
|
业务:按时间维度统计交易所已完成订单的交易量、订单数、买卖商户数。 |
||||||
|
|
||||||
|
参数:dimension(可选)day / week / month / year,不传返回全部 |
||||||
|
|
||||||
|
表:yak_sn_order |
||||||
|
字段:quantity, seller_id, buyer_id, transaction_time, status, del_flag |
||||||
|
条件:del_flag='0',status='COMPLETED' |
||||||
|
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------- |
||||||
|
3. 交易所牦牛成交数据 |
||||||
|
-------------------------------------------------------------------------------- |
||||||
|
GET /dashboard/yak-trading-data |
||||||
|
文件:交易所牦牛成交数据.ms | 前端:YakTradingData.vue |
||||||
|
|
||||||
|
业务:近 6 个日/周/月时间桶的牦牛头数与订单数趋势。 |
||||||
|
|
||||||
|
参数:period(可选)day / week / month,不传返回全部 |
||||||
|
|
||||||
|
表:yak_sn_order |
||||||
|
字段:id, quantity, transaction_time, status, del_flag |
||||||
|
条件:del_flag='0',status='COMPLETED' |
||||||
|
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------- |
||||||
|
4. 综合销售统计 |
||||||
|
-------------------------------------------------------------------------------- |
||||||
|
GET /dashboard/comprehensive-sales-stats |
||||||
|
文件:综合销售统计.ms | 前端:ComprehensiveSalesStats.vue |
||||||
|
|
||||||
|
业务:三个饼图——本月销售、总体销售(按产地乡镇 Top5)、采购地区(Top5)。 |
||||||
|
|
||||||
|
参数:type(可选)monthlySales / overallSalesDistribution / purchaseRegionDistribution |
||||||
|
|
||||||
|
表:yak_sn_order;采购地区 LEFT JOIN yak_sn_customer |
||||||
|
字段: |
||||||
|
yak_sn_order — quantity, origin_place, destination_place, buyer_id, transaction_time, status, del_flag |
||||||
|
yak_sn_customer — id, region_name, del_flag |
||||||
|
条件:del_flag='0',status='COMPLETED' |
||||||
|
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------- |
||||||
|
5. 活牛/鲜肉价格趋势 |
||||||
|
-------------------------------------------------------------------------------- |
||||||
|
GET /dashboard/price-trend |
||||||
|
文件:活牛鲜肉价格趋势.ms | 前端:YakPriceTrend.vue |
||||||
|
|
||||||
|
业务:近 8 个月活牛、鲜肉市场采集均价(不做重量折算)。 |
||||||
|
|
||||||
|
表:yak_trade_market_price |
||||||
|
字段:price_date, price_type(LIVE_YAK / FRESH_MEAT), price, unit |
||||||
|
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------- |
||||||
|
6. 交易中心实时服务信息 |
||||||
|
-------------------------------------------------------------------------------- |
||||||
|
GET /dashboard/exchange-service-info |
||||||
|
文件:交易中心实时服务信息.ms | 前端:ExchangeMonitor.vue |
||||||
|
|
||||||
|
业务:交易中心实时概览——供应/待售/已售头数、剩余车位、今日进场车辆、供应商数。 |
||||||
|
|
||||||
|
表及字段: |
||||||
|
yak_trade_ear_tag_inventory — status(计总供应/待售/已售) |
||||||
|
yak_car_parking_zone — total_capacity, current_count, status |
||||||
|
yak_car_record — plate_no, entry_time, del_flag |
||||||
|
yak_trade_entry_record — vehicle_no, entered_at |
||||||
|
yak_sn_customer — customer_type='SELLER', del_flag |
||||||
|
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------- |
||||||
|
7. 牦牛销售类型统计 |
||||||
|
-------------------------------------------------------------------------------- |
||||||
|
GET /dashboard/yak-sales-type-stats |
||||||
|
文件:牦牛销售类型统计.ms | 前端:YakSalesTypeStats.vue |
||||||
|
|
||||||
|
业务:按订单用途归类为屠宰/养殖/其他,统计头数与占比。 |
||||||
|
|
||||||
|
表:yak_sn_order |
||||||
|
字段:quantity, purpose, status, del_flag |
||||||
|
条件:del_flag='0',status='COMPLETED' |
||||||
|
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------- |
||||||
|
8. 采购商户来源分析 |
||||||
|
-------------------------------------------------------------------------------- |
||||||
|
GET /dashboard/buyer-source-analysis |
||||||
|
文件:采购商户来源分析.ms | 前端:PurchaserAnalysis.vue |
||||||
|
|
||||||
|
业务:按地区统计采购商户(BUYER)数量 Top9。 |
||||||
|
|
||||||
|
表:yak_sn_customer |
||||||
|
字段:region_name, customer_type, del_flag |
||||||
|
条件:customer_type='BUYER',del_flag='0',region_name 非空 |
||||||
|
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------- |
||||||
|
9. 牦牛供应实时信息 |
||||||
|
-------------------------------------------------------------------------------- |
||||||
|
GET /dashboard/yak-supply-realtime-info |
||||||
|
文件:牦牛供应实时信息.ms | 前端:SupplyDemandData.vue |
||||||
|
|
||||||
|
业务:卖方进场登记列表(分页)及汇总(今日进场、总头数、交易中批次)。 |
||||||
|
|
||||||
|
参数:pageNo(默认 1)、pageSize(默认 5;勿用 page) |
||||||
|
|
||||||
|
表及字段: |
||||||
|
yak_trade_entry_record — id, name, vehicle_no, phone, party_id, quarantine_certificate_no, entered_at, entry_type |
||||||
|
yak_trade_ear_tag_inventory — seller_entry_record_id, status, ear_tag_no |
||||||
|
yak_trade_entry_record_ear_tag — entry_record_id |
||||||
|
yak_sn_customer — id, region_name, address |
||||||
|
条件:entry_type='SELLER' |
||||||
|
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------- |
||||||
|
10. 牦牛供应详情 |
||||||
|
-------------------------------------------------------------------------------- |
||||||
|
GET /dashboard/yak-supply-detail |
||||||
|
文件:牦牛供应详情.ms | 前端:SupplyDemandData.vue |
||||||
|
|
||||||
|
业务:单条卖方进场详情(基本信息、耳标进度、关联成交订单、进场/检疫/称重图片 URL)。 |
||||||
|
|
||||||
|
参数:id(必填,yak_trade_entry_record.id) |
||||||
|
|
||||||
|
表及字段: |
||||||
|
yak_trade_entry_record — id, name, vehicle_no, phone, party_id, quarantine_certificate_no, entry_photo_id, entered_at, entry_type |
||||||
|
yak_trade_ear_tag_inventory — seller_entry_record_id, ear_tag_no, status, locked_order_id |
||||||
|
yak_trade_entry_record_ear_tag — entry_record_id, certificate_image_file_id |
||||||
|
yak_trade_order / yak_trade_order_item — 订单与耳标明细 |
||||||
|
yak_trade_weighing_record — weight, photo_id |
||||||
|
yak_trade_quarantine_certificate — certificate_no, image_file_id |
||||||
|
yak_sn_customer — region_name, address(买卖方名称) |
||||||
|
yak_car_record — 关联车辆 |
||||||
|
sys_oss — url(图片解析) |
||||||
|
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------- |
||||||
|
11. 市场环境监控 |
||||||
|
-------------------------------------------------------------------------------- |
||||||
|
GET /dashboard/market-environment |
||||||
|
文件:市场环境监控.ms | 前端:MarketEnvironmentMonitor.vue |
||||||
|
|
||||||
|
业务:环境温湿度 + 气象数据各取最新一条。 |
||||||
|
|
||||||
|
表及字段: |
||||||
|
iot_device_env_data — temperature, humidity, collect_time |
||||||
|
iot_device_weather_data — air_pressure, pm25, pm10, uv_index, rainfall, wind_speed, wind_direction, collect_time |
||||||
|
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------- |
||||||
|
12. 市场实时监控 |
||||||
|
-------------------------------------------------------------------------------- |
||||||
|
GET /dashboard/market-realtime-monitor |
||||||
|
文件:市场实时监控.ms | 前端:MarketRealtimeMonitor.vue |
||||||
|
|
||||||
|
业务:视频监控设备列表及播放/预览地址。 |
||||||
|
|
||||||
|
表及字段: |
||||||
|
iot_device_video — id, name, number, location, address, status, play_url, hd_play_url, preview_img_url, channel_number, index, del_flag, is_show |
||||||
|
iot_device_video_data — device_id, stream_url, snapshot_url, online_status, stream_status, fault_code, collect_time |
||||||
|
条件:del_flag='0',is_show=true |
||||||
|
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------- |
||||||
|
13. 市场实时监控播放配置 |
||||||
|
-------------------------------------------------------------------------------- |
||||||
|
GET /dashboard/market-realtime-player-config |
||||||
|
文件:市场实时监控播放配置.ms | 前端:MarketRealtimeMonitor.vue(liveStreamPlayer.js) |
||||||
|
|
||||||
|
业务:直播流协议识别规则与 hls.js / flv.js 播放器参数(静态配置)。 |
||||||
|
|
||||||
|
数据表:无 |
||||||
|
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------- |
||||||
|
14. 地图迁徙数据 |
||||||
|
-------------------------------------------------------------------------------- |
||||||
|
GET /dashboard/map-trading-network |
||||||
|
文件:地图迁徙数据.ms | 前端:ChinaMap.vue |
||||||
|
|
||||||
|
业务:中央地图三种模式流向——销售网络(红原→全国)、源地供应(全国→红原)、红原出栏(乡镇→红原)。 |
||||||
|
|
||||||
|
表:yak_sn_order;outflow/inflow LEFT JOIN yak_sn_customer |
||||||
|
字段: |
||||||
|
yak_sn_order — quantity, origin_place, destination_place, buyer_id, seller_id, status, del_flag |
||||||
|
yak_sn_customer — id, region_name, del_flag |
||||||
|
条件:del_flag='0',status='COMPLETED';local 模式另要求 origin_place 含「红原」 |
||||||
|
关联:系统配置 mapHub.name 与脚本 hubName 需一致(默认「红原县」) |
||||||
|
|
||||||
|
|
||||||
|
================================================================================ |
||||||
|
表 → 接口索引 |
||||||
|
================================================================================ |
||||||
|
yak_sn_order 2, 3, 4, 7, 14 |
||||||
|
yak_sn_customer 4, 6, 8, 9, 10, 14 |
||||||
|
yak_trade_market_price 5 |
||||||
|
yak_trade_ear_tag_inventory 6, 9, 10 |
||||||
|
yak_trade_entry_record 6, 9, 10 |
||||||
|
yak_trade_entry_record_ear_tag 9, 10 |
||||||
|
yak_trade_order / _item 10 |
||||||
|
yak_trade_weighing_record 10 |
||||||
|
yak_trade_quarantine_certificate 10 |
||||||
|
yak_car_parking_zone 6 |
||||||
|
yak_car_record 6, 10 |
||||||
|
sys_oss 10 |
||||||
|
iot_device_env_data 11 |
||||||
|
iot_device_weather_data 11 |
||||||
|
iot_device_video 12 |
||||||
|
iot_device_video_data 12 |
||||||
|
(无表) 1, 13 |
||||||
@ -0,0 +1,59 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" |
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||||
|
<modelVersion>4.0.0</modelVersion> |
||||||
|
|
||||||
|
<parent> |
||||||
|
<groupId>org.springframework.boot</groupId> |
||||||
|
<artifactId>spring-boot-starter-parent</artifactId> |
||||||
|
<version>3.4.5</version> |
||||||
|
<relativePath/> |
||||||
|
</parent> |
||||||
|
|
||||||
|
<groupId>com.livestock</groupId> |
||||||
|
<artifactId>livestock-trading-backend</artifactId> |
||||||
|
<version>1.0.0-SNAPSHOT</version> |
||||||
|
<name>livestock-trading-backend</name> |
||||||
|
<description>Livestock trading backend based on magic-api</description> |
||||||
|
|
||||||
|
<properties> |
||||||
|
<java.version>17</java.version> |
||||||
|
<magic-api.version>2.2.2</magic-api.version> |
||||||
|
</properties> |
||||||
|
|
||||||
|
<dependencies> |
||||||
|
<dependency> |
||||||
|
<groupId>org.springframework.boot</groupId> |
||||||
|
<artifactId>spring-boot-starter-web</artifactId> |
||||||
|
</dependency> |
||||||
|
<dependency> |
||||||
|
<groupId>org.springframework.boot</groupId> |
||||||
|
<artifactId>spring-boot-starter-jdbc</artifactId> |
||||||
|
</dependency> |
||||||
|
<dependency> |
||||||
|
<groupId>org.ssssssss</groupId> |
||||||
|
<artifactId>magic-api-spring-boot-starter</artifactId> |
||||||
|
<version>${magic-api.version}</version> |
||||||
|
</dependency> |
||||||
|
<dependency> |
||||||
|
<groupId>org.postgresql</groupId> |
||||||
|
<artifactId>postgresql</artifactId> |
||||||
|
<scope>runtime</scope> |
||||||
|
</dependency> |
||||||
|
<dependency> |
||||||
|
<groupId>org.springframework.boot</groupId> |
||||||
|
<artifactId>spring-boot-starter-test</artifactId> |
||||||
|
<scope>test</scope> |
||||||
|
</dependency> |
||||||
|
</dependencies> |
||||||
|
|
||||||
|
<build> |
||||||
|
<plugins> |
||||||
|
<plugin> |
||||||
|
<groupId>org.springframework.boot</groupId> |
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId> |
||||||
|
</plugin> |
||||||
|
</plugins> |
||||||
|
</build> |
||||||
|
</project> |
||||||
@ -0,0 +1,12 @@ |
|||||||
|
package com.livestock.trading; |
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication; |
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication; |
||||||
|
|
||||||
|
@SpringBootApplication |
||||||
|
public class LivestockTradingApplication { |
||||||
|
|
||||||
|
public static void main(String[] args) { |
||||||
|
SpringApplication.run(LivestockTradingApplication.class, args); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,33 @@ |
|||||||
|
server: |
||||||
|
port: 8080 |
||||||
|
|
||||||
|
spring: |
||||||
|
application: |
||||||
|
name: livestock-trading-backend |
||||||
|
datasource: |
||||||
|
driver-class-name: org.postgresql.Driver |
||||||
|
url: jdbc:postgresql://123.57.211.165:15432/ry-vue |
||||||
|
username: postgres |
||||||
|
password: admin123 |
||||||
|
|
||||||
|
magic-api: |
||||||
|
web: /magic/web |
||||||
|
resource: |
||||||
|
location: ./data/magic-api |
||||||
|
show-sql: true |
||||||
|
sql-column-case: camel |
||||||
|
response: |- |
||||||
|
{ |
||||||
|
code: code, |
||||||
|
message: message, |
||||||
|
data, |
||||||
|
timestamp, |
||||||
|
executeTime |
||||||
|
} |
||||||
|
response-code: |
||||||
|
success: 1 |
||||||
|
invalid: 0 |
||||||
|
exception: -1 |
||||||
|
security: |
||||||
|
username: admin |
||||||
|
password: admin123 |
||||||
@ -0,0 +1,12 @@ |
|||||||
|
package com.livestock.trading; |
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
import org.springframework.boot.test.context.SpringBootTest; |
||||||
|
|
||||||
|
@SpringBootTest |
||||||
|
class LivestockTradingApplicationTests { |
||||||
|
|
||||||
|
@Test |
||||||
|
void contextLoads() { |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue