This commit is contained in:
2025-09-17 14:40:16 +08:00
parent 46c5682960
commit a3bbc0098a
94 changed files with 3549 additions and 105 deletions

View File

@@ -11,6 +11,7 @@ const _sfc_main = {
loadingNotices: false,
noticeError: "",
features: [
{ key: "product", title: "货品", img: "/static/icons/product.png", emoji: "📦" },
{ key: "customer", title: "客户", img: "/static/icons/customer.png", emoji: "👥" },
{ key: "sale", title: "销售", img: "/static/icons/sale.png", emoji: "💰" },
{ key: "account", title: "账户", img: "/static/icons/account.png", emoji: "💳" },
@@ -30,12 +31,14 @@ const _sfc_main = {
methods: {
async fetchMetrics() {
try {
const d = await common_http.get("/api/metrics/overview");
const d = await common_http.get("/api/dashboard/overview");
const toNum = (v) => typeof v === "number" ? v : Number(v || 0);
this.kpi = {
todaySales: d && d.todaySales || "0.00",
monthSales: d && d.monthSales || "0.00",
monthProfit: d && d.monthProfit || "0.00",
stockCount: d && d.stockCount || "0"
...this.kpi,
todaySales: toNum(d && d.todaySalesAmount).toFixed(2),
monthSales: toNum(d && d.monthSalesAmount).toFixed(2),
monthProfit: toNum(d && d.monthGrossProfit).toFixed(2),
stockCount: String((d && d.stockTotalQuantity) != null ? d.stockTotalQuantity : 0)
};
} catch (e) {
}
@@ -56,8 +59,16 @@ const _sfc_main = {
}
},
onFeatureTap(item) {
if (item.key === "product") {
common_vendor.index.navigateTo({ url: "/pages/product/list" });
return;
}
common_vendor.index.showToast({ title: item.title + "(开发中)", icon: "none" });
},
goProduct() {
this.activeTab = "product";
common_vendor.index.navigateTo({ url: "/pages/product/list" });
},
onCreateOrder() {
common_vendor.index.navigateTo({ url: "/pages/order/create" });
},
@@ -116,7 +127,7 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
l: $data.activeTab === "home" ? 1 : "",
m: common_vendor.o(($event) => $data.activeTab = "home"),
n: $data.activeTab === "product" ? 1 : "",
o: common_vendor.o(($event) => $data.activeTab = "product"),
o: common_vendor.o((...args) => $options.goProduct && $options.goProduct(...args)),
p: common_vendor.o((...args) => $options.onCreateOrder && $options.onCreateOrder(...args)),
q: $data.activeTab === "detail" ? 1 : "",
r: common_vendor.o(($event) => $data.activeTab = "detail"),

View File

@@ -0,0 +1,62 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
const _sfc_main = {
data() {
return { name: "", list: [] };
},
onLoad() {
this.reload();
},
methods: {
async reload() {
try {
const res = await common_http.get("/api/product-categories");
this.list = Array.isArray(res == null ? void 0 : res.list) ? res.list : Array.isArray(res) ? res : [];
} catch (_) {
}
},
async create() {
if (!this.name)
return;
await common_http.post("/api/product-categories", { name: this.name });
this.name = "";
this.reload();
},
async update(c) {
await common_http.put("/api/product-categories/" + c.id, { name: c.name });
common_vendor.index.showToast({ title: "已保存", icon: "success" });
},
async remove(c) {
common_vendor.index.showModal({ content: "确定删除该类别?", success: async (r) => {
if (!r.confirm)
return;
await common_http.del("/api/product-categories/" + c.id);
this.reload();
} });
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: $data.name,
b: common_vendor.o(common_vendor.m(($event) => $data.name = $event.detail.value, {
trim: true
})),
c: common_vendor.o((...args) => $options.create && $options.create(...args)),
d: common_vendor.f($data.list, (c, k0, i0) => {
return {
a: c.name,
b: common_vendor.o(common_vendor.m(($event) => c.name = $event.detail.value, {
trim: true
}), c.id),
c: common_vendor.o(($event) => $options.update(c), c.id),
d: common_vendor.o(($event) => $options.remove(c), c.id),
e: c.id
};
})
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/product/categories.js.map

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "类别管理",
"usingComponents": {}
}

View File

@@ -0,0 +1 @@
<view class="page"><view class="toolbar"><input placeholder="新类别名称" value="{{a}}" bindinput="{{b}}"/><button size="mini" bindtap="{{c}}">新增</button></view><scroll-view scroll-y class="list"><view wx:for="{{d}}" wx:for-item="c" wx:key="e" class="item"><input value="{{c.a}}" bindinput="{{c.b}}"/><view class="ops"><button size="mini" bindtap="{{c.c}}">保存</button><button size="mini" type="warn" bindtap="{{c.d}}">删除</button></view></view></scroll-view></view>

View File

@@ -0,0 +1,15 @@
.page { display:flex; flex-direction: column; height: 100vh;
}
.toolbar { display:flex; gap: 12rpx; padding: 16rpx; background:#fff;
}
.toolbar input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx;
}
.list { flex:1;
}
.item { display:flex; gap: 12rpx; align-items:center; padding: 16rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1;
}
.item input { flex:1; background:#f7f7f7; border-radius: 10rpx; padding: 12rpx;
}
.ops { display:flex; gap: 10rpx;
}

View File

@@ -0,0 +1,251 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
function parseSafeStock(text) {
const m = String(text || "").match(/\s*(\d+(?:\.\d+)?)\s*(?:-|~|—|到|,)?\s*(\d+(?:\.\d+)?)?\s*/);
if (!m)
return { min: null, max: null };
return { min: m[1] ? Number(m[1]) : null, max: m[2] ? Number(m[2]) : null };
}
const _sfc_main = {
data() {
return {
id: null,
units: [],
categories: [],
form: {
name: "",
categoryId: null,
unitId: null,
barcode: "",
brand: "",
model: "",
spec: "",
purchasePrice: 0,
wholesalePrice: 0,
bigClientPrice: 0,
retailPrice: 0,
stock: 0,
safeMin: null,
safeMax: null,
images: []
}
};
},
onLoad(query) {
this.id = query && query.id ? Number(query.id) : null;
this.bootstrap();
},
computed: {
categoryLabel() {
const c = this.categories.find((x) => x.id === this.form.categoryId);
return c ? c.name : "未选择";
},
unitLabel() {
const u = this.units.find((x) => x.id === this.form.unitId);
return u ? u.name : "未选择";
},
safeStockText: {
get() {
const a = this.form.safeMin != null ? String(this.form.safeMin) : "";
const b = this.form.safeMax != null ? String(this.form.safeMax) : "";
if (!a && !b)
return "";
if (a && b)
return a + "-" + b;
return a || b;
},
set(v) {
const { min, max } = parseSafeStock(v);
this.form.safeMin = min;
this.form.safeMax = max;
}
}
},
methods: {
async bootstrap() {
try {
const [units, cats] = await Promise.all([
common_http.get("/api/product/units"),
common_http.get("/api/product/categories")
]);
this.units = Array.isArray(units == null ? void 0 : units.list) ? units.list : Array.isArray(units) ? units : [];
this.categories = Array.isArray(cats == null ? void 0 : cats.list) ? cats.list : Array.isArray(cats) ? cats : [];
} catch (_) {
}
if (this.id) {
try {
const d = await common_http.get("/api/product/detail", { id: this.id });
Object.assign(this.form, {
name: d.name || "",
categoryId: d.categoryId || null,
unitId: d.unitId || null,
barcode: d.barcode || "",
brand: d.brand || "",
model: d.model || "",
spec: d.spec || "",
purchasePrice: Number(d.purchasePrice || 0),
wholesalePrice: Number(d.wholesalePrice || 0),
bigClientPrice: Number(d.bigClientPrice || 0),
retailPrice: Number(d.retailPrice || 0),
stock: Number(d.stock || 0),
safeMin: d.safeMin ?? null,
safeMax: d.safeMax ?? null,
images: Array.isArray(d.images) ? d.images : []
});
} catch (_) {
}
}
},
chooseCategory() {
if (!this.categories.length)
return;
common_vendor.index.showActionSheet({ itemList: this.categories.map((c) => c.name), success: ({ tapIndex }) => {
this.form.categoryId = this.categories[tapIndex].id;
} });
},
chooseUnit() {
if (!this.units.length)
return;
common_vendor.index.showActionSheet({ itemList: this.units.map((u) => u.name), success: ({ tapIndex }) => {
this.form.unitId = this.units[tapIndex].id;
} });
},
scanBarcode() {
common_vendor.index.scanCode({ onlyFromCamera: false, success: (res) => {
this.form.barcode = res.result;
}, fail: () => {
common_vendor.index.showToast({ title: "扫码失败,请手动录入", icon: "none" });
} });
},
async chooseImages() {
common_vendor.index.chooseImage({ count: 6, sizeType: ["compressed"], success: async (res) => {
try {
const uploaded = [];
for (const path of res.tempFilePaths) {
const ret = await common_http.upload("/api/upload", path, { biz: "product" });
uploaded.push(ret.url || ret.path || path);
}
this.form.images = (this.form.images || []).concat(uploaded).slice(0, 9);
} catch (e) {
common_vendor.index.showToast({ title: "上传失败", icon: "none" });
}
} });
},
moveLeft(i) {
if (i <= 0)
return;
const a = this.form.images;
[a[i - 1], a[i]] = [a[i], a[i - 1]];
},
moveRight(i) {
const a = this.form.images;
if (i >= a.length - 1)
return;
[a[i + 1], a[i]] = [a[i], a[i + 1]];
},
removeImg(i) {
this.form.images.splice(i, 1);
},
async submit() {
if (!this.form.name) {
common_vendor.index.showToast({ title: "请填写名称", icon: "none" });
return;
}
const payload = {
name: this.form.name,
categoryId: this.form.categoryId,
unitId: this.form.unitId,
barcode: this.form.barcode,
brand: this.form.brand,
model: this.form.model,
spec: this.form.spec,
purchasePrice: Number(this.form.purchasePrice || 0),
wholesalePrice: Number(this.form.wholesalePrice || 0),
bigClientPrice: Number(this.form.bigClientPrice || 0),
retailPrice: Number(this.form.retailPrice || 0),
stock: Number(this.form.stock || 0),
safeMin: this.form.safeMin,
safeMax: this.form.safeMax,
images: this.form.images
};
try {
if (this.id)
await common_http.put("/api/product", { id: this.id, ...payload });
else
await common_http.post("/api/product", payload);
common_vendor.index.showToast({ title: "已保存", icon: "success" });
setTimeout(() => {
common_vendor.index.navigateBack();
}, 600);
} catch (e) {
common_vendor.index.showToast({ title: e && e.message || "保存失败", icon: "none" });
}
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: $data.form.name,
b: common_vendor.o(common_vendor.m(($event) => $data.form.name = $event.detail.value, {
trim: true
})),
c: common_vendor.t($options.categoryLabel),
d: common_vendor.o((...args) => $options.chooseCategory && $options.chooseCategory(...args)),
e: common_vendor.t($options.unitLabel),
f: common_vendor.o((...args) => $options.chooseUnit && $options.chooseUnit(...args)),
g: $data.form.barcode,
h: common_vendor.o(common_vendor.m(($event) => $data.form.barcode = $event.detail.value, {
trim: true
})),
i: common_vendor.o((...args) => $options.scanBarcode && $options.scanBarcode(...args)),
j: $data.form.brand,
k: common_vendor.o(common_vendor.m(($event) => $data.form.brand = $event.detail.value, {
trim: true
})),
l: $data.form.model,
m: common_vendor.o(common_vendor.m(($event) => $data.form.model = $event.detail.value, {
trim: true
})),
n: $data.form.spec,
o: common_vendor.o(common_vendor.m(($event) => $data.form.spec = $event.detail.value, {
trim: true
})),
p: $data.form.purchasePrice,
q: common_vendor.o(common_vendor.m(($event) => $data.form.purchasePrice = $event.detail.value, {
number: true
})),
r: $data.form.wholesalePrice,
s: common_vendor.o(common_vendor.m(($event) => $data.form.wholesalePrice = $event.detail.value, {
number: true
})),
t: $data.form.bigClientPrice,
v: common_vendor.o(common_vendor.m(($event) => $data.form.bigClientPrice = $event.detail.value, {
number: true
})),
w: $data.form.retailPrice,
x: common_vendor.o(common_vendor.m(($event) => $data.form.retailPrice = $event.detail.value, {
number: true
})),
y: $data.form.stock,
z: common_vendor.o(common_vendor.m(($event) => $data.form.stock = $event.detail.value, {
number: true
})),
A: $options.safeStockText,
B: common_vendor.o(($event) => $options.safeStockText = $event.detail.value),
C: common_vendor.o((...args) => $options.chooseImages && $options.chooseImages(...args)),
D: common_vendor.f($data.form.images, (url, idx, i0) => {
return {
a: url,
b: common_vendor.o(($event) => $options.moveLeft(idx), idx),
c: common_vendor.o(($event) => $options.moveRight(idx), idx),
d: common_vendor.o(($event) => $options.removeImg(idx), idx),
e: idx
};
}),
E: common_vendor.o((...args) => $options.submit && $options.submit(...args))
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/product/edit.js.map

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "编辑货品",
"usingComponents": {}
}

View File

@@ -0,0 +1 @@
<view class="page"><view class="form"><view class="field"><text class="label">名称</text><input class="value" placeholder="必填" value="{{a}}" bindinput="{{b}}"/></view><view class="field" bindtap="{{d}}"><text class="label">类别</text><text class="value">{{c}}</text></view><view class="field" bindtap="{{f}}"><text class="label">主单位</text><text class="value">{{e}}</text></view><view class="field"><text class="label">条形码</text><input class="value" placeholder="手动录入或扫码" value="{{g}}" bindinput="{{h}}"/><button size="mini" class="scan" bindtap="{{i}}">扫码</button></view><view class="field"><text class="label">品牌</text><input class="value" placeholder="可选" value="{{j}}" bindinput="{{k}}"/></view><view class="field"><text class="label">型号</text><input class="value" placeholder="可选" value="{{l}}" bindinput="{{m}}"/></view><view class="field"><text class="label">规格</text><input class="value" placeholder="可选" value="{{n}}" bindinput="{{o}}"/></view><view class="grid2"><view class="field"><text class="label">进货价</text><input class="value" type="digit" value="{{p}}" bindinput="{{q}}"/></view><view class="field"><text class="label">批发价</text><input class="value" type="digit" value="{{r}}" bindinput="{{s}}"/></view><view class="field"><text class="label">大单报价</text><input class="value" type="digit" value="{{t}}" bindinput="{{v}}"/></view><view class="field"><text class="label">零售价</text><input class="value" type="digit" value="{{w}}" bindinput="{{x}}"/></view></view><view class="grid2"><view class="field"><text class="label">当前库存</text><input class="value" type="digit" value="{{y}}" bindinput="{{z}}"/></view><view class="field"><text class="label">安全库存</text><input class="value" type="text" placeholder="如 10-100" value="{{A}}" bindinput="{{B}}"/></view></view><view class="photos"><view class="photos-header"><text>商品图片</text><button size="mini" bindtap="{{C}}">选择图片</button></view><view class="photo-list"><view wx:for="{{D}}" wx:for-item="url" wx:key="e" class="photo"><image src="{{url.a}}" mode="aspectFill"/><view class="photo-actions"><text class="btn" bindtap="{{url.b}}">←</text><text class="btn" bindtap="{{url.c}}">→</text><text class="btn danger" bindtap="{{url.d}}">删</text></view></view></view></view></view><view class="bottom"><button class="primary" bindtap="{{E}}">保存</button></view></view>

View File

@@ -0,0 +1,35 @@
.page{ padding-bottom: 140rpx;
}
.form{ background:#fff;
}
.field{ display:flex; align-items:center; gap: 12rpx; padding: 22rpx 20rpx; border-bottom: 1rpx solid #f2f2f2;
}
.label{ color:#666; width: 160rpx;
}
.value{ flex:1; color:#333;
}
.scan{ flex: 0 0 auto;
}
.grid2{ display:grid; grid-template-columns: 1fr 1fr;
}
.photos{ padding: 16rpx 20rpx;
}
.photos-header{ display:flex; justify-content: space-between; align-items:center; margin-bottom: 12rpx;
}
.photo-list{ display:grid; grid-template-columns: repeat(3, 1fr); gap: 12rpx;
}
.photo{ position: relative;
}
.photo image{ width: 100%; height: 200rpx; border-radius: 12rpx; background:#f6f6f6; display:block;
}
.photo-actions{ position:absolute; right:6rpx; bottom:6rpx; display:flex; gap: 6rpx;
}
.btn{ font-size: 22rpx; padding: 4rpx 8rpx; background: rgba(0,0,0,0.45); color:#fff; border-radius: 8rpx;
}
.btn.danger{ background: rgba(221,82,77,0.85);
}
.bottom{ position: fixed; left:0; right:0; bottom:0; background:#fff; padding: 16rpx 24rpx calc(env(safe-area-inset-bottom) + 16rpx); box-shadow: 0 -4rpx 12rpx rgba(0,0,0,0.06);
}
.primary{ width: 100%; background: linear-gradient(135deg, #FFE69A 0%, #F4CF62 45%, #D7A72E 100%); color:#493c1b; border-radius: 999rpx; padding: 20rpx 0; font-weight:800;
}

View File

@@ -0,0 +1,249 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
const ImageUploader = () => "../../components/ImageUploader.js";
const _sfc_main = {
components: { ImageUploader },
data() {
return {
id: "",
form: {
name: "",
barcode: "",
brand: "",
model: "",
spec: "",
origin: "",
categoryId: "",
unitId: "",
stock: null,
safeMin: null,
safeMax: null,
purchasePrice: null,
retailPrice: null,
wholesalePrice: null,
bigClientPrice: null,
images: [],
remark: ""
},
units: [],
categories: []
};
},
onLoad(query) {
this.id = (query == null ? void 0 : query.id) || "";
this.bootstrap();
},
computed: {
unitNames() {
return this.units.map((u) => u.name);
},
categoryNames() {
return this.categories.map((c) => c.name);
},
unitLabel() {
const u = this.units.find((x) => String(x.id) === String(this.form.unitId));
return u ? u.name : "选择单位";
},
categoryLabel() {
const c = this.categories.find((x) => String(x.id) === String(this.form.categoryId));
return c ? c.name : "选择类别";
}
},
methods: {
async bootstrap() {
await Promise.all([this.fetchUnits(), this.fetchCategories()]);
if (this.id)
this.loadDetail();
},
async fetchUnits() {
try {
const res = await common_http.get("/api/product-units");
this.units = Array.isArray(res == null ? void 0 : res.list) ? res.list : Array.isArray(res) ? res : [];
} catch (_) {
}
},
async fetchCategories() {
try {
const res = await common_http.get("/api/product-categories");
this.categories = Array.isArray(res == null ? void 0 : res.list) ? res.list : Array.isArray(res) ? res : [];
} catch (_) {
}
},
onPickUnit(e) {
const idx = Number(e.detail.value);
const u = this.units[idx];
this.form.unitId = u ? u.id : "";
},
onPickCategory(e) {
const idx = Number(e.detail.value);
const c = this.categories[idx];
this.form.categoryId = c ? c.id : "";
},
scan() {
common_vendor.index.scanCode({ onlyFromCamera: false, success: (res) => {
this.form.barcode = res.result;
} });
},
async loadDetail() {
try {
const data = await common_http.get("/api/products/" + this.id);
Object.assign(this.form, {
name: data.name,
barcode: data.barcode,
brand: data.brand,
model: data.model,
spec: data.spec,
origin: data.origin,
categoryId: data.categoryId,
unitId: data.unitId,
stock: data.stock,
safeMin: data.safeMin,
safeMax: data.safeMax,
purchasePrice: data.purchasePrice,
retailPrice: data.retailPrice,
wholesalePrice: data.wholesalePrice,
bigClientPrice: data.bigClientPrice,
images: (data.images || []).map((i) => i.url || i)
});
} catch (_) {
}
},
validate() {
if (!this.form.name) {
common_vendor.index.showToast({ title: "请填写名称", icon: "none" });
return false;
}
if (this.form.safeMin != null && this.form.safeMax != null && Number(this.form.safeMin) > Number(this.form.safeMax)) {
common_vendor.index.showToast({ title: "安全库存区间不合法", icon: "none" });
return false;
}
return true;
},
buildPayload() {
const f = this.form;
return {
name: f.name,
barcode: f.barcode,
brand: f.brand,
model: f.model,
spec: f.spec,
origin: f.origin,
categoryId: f.categoryId || null,
unitId: f.unitId,
safeMin: f.safeMin,
safeMax: f.safeMax,
prices: {
purchasePrice: f.purchasePrice,
retailPrice: f.retailPrice,
wholesalePrice: f.wholesalePrice,
bigClientPrice: f.bigClientPrice
},
stock: f.stock,
images: f.images,
remark: f.remark
};
},
async save(goOn) {
if (!this.validate())
return;
const payload = this.buildPayload();
try {
if (this.id)
await common_http.put("/api/products/" + this.id, payload);
else
await common_http.post("/api/products", payload);
common_vendor.index.showToast({ title: "保存成功", icon: "success" });
if (goOn && !this.id) {
this.form = { name: "", barcode: "", brand: "", model: "", spec: "", origin: "", categoryId: "", unitId: "", stock: null, safeMin: null, safeMax: null, purchasePrice: null, retailPrice: null, wholesalePrice: null, bigClientPrice: null, images: [], remark: "" };
} else {
setTimeout(() => common_vendor.index.navigateBack(), 400);
}
} catch (e) {
common_vendor.index.showToast({ title: "保存失败", icon: "none" });
}
}
}
};
if (!Array) {
const _component_ImageUploader = common_vendor.resolveComponent("ImageUploader");
_component_ImageUploader();
}
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: $data.form.name,
b: common_vendor.o(common_vendor.m(($event) => $data.form.name = $event.detail.value, {
trim: true
})),
c: $data.form.barcode,
d: common_vendor.o(common_vendor.m(($event) => $data.form.barcode = $event.detail.value, {
trim: true
})),
e: $data.form.brand,
f: common_vendor.o(common_vendor.m(($event) => $data.form.brand = $event.detail.value, {
trim: true
})),
g: $data.form.model,
h: common_vendor.o(common_vendor.m(($event) => $data.form.model = $event.detail.value, {
trim: true
})),
i: $data.form.spec,
j: common_vendor.o(common_vendor.m(($event) => $data.form.spec = $event.detail.value, {
trim: true
})),
k: $data.form.origin,
l: common_vendor.o(common_vendor.m(($event) => $data.form.origin = $event.detail.value, {
trim: true
})),
m: common_vendor.t($options.unitLabel),
n: $options.unitNames,
o: common_vendor.o((...args) => $options.onPickUnit && $options.onPickUnit(...args)),
p: common_vendor.t($options.categoryLabel),
q: $options.categoryNames,
r: common_vendor.o((...args) => $options.onPickCategory && $options.onPickCategory(...args)),
s: $data.form.stock,
t: common_vendor.o(common_vendor.m(($event) => $data.form.stock = $event.detail.value, {
number: true
})),
v: $data.form.safeMin,
w: common_vendor.o(common_vendor.m(($event) => $data.form.safeMin = $event.detail.value, {
number: true
})),
x: $data.form.safeMax,
y: common_vendor.o(common_vendor.m(($event) => $data.form.safeMax = $event.detail.value, {
number: true
})),
z: $data.form.purchasePrice,
A: common_vendor.o(common_vendor.m(($event) => $data.form.purchasePrice = $event.detail.value, {
number: true
})),
B: $data.form.retailPrice,
C: common_vendor.o(common_vendor.m(($event) => $data.form.retailPrice = $event.detail.value, {
number: true
})),
D: $data.form.wholesalePrice,
E: common_vendor.o(common_vendor.m(($event) => $data.form.wholesalePrice = $event.detail.value, {
number: true
})),
F: $data.form.bigClientPrice,
G: common_vendor.o(common_vendor.m(($event) => $data.form.bigClientPrice = $event.detail.value, {
number: true
})),
H: common_vendor.o(($event) => $data.form.images = $event),
I: common_vendor.p({
formData: {
ownerType: "product"
},
modelValue: $data.form.images
}),
J: $data.form.remark,
K: common_vendor.o(common_vendor.m(($event) => $data.form.remark = $event.detail.value, {
trim: true
})),
L: common_vendor.o(($event) => $options.save(false)),
M: common_vendor.o(($event) => $options.save(true))
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/product/form.js.map

View File

@@ -0,0 +1,6 @@
{
"navigationBarTitleText": "编辑货品",
"usingComponents": {
"image-uploader": "../../components/ImageUploader"
}
}

View File

@@ -0,0 +1 @@
<scroll-view scroll-y class="page"><view class="card"><view class="row"><text class="label">商品名称</text><input placeholder="必填" value="{{a}}" bindinput="{{b}}"/></view><view class="row"><text class="label">条形码</text><input placeholder="可扫码或输入" value="{{c}}" bindinput="{{d}}"/></view><view class="row"><text class="label">品牌/型号/规格/产地</text></view><view class="row"><input placeholder="品牌" value="{{e}}" bindinput="{{f}}"/></view><view class="row"><input placeholder="型号" value="{{g}}" bindinput="{{h}}"/></view><view class="row"><input placeholder="规格" value="{{i}}" bindinput="{{j}}"/></view><view class="row"><input placeholder="产地" value="{{k}}" bindinput="{{l}}"/></view><view class="row"><picker mode="selector" range="{{n}}" bindchange="{{o}}"><view class="picker">主单位:{{m}}</view></picker><picker mode="selector" range="{{q}}" bindchange="{{r}}"><view class="picker">类别:{{p}}</view></picker></view></view><view class="card"><view class="row"><text class="label">库存与安全库存</text></view><view class="row"><input type="number" placeholder="当前库存" value="{{s}}" bindinput="{{t}}"/><input type="number" placeholder="安全库存下限" value="{{v}}" bindinput="{{w}}"/><input type="number" placeholder="安全库存上限" value="{{x}}" bindinput="{{y}}"/></view></view><view class="card"><view class="row"><text class="label">价格(进价/零售/批发/大单)</text></view><view class="row prices"><input type="number" placeholder="进货价" value="{{z}}" bindinput="{{A}}"/><input type="number" placeholder="零售价" value="{{B}}" bindinput="{{C}}"/><input type="number" placeholder="批发价" value="{{D}}" bindinput="{{E}}"/><input type="number" placeholder="大单价" value="{{F}}" bindinput="{{G}}"/></view></view><view class="card"><text class="label">图片</text><image-uploader wx:if="{{I}}" u-i="4a3f460a-0" bind:__l="__l" bindupdateModelValue="{{H}}" u-p="{{I}}"/></view><view class="card"><text class="label">备注</text><block wx:if="{{r0}}"><textarea placeholder="可选" auto-height value="{{J}}" bindinput="{{K}}"/></block></view><view class="fixed"><button type="default" bindtap="{{L}}">保存</button><button type="primary" bindtap="{{M}}">保存并继续</button></view></scroll-view>

View File

@@ -0,0 +1,17 @@
.page { background:#f6f6f6; height: 100vh;
}
.card { background:#fff; margin: 16rpx; padding: 16rpx; border-radius: 12rpx;
}
.row { display:flex; gap: 12rpx; align-items: center; margin-bottom: 12rpx;
}
.label { width: 180rpx; color:#666;
}
.row input { flex:1; background:#f7f7f7; border-radius: 10rpx; padding: 12rpx;
}
.picker { padding: 8rpx 12rpx; background:#f0f0f0; border-radius: 10rpx; color:#666; margin-left: 8rpx;
}
.prices input { width: 30%;
}
.fixed { position: fixed; left: 0; right: 0; bottom: 0; background:#fff; padding: 12rpx 16rpx; display:flex; gap: 16rpx;
}

View File

@@ -0,0 +1,122 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
const _sfc_main = {
data() {
return {
items: [],
query: { kw: "", page: 1, size: 20, categoryId: "" },
finished: false,
loading: false,
tab: "all",
categories: []
};
},
onLoad() {
this.fetchCategories();
this.reload();
},
computed: {
categoryNames() {
return this.categories.map((c) => c.name);
},
categoryLabel() {
const c = this.categories.find((x) => String(x.id) === String(this.query.categoryId));
return c ? "类别:" + c.name : "选择类别";
}
},
methods: {
switchTab(t) {
this.tab = t;
this.query.categoryId = "";
this.reload();
},
onPickCategory(e) {
const idx = Number(e.detail.value);
const c = this.categories[idx];
this.query.categoryId = c ? c.id : "";
this.reload();
},
async fetchCategories() {
try {
const res = await common_http.get("/api/product-categories", {});
this.categories = Array.isArray(res == null ? void 0 : res.list) ? res.list : Array.isArray(res) ? res : [];
} catch (_) {
}
},
reload() {
this.items = [];
this.query.page = 1;
this.finished = false;
this.loadMore();
},
async loadMore() {
if (this.loading || this.finished)
return;
this.loading = true;
try {
const params = { kw: this.query.kw, page: this.query.page, size: this.query.size };
if (this.tab === "category" && this.query.categoryId)
params.categoryId = this.query.categoryId;
const res = await common_http.get("/api/products", params);
const list = Array.isArray(res == null ? void 0 : res.list) ? res.list : Array.isArray(res) ? res : [];
this.items = this.items.concat(list);
if (list.length < this.query.size)
this.finished = true;
this.query.page += 1;
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
} finally {
this.loading = false;
}
},
openForm(id) {
const url = "/pages/product/form" + (id ? "?id=" + id : "");
common_vendor.index.navigateTo({ url });
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return common_vendor.e({
a: $data.tab === "all" ? 1 : "",
b: common_vendor.o(($event) => $options.switchTab("all")),
c: $data.tab === "category" ? 1 : "",
d: common_vendor.o(($event) => $options.switchTab("category")),
e: common_vendor.o((...args) => $options.reload && $options.reload(...args)),
f: $data.query.kw,
g: common_vendor.o(common_vendor.m(($event) => $data.query.kw = $event.detail.value, {
trim: true
})),
h: $data.tab === "category"
}, $data.tab === "category" ? {
i: common_vendor.t($options.categoryLabel),
j: $options.categoryNames,
k: common_vendor.o((...args) => $options.onPickCategory && $options.onPickCategory(...args))
} : {}, {
l: common_vendor.o((...args) => $options.reload && $options.reload(...args)),
m: $data.items.length
}, $data.items.length ? {
n: common_vendor.f($data.items, (it, k0, i0) => {
return common_vendor.e({
a: it.cover
}, it.cover ? {
b: it.cover
} : {}, {
c: common_vendor.t(it.name),
d: common_vendor.t(it.brand || "-"),
e: common_vendor.t(it.model || ""),
f: common_vendor.t(it.spec || ""),
g: common_vendor.t(it.stock ?? 0),
h: common_vendor.t((it.retailPrice ?? it.price ?? 0).toFixed(2)),
i: it.id,
j: common_vendor.o(($event) => $options.openForm(it.id), it.id)
});
})
} : {}, {
o: common_vendor.o((...args) => $options.loadMore && $options.loadMore(...args)),
p: common_vendor.o(($event) => $options.openForm())
});
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/product/list.js.map

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "货品列表",
"usingComponents": {}
}

View File

@@ -0,0 +1 @@
<view class="page"><view class="tabs"><view class="{{['tab', a && 'active']}}" bindtap="{{b}}">全部</view><view class="{{['tab', c && 'active']}}" bindtap="{{d}}">按类别</view></view><view class="search"><input placeholder="输入名称/条码/规格查询" bindconfirm="{{e}}" value="{{f}}" bindinput="{{g}}"/><picker wx:if="{{h}}" mode="selector" range="{{j}}" bindchange="{{k}}"><view class="picker">{{i}}</view></picker><button size="mini" bindtap="{{l}}">查询</button></view><scroll-view scroll-y class="list" bindscrolltolower="{{o}}"><block wx:if="{{m}}"><view wx:for="{{n}}" wx:for-item="it" wx:key="i" class="item" bindtap="{{it.j}}"><image wx:if="{{it.a}}" src="{{it.b}}" class="thumb" mode="aspectFill"/><view class="content"><view class="name">{{it.c}}</view><view class="meta">{{it.d}} {{it.e}} {{it.f}}</view><view class="meta">库存:{{it.g}} <text class="price">零售价:¥{{it.h}}</text></view></view></view></block><view wx:else class="empty"><text>暂无数据,点击右上角“+”新增</text></view></scroll-view><view class="fab" bindtap="{{p}}"></view></view>

View File

@@ -0,0 +1,33 @@
.page { display:flex; flex-direction: column; height: 100vh;
}
.tabs { display:flex; background:#fff;
}
.tab { flex:1; text-align:center; padding: 20rpx 0; color:#666;
}
.tab.active { color:#18b566; font-weight: 600;
}
.search { display:flex; gap: 12rpx; padding: 16rpx; background:#fff; align-items: center;
}
.search input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx;
}
.picker { padding: 8rpx 12rpx; background:#f0f0f0; border-radius: 10rpx; color:#666;
}
.list { flex:1;
}
.item { display:flex; padding: 20rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1;
}
.thumb { width: 120rpx; height: 120rpx; border-radius: 12rpx; margin-right: 16rpx; background:#fafafa;
}
.content { flex:1;
}
.name { color:#333; margin-bottom: 6rpx; font-weight: 600;
}
.meta { color:#888; font-size: 24rpx;
}
.price { margin-left: 20rpx; color:#f60;
}
.empty { height: 60vh; display:flex; align-items:center; justify-content:center; color:#999;
}
.fab { position: fixed; right: 30rpx; bottom: 120rpx; width: 100rpx; height: 100rpx; background:#18b566; color:#fff; border-radius: 50rpx; text-align:center; line-height: 100rpx; font-size: 48rpx; box-shadow: 0 8rpx 20rpx rgba(0,0,0,0.15);
}

View File

@@ -0,0 +1,39 @@
"use strict";
const common_http = require("../../common/http.js");
const common_vendor = require("../../common/vendor.js");
const _sfc_main = {
data() {
return { settings: { hideZeroStock: false, hidePurchasePrice: false } };
},
onLoad() {
this.load();
},
methods: {
async load() {
try {
const res = await common_http.get("/api/product-settings");
this.settings = { hideZeroStock: !!(res == null ? void 0 : res.hideZeroStock), hidePurchasePrice: !!(res == null ? void 0 : res.hidePurchasePrice) };
} catch (_) {
}
},
async update(key, val) {
const next = { ...this.settings, [key]: val };
this.settings = next;
try {
await common_http.put("/api/product-settings", next);
} catch (_) {
}
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: $data.settings.hideZeroStock,
b: common_vendor.o((e) => $options.update("hideZeroStock", e.detail.value)),
c: $data.settings.hidePurchasePrice,
d: common_vendor.o((e) => $options.update("hidePurchasePrice", e.detail.value))
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/product/settings.js.map

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "货品设置",
"usingComponents": {}
}

View File

@@ -0,0 +1 @@
<view class="page"><view class="item"><text>隐藏零库存商品</text><switch checked="{{a}}" bindchange="{{b}}"/></view><view class="item"><text>隐藏进货价</text><switch checked="{{c}}" bindchange="{{d}}"/></view></view>

View File

@@ -0,0 +1,5 @@
.page { background:#fff;
}
.item { display:flex; justify-content: space-between; align-items:center; padding: 20rpx; border-bottom: 1rpx solid #f1f1f1;
}

View File

@@ -0,0 +1,62 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
const _sfc_main = {
data() {
return { name: "", list: [] };
},
onLoad() {
this.reload();
},
methods: {
async reload() {
try {
const res = await common_http.get("/api/product-units");
this.list = Array.isArray(res == null ? void 0 : res.list) ? res.list : Array.isArray(res) ? res : [];
} catch (_) {
}
},
async create() {
if (!this.name)
return;
await common_http.post("/api/product-units", { name: this.name });
this.name = "";
this.reload();
},
async update(u) {
await common_http.put("/api/product-units/" + u.id, { name: u.name });
common_vendor.index.showToast({ title: "已保存", icon: "success" });
},
async remove(u) {
common_vendor.index.showModal({ content: "确定删除该单位?", success: async (r) => {
if (!r.confirm)
return;
await common_http.del("/api/product-units/" + u.id);
this.reload();
} });
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: $data.name,
b: common_vendor.o(common_vendor.m(($event) => $data.name = $event.detail.value, {
trim: true
})),
c: common_vendor.o((...args) => $options.create && $options.create(...args)),
d: common_vendor.f($data.list, (u, k0, i0) => {
return {
a: u.name,
b: common_vendor.o(common_vendor.m(($event) => u.name = $event.detail.value, {
trim: true
}), u.id),
c: common_vendor.o(($event) => $options.update(u), u.id),
d: common_vendor.o(($event) => $options.remove(u), u.id),
e: u.id
};
})
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/product/units.js.map

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "单位管理",
"usingComponents": {}
}

View File

@@ -0,0 +1 @@
<view class="page"><view class="toolbar"><input placeholder="新单位名称" value="{{a}}" bindinput="{{b}}"/><button size="mini" bindtap="{{c}}">新增</button></view><scroll-view scroll-y class="list"><view wx:for="{{d}}" wx:for-item="u" wx:key="e" class="item"><input value="{{u.a}}" bindinput="{{u.b}}"/><view class="ops"><button size="mini" bindtap="{{u.c}}">保存</button><button size="mini" type="warn" bindtap="{{u.d}}">删除</button></view></view></scroll-view></view>

View File

@@ -0,0 +1,15 @@
.page { display:flex; flex-direction: column; height: 100vh;
}
.toolbar { display:flex; gap: 12rpx; padding: 16rpx; background:#fff;
}
.toolbar input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx;
}
.list { flex:1;
}
.item { display:flex; gap: 12rpx; align-items:center; padding: 16rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1;
}
.item input { flex:1; background:#f7f7f7; border-radius: 10rpx; padding: 12rpx;
}
.ops { display:flex; gap: 10rpx;
}