This commit is contained in:
2025-09-27 22:57:59 +08:00
parent 8a458ff0a4
commit ed26244cdb
12585 changed files with 1914308 additions and 3474 deletions

View File

@@ -1,6 +1,6 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
const common_vendor = require("../../common/vendor.js");
function formatDate(d) {
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, "0");
@@ -14,39 +14,62 @@ const _sfc_main = {
return {
startDate: formatDate(start),
endDate: formatDate(now),
mode: "sale",
dim: "customer",
rows: [],
total: { sales: 0, cost: 0, profit: 0 }
summary: { salesAmount: 0, costAmount: 0, profit: 0, profitRate: 0, itemCount: 0 },
loading: false,
error: ""
};
},
onLoad(query) {
try {
const m = query && query.mode;
const d = query && query.dim;
if (m)
this.mode = m;
if (d)
if (d === "product" || d === "customer")
this.dim = d;
} catch (e) {
}
this.refresh();
},
computed: {
profitRate() {
const { sales, profit } = this.total;
if (!sales)
return "0.00%";
return (profit / sales * 100).toFixed(2) + "%";
profitRateText() {
var _a;
const rate = Number(((_a = this.summary) == null ? void 0 : _a.profitRate) || 0);
return rate.toFixed(2) + "%";
},
summaryItems() {
if (!this.rows.length)
return [];
return [
{ label: "销售额", value: `${this.fmt(this.summary.salesAmount)}` },
{ label: "成本", value: `${this.fmt(this.summary.costAmount)}` },
{ label: "利润", value: `${this.fmt(this.summary.profit)}` },
{ label: "利润率", value: this.profitRateText }
];
}
},
methods: {
fmt(n) {
return Number(n || 0).toFixed(2);
},
setMode(m) {
this.mode = m;
this.dim = m === "sale" ? "customer" : m === "purchase" ? "supplier" : m === "inventory" ? "qty" : "ar";
showProductSpec(row) {
return this.dim === "product" && row && row.spec;
},
rowMetrics(row) {
if (!row)
return [];
return [
{ label: "销售额", value: `${this.fmt(row.salesAmount)}` },
{ label: "成本", value: `${this.fmt(row.costAmount)}` },
{ label: "利润", value: `${this.fmt(row.profit)}` },
{ label: "利润率", value: `${Number(row.profitRate || 0).toFixed(2)}%` }
];
},
setDimension(d) {
if (d !== "customer" && d !== "product")
return;
if (this.dim === d)
return;
this.dim = d;
this.refresh();
},
onStartChange(e) {
@@ -58,279 +81,85 @@ const _sfc_main = {
this.refresh();
},
async refresh() {
if (this.mode === "sale") {
if (this.dim === "customer")
return this.loadByCustomer();
if (this.dim === "product")
return this.loadByProduct();
}
if (this.mode === "purchase") {
if (this.dim === "supplier")
return this.loadPurchaseBySupplier();
if (this.dim === "product")
return this.loadPurchaseByProduct();
}
if (this.mode === "inventory") {
if (this.dim === "qty")
return this.loadInventoryByQty();
if (this.dim === "amount")
return this.loadInventoryByAmount();
}
if (this.mode === "arap") {
if (this.dim === "ar")
return this.loadAR();
if (this.dim === "ap")
return this.loadAP();
}
},
async loadByCustomer() {
var _a, _b, _c, _d, _e;
this.loading = true;
this.error = "";
try {
const listResp = await common_http.get("/api/orders", { biz: "sale", type: "out", startDate: this.startDate, endDate: this.endDate, page: 1, size: 200 });
const list = listResp && (listResp.list || listResp) || [];
const map = /* @__PURE__ */ new Map();
let totalSales = 0;
for (const it of list) {
const name = it.customerName || "未知客户";
const amount = Number(it.amount || 0);
totalSales += amount;
if (!map.has(name))
map.set(name, { name, sales: 0, cost: 0, profit: 0 });
const row = map.get(name);
row.sales += amount;
}
const rows = Array.from(map.values()).map((r) => ({ ...r, profit: r.sales - r.cost }));
const total = { sales: totalSales, cost: 0, profit: totalSales };
this.rows = rows;
this.total = total;
const resp = await common_http.get("/api/report/sales", {
dimension: this.dim,
startDate: this.startDate,
endDate: this.endDate
});
const items = Array.isArray(resp == null ? void 0 : resp.items) ? resp.items : [];
this.rows = items.map((it) => ({
name: (it == null ? void 0 : it.name) || (this.dim === "product" ? "未命名商品" : "未指定客户"),
spec: (it == null ? void 0 : it.spec) || "",
salesAmount: Number((it == null ? void 0 : it.salesAmount) || 0),
costAmount: Number((it == null ? void 0 : it.costAmount) || 0),
profit: Number((it == null ? void 0 : it.profit) || 0),
profitRate: Number((it == null ? void 0 : it.profitRate) || 0)
}));
this.summary = {
salesAmount: Number(((_a = resp == null ? void 0 : resp.summary) == null ? void 0 : _a.salesAmount) || 0),
costAmount: Number(((_b = resp == null ? void 0 : resp.summary) == null ? void 0 : _b.costAmount) || 0),
profit: Number(((_c = resp == null ? void 0 : resp.summary) == null ? void 0 : _c.profit) || 0),
profitRate: Number(((_d = resp == null ? void 0 : resp.summary) == null ? void 0 : _d.profitRate) || 0),
itemCount: Number(((_e = resp == null ? void 0 : resp.summary) == null ? void 0 : _e.itemCount) || this.rows.length)
};
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
}
},
async loadByProduct() {
try {
const listResp = await common_http.get("/api/orders", { biz: "sale", type: "out", startDate: this.startDate, endDate: this.endDate, page: 1, size: 200 });
const list = listResp && (listResp.list || listResp) || [];
const agg = /* @__PURE__ */ new Map();
for (const it of list) {
try {
const d = await common_http.get(`/api/orders/${it.id}`);
const items = d && d.items || [];
for (const m of items) {
const key = String(m.productId || m.name);
if (!agg.has(key))
agg.set(key, { name: m.name || "#" + key, sales: 0, cost: 0, profit: 0 });
const row = agg.get(key);
const sales = Number(m.amount || 0);
row.sales += sales;
}
} catch (_) {
}
}
const rows = Array.from(agg.values()).map((r) => ({ ...r, profit: r.sales - r.cost }));
const totalSales = rows.reduce((s, r) => s + r.sales, 0);
this.rows = rows;
this.total = { sales: totalSales, cost: 0, profit: totalSales };
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
}
},
async loadPurchaseBySupplier() {
try {
const listResp = await common_http.get("/api/purchase-orders", { startDate: this.startDate, endDate: this.endDate, page: 1, size: 200 });
const list = listResp && (listResp.list || listResp) || [];
const map = /* @__PURE__ */ new Map();
let total = 0;
for (const it of list) {
const name = it.supplierName || "未知供应商";
const amount = Number(it.amount || 0);
total += amount;
if (!map.has(name))
map.set(name, { name, sales: 0, cost: 0, profit: 0 });
const row = map.get(name);
row.sales += amount;
}
this.rows = Array.from(map.values());
this.total = { sales: total, cost: 0, profit: 0 };
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
}
},
async loadPurchaseByProduct() {
try {
const listResp = await common_http.get("/api/purchase-orders", { startDate: this.startDate, endDate: this.endDate, page: 1, size: 200 });
const list = listResp && (listResp.list || listResp) || [];
const agg = /* @__PURE__ */ new Map();
for (const it of list) {
try {
const d = await common_http.get(`/api/purchase-orders/${it.id}`);
for (const m of (d == null ? void 0 : d.items) || []) {
const key = String(m.productId || m.name);
if (!agg.has(key))
agg.set(key, { name: m.name || "#" + key, sales: 0, cost: 0, profit: 0 });
const row = agg.get(key);
row.sales += Number(m.amount || 0);
}
} catch (_) {
}
}
const rows = Array.from(agg.values());
const total = rows.reduce((s, r) => s + r.sales, 0);
this.rows = rows;
this.total = { sales: total, cost: 0, profit: 0 };
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
}
},
async loadInventoryByQty() {
try {
const resp = await common_http.get("/api/inventories/logs", { startDate: this.startDate, endDate: this.endDate, page: 1, size: 200 });
const list = resp && (resp.list || resp) || [];
const map = /* @__PURE__ */ new Map();
let totalQty = 0;
for (const it of list) {
const key = it.productId || "未知";
if (!map.has(key))
map.set(key, { name: String(key), sales: 0, cost: 0, profit: 0 });
const row = map.get(key);
const q = Number(it.qtyDelta || 0);
row.sales += q;
totalQty += q;
}
this.rows = Array.from(map.values());
this.total = { sales: totalQty, cost: 0, profit: 0 };
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
}
},
async loadInventoryByAmount() {
try {
const resp = await common_http.get("/api/inventories/logs", { startDate: this.startDate, endDate: this.endDate, page: 1, size: 200 });
const list = resp && (resp.list || resp) || [];
const map = /* @__PURE__ */ new Map();
let totalAmt = 0;
for (const it of list) {
const key = it.productId || "未知";
if (!map.has(key))
map.set(key, { name: String(key), sales: 0, cost: 0, profit: 0 });
const row = map.get(key);
const a = Number(it.amount || it.amountDelta || 0);
row.sales += a;
totalAmt += a;
}
this.rows = Array.from(map.values());
this.total = { sales: totalAmt, cost: 0, profit: 0 };
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
}
},
async loadAR() {
try {
const res = await common_http.get("/api/customers", { page: 1, size: 100, debtOnly: false });
const list = Array.isArray(res == null ? void 0 : res.list) ? res.list : Array.isArray(res) ? res : [];
const rows = list.map((c) => ({ name: c.name, sales: Number(c.receivable || 0), cost: 0, profit: 0 }));
const total = rows.reduce((s, r) => s + r.sales, 0);
this.rows = rows;
this.total = { sales: total, cost: 0, profit: 0 };
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
}
},
async loadAP() {
try {
const res = await common_http.get("/api/suppliers", { page: 1, size: 100 });
const list = Array.isArray(res == null ? void 0 : res.list) ? res.list : Array.isArray(res) ? res : [];
const rows = list.map((s) => ({ name: s.name, sales: Number(s.apPayable || 0), cost: 0, profit: 0 }));
const total = rows.reduce((s, r) => s + r.sales, 0);
this.rows = rows;
this.total = { sales: total, cost: 0, profit: 0 };
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
this.error = e && e.message || "报表加载失败";
} finally {
this.loading = false;
}
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return common_vendor.e({
a: $data.mode === "sale" ? 1 : "",
b: common_vendor.o(($event) => $options.setMode("sale")),
c: $data.mode === "purchase" ? 1 : "",
d: common_vendor.o(($event) => $options.setMode("purchase")),
e: $data.mode === "inventory" ? 1 : "",
f: common_vendor.o(($event) => $options.setMode("inventory")),
g: $data.mode === "arap" ? 1 : "",
h: common_vendor.o(($event) => $options.setMode("arap")),
i: common_vendor.t($data.startDate),
j: $data.startDate,
k: common_vendor.o((...args) => $options.onStartChange && $options.onStartChange(...args)),
l: common_vendor.t($data.endDate),
m: $data.endDate,
n: common_vendor.o((...args) => $options.onEndChange && $options.onEndChange(...args)),
o: $data.mode === "sale"
}, $data.mode === "sale" ? {
p: $data.dim === "customer" ? 1 : "",
q: common_vendor.o(($event) => {
$data.dim = "customer";
$options.refresh();
}),
r: $data.dim === "product" ? 1 : "",
s: common_vendor.o(($event) => {
$data.dim = "product";
$options.refresh();
})
} : $data.mode === "purchase" ? {
v: $data.dim === "supplier" ? 1 : "",
w: common_vendor.o(($event) => {
$data.dim = "supplier";
$options.refresh();
}),
x: $data.dim === "product" ? 1 : "",
y: common_vendor.o(($event) => {
$data.dim = "product";
$options.refresh();
})
} : $data.mode === "inventory" ? {
A: $data.dim === "qty" ? 1 : "",
B: common_vendor.o(($event) => {
$data.dim = "qty";
$options.refresh();
}),
C: $data.dim === "amount" ? 1 : "",
D: common_vendor.o(($event) => {
$data.dim = "amount";
$options.refresh();
})
} : $data.mode === "arap" ? {
F: $data.dim === "ar" ? 1 : "",
G: common_vendor.o(($event) => {
$data.dim = "ar";
$options.refresh();
}),
H: $data.dim === "ap" ? 1 : "",
I: common_vendor.o(($event) => {
$data.dim = "ap";
$options.refresh();
a: common_vendor.t($data.startDate),
b: $data.startDate,
c: common_vendor.o((...args) => $options.onStartChange && $options.onStartChange(...args)),
d: common_vendor.t($data.endDate),
e: $data.endDate,
f: common_vendor.o((...args) => $options.onEndChange && $options.onEndChange(...args)),
g: $data.dim === "customer" ? 1 : "",
h: common_vendor.o(($event) => $options.setDimension("customer")),
i: $data.dim === "product" ? 1 : "",
j: common_vendor.o(($event) => $options.setDimension("product")),
k: $options.summaryItems.length
}, $options.summaryItems.length ? {
l: common_vendor.f($options.summaryItems, (item, ix, i0) => {
return {
a: common_vendor.t(item.label),
b: common_vendor.t(item.value),
c: ix
};
})
} : {}, {
t: $data.mode === "purchase",
z: $data.mode === "inventory",
E: $data.mode === "arap",
J: common_vendor.t($options.fmt($data.total.sales)),
K: common_vendor.t($options.fmt($data.total.cost)),
L: common_vendor.t($options.fmt($data.total.profit)),
M: common_vendor.t($options.profitRate),
N: common_vendor.f($data.rows, (row, idx, i0) => {
m: $data.loading
}, $data.loading ? {} : $data.error ? {
o: common_vendor.t($data.error)
} : !$data.rows.length ? {} : {
q: common_vendor.f($data.rows, (row, idx, i0) => {
return common_vendor.e({
a: row.avatar
}, row.avatar ? {
b: row.avatar
a: common_vendor.t(row.name),
b: $options.showProductSpec(row)
}, $options.showProductSpec(row) ? {
c: common_vendor.t(row.spec)
} : {}, {
c: common_vendor.t(row.name),
d: common_vendor.t($options.fmt(row.sales)),
e: common_vendor.t($options.fmt(row.cost)),
f: common_vendor.t($options.fmt(row.profit)),
g: idx
d: common_vendor.f($options.rowMetrics(row), (metric, mIdx, i1) => {
return {
a: common_vendor.t(metric.label),
b: common_vendor.t(metric.value),
c: mIdx
};
}),
e: idx
});
})
}, {
n: $data.error,
p: !$data.rows.length
});
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);

View File

@@ -1 +1 @@
<view class="report"><view class="modes"><view class="{{['mode-tab', a && 'active']}}" bindtap="{{b}}">销售统计</view><view class="{{['mode-tab', c && 'active']}}" bindtap="{{d}}">进货统计</view><view class="{{['mode-tab', e && 'active']}}" bindtap="{{f}}">库存统计</view><view class="{{['mode-tab', g && 'active']}}" bindtap="{{h}}">应收/应付对账</view></view><view class="toolbar"><picker mode="date" value="{{j}}" bindchange="{{k}}"><view class="date">{{i}}</view></picker><text style="margin:0 8rpx">—</text><picker mode="date" value="{{m}}" bindchange="{{n}}"><view class="date">{{l}}</view></picker></view><view wx:if="{{o}}" class="tabs"><view class="{{['tab', p && 'active']}}" bindtap="{{q}}">按客户</view><view class="{{['tab', r && 'active']}}" bindtap="{{s}}">按货品</view></view><view wx:elif="{{t}}" class="tabs"><view class="{{['tab', v && 'active']}}" bindtap="{{w}}">按供应商</view><view class="{{['tab', x && 'active']}}" bindtap="{{y}}">按货品</view></view><view wx:elif="{{z}}" class="tabs"><view class="{{['tab', A && 'active']}}" bindtap="{{B}}">按数量</view><view class="{{['tab', C && 'active']}}" bindtap="{{D}}">按金额</view></view><view wx:elif="{{E}}" class="tabs"><view class="{{['tab', F && 'active']}}" bindtap="{{G}}">应收对账</view><view class="{{['tab', H && 'active']}}" bindtap="{{I}}">应付对账</view></view><view class="summary"><view class="item"><text class="label">销售额</text><text class="value">¥ {{J}}</text></view><view class="item"><text class="label">成本</text><text class="value">¥ {{K}}</text></view><view class="item"><text class="label">利润</text><text class="value">¥ {{L}}</text></view><view class="item"><text class="label">利润率</text><text class="value">{{M}}</text></view></view><view wx:for="{{N}}" wx:for-item="row" wx:key="g" class="card"><view class="row-head"><image wx:if="{{row.a}}" class="thumb" src="{{row.b}}"/><view class="title">{{row.c}}</view></view><view class="row-body"><text>销售额:¥ {{row.d}}</text><text style="margin-left:18rpx">成本:¥ {{row.e}}</text><text style="margin-left:18rpx">利润:¥ {{row.f}}</text></view></view></view>
<view class="report"><view class="header">销售报表</view><view class="toolbar"><picker mode="date" value="{{b}}" bindchange="{{c}}"><view class="date">{{a}}</view></picker><text style="margin:0 8rpx">—</text><picker mode="date" value="{{e}}" bindchange="{{f}}"><view class="date">{{d}}</view></picker></view><view class="tabs"><view class="{{['tab', g && 'active']}}" bindtap="{{h}}">按客户</view><view class="{{['tab', i && 'active']}}" bindtap="{{j}}">按货品</view></view><view wx:if="{{k}}" class="summary"><view wx:for="{{l}}" wx:for-item="item" wx:key="c" class="summary-item"><text class="label">{{item.a}}</text><text class="value">{{item.b}}</text></view></view><view wx:if="{{m}}" class="loading">加载中...</view><view wx:elif="{{n}}" class="empty">{{o}}</view><view wx:elif="{{p}}" class="empty">暂无统计数据</view><view wx:else><view wx:for="{{q}}" wx:for-item="row" wx:key="e" class="card"><view class="row-head"><view class="row-title"><view class="title">{{row.a}}</view><view wx:if="{{row.b}}" class="subtitle">{{row.c}}</view></view></view><view class="row-body"><view wx:for="{{row.d}}" wx:for-item="metric" wx:key="c" class="metric"><text class="metric-label">{{metric.a}}</text><text class="metric-value">{{metric.b}}</text></view></view></view></view></view>

View File

@@ -25,101 +25,133 @@
/* 透明度 */
/* 文章场景相关 */
.report {
padding: 20rpx;
}
.modes {
padding: 24rpx 20rpx 36rpx;
display: flex;
gap: 12rpx;
margin-bottom: 14rpx;
flex-direction: column;
gap: 18rpx;
}
.mode-tab {
flex: 1;
text-align: center;
padding: 16rpx 0;
border-radius: 999rpx;
background: #f1f1f1;
color: #444;
border: 1rpx solid #e5e7eb;
}
.mode-tab.active {
background: #4C8DFF;
color: #fff;
border-color: #4C8DFF;
.header {
font-size: 34rpx;
font-weight: 700;
color: #1f2a44;
padding-left: 8rpx;
}
.toolbar {
display: flex;
align-items: center;
gap: 8rpx;
background: #ffffff;
padding: 14rpx 16rpx;
border-radius: 12rpx;
justify-content: center;
gap: 12rpx;
background: #f7f9fc;
border-radius: 16rpx;
padding: 18rpx;
}
.date {
padding: 10rpx 16rpx;
border: 1rpx solid #e5e7eb;
border-radius: 8rpx;
color: #111;
min-width: 200rpx;
padding: 12rpx 18rpx;
border-radius: 12rpx;
background: #fff;
border: 1rpx solid rgba(91, 107, 139, 0.16);
text-align: center;
color: #32445b;
}
.tabs {
display: flex;
gap: 16rpx;
margin-top: 14rpx;
gap: 12rpx;
justify-content: center;
}
.tab {
padding: 12rpx 18rpx;
padding: 10rpx 20rpx;
border-radius: 999rpx;
background: #f1f1f1;
color: #444;
background: #f0f4ff;
color: #5b6b8b;
transition: all 0.2s ease;
}
.tab.active {
background: #4C8DFF;
color: #fff;
background: rgba(76, 141, 255, 0.18);
color: #3467d6;
box-shadow: inset 0 0 0 2rpx rgba(76, 141, 255, 0.45);
}
.summary {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 8rpx;
margin-top: 14rpx;
grid-template-columns: repeat(auto-fill, minmax(240rpx, 1fr));
gap: 12rpx;
}
.summary .item {
background: #ffffff;
border-radius: 12rpx;
padding: 16rpx;
.summary-item {
background: #f7f9fc;
border-radius: 16rpx;
padding: 20rpx;
display: flex;
flex-direction: column;
gap: 10rpx;
}
.summary .label {
font-size: 22rpx;
color: #444;
.summary-item .label {
font-size: 24rpx;
color: #6e7a96;
}
.summary .value {
display: block;
margin-top: 8rpx;
.summary-item .value {
font-size: 32rpx;
font-weight: 700;
color: #111;
color: #1f2a44;
}
.card {
margin-top: 16rpx;
background: #ffffff;
border-radius: 12rpx;
padding: 16rpx;
background: #fff;
border-radius: 18rpx;
padding: 20rpx;
box-shadow: 0 8rpx 20rpx rgba(31, 42, 68, 0.08);
display: flex;
flex-direction: column;
gap: 14rpx;
}
.row-head {
display: flex;
align-items: center;
gap: 12rpx;
justify-content: space-between;
align-items: flex-start;
}
.thumb {
width: 72rpx;
height: 72rpx;
border-radius: 8rpx;
background: #f1f1f1;
.row-title {
display: flex;
flex-direction: column;
gap: 6rpx;
}
.title {
font-size: 28rpx;
font-size: 30rpx;
font-weight: 700;
color: #111;
color: #1f2a44;
}
.subtitle {
font-size: 24rpx;
color: #6e7a96;
}
.row-body {
margin-top: 10rpx;
color: #444;
display: flex;
flex-wrap: wrap;
gap: 12rpx 24rpx;
}
.metric {
display: flex;
gap: 8rpx;
align-items: center;
background: #f4f6fb;
border-radius: 12rpx;
padding: 10rpx 16rpx;
}
.metric-label {
font-size: 24rpx;
color: #6e7a96;
}
.metric-value {
font-size: 28rpx;
color: #1f2a44;
font-weight: 600;
}
.empty {
text-align: center;
padding: 80rpx 0;
color: #9aa4be;
font-size: 26rpx;
}
.loading {
text-align: center;
padding: 40rpx 0;
color: #5b6b8b;
font-size: 24rpx;
}