<template>
|
<view class="page">
|
<scroll-view class="scroll" scroll-y>
|
<!-- 顶部 Banner:放入滚动区域,随页面一起滚动,不固定在顶部 -->
|
<view class="hero-section">
|
<view class="bg-img">
|
<view class="hero-content">
|
<!-- 预留:如后续需要可在此放 logo 或简短提示 -->
|
</view>
|
<view class="hero-wave"></view>
|
</view>
|
</view>
|
|
<!-- 快捷入口 -->
|
<view class="quick-section">
|
<up-grid :border="false" col="4">
|
<up-grid-item
|
v-for="item in quickTools"
|
:key="item.label"
|
@click="handleQuickTool(item)"
|
>
|
<view class="quick-icon" :style="{ background: item.bgColor }">
|
<image :src="item.icon" class="quick-icon-img" mode="aspectFit" />
|
</view>
|
<text class="quick-text">{{ item.label }}</text>
|
</up-grid-item>
|
</up-grid>
|
</view>
|
|
<!-- 数据总览 -->
|
<view class="section">
|
<view class="section-header">
|
<view class="section-title">
|
<view class="title-bar" />
|
<text class="title-text">数据总览</text>
|
</view>
|
<view class="section-action" @tap="toggleOverview">
|
<text class="action-text">{{ overviewExpanded ? "收起" : "展开" }}</text>
|
<view :class="['chev', overviewExpanded ? 'up' : 'down']" />
|
</view>
|
</view>
|
|
<view v-show="overviewExpanded" class="overview">
|
<view class="overview-card sales">
|
<view class="card-left">
|
<text class="card-title">销售数据</text>
|
<view class="card-metrics">
|
<view class="metric">
|
<text class="metric-label">本月营业额(元)</text>
|
<text class="metric-value">{{ overviewCards.sales.today }}</text>
|
</view>
|
<view class="metric">
|
<text class="metric-label">本月已开票(元)</text>
|
<text class="metric-value">{{ overviewCards.sales.unbilled }}</text>
|
</view>
|
</view>
|
</view>
|
</view>
|
|
<view class="overview-card purchase">
|
<view class="card-left">
|
<text class="card-title">采购数据</text>
|
<view class="card-metrics">
|
<view class="metric">
|
<text class="metric-label">本月营业额(元)</text>
|
<text class="metric-value">{{ overviewCards.purchase.today }}</text>
|
</view>
|
<view class="metric">
|
<text class="metric-label">本月已开票(元)</text>
|
<text class="metric-value">{{ overviewCards.purchase.unbilled }}</text>
|
</view>
|
</view>
|
</view>
|
</view>
|
|
<view class="overview-card stock">
|
<view class="card-left">
|
<text class="card-title">库存数据</text>
|
<view class="card-metrics">
|
<view class="metric">
|
<text class="metric-label">库存数量</text>
|
<text class="metric-value">{{ overviewCards.stock.today }}</text>
|
</view>
|
<view class="metric">
|
<text class="metric-label">今日库存数量</text>
|
<text class="metric-value">{{ overviewCards.stock.unbilled }}</text>
|
</view>
|
</view>
|
</view>
|
</view>
|
</view>
|
</view>
|
|
<!-- 客户合同金额分析 -->
|
<view class="section">
|
<view class="section-header">
|
<view class="section-title">
|
<view class="title-bar" />
|
<text class="title-text">客户合同金额分析</text>
|
</view>
|
<view class="section-action" @tap="goMore">
|
<text class="action-text">更多</text>
|
<view class="chev right" />
|
</view>
|
</view>
|
|
<view class="analysis-card">
|
<view class="chart-row">
|
<view class="chart-box big">
|
<qiun-data-charts
|
type="ring"
|
canvasId="home_contract_ring"
|
:canvas2d="isCanvas2d"
|
:reshow="chartReady"
|
:opts="ringOpts"
|
:chartData="ringChartData"
|
/>
|
</view>
|
</view>
|
|
<view class="summary">
|
<view class="summary-main">
|
<text class="summary-label">总合同金额(元)</text>
|
<text class="summary-value">{{ contractSummaryView.sumText }}</text>
|
</view>
|
<view class="summary-sub">
|
<view class="summary-chip">
|
<text class="chip-label">周同比</text>
|
<text class="chip-value">{{ contractSummaryView.ynyText }}</text>
|
</view>
|
<view class="summary-chip">
|
<text class="chip-label">日环比</text>
|
<text class="chip-value">{{ contractSummaryView.chainText }}</text>
|
</view>
|
</view>
|
</view>
|
|
<view class="list-row">
|
<swiper
|
class="customer-swiper"
|
:current="customerSwiperIndex"
|
:indicator-dots="false"
|
:autoplay="false"
|
:circular="false"
|
@change="onCustomerSwiperChange"
|
>
|
<swiper-item v-for="(page, pIdx) in customerPages" :key="pIdx">
|
<view class="customer-page">
|
<view v-for="item in page" :key="item.key" class="customer-item">
|
<view class="customer-row">
|
<view class="customer-name">
|
<image
|
v-if="item.rank === 1"
|
class="rank-icon"
|
:src="imgNum1"
|
mode="heightFix"
|
/>
|
<image
|
v-else-if="item.rank === 2"
|
class="rank-icon"
|
:src="imgNum2"
|
mode="heightFix"
|
/>
|
<image
|
v-else-if="item.rank === 3"
|
class="rank-icon"
|
:src="imgNum3"
|
mode="heightFix"
|
/>
|
<text class="name-text">{{ item.name }}</text>
|
</view>
|
<view class="customer-rate">
|
<text class="rate-label">占比</text>
|
<text class="rate-value">{{ item.rateText }}</text>
|
</view>
|
<view class="amount">
|
<text class="amount-num">{{ item.valueText }}</text>
|
<text class="amount-unit">元</text>
|
</view>
|
</view>
|
</view>
|
</view>
|
</swiper-item>
|
</swiper>
|
|
<view v-if="customerPages.length > 1" class="dots">
|
<view
|
v-for="i in customerPages.length"
|
:key="i"
|
:class="['dot', customerSwiperIndex === i - 1 ? 'active' : '']"
|
/>
|
</view>
|
</view>
|
</view>
|
</view>
|
|
<view class="bottom-space" />
|
</scroll-view>
|
|
</view>
|
</template>
|
|
<script setup>
|
import { computed, onMounted, ref } from "vue";
|
import { analysisCustomerContractAmounts, getBusiness } from "@/api/viewIndex";
|
|
const imgNum1 = "/static/images/index/num1.png";
|
const imgNum2 = "/static/images/index/num2.png";
|
const imgNum3 = "/static/images/index/num3.png";
|
|
const quickTools = [
|
{
|
label: "生产报工",
|
icon: "/static/images/icon/shengchanbaogong@2x.png",
|
bgColor: "linear-gradient(135deg,#3b82f6,#2563eb)",
|
action: "scan",
|
},
|
{
|
label: "设备巡检",
|
icon: "/static/images/icon/xunjianshangchuan@2x.png",
|
bgColor: "linear-gradient(135deg,#22c55e,#16a34a)",
|
route: "/pages/inspectionUpload/index",
|
},
|
{
|
label: "设备保养",
|
icon: "/static/images/icon/shbeibaoyang@2x.png",
|
bgColor: "linear-gradient(135deg,#f97316,#ea580c)",
|
route: "/pages/equipmentManagement/upkeep/index",
|
},
|
{
|
label: "设备报修",
|
icon: "/static/images/icon/shbeibaoxiu@2x.png",
|
bgColor: "linear-gradient(135deg,#a855f7,#7c3aed)",
|
route: "/pages/equipmentManagement/repair/index",
|
},
|
];
|
|
const isCanvas2d = ref(false);
|
|
const overviewExpanded = ref(true);
|
const businessRaw = ref({});
|
const contractRawList = ref([]);
|
const chartReady = ref(false);
|
const contractSummary = ref({ sum: "0", chain: "0", yny: "0" });
|
|
// 客户合同金额分析:无需筛选项(按接口默认返回展示)
|
|
function toggleOverview() {
|
overviewExpanded.value = !overviewExpanded.value;
|
}
|
|
function handleQuickTool(item) {
|
if (item?.action === "scan") {
|
// 生产报工 - 调用扫码
|
uni.scanCode({
|
success: (res) => {
|
console.log("扫码结果:", res);
|
// 解析扫码结果并跳转到生产报工页面
|
try {
|
const scanResult = JSON.parse(res.result);
|
uni.navigateTo({
|
url: `/pages/productionManagement/productionReport/index?orderRow=${encodeURIComponent(JSON.stringify(scanResult))}`
|
});
|
} catch (e) {
|
console.error("扫码结果解析失败:", e);
|
uni.showToast({ title: "无效的二维码", icon: "none" });
|
}
|
},
|
fail: (err) => {
|
console.error("扫码失败:", err);
|
}
|
});
|
return;
|
}
|
if (!item?.route) return;
|
uni.navigateTo({ url: item.route });
|
}
|
|
function goMore() {
|
uni.showToast({ title: "更多功能待接入", icon: "none" });
|
}
|
|
|
function getByPath(obj, path) {
|
if (!obj || !path) return undefined;
|
const seg = String(path).split(".");
|
let cur = obj;
|
for (const k of seg) {
|
if (cur == null) return undefined;
|
cur = cur[k];
|
}
|
return cur;
|
}
|
|
function pick(obj, paths, fallback = undefined) {
|
for (const p of paths) {
|
const v = getByPath(obj, p);
|
if (v !== undefined && v !== null && v !== "") return v;
|
}
|
return fallback;
|
}
|
|
function toNumber(v) {
|
const n = Number(v);
|
return Number.isFinite(n) ? n : NaN;
|
}
|
|
function formatMoneyWan(v) {
|
const n = toNumber(v);
|
if (!Number.isFinite(n)) return "--";
|
const wan = n >= 10000 ? n / 10000 : n;
|
const fixed = wan >= 100 ? wan.toFixed(0) : wan >= 10 ? wan.toFixed(1) : wan.toFixed(2);
|
return fixed.replace(/\.0+$/, "");
|
}
|
|
function formatWanFromYuan(v) {
|
const n = toNumber(v);
|
if (!Number.isFinite(n)) return "--";
|
const wan = n / 10000;
|
const fixed = wan >= 100 ? wan.toFixed(0) : wan >= 10 ? wan.toFixed(1) : wan.toFixed(2);
|
return fixed.replace(/\.0+$/, "");
|
}
|
|
function formatPlain(v) {
|
const n = toNumber(v);
|
if (!Number.isFinite(n)) return "--";
|
const fixed = n >= 100 ? n.toFixed(0) : n >= 10 ? n.toFixed(1) : n.toFixed(2);
|
return fixed.replace(/\.0+$/, "");
|
}
|
|
function formatPercent(v) {
|
const n = toNumber(v);
|
if (!Number.isFinite(n)) return "--";
|
const p = n > 1 ? n : n * 100;
|
return `${p.toFixed(1).replace(/\.0$/, "")}%`;
|
}
|
|
const overviewCards = computed(() => {
|
const b = businessRaw.value || {};
|
|
// /home/business 兼容字段(你提供的结构优先)
|
const monthSaleMoney = pick(b, ["monthSaleMoney", "sale.monthMoney", "sales.monthMoney", "sales.monthSaleMoney"]);
|
const monthSaleHaveMoney = pick(b, ["monthSaleHaveMoney", "sale.monthHaveMoney", "sales.monthHaveMoney"]);
|
const monthPurchaseMoney = pick(b, ["monthPurchaseMoney", "purchase.monthMoney", "procurement.monthMoney"]);
|
const monthPurchaseHaveMoney = pick(b, ["monthPurchaseHaveMoney", "purchase.monthHaveMoney", "procurement.monthHaveMoney"]);
|
const inventoryNum = pick(b, ["inventoryNum", "stock.inventoryNum", "inventory.num", "stock.num"]);
|
const todayInventoryNum = pick(b, ["todayInventoryNum", "stock.todayInventoryNum", "inventory.todayNum", "stock.todayNum"]);
|
|
return {
|
// 金额按“元”展示
|
sales: { today: formatPlain(monthSaleMoney), unbilled: formatPlain(monthSaleHaveMoney) },
|
purchase: { today: formatPlain(monthPurchaseMoney), unbilled: formatPlain(monthPurchaseHaveMoney) },
|
// 库存为数量(不做“万”换算)
|
stock: { today: formatPlain(inventoryNum), unbilled: formatPlain(todayInventoryNum) },
|
};
|
});
|
|
const contractSummaryView = computed(() => {
|
const sumText = formatPlain(contractSummary.value?.sum);
|
const chainText = formatPlain(contractSummary.value?.chain);
|
const ynyText = formatPlain(contractSummary.value?.yny);
|
return {
|
sumText,
|
chainText,
|
ynyText,
|
};
|
});
|
|
const ringOpts = computed(() => {
|
const totalYuan = formatPlain(contractSummary.value?.sum);
|
return {
|
padding: [0, 0, 0, 0],
|
legend: { show: false },
|
rotate: false,
|
dataLabel: false,
|
title: { name: totalYuan, fontSize: 18, color: "#2b2b2b" },
|
subtitle: { name: "合同金额(元)", fontSize: 12, color: "#8a94a6" },
|
extra: {
|
ring: {
|
ringWidth: 18,
|
activeOpacity: 0.5,
|
activeRadius: 8,
|
offsetAngle: 0,
|
labelWidth: 0,
|
border: true,
|
borderWidth: 2,
|
borderColor: "#FFFFFF",
|
},
|
},
|
};
|
});
|
|
const ringChartData = computed(() => {
|
const list = Array.isArray(contractRawList.value) ? contractRawList.value : [];
|
const top = list.slice(0, 5);
|
const other = list.slice(5);
|
const otherSum = other.reduce((acc, cur) => acc + (Number(cur.value) || 0), 0);
|
|
const data = [
|
...top.map((x) => ({ name: x.name, value: Number(x.value) || 0 })),
|
...(otherSum > 0 ? [{ name: "其他", value: otherSum }] : []),
|
].filter((x) => x.value > 0);
|
|
return { series: [{ data }] };
|
});
|
|
const customerSwiperIndex = ref(0);
|
function onCustomerSwiperChange(e) {
|
customerSwiperIndex.value = e.detail?.current || 0;
|
}
|
|
const customerPages = computed(() => {
|
const list = Array.isArray(contractRawList.value) ? contractRawList.value : [];
|
const pageSize = 2;
|
const pages = [];
|
for (let i = 0; i < list.length; i += pageSize) pages.push(list.slice(i, i + pageSize));
|
return pages.length ? pages : [[]];
|
});
|
|
function normalizeContractList(raw) {
|
const rows = Array.isArray(raw?.item) ? raw.item : [];
|
|
const mapped = rows
|
.map((it, idx) => {
|
const name = pick(it, ["name"], `公司${idx + 1}`);
|
const value = pick(it, ["value"], 0);
|
const rate = pick(it, ["rate"], 0);
|
|
return {
|
key: `${idx}-${name}`,
|
name: String(name),
|
value: toNumber(value),
|
valueText: formatPlain(value),
|
rate: toNumber(rate),
|
rateText: `${formatPlain(rate)}%`,
|
};
|
})
|
.sort((a, b) => (b.rate || 0) - (a.rate || 0))
|
.map((it, index) => ({ ...it, rank: index + 1 }));
|
|
return mapped;
|
}
|
|
async function loadHome() {
|
chartReady.value = false;
|
try {
|
const [bRes, cRes] = await Promise.all([getBusiness(), analysisCustomerContractAmounts()]);
|
businessRaw.value = bRes?.data || {};
|
const cData = cRes?.data || {};
|
contractSummary.value = {
|
sum: pick(cData, ["sum"], "0"),
|
chain: pick(cData, ["chain"], "0"),
|
yny: pick(cData, ["yny"], "0"),
|
};
|
contractRawList.value = normalizeContractList(cData);
|
} catch (e) {
|
contractSummary.value = { sum: "0", chain: "0", yny: "0" };
|
contractRawList.value = normalizeContractList({ item: [] });
|
} finally {
|
customerSwiperIndex.value = 0;
|
chartReady.value = true;
|
}
|
}
|
|
onMounted(() => {
|
try {
|
const platform = uni.getSystemInfoSync().platform;
|
isCanvas2d.value = platform === "android" || platform === "ios";
|
} catch (e) {
|
isCanvas2d.value = false;
|
}
|
loadHome();
|
});
|
</script>
|
|
<style scoped lang="scss">
|
.page {
|
background: #f6f7fb;
|
min-height: 100vh;
|
// padding: 12px;
|
/* 为所有设备设置基础padding-top(包含安全区) */
|
padding-top: calc(env(safe-area-inset-top) + 30px);
|
position: relative;
|
|
/* 为安卓设备设置更大的顶部内边距 */
|
/* #ifdef APP-PLUS && !MP && !H5 */
|
padding-top: calc(env(safe-area-inset-top) + 45px);
|
/* #endif */
|
|
/* H5和小程序平台的通用样式 */
|
/* #ifdef H5 || MP */
|
padding-top: calc(env(safe-area-inset-top) + 30px);
|
/* #endif */
|
&::before {
|
content: "";
|
position: fixed;
|
top: 0;
|
left: 0;
|
right: 0;
|
bottom: 0;
|
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="dots" width="24" height="24" patternUnits="userSpaceOnUse"><circle cx="12" cy="12" r="1" fill="rgba(41, 121, 255, 0.02)"/></pattern></defs><rect width="100" height="100" fill="url(%23dots)"/></svg>');
|
pointer-events: none;
|
z-index: -1;
|
}
|
|
&::after {
|
content: "";
|
position: fixed;
|
top: 0;
|
left: 0;
|
right: 0;
|
bottom: 0;
|
background: radial-gradient(
|
circle at 20% 80%,
|
rgba(41, 121, 255, 0.02) 0%,
|
transparent 55%
|
),
|
radial-gradient(
|
circle at 80% 20%,
|
rgba(156, 39, 176, 0.015) 0%,
|
transparent 55%
|
);
|
pointer-events: none;
|
z-index: -1;
|
}
|
}
|
.hero-section {
|
margin: 0 12px;
|
margin-bottom: 12px;
|
animation: fadeInUp 0.6s ease-out 0.1s both;
|
}
|
|
.bg-img {
|
width: 100%;
|
height: 9.25rem;
|
background-image: url("../static/images/banner/backview.png");
|
background-size: cover;
|
border-radius: 14px;
|
position: relative;
|
overflow: hidden;
|
box-shadow: 0 10px 26px rgba(17, 24, 39, 0.08);
|
|
&::before {
|
content: "";
|
position: absolute;
|
top: -50%;
|
left: -50%;
|
width: 200%;
|
height: 200%;
|
background: conic-gradient(
|
from 0deg,
|
transparent,
|
rgba(255, 255, 255, 0.1),
|
transparent,
|
rgba(255, 255, 255, 0.05),
|
transparent
|
);
|
animation: rotate 20s linear infinite;
|
}
|
|
&::after {
|
content: "";
|
position: absolute;
|
top: 0;
|
right: 0;
|
width: 7.5rem;
|
height: 7.5rem;
|
background: radial-gradient(
|
circle,
|
rgba(255, 255, 255, 0.15) 0%,
|
transparent 70%
|
);
|
border-radius: 50%;
|
transform: translate(2.5rem, -2.5rem);
|
}
|
}
|
|
.hero-content {
|
position: relative;
|
z-index: 1;
|
|
padding: 14px 14px 18px 14px;
|
margin: 0 12px;
|
height: 100%;
|
}
|
.hero-wave {
|
height: 2.75rem;
|
}
|
|
.safe-top {
|
height: calc(env(safe-area-inset-top) + 10px);
|
}
|
|
.scroll {
|
min-height: calc(100vh - env(safe-area-inset-top) - 10px);
|
}
|
|
.section {
|
margin: 0 14px 14px;
|
}
|
|
.quick-section {
|
margin: 0 14px 10px;
|
padding: 6px 0 2px;
|
}
|
|
.quick-icon {
|
width: 44px;
|
height: 44px;
|
border-radius: 12px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
box-shadow: 0 8px 18px rgba(15, 23, 42, 0.18);
|
}
|
|
.quick-icon-img {
|
width: 26px;
|
height: 26px;
|
}
|
|
.quick-text {
|
margin-top: 6px;
|
font-size: 12px;
|
color: #555555;
|
text-align: center;
|
white-space: nowrap;
|
}
|
|
.section-header {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
padding: 8px 2px 10px;
|
}
|
|
.section-title {
|
display: flex;
|
align-items: center;
|
gap: 8px;
|
}
|
|
.title-bar {
|
width: 4px;
|
height: 16px;
|
border-radius: 2px;
|
background: #2f6bff;
|
}
|
|
.title-text {
|
font-size: 16px;
|
font-weight: 700;
|
color: #1f2937;
|
}
|
|
.section-action {
|
display: flex;
|
align-items: center;
|
gap: 6px;
|
color: #8a94a6;
|
font-size: 12px;
|
}
|
|
.action-text {
|
color: #8a94a6;
|
}
|
|
.chev {
|
width: 8px;
|
height: 8px;
|
border-right: 2px solid #b9c0cf;
|
border-bottom: 2px solid #b9c0cf;
|
transform: rotate(45deg);
|
}
|
.chev.up {
|
transform: rotate(-135deg);
|
}
|
.chev.down {
|
transform: rotate(45deg);
|
}
|
.chev.right {
|
transform: rotate(-45deg);
|
}
|
.chev.small {
|
width: 7px;
|
height: 7px;
|
border-width: 2px;
|
}
|
|
.overview {
|
display: flex;
|
flex-direction: column;
|
gap: 10px;
|
}
|
|
.overview-card {
|
position: relative;
|
overflow: hidden;
|
border-radius: 12px;
|
padding: 14px 14px 12px;
|
border: 1px dashed rgba(31, 41, 55, 0.08);
|
}
|
|
.overview-card.sales {
|
background: url("/static/images/index/xiaoshoushuju.png") no-repeat center center;
|
background-size: cover;
|
}
|
.overview-card.purchase {
|
background: url("/static/images/index/caigoushuju.png") no-repeat center center;
|
background-size: cover;
|
}
|
.overview-card.stock {
|
background: url("/static/images/index/kucunshuju.png") no-repeat center center;
|
background-size: cover;
|
}
|
|
.card-left {
|
position: relative;
|
z-index: 1;
|
}
|
|
.card-title {
|
font-size: 14px;
|
font-weight: 700;
|
color: rgba(17, 24, 39, 0.85);
|
margin-bottom: 10px;
|
}
|
|
.card-metrics {
|
display: flex;
|
gap: 12px;
|
justify-content: space-between;
|
width: 100%;
|
}
|
|
.metric {
|
display: flex;
|
align-items: center;
|
min-width: 0;
|
flex: 1;
|
}
|
|
.metric-label {
|
font-size: 11px;
|
color: rgba(107, 114, 128, 0.9);
|
white-space: nowrap;
|
width: 80px;
|
}
|
|
.metric-value {
|
font-size: 14px;
|
font-weight: 800;
|
color: rgba(17, 24, 39, 0.9);
|
white-space: nowrap;
|
flex: 1 1 auto;
|
min-width: 0;
|
overflow: visible;
|
text-overflow: clip;
|
}
|
|
.metric-label,
|
.metric-value {
|
line-height: 1.2;
|
}
|
|
.metric-value {
|
font-variant-numeric: tabular-nums;
|
}
|
|
.overview-card.purchase .metric-value {
|
color: rgba(124, 84, 28, 0.95);
|
}
|
.overview-card.stock .metric-value {
|
color: rgba(24, 64, 136, 0.95);
|
}
|
|
.card-icon {
|
position: absolute;
|
right: 10px;
|
top: 10px;
|
width: 78px;
|
opacity: 0.35;
|
}
|
|
.analysis-card {
|
background: #ffffff;
|
border-radius: 14px;
|
padding: 12px;
|
border: 1px solid rgba(148, 163, 184, 0.18);
|
display: flex;
|
flex-direction: column;
|
gap: 10px;
|
}
|
|
.chart-row {
|
width: 100%;
|
display: flex;
|
justify-content: center;
|
}
|
|
.summary {
|
padding: 2px 2px 10px;
|
}
|
.summary-main {
|
display: flex;
|
align-items: baseline;
|
justify-content: space-between;
|
gap: 10px;
|
}
|
.summary-label {
|
font-size: 12px;
|
color: #6b7280;
|
white-space: nowrap;
|
}
|
.summary-value {
|
font-size: 18px;
|
font-weight: 800;
|
color: #1f2937;
|
text-align: right;
|
min-width: 0;
|
white-space: nowrap;
|
}
|
.summary-sub {
|
margin-top: 6px;
|
display: flex;
|
gap: 8px;
|
}
|
|
.list-row {
|
width: 100%;
|
}
|
.summary-chip {
|
flex: 1;
|
background: rgba(47, 107, 255, 0.06);
|
border: 1px solid rgba(47, 107, 255, 0.12);
|
border-radius: 10px;
|
padding: 6px 8px;
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
gap: 8px;
|
min-width: 0;
|
}
|
.chip-label {
|
font-size: 11px;
|
color: #6b7280;
|
white-space: nowrap;
|
}
|
.chip-value {
|
font-size: 12px;
|
font-weight: 700;
|
color: #2f6bff;
|
white-space: nowrap;
|
}
|
|
.chart-box {
|
height: 170px;
|
width: 100%;
|
}
|
.chart-box.big {
|
height: 220px;
|
width: 100%;
|
}
|
|
.customer-swiper {
|
height: 140px;
|
}
|
|
.customer-page {
|
display: flex;
|
flex-direction: column;
|
gap: 14px;
|
padding-top: 4px;
|
}
|
|
.customer-item {
|
display: flex;
|
flex-direction: column;
|
gap: 2px;
|
}
|
|
.customer-row {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
gap: 8px;
|
}
|
|
.customer-name {
|
display: flex;
|
align-items: center;
|
gap: 8px;
|
min-width: 0;
|
flex: 1 1 auto;
|
}
|
|
.rank-icon {
|
width: 18px;
|
height: 18px;
|
flex: 0 0 auto;
|
}
|
|
.name-text {
|
font-size: 13px;
|
color: #1f2937;
|
font-weight: 600;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
white-space: nowrap;
|
}
|
|
.amount {
|
display: flex;
|
align-items: baseline;
|
gap: 4px;
|
color: #1f2937;
|
font-weight: 800;
|
flex: 0 0 auto;
|
}
|
.amount-num {
|
font-size: 18px;
|
line-height: 1;
|
}
|
.amount-unit {
|
font-size: 12px;
|
color: #6b7280;
|
font-weight: 600;
|
}
|
|
.customer-rate {
|
display: flex;
|
align-items: center;
|
gap: 4px;
|
flex: 0 0 auto;
|
white-space: nowrap;
|
}
|
.rate-label {
|
font-size: 11px;
|
color: #6b7280;
|
}
|
.rate-value {
|
font-size: 12px;
|
font-weight: 600;
|
color: #2f6bff;
|
}
|
|
.customer-sub {
|
display: flex;
|
align-items: center;
|
gap: 8px;
|
color: #6b7280;
|
font-size: 12px;
|
}
|
.sub-label {
|
color: #6b7280;
|
}
|
.sub-value {
|
font-weight: 700;
|
color: #6b7280;
|
}
|
.sub-value.up {
|
color: #16a34a;
|
}
|
.sub-value.down {
|
color: #dc2626;
|
}
|
|
.arrow {
|
width: 0;
|
height: 0;
|
border-left: 4px solid transparent;
|
border-right: 4px solid transparent;
|
}
|
.arrow.up {
|
border-bottom: 6px solid #16a34a;
|
transform: translateY(-1px);
|
}
|
.arrow.down {
|
border-top: 6px solid #dc2626;
|
transform: translateY(1px);
|
}
|
|
.dots {
|
display: flex;
|
gap: 6px;
|
justify-content: center;
|
margin-top: 6px;
|
}
|
.dot {
|
width: 8px;
|
height: 4px;
|
border-radius: 99px;
|
background: rgba(148, 163, 184, 0.35);
|
}
|
.dot.active {
|
width: 14px;
|
background: #2f6bff;
|
}
|
|
.bottom-space {
|
height: 24px;
|
}
|
|
@media (prefers-color-scheme: dark) {
|
.page {
|
background: #121317;
|
}
|
.title-text {
|
color: #e9edf3;
|
}
|
.overview-card,
|
.filter-item,
|
.analysis-card {
|
border-color: rgba(255, 255, 255, 0.08);
|
}
|
.filter-item,
|
.analysis-card {
|
background: #1e1f24;
|
}
|
.name-text,
|
.amount {
|
color: #e9edf3;
|
}
|
.amount-unit,
|
.metric-label,
|
.action-text,
|
.section-action,
|
.customer-sub,
|
.sub-label,
|
.sub-value {
|
color: #aab2c1;
|
}
|
.summary-value {
|
color: #e9edf3;
|
}
|
.summary-chip {
|
background: rgba(47, 107, 255, 0.12);
|
border-color: rgba(47, 107, 255, 0.18);
|
}
|
.overview-card.sales {
|
background: linear-gradient(135deg, rgba(30, 91, 104, 0.35) 0%, rgba(18, 68, 80, 0.35) 100%);
|
}
|
.overview-card.purchase {
|
background: linear-gradient(135deg, rgba(108, 76, 33, 0.35) 0%, rgba(80, 55, 20, 0.35) 100%);
|
}
|
.overview-card.stock {
|
background: linear-gradient(135deg, rgba(36, 54, 110, 0.35) 0%, rgba(22, 35, 74, 0.35) 100%);
|
}
|
.metric-value {
|
color: #e9edf3;
|
}
|
}
|
</style>
|