fix dashboard empty data and split dev scripts

This commit is contained in:
david
2026-07-29 17:28:18 +08:00
parent a6fbb8da21
commit 82742bde58
5 changed files with 95 additions and 53 deletions

View File

@@ -71,7 +71,12 @@ var productStatusNames = map[int]string{
// GetDashboardOverview 汇总组织、人员、资产、订单、支付及客服维度的数据。 // GetDashboardOverview 汇总组织、人员、资产、订单、支付及客服维度的数据。
func GetDashboardOverview() (DashboardStatistics, error) { func GetDashboardOverview() (DashboardStatistics, error) {
var result DashboardStatistics result := DashboardStatistics{
OrderStatuses: make([]MetricSlice, 0),
ProductStatuses: make([]MetricSlice, 0),
PaymentChannels: make([]MetricSlice, 0),
RecentOrders: make([]DailyMetric, 0),
}
counts := []struct { counts := []struct {
model any model any
where string where string

View File

@@ -85,34 +85,45 @@ const availableActions = computed(() => actions.filter(
(action) => userStore.role === 'root' || userStore.menuCodes.includes(action.menu), (action) => userStore.role === 'root' || userStore.menuCodes.includes(action.menu),
)); ));
const centsToYuan = (value: number) => value / 100; const centsToYuan = (value: number) => value / 100;
const safeArray = <T,>(value: T[] | null | undefined): T[] =>
Array.isArray(value) ? value : [];
const pieOption = (data: { name: string; value: number }[]): EChartsOption => ({ const pieOption = (data: { name: string; value: number }[]): EChartsOption => ({
tooltip: { trigger: 'item' }, legend: { bottom: 0 }, tooltip: { trigger: 'item' }, legend: { bottom: 0 },
series: [{ type: 'pie', radius: ['42%', '68%'], center: ['50%', '44%'], data, label: { formatter: '{b}\\n{c}' } }], series: [{ type: 'pie', radius: ['42%', '68%'], center: ['50%', '44%'], data, label: { formatter: '{b}\\n{c}' } }],
}); });
const orderStatusOption = computed(() => pieOption(overview.value.order_statuses)); const orderStatusOption = computed(() => pieOption(safeArray(overview.value.order_statuses)));
const productStatusOption = computed(() => pieOption(overview.value.product_statuses)); const productStatusOption = computed(() => pieOption(safeArray(overview.value.product_statuses)));
const orderTrendOption = computed<EChartsOption>(() => ({ const orderTrendOption = computed<EChartsOption>(() => ({
tooltip: { trigger: 'axis' }, legend: { data: ['订单数', '订单金额'] }, tooltip: { trigger: 'axis' }, legend: { data: ['订单数', '订单金额'] },
grid: { left: 48, right: 58, bottom: 34 }, grid: { left: 48, right: 58, bottom: 34 },
xAxis: { type: 'category', data: overview.value.recent_orders.map((item) => item.date.slice(5)) }, xAxis: { type: 'category', data: safeArray(overview.value.recent_orders).map((item) => item.date.slice(5)) },
yAxis: [{ type: 'value', name: '单' }, { type: 'value', name: '元' }], yAxis: [{ type: 'value', name: '单' }, { type: 'value', name: '元' }],
series: [ series: [
{ name: '订单数', type: 'bar', data: overview.value.recent_orders.map((item) => item.order_count) }, { name: '订单数', type: 'bar', data: safeArray(overview.value.recent_orders).map((item) => item.order_count) },
{ name: '订单金额', type: 'line', yAxisIndex: 1, smooth: true, data: overview.value.recent_orders.map((item) => centsToYuan(item.order_amount)) }, { name: '订单金额', type: 'line', yAxisIndex: 1, smooth: true, data: safeArray(overview.value.recent_orders).map((item) => centsToYuan(item.order_amount)) },
], ],
})); }));
const paymentChannelOption = computed<EChartsOption>(() => ({ const paymentChannelOption = computed<EChartsOption>(() => ({
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } }, grid: { left: 70, right: 24, bottom: 28 }, tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } }, grid: { left: 70, right: 24, bottom: 28 },
xAxis: { type: 'value', name: '元' }, xAxis: { type: 'value', name: '元' },
yAxis: { type: 'category', data: overview.value.payment_channels.map((item) => item.name || '未知渠道') }, yAxis: { type: 'category', data: safeArray(overview.value.payment_channels).map((item) => item.name || '未知渠道') },
series: [{ type: 'bar', data: overview.value.payment_channels.map((item) => centsToYuan(item.value)) }], series: [{ type: 'bar', data: safeArray(overview.value.payment_channels).map((item) => centsToYuan(item.value)) }],
})); }));
onMounted(async () => { onMounted(async () => {
loading.value = true; loading.value = true;
try { try {
const data = await platformApi.overview(); const data = await platformApi.overview();
overview.value = { ...data, today_order_amount: centsToYuan(data.today_order_amount), paid_amount: centsToYuan(data.paid_amount) }; overview.value = {
...emptyOverview(),
...data,
today_order_amount: centsToYuan(data.today_order_amount ?? 0),
paid_amount: centsToYuan(data.paid_amount ?? 0),
order_statuses: safeArray(data.order_statuses),
product_statuses: safeArray(data.product_statuses),
payment_channels: safeArray(data.payment_channels),
recent_orders: safeArray(data.recent_orders),
};
} catch (error) { } catch (error) {
Message.error((error as Error).message); Message.error((error as Error).message);
} finally { } finally {

44
scripts/run_backend.sh Executable file
View File

@@ -0,0 +1,44 @@
#!/usr/bin/env bash
set -Eeuo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
BACKEND_DIR="${PROJECT_ROOT}/backend/api"
RUNTIME_CONFIG_ROOT=""
BACKEND_PID=""
cleanup() {
trap - EXIT INT TERM
if [[ -n "${BACKEND_PID}" ]] && kill -0 "${BACKEND_PID}" 2>/dev/null; then
kill "${BACKEND_PID}" 2>/dev/null || true
wait "${BACKEND_PID}" 2>/dev/null || true
fi
if [[ -n "${RUNTIME_CONFIG_ROOT}" ]]; then
rm -f "${RUNTIME_CONFIG_ROOT}/etc/heqi_dev.yaml"
rmdir "${RUNTIME_CONFIG_ROOT}/etc" "${RUNTIME_CONFIG_ROOT}" 2>/dev/null || true
fi
}
if ! command -v go >/dev/null 2>&1; then
echo "缺少命令go" >&2
exit 1
fi
export BSM_RuntimeMode="${BSM_RuntimeMode:-dev}"
export BSM_JwtSecretKey="${BSM_JwtSecretKey:-local-dev-key-16}"
if [[ -z "${BSM_Prefix:-}" ]]; then
RUNTIME_CONFIG_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/heqi-platform-backend.XXXXXX")"
mkdir -p "${RUNTIME_CONFIG_ROOT}/etc"
cp "${BACKEND_DIR}/etc/platform_dev.yaml" "${RUNTIME_CONFIG_ROOT}/etc/heqi_dev.yaml"
export BSM_Prefix="${RUNTIME_CONFIG_ROOT}"
fi
trap cleanup EXIT INT TERM
echo "启动平台后端http://localhost:12426/heqi/platform/v1"
cd "${BACKEND_DIR}"
go run ./cmd/main/main.go &
BACKEND_PID=$!
wait "${BACKEND_PID}"

23
scripts/run_frontend.sh Executable file
View File

@@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -Eeuo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
FRONTEND_DIR="${PROJECT_ROOT}/frontend/platform_admin"
if ! command -v pnpm >/dev/null 2>&1; then
echo "缺少命令pnpm" >&2
exit 1
fi
if [[ ! -d "${FRONTEND_DIR}/node_modules" ]]; then
echo "前端依赖尚未安装请先运行cd ${FRONTEND_DIR} && pnpm install" >&2
exit 1
fi
export VITE_API_BASE_URL="${VITE_API_BASE_URL:-http://localhost:12426/heqi/platform/v1}"
echo "启动平台前端http://localhost:5173"
cd "${FRONTEND_DIR}"
exec pnpm exec vite --config ./config/vite.config.ts --host 0.0.0.0

View File

@@ -3,14 +3,10 @@
set -Eeuo pipefail set -Eeuo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
BACKEND_DIR="${PROJECT_ROOT}/backend/api"
FRONTEND_DIR="${PROJECT_ROOT}/frontend/platform_admin"
BACKEND_PID="" BACKEND_PID=""
FRONTEND_PID="" FRONTEND_PID=""
CLEANED_UP=0 CLEANED_UP=0
RUNTIME_CONFIG_ROOT=""
cleanup() { cleanup() {
if [[ "${CLEANED_UP}" -eq 1 ]]; then if [[ "${CLEANED_UP}" -eq 1 ]]; then
@@ -29,55 +25,18 @@ cleanup() {
wait "${pid}" 2>/dev/null || true wait "${pid}" 2>/dev/null || true
fi fi
done done
if [[ -n "${RUNTIME_CONFIG_ROOT}" ]]; then
rm -f "${RUNTIME_CONFIG_ROOT}/etc/heqi_dev.yaml"
rmdir "${RUNTIME_CONFIG_ROOT}/etc" "${RUNTIME_CONFIG_ROOT}" 2>/dev/null || true
fi
} }
require_command() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "缺少命令:$1" >&2
exit 1
fi
}
require_command go
require_command pnpm
if [[ ! -d "${FRONTEND_DIR}/node_modules" ]]; then
echo "前端依赖尚未安装请先运行cd ${FRONTEND_DIR} && pnpm install" >&2
exit 1
fi
export BSM_RuntimeMode="${BSM_RuntimeMode:-dev}"
export BSM_JwtSecretKey="${BSM_JwtSecretKey:-local-dev-key-16}"
export VITE_API_BASE_URL="${VITE_API_BASE_URL:-http://localhost:12426/heqi/platform/v1}"
if [[ -z "${BSM_Prefix:-}" ]]; then
RUNTIME_CONFIG_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/heqi-platform.XXXXXX")"
mkdir -p "${RUNTIME_CONFIG_ROOT}/etc"
cp "${BACKEND_DIR}/etc/platform_dev.yaml" "${RUNTIME_CONFIG_ROOT}/etc/heqi_dev.yaml"
export BSM_Prefix="${RUNTIME_CONFIG_ROOT}"
fi
trap cleanup EXIT INT TERM trap cleanup EXIT INT TERM
echo "启动平台后端http://localhost:12426/heqi/platform/v1" "${SCRIPT_DIR}/run_backend.sh" &
(
cd "${BACKEND_DIR}"
exec go run ./cmd/main/main.go
) &
BACKEND_PID=$! BACKEND_PID=$!
echo "启动平台前端http://localhost:5173" "${SCRIPT_DIR}/run_frontend.sh" &
(
cd "${FRONTEND_DIR}"
exec pnpm exec vite --config ./config/vite.config.ts --host 0.0.0.0
) &
FRONTEND_PID=$! FRONTEND_PID=$!
echo "前后端调试服务已启动,按 Ctrl+C 同时停止。" echo "前后端调试服务已启动,按 Ctrl+C 同时停止。"
echo "日常开发建议在两个终端分别运行 run_backend.sh 和 run_frontend.sh。"
while true; do while true; do
if ! kill -0 "${BACKEND_PID}" 2>/dev/null; then if ! kill -0 "${BACKEND_PID}" 2>/dev/null; then