This commit is contained in:
2025-09-16 22:11:19 +08:00
parent 562ec4abf9
commit 46c5682960
65 changed files with 1997 additions and 56 deletions

View File

@@ -13,6 +13,20 @@
- User: `root`
- 说明:所有结构变更均通过 MysqlMCP 执行并已落地到线上库。
### 角色与模拟数据策略(统一为店长)
- 当前不进行角色划分,系统仅保留“店长”角色。
- 已将所有用户记录统一为:`role='owner'``is_owner=1`
- 前端/后端权限逻辑暂未启用,后续若引入权限体系,再行扩展角色与边界。
### 小程序默认用户(可开关,默认关闭)
- 目的:开发/演示阶段,便于免登录联调。
- 机制:前端在请求头附加 `X-User-Id`(值为张老板 id=2仅当开关开启时。
- 开关:
- 环境变量:`VITE_APP_ENABLE_DEFAULT_USER=true``VITE_APP_DEFAULT_USER_ID=2`
- 或本地存储:`ENABLE_DEFAULT_USER=true``DEFAULT_USER_ID=2`
- 关闭:不设置/置为 `false` 即可停用(生产环境默认关闭)。
- 完全移除:删除 `frontend/common/config.js` 中默认用户配置与 `frontend/common/http.js` 中注入逻辑。
### 后端Spring Boot数据库状态
- 依赖:`pom.xml` 未包含 `spring-boot-starter-web``spring-boot-starter-data-jpa``mysql-connector-j` 等数据库相关依赖。
- 配置:`src/main/resources/application.properties` 仅有 `spring.application.name=demo`;未配置 `spring.datasource.*``spring.jpa.*`

View File

@@ -0,0 +1,27 @@
package com.example.demo.dashboard;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/dashboard")
public class DashboardController {
private final DashboardService dashboardService;
public DashboardController(DashboardService dashboardService) {
this.dashboardService = dashboardService;
}
@GetMapping("/overview")
public ResponseEntity<DashboardOverviewResponse> overview(@RequestHeader(name = "X-Shop-Id", required = false) Long shopId) {
long sid = (shopId == null ? 1L : shopId);
return ResponseEntity.ok(dashboardService.getOverview(sid));
}
}

View File

@@ -0,0 +1,30 @@
package com.example.demo.dashboard;
import java.math.BigDecimal;
public class DashboardOverviewResponse {
private BigDecimal todaySalesAmount;
private BigDecimal monthGrossProfit;
private BigDecimal stockTotalQuantity;
public DashboardOverviewResponse(BigDecimal todaySalesAmount, BigDecimal monthGrossProfit, BigDecimal stockTotalQuantity) {
this.todaySalesAmount = todaySalesAmount;
this.monthGrossProfit = monthGrossProfit;
this.stockTotalQuantity = stockTotalQuantity;
}
public BigDecimal getTodaySalesAmount() {
return todaySalesAmount;
}
public BigDecimal getMonthGrossProfit() {
return monthGrossProfit;
}
public BigDecimal getStockTotalQuantity() {
return stockTotalQuantity;
}
}

View File

@@ -0,0 +1,56 @@
package com.example.demo.dashboard;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.springframework.stereotype.Repository;
import java.math.BigDecimal;
@Repository
public class DashboardRepository {
@PersistenceContext
private EntityManager entityManager;
public BigDecimal sumTodaySalesOrders(Long shopId) {
Object result = entityManager.createNativeQuery(
"SELECT COALESCE(SUM(amount), 0) FROM sales_orders " +
"WHERE shop_id = :shopId AND status = 'approved' AND DATE(order_time) = CURRENT_DATE()"
).setParameter("shopId", shopId).getSingleResult();
return toBigDecimal(result);
}
public BigDecimal sumMonthGrossProfitApprox(Long shopId) {
Object result = entityManager.createNativeQuery(
"SELECT COALESCE(SUM(soi.amount - soi.quantity * COALESCE(pp.purchase_price, 0)), 0) AS gp " +
"FROM sales_orders so " +
"JOIN sales_order_items soi ON soi.order_id = so.id " +
"LEFT JOIN product_prices pp ON pp.product_id = soi.product_id AND pp.shop_id = so.shop_id " +
"WHERE so.shop_id = :shopId AND so.status = 'approved' " +
"AND so.order_time >= DATE_FORMAT(CURRENT_DATE(), '%Y-%m-01') " +
"AND so.order_time < DATE_ADD(DATE_FORMAT(CURRENT_DATE(), '%Y-%m-01'), INTERVAL 1 MONTH)"
).setParameter("shopId", shopId).getSingleResult();
return toBigDecimal(result);
}
public BigDecimal sumTotalInventoryQty(Long shopId) {
Object result = entityManager.createNativeQuery(
"SELECT COALESCE(SUM(quantity), 0) FROM inventories WHERE shop_id = :shopId"
).setParameter("shopId", shopId).getSingleResult();
return toBigDecimal(result);
}
private BigDecimal toBigDecimal(Object value) {
if (value == null) return BigDecimal.ZERO;
if (value instanceof BigDecimal) return (BigDecimal) value;
if (value instanceof Number) return BigDecimal.valueOf(((Number) value).doubleValue());
try {
return new BigDecimal(value.toString());
} catch (Exception e) {
return BigDecimal.ZERO;
}
}
}

View File

@@ -0,0 +1,24 @@
package com.example.demo.dashboard;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
@Service
public class DashboardService {
private final DashboardRepository dashboardRepository;
public DashboardService(DashboardRepository dashboardRepository) {
this.dashboardRepository = dashboardRepository;
}
public DashboardOverviewResponse getOverview(long shopId) {
BigDecimal todaySales = dashboardRepository.sumTodaySalesOrders(shopId);
BigDecimal monthGrossProfit = dashboardRepository.sumMonthGrossProfitApprox(shopId);
BigDecimal stockTotalQty = dashboardRepository.sumTotalInventoryQty(shopId);
return new DashboardOverviewResponse(todaySales, monthGrossProfit, stockTotalQty);
}
}

View File

@@ -1,6 +1,6 @@
## partsinquiry 数据库文档
更新日期2025-09-16
更新日期2025-09-16(已插入演示数据)
说明:本文件根据远程库 mysql.tonaspace.com 中 `partsinquiry` 的实际结构生成,字段/索引/外键信息以线上为准。
@@ -371,8 +371,8 @@
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_ot_shop_time` (`shop_id`,`tx_time`) - KEY: `idx_ot_account` (`account_id`)
**Foreign Keys**: - `fk_ot_shop`: `shop_id``shops(id)` - `fk_ot_user`: `user_id``users(id)` - `fk_ot_account`: `account_id``accounts(id)`
### product_images 触发器
- `trg_products_ai`: AFTER INSERT ON `products`更新 `products.search_text`
### 触发器
- `trg_products_bi`: BEFORE INSERT ON `products`设置 `products.search_text`
- `trg_products_au`: BEFORE UPDATE ON `products` → 维护 `products.search_text`
- `trg_palias_ai`: AFTER INSERT ON `product_aliases` → 重建 `products.search_text`
- `trg_palias_au`: AFTER UPDATE ON `product_aliases` → 重建 `products.search_text`
@@ -399,3 +399,10 @@
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_notices_shop` (`shop_id`,`status`,`is_pinned`,`created_at`) - KEY: `idx_notices_time` (`starts_at`,`ends_at`)
**Foreign Keys**: - `fk_notices_shop`: `shop_id``shops(id)` ON UPDATE NO ACTION ON DELETE NO ACTION - `fk_notices_user`: `user_id``users(id)` ON UPDATE NO ACTION ON DELETE NO ACTION
### 附:演示种子数据(非完整,仅用于联调验证)
- 演示店铺演示店A用户 3全部店长 owner
- 商品域基础单位3条、类别2条、全局SKU2条、商品2条含别名/价格/库存/图片)
- 往来与账户客户2、供应商2、账户3
- 单据销售单1含明细2与进货单1含明细2、收付款各1、其他收支2
- 审核与公告part_submissions 1、attachments 1、notices 2、新增 wechat 身份与会话各1

View File

@@ -27,6 +27,174 @@ paths:
type: array
items:
$ref: '#/components/schemas/Notice'
/api/metrics/overview:
get:
summary: 概览统计(❌ Partially Implemented
description: 返回今日/本月销售额、本月利润与库存商品数量。前端已接入,后端待实现。
responses:
'200':
description: 成功
content:
application/json:
schema:
$ref: '#/components/schemas/MetricsOverview'
/api/accounts:
get:
summary: 账户列表(❌ Partially Implemented
description: 前端账户选择页已接入,后端返回数组或 {list:[]} 皆可。
responses:
'200':
description: 成功
content:
application/json:
schema:
oneOf:
- type: array
items:
$ref: '#/components/schemas/Account'
- type: object
properties:
list:
type: array
items:
$ref: '#/components/schemas/Account'
/api/suppliers:
get:
summary: 供应商搜索(❌ Partially Implemented
parameters:
- in: query
name: kw
schema:
type: string
responses:
'200':
description: 成功
content:
application/json:
schema:
oneOf:
- type: array
items:
$ref: '#/components/schemas/Supplier'
- type: object
properties:
list:
type: array
items:
$ref: '#/components/schemas/Supplier'
/api/other-transactions:
post:
summary: 新建其他收入/支出(❌ Partially Implemented
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateOtherTransactionRequest'
responses:
'200':
description: 成功
content:
application/json:
schema:
type: object
properties:
id:
type: integer
format: int64
/api/products:
get:
summary: 商品搜索(❌ Partially Implemented
description: 前端已接入查询参数 kw/page/size后端待实现或对齐。
parameters:
- in: query
name: kw
schema:
type: string
- in: query
name: page
schema:
type: integer
default: 1
- in: query
name: size
schema:
type: integer
default: 50
responses:
'200':
description: 成功
content:
application/json:
schema:
oneOf:
- type: array
items:
$ref: '#/components/schemas/Product'
- type: object
properties:
list:
type: array
items:
$ref: '#/components/schemas/Product'
/api/customers:
get:
summary: 客户搜索(❌ Partially Implemented
description: 前端已接入查询参数 kw/page/size后端待实现或对齐。
parameters:
- in: query
name: kw
schema:
type: string
- in: query
name: page
schema:
type: integer
default: 1
- in: query
name: size
schema:
type: integer
default: 50
responses:
'200':
description: 成功
content:
application/json:
schema:
oneOf:
- type: array
items:
$ref: '#/components/schemas/Customer'
- type: object
properties:
list:
type: array
items:
$ref: '#/components/schemas/Customer'
/api/orders:
post:
summary: 新建单据(❌ Partially Implemented
description: 前端开单页已提交 payload后端待实现。
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateOrderRequest'
responses:
'200':
description: 成功
content:
application/json:
schema:
type: object
properties:
id:
type: integer
format: int64
orderNo:
type: string
components:
schemas:
Notice:
@@ -52,6 +220,66 @@ components:
startsAt:
type: string
format: date-time
MetricsOverview:
type: object
properties:
todaySales:
type: string
example: '1234.56'
monthSales:
type: string
example: '23456.78'
monthProfit:
type: string
example: '3456.78'
stockCount:
type: string
example: '1200'
Account:
type: object
properties:
id:
type: integer
format: int64
name:
type: string
type:
type: string
enum: [cash, bank, alipay, wechat, other]
balance:
type: number
Supplier:
type: object
properties:
id:
type: integer
format: int64
name:
type: string
mobile:
type: string
CreateOtherTransactionRequest:
type: object
properties:
type:
type: string
enum: [income, expense]
category:
type: string
counterpartyId:
type: integer
format: int64
nullable: true
accountId:
type: integer
format: int64
amount:
type: number
txTime:
type: string
format: date
remark:
type: string
endsAt:
type: string
format: date-time
@@ -64,4 +292,55 @@ components:
updatedAt:
type: string
format: date-time
Product:
type: object
properties:
id:
type: integer
format: int64
code:
type: string
name:
type: string
price:
type: number
stock:
type: number
Customer:
type: object
properties:
id:
type: integer
format: int64
name:
type: string
mobile:
type: string
CreateOrderRequest:
type: object
properties:
type:
type: string
description: 'sale.out/sale.return/sale.collect/purchase/income/expense 等'
orderTime:
type: string
format: date
customerId:
type: integer
format: int64
nullable: true
items:
type: array
items:
type: object
properties:
productId:
type: integer
format: int64
quantity:
type: number
unitPrice:
type: number
amount:
type: number

167
doc/requirements.md Normal file
View File

@@ -0,0 +1,167 @@
* ### **配件查询App需求规格说明书**
#### 1.0 项目概述
本项目旨在开发一款面向小微商户的移动端进销存管理应用,命名为“配件查询”。该应用核心功能是帮助用户高效管理商品、库存、销售、采购、客户、供应商及财务收支,并通过数据报表提供经营状况分析,助力商户实现数字化经营。
参考的小程序“智慧记进销存”,但是多了一个配件查询功能,以下所罗列的内容大多也参考至该小程序,如有歧义可优先参照这个小程序,拿不准优先问。
#### 2.0 功能模块需求
**2.1 首页 (Dashboard)**
* **2.1.1 核心数据概览:** 首页需直观展示当日、当月的核心经营数据。
* 今日销售额
* 本月销售额
* 本月利润
* 库存商品数量
**2.1.2 广告位:** 在首页区域提供一个展示广告的区域。
* **2.1.3 快捷功能入口:** 提供一个快捷功能区域,方便用户快速访问常用功能。
* 默认应包含客户管理、销售开单、账户管理、供应商管理、进货开单、其他支出、VIP会员、报表统计等。
* **2.1.4 在线客服:** 提供一个悬浮的“咨询”或“在线客服”入口,方便用户随时获取帮助。
**2.2 货品管理模块**
* **2.2.1 货品列表与库存:**
* 展示所有货品的列表,包含名称、库存等基本信息。
* 支持按“全部类别”或指定类别筛选货品。
* 提供搜索功能,支持通过货品名称或条形码进行模糊查找。
* 列表底部显示总货品种类数量。
* 当库存为空时,应有明显的空状态提示,并引导用户“点击右上角‘+’录入货品信息”。
* **2.2.2 新增/编辑货品:**
* 支持添加商品图片。
* App端支持录入或扫描商品条形码小程序端仅支持手动录入不支持扫码。
* **货品名称**为必填项。
* 可为货品选择**类别**和**主单位**。
* 支持自定义扩展货品属性(如品牌、型号、产地、保质期等)。
* 货品图片支持多图上传,支持拖拽排序,支持图片放大预览。
* 可录入**当前库存**、**安全库存**(一个数值区间,用于库存预警)。
* 需分别录入四种价格,**进货价**、**批发价**、**大单报价**和**零售价**。
* 提供**备注**字段,用于记录额外信息。
* 保存后,可选择“继续新增”或返回列表。
* **2.2.3 货品设置:**
* 支持自定义**货品类别**管理。
* 支持自定义**货品单位**管理。
* 提供开关选项,允许用户选择是否“隐藏零库存商品”和“隐藏进货价”。
**2.3 开单(交易)模块**
* **2.3.1 核心功能:** 该模块是应用的核心操作区,整合了销售、进货和财务记账功能。
* **2.3.2 销售开单:**
* **出货单:**
* 自动记录开单**时间**,并支持手动修改。
* 可选择**客户**,默认为“零售客户”。
* 通过“+”号从货品列表中选择商品,自动计算**合计金额**。
* 支持在订单中对单个商品进行操作(如修改数量、价格等)。
* **退货单:** 用于处理客户退货业务。
* **收款单:** 用于处理销售单的后续收款或直接创建收款记录。
* **2.3.3 进货开单:** 用于记录从供应商处采购商品的业务流程。
* **2.3.4 其他收入/支出:**
* **其他收入:**
* 支持对收入进行分类,如“销售收入”、“经营所得”、“利息收入”等。
* 可选择**往来单位**和**结算账户**(如现金、银行等)。
* 可添加备注并选择日期。
* **其他支出:**
* 支持对支出进行分类,如“经营支出”、“办公用品”、“房租”等。
* 同样支持选择**往来单位**和**结算账户**。
**2.4 明细查询模块**
* **2.4.1 维度筛选:**
* 提供按时间维度(自定义、本周、今日、本月、本年)快速筛选单据。
* 提供按业务类型(销售、进货、收银、资金、盘点)进行分类查看。
* **2.4.2 单据列表:**
* 在选定维度下,以列表形式展示所有相关单据。
* 提供搜索功能,支持通过单据号、客户/供应商名、品名、备注等关键字查询。
* 显示当前筛选条件下的总金额。
* 当无数据时,提供清晰的空状态提示。
* 提供“+”号,支持在当前分类下快速新建单据。
**2.5 报表统计模块**
* **2.5.1 资金报表:**
* **利润统计:** 分析指定时间范围内的收入、支出和利润。
* **营业员统计:** 按销售人员维度统计销售业绩。
* **经营业绩:** 提供综合性的经营状况分析。
* **导入导出模块:** 提供导入导出功能方便用户切换手机或账号后仍能将旧数据导入。
* **2.5.2 进销存报表:**
* **销售统计:** 按商品、客户、时间等维度分析销售数据。
* **进货统计:** 按商品、供应商、时间等维度分析采购数据。
* **库存统计:** 提供当前库存成本、数量及分布情况的报告。
* **应收/应付对账单:** 生成与客户和供应商的对账单据。
**2.6 “我的”(用户中心)模块**
* **2.6.1 用户信息:** 显示用户头像、店铺名称、注册手机号及“老板”身份标识。
* **2.6.2 会员与订单:**
* 提供**VIP会员**入口,展示会员特权。
* 提供**我的订单**入口,可能用于查看应用内服务订单。
* **2.6.3 基础管理:**
* **供应商管理**
* **客户管理**
* **客户报价**
* **店铺管理**
* **2.6.4 设置中心:**
* **账号与安全:**
* 修改个人信息(头像、姓名)。
* 修改登录密码。
* **商品设置:**
* **系统参数:**
* 提供多种业务逻辑开关,如:“销售价低于进货价时提示”、“销售单默认全部收款”、“启用单行折扣”、“启用客户/供应商双身份”。
* **关于与协议:** 包含“关于我们”、“隐私协议”、“个人信息安全投诉”等静态页面。
* **账号操作:** 提供“账号注销”和“退出登录”功能。
#### 3.0 全局性需求
* **3.1 导航:** 采用底部Tab栏导航包含“首页”、“货品”、“开单”、“明细”、“我的”五个主要模块。
* **3.2 统一的UI/UX:** 应用整体风格简洁、清晰,操作流程符合移动端使用习惯。
* **3.3 空状态页面:** 在列表、报表等数据为空的页面,需提供友好的空状态提示图和引导性文字。
* **3.4 数据同步:** 应用数据应在云端同步,保证用户更换设备或多设备使用时数据一致性。
* **3.5 多租户数据隔离:** 所有业务数据按店铺(租户)隔离,用户不可访问他人数据。
* 所有业务表需包含`user_id`并在读取/写入中强制按`user_id`过滤。
* 支持租户内角色与权限控制;导出仅限本租户数据。
* **3.6 公共SKU全局商品库众包与审核:** 全体用户共同补充、纠错SKU经审核发布为全局可选SKU。
* 用户可提交“新增SKU/编辑建议”,进入审核流(草稿/待审/驳回/发布/下架)。
* 全局SKU字段名称、品牌、规格、条码、主单位、图片、别名、分类标签等。
* 各用户通过“本地商品”引用全局SKU并保留本地私有字段价格、库存、分类、单位换算、条码别名等
* **3.7 商品模糊查询(增强):** 在货品列表、开单选品、对账等场景支持多字段模糊匹配。
* 支持名称/条码/别名/拼音/品牌/规格模糊匹配,并高亮命中片段。
* 支持全局SKU与本地商品联合检索优先展示本地商品结果可分页。
* 需满足大规模SKU下的性能目标可通过系统参数配置匹配策略。
* **3.8 客户端平台:** 提供移动App与小程序小程序不支持商品条形码扫描功能。
* **3.9 多列销售价格:** 销售价格分四列,即同一种商品有四个销售价格
### 配件查询
1. **数据查询功能**
- 多参数组合查询(分类、尺寸、型号等)
- 模糊匹配关键字
- 分页展示查询结果
- 一键导出Excel数据
2. **数据提交系统**
- 用户提交新配件数据
- 型号为唯一必填项
- 支持图片上传
- 提交后等待管理员审核
3. **审核管理系统**
- 管理员查看待审核列表
- 可编辑所有字段
- 支持图片更新和删除
- 一键批准或拒绝提交
4. **图片管理系统**
- 每条数据可关联多张图片
- 点击图片可放大查看
- 管理员可管理所有图片
- 自动处理文件名冲突
## 全局说明(必看)
由于这个文档写的还不是很完善,目前有存疑的部分先行参考小程序小程序“智慧记进销存”(功能和按钮可以参考,界面样式除外),管理端文档目前待定。
客户要求的是做双端应用app端+小程序端),需要考虑兼容性相关问题。
本程序和“智慧记进销存”大多一致,主要的区别在于客户有配件查询要求,即在产品页面中要额外加一个配件查询按钮或入口,且要求一个产品要有四个销售价格(先按零售价 分销价 批发价 大客户价),且要求能自定义添加各种规格(尺寸,孔径等)。
有疑惑的部分一定要及时沟通(如未提及的页面和功能需要确认的时候)

View File

@@ -12,3 +12,16 @@ const storageShopId = typeof uni !== 'undefined' ? (uni.getStorageSync('SHOP_ID'
export const SHOP_ID = Number(envShopId || storageShopId || 1);
// 默认用户(可移除):
// - 用途:开发/演示环境自动将用户固定为“张老板”id=2
// - 开关优先级:环境变量 > 本地存储 > 默认值
// - 生产默认关闭false开发可通过本地存储或环境变量开启
const envEnableDefaultUser = (typeof process !== 'undefined' && process.env && (process.env.VITE_APP_ENABLE_DEFAULT_USER || process.env.ENABLE_DEFAULT_USER)) || '';
const storageEnableDefaultUser = typeof uni !== 'undefined' ? (uni.getStorageSync('ENABLE_DEFAULT_USER') || '') : '';
export const ENABLE_DEFAULT_USER = String(envEnableDefaultUser || storageEnableDefaultUser || 'false').toLowerCase() === 'true';
const envDefaultUserId = (typeof process !== 'undefined' && process.env && (process.env.VITE_APP_DEFAULT_USER_ID || process.env.DEFAULT_USER_ID)) || '';
const storageDefaultUserId = typeof uni !== 'undefined' ? (uni.getStorageSync('DEFAULT_USER_ID') || '') : '';
export const DEFAULT_USER_ID = Number(envDefaultUserId || storageDefaultUserId || 2);

View File

@@ -0,0 +1,18 @@
// 统一常量配置:其他收入/支出分类,禁止在业务中硬编码
export const INCOME_CATEGORIES = [
{ key: 'sale_income', label: '销售收入' },
{ key: 'operation_income', label: '经营所得' },
{ key: 'interest_income', label: '利息收入' },
{ key: 'investment_income', label: '投资收入' },
{ key: 'other_income', label: '其它收入' }
]
export const EXPENSE_CATEGORIES = [
{ key: 'operation_expense', label: '经营支出' },
{ key: 'office_supplies', label: '办公用品' },
{ key: 'rent', label: '房租' },
{ key: 'interest_expense', label: '利息支出' },
{ key: 'other_expense', label: '其它支出' }
]

View File

@@ -1,4 +1,4 @@
import { API_BASE_URL, SHOP_ID } from './config.js'
import { API_BASE_URL, SHOP_ID, ENABLE_DEFAULT_USER, DEFAULT_USER_ID } from './config.js'
function buildUrl(path) {
if (!path) return API_BASE_URL
@@ -8,11 +8,33 @@ function buildUrl(path) {
export function get(path, params = {}) {
return new Promise((resolve, reject) => {
const headers = { 'X-Shop-Id': SHOP_ID }
if (ENABLE_DEFAULT_USER && DEFAULT_USER_ID) headers['X-User-Id'] = DEFAULT_USER_ID
uni.request({
url: buildUrl(path),
method: 'GET',
data: params,
header: { 'X-Shop-Id': SHOP_ID },
header: headers,
success: (res) => {
const { statusCode, data } = res
if (statusCode >= 200 && statusCode < 300) return resolve(data)
reject(new Error('HTTP ' + statusCode))
},
fail: (err) => reject(err)
})
})
}
export function post(path, body = {}) {
return new Promise((resolve, reject) => {
const headers = { 'Content-Type': 'application/json', 'X-Shop-Id': SHOP_ID }
if (ENABLE_DEFAULT_USER && DEFAULT_USER_ID) headers['X-User-Id'] = DEFAULT_USER_ID
uni.request({
url: buildUrl(path),
method: 'POST',
data: body,
header: headers,
success: (res) => {
const { statusCode, data } = res
if (statusCode >= 200 && statusCode < 300) return resolve(data)

View File

@@ -5,6 +5,36 @@
"style": {
"navigationBarTitleText": "五金配件管家"
}
},
{
"path": "pages/order/create",
"style": {
"navigationBarTitleText": "开单"
}
},
{
"path": "pages/product/select",
"style": {
"navigationBarTitleText": "选择商品"
}
},
{
"path": "pages/customer/select",
"style": {
"navigationBarTitleText": "选择客户"
}
},
{
"path": "pages/supplier/select",
"style": {
"navigationBarTitleText": "选择供应商"
}
},
{
"path": "pages/account/select",
"style": {
"navigationBarTitleText": "选择账户"
}
}
],
"globalStyle": {

View File

@@ -0,0 +1,45 @@
<template>
<view class="page">
<scroll-view scroll-y class="list">
<view class="item" v-for="a in accounts" :key="a.id" @click="select(a)">
<view class="name">{{ a.name }}</view>
<view class="meta">{{ typeLabel(a.type) }} · 余额{{ a.balance?.toFixed ? a.balance.toFixed(2) : a.balance }}</view>
</view>
</scroll-view>
</view>
</template>
<script>
import { get } from '../../common/http.js'
const TYPE_MAP = { cash: '现金', bank: '银行', alipay: '支付宝', wechat: '微信', other: '其他' }
export default {
data() { return { accounts: [] } },
async onLoad() {
try {
const res = await get('/api/accounts')
this.accounts = Array.isArray(res) ? res : (res?.list || [])
} catch(e) { uni.showToast({ title: '加载失败', icon: 'none' }) }
},
methods: {
select(a) {
const opener = getCurrentPages()[getCurrentPages().length-2]
if (opener && opener.$vm) {
opener.$vm.selectedAccountId = a.id
opener.$vm.selectedAccountName = a.name
}
uni.navigateBack()
},
typeLabel(t) { return TYPE_MAP[t] || t }
}
}
</script>
<style>
.page { display:flex; flex-direction: column; height: 100vh; }
.list { flex:1; }
.item { padding: 20rpx 24rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1; }
.name { color:#333; margin-bottom: 6rpx; }
.meta { color:#888; font-size: 24rpx; }
</style>

View File

@@ -0,0 +1,50 @@
<template>
<view class="page">
<view class="search">
<input v-model="kw" placeholder="搜索客户名称/电话" @confirm="search" />
<button size="mini" @click="search">搜索</button>
</view>
<scroll-view scroll-y class="list">
<view class="item" v-for="c in customers" :key="c.id" @click="select(c)">
<view class="name">{{ c.name }}</view>
<view class="meta">{{ c.mobile || '—' }}</view>
</view>
</scroll-view>
</view>
</template>
<script>
import { get } from '../../common/http.js'
export default {
data() { return { kw: '', customers: [] } },
onLoad() { this.search() },
methods: {
async search() {
try {
const res = await get('/api/customers', { kw: this.kw, page: 1, size: 50 })
this.customers = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])
} catch(e) { uni.showToast({ title: '加载失败', icon: 'none' }) }
},
select(c) {
const opener = getCurrentPages()[getCurrentPages().length-2]
if (opener && opener.$vm) {
opener.$vm.order.customerId = c.id
opener.$vm.customerName = c.name
}
uni.navigateBack()
}
}
}
</script>
<style>
.page { display:flex; flex-direction: column; height: 100vh; }
.search { display:flex; gap: 12rpx; padding: 16rpx; background:#fff; }
.search input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx; }
.list { flex:1; }
.item { padding: 20rpx 24rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1; }
.name { color:#333; margin-bottom: 6rpx; }
.meta { color:#888; font-size: 24rpx; }
</style>

View File

@@ -12,22 +12,26 @@
<view class="kpi">
<view class="kpi-item">
<text class="kpi-label">今日销售额</text>
<text class="kpi-value">{{ todayAmount }}</text>
<text class="kpi-value">{{ kpi.todaySales }}</text>
</view>
<view class="kpi-item">
<text class="kpi-label">本月销售额</text>
<text class="kpi-value">{{ kpi.monthSales }}</text>
</view>
<view class="kpi-item">
<text class="kpi-label">本月利润</text>
<text class="kpi-value">{{ monthProfit }}</text>
<text class="kpi-value">{{ kpi.monthProfit }}</text>
</view>
<view class="kpi-item">
<text class="kpi-label">库存数量</text>
<text class="kpi-value">{{ stockQty }}</text>
<text class="kpi-label">库存商品数量</text>
<text class="kpi-value">{{ kpi.stockCount }}</text>
</view>
</view>
</view>
<!-- 告栏自动轮播可点击查看详情 -->
<!-- 广告栏自动轮播可点击查看详情 -->
<view class="notice">
<view class="notice-left"></view>
<view class="notice-left">广</view>
<view v-if="loadingNotices" class="notice-swiper" style="display:flex;align-items:center;color:#6b5a2a;">加载中...</view>
<view v-else-if="noticeError" class="notice-swiper" style="display:flex;align-items:center;color:#dd524d;">{{ noticeError }}</view>
<view v-else-if="!notices.length" class="notice-swiper" style="display:flex;align-items:center;color:#6b5a2a;">暂无公告</view>
@@ -39,7 +43,6 @@
</view>
</swiper-item>
</swiper>
<view class="notice-right" @click="onNoticeList">更多</view>
</view>
<!-- 分割标题产品与功能 -->
@@ -66,12 +69,18 @@
<view class="tab" :class="{ active: activeTab==='home' }" @click="activeTab='home'">
<text>首页</text>
</view>
<view class="tab" :class="{ active: activeTab==='product' }" @click="activeTab='product'">
<text>货品</text>
</view>
<view class="tab primary" @click="onCreateOrder">
<text>开单</text>
</view>
<view class="tab" :class="{ active: activeTab==='detail' }" @click="activeTab='detail'">
<text>明细</text>
</view>
<view class="tab" :class="{ active: activeTab==='report' }" @click="activeTab='report'">
<text>报表</text>
</view>
<view class="tab" :class="{ active: activeTab==='me' }" @click="activeTab='me'">
<text>我的</text>
</view>
@@ -84,9 +93,7 @@
export default {
data() {
return {
todayAmount: '0.00',
monthProfit: '0.00',
stockQty: '0.00',
kpi: { todaySales: '0.00', monthSales: '0.00', monthProfit: '0.00', stockCount: '0' },
activeTab: 'home',
notices: [],
loadingNotices: false,
@@ -105,9 +112,23 @@
}
},
onLoad() {
this.fetchMetrics()
this.fetchNotices()
},
methods: {
async fetchMetrics() {
try {
const d = await get('/api/metrics/overview')
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'
}
} catch (e) {
// 忽略错误,保留默认值
}
},
async fetchNotices() {
this.loadingNotices = true
this.noticeError = ''
@@ -127,18 +148,16 @@
uni.showToast({ title: item.title + '(开发中)', icon: 'none' })
},
onCreateOrder() {
uni.showToast({ title: '开单(开发中)', icon: 'none' })
uni.navigateTo({ url: '/pages/order/create' })
},
onNoticeTap(n) {
uni.showModal({
title: '告',
title: '广告',
content: n && (n.text || n.title || n.content) || '',
showCancel: false
})
},
onNoticeList() {
uni.showToast({ title: '公告列表(开发中)', icon: 'none' })
},
onIconError(item) {
item.img = ''
}
@@ -192,7 +211,7 @@
.notice-item { display: flex; align-items: center; gap: 12rpx; min-height: 72rpx; }
.notice-text { color: #4b3e19; font-size: 28rpx; line-height: 36rpx; font-weight: 600; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.notice-tag { color: #B4880F; font-size: 22rpx; padding: 4rpx 10rpx; border-radius: 999rpx; background: rgba(215,167,46,0.18); }
.notice-right { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; min-width: 72rpx; height: 44rpx; color: #B4880F; font-size: 26rpx; padding-left: 8rpx; }
/* 分割标题 */
.section-title { display: flex; align-items: center; gap: 16rpx; padding: 10rpx 28rpx 0; }

View File

@@ -0,0 +1,224 @@
<template>
<view class="order">
<!-- 顶部 Tab -->
<view class="tabs">
<text :class="{ active: biz==='sale' }" @click="switchBiz('sale')">销售</text>
<text :class="{ active: biz==='purchase' }" @click="switchBiz('purchase')">进货</text>
<text :class="{ active: biz==='income' }" @click="switchBiz('income')">其他收入</text>
<text :class="{ active: biz==='expense' }" @click="switchBiz('expense')">其他支出</text>
</view>
<!-- 子类目按钮 -->
<view class="subtabs" v-if="biz==='sale'">
<button class="subbtn" :class="{ active: saleType==='out' }" @click="saleType='out'">出货</button>
<button class="subbtn" :class="{ active: saleType==='return' }" @click="saleType='return'">退货</button>
<button class="subbtn" :class="{ active: saleType==='collect' }" @click="saleType='collect'">收款</button>
</view>
<view class="subtabs" v-else-if="biz==='purchase'">
<button class="subbtn" :class="{ active: purchaseType==='in' }" @click="purchaseType='in'">进货</button>
<button class="subbtn" :class="{ active: purchaseType==='return' }" @click="purchaseType='return'">退货</button>
<button class="subbtn" :class="{ active: purchaseType==='pay' }" @click="purchaseType='pay'">付款</button>
</view>
<!-- 日期与客户 -->
<picker mode="date" :value="order.orderTime" @change="onDateChange">
<view class="field">
<text class="label">时间</text>
<text class="value">{{ order.orderTime }}</text>
</view>
</picker>
<view class="field" v-if="biz==='sale'" @click="chooseCustomer">
<text class="label">客户</text>
<text class="value">{{ customerLabel }}</text>
</view>
<view class="field" v-else-if="biz==='purchase'" @click="chooseSupplier">
<text class="label">供应商</text>
<text class="value">{{ supplierLabel }}</text>
</view>
<!-- 已选商品与合计销售/进货 -->
<view v-if="biz==='sale' || biz==='purchase'">
<view class="summary">
<text>选中货品{{ totalQuantity }}</text>
<text>合计金额¥ {{ totalAmount.toFixed(2) }}</text>
</view>
<!-- 加号添加商品 -->
<view class="add" @click="chooseProduct">+</view>
</view>
<!-- 其它收入/支出 表单 -->
<view v-else>
<view class="chips">
<view v-for="c in (biz==='income' ? incomeCategories : expenseCategories)" :key="c.key" class="chip" :class="{ active: activeCategory===c.key }" @click="activeCategory=c.key">{{ c.label }}</view>
</view>
<view class="field" @click="chooseCounterparty">
<text class="label">往来单位</text>
<text class="value">{{ counterpartyLabel }}</text>
</view>
<view class="field" @click="chooseAccount">
<text class="label">结算账户</text>
<text class="value">{{ accountLabel }}</text>
</view>
<view class="field">
<text class="label">金额</text>
<input class="value" type="digit" v-model.number="trxAmount" placeholder="0.00" />
</view>
<view class="textarea">
<textarea v-model="order.remark" maxlength="200" placeholder="备注最多输入200个字"></textarea>
</view>
</view>
<!-- 购物车空态 -->
<view class="empty" v-if="!items.length">
<image src="/static/logo.png" mode="widthFix" class="empty-img"></image>
<text class="empty-text">购物车里空空如也</text>
<text class="empty-sub">扫描或点击 + 选择商品吧</text>
</view>
<!-- 商品列表 -->
<view v-else class="list">
<view class="row" v-for="(it, idx) in items" :key="idx">
<view class="col name">{{ it.productName }}</view>
<view class="col qty">
<input type="number" v-model.number="it.quantity" @input="recalc()" />
</view>
<view class="col price">
<input type="number" v-model.number="it.unitPrice" @input="recalc()" />
</view>
<view class="col amount">¥ {{ (Number(it.quantity)*Number(it.unitPrice)).toFixed(2) }}</view>
</view>
</view>
<!-- 底部提交栏 -->
<view class="bottom">
<button class="ghost" @click="saveAndReset">再记一笔</button>
<button class="primary" @click="submit">保存</button>
</view>
</view>
</template>
<script>
import { post } from '../../common/http.js'
import { INCOME_CATEGORIES, EXPENSE_CATEGORIES } from '../../common/constants.js'
function todayString() {
const d = new Date()
const m = (d.getMonth()+1).toString().padStart(2,'0')
const day = d.getDate().toString().padStart(2,'0')
return `${d.getFullYear()}-${m}-${day}`
}
export default {
data() {
return {
biz: 'sale',
saleType: 'out',
purchaseType: 'in',
order: {
orderTime: todayString(),
customerId: null,
supplierId: null,
remark: ''
},
customerName: '',
supplierName: '',
items: [],
activeCategory: 'sale_income',
trxAmount: 0,
selectedAccountId: null,
selectedAccountName: ''
}
},
computed: {
totalQuantity() {
return this.items.reduce((s, it) => s + Number(it.quantity || 0), 0)
},
totalAmount() {
return this.items.reduce((s, it) => s + Number(it.quantity || 0) * Number(it.unitPrice || 0), 0)
},
customerLabel() { return this.customerName || '零售客户' },
supplierLabel() { return this.supplierName || '零散供应商' },
incomeCategories() { return INCOME_CATEGORIES },
expenseCategories() { return EXPENSE_CATEGORIES },
accountLabel() { return this.selectedAccountName || '现金' },
counterpartyLabel() { return this.customerName || this.supplierName || '—' }
},
methods: {
switchBiz(type) { this.biz = type },
onDateChange(e) { this.order.orderTime = e.detail.value },
chooseCustomer() {
uni.navigateTo({ url: '/pages/customer/select' })
},
chooseSupplier() { uni.navigateTo({ url: '/pages/supplier/select' }) },
chooseProduct() {
uni.navigateTo({ url: '/pages/product/select' })
},
chooseAccount() { uni.navigateTo({ url: '/pages/account/select' }) },
chooseCounterparty() {
if (this.biz==='income' || this.biz==='expense') {
uni.navigateTo({ url: '/pages/customer/select' })
}
},
recalc() { this.$forceUpdate() },
async submit() {
const isSaleOrPurchase = (this.biz==='sale' || this.biz==='purchase')
const payload = isSaleOrPurchase ? {
type: this.biz === 'sale' ? (this.saleType) : ('purchase.' + this.purchaseType),
orderTime: this.order.orderTime,
customerId: this.order.customerId,
supplierId: this.order.supplierId,
items: this.items.map(it => ({ productId: it.productId, quantity: Number(it.quantity||0), unitPrice: Number(it.unitPrice||0) })),
amount: this.totalAmount
} : {
type: this.biz,
category: this.activeCategory,
counterpartyId: this.order.customerId || null,
accountId: this.selectedAccountId || null,
amount: Number(this.trxAmount||0),
txTime: this.order.orderTime,
remark: this.order.remark
}
try {
const url = isSaleOrPurchase ? '/api/orders' : '/api/other-transactions'
await post(url, payload)
uni.showToast({ title: '已保存', icon: 'success' })
setTimeout(() => { uni.navigateBack() }, 600)
} catch (e) {
uni.showToast({ title: e && e.message || '保存失败', icon: 'none' })
}
},
saveAndReset() {
this.items = []
this.trxAmount = 0
this.order.remark = ''
}
}
}
</script>
<style>
.order { padding-bottom: 140rpx; }
.tabs { display: flex; justify-content: space-around; padding: 16rpx 24rpx; }
.tabs text { color: #666; }
.tabs text.active { color: #333; font-weight: 700; }
.subtabs { display: flex; gap: 16rpx; padding: 0 24rpx 16rpx; }
.subbtn { padding: 10rpx 20rpx; border-radius: 999rpx; background: #f4f4f4; color: #666; }
.subbtn.active { background: #ffe69a; color: #3f320f; }
.field { display:flex; justify-content: space-between; padding: 22rpx 24rpx; background: #fff; border-bottom: 1rpx solid #eee; }
.label { color:#666; }
.value { color:#333; }
.summary { display:flex; justify-content: space-between; padding: 22rpx 24rpx; color:#333; }
.add { margin: 24rpx auto; width: 120rpx; height: 120rpx; border-radius: 20rpx; background: #c7eef7; color:#16a1c4; font-size: 72rpx; display:flex; align-items:center; justify-content:center; }
.empty { display:flex; flex-direction: column; align-items:center; padding: 60rpx 0; color:#888; }
.empty-img { width: 220rpx; margin-bottom: 20rpx; }
.empty-text { margin-bottom: 8rpx; }
.list { background:#fff; }
.row { display:grid; grid-template-columns: 1.5fr 1fr 1fr 1fr; gap: 12rpx; padding: 16rpx 12rpx; align-items:center; border-bottom: 1rpx solid #f3f3f3; }
.col.name { padding-left: 12rpx; }
.col.amount { text-align:right; padding-right: 12rpx; color:#333; }
.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; }
</style>

View File

@@ -0,0 +1,49 @@
<template>
<view class="page">
<view class="search">
<input v-model="kw" placeholder="搜索商品名称/编码" @confirm="search" />
<button size="mini" @click="search">搜索</button>
</view>
<scroll-view scroll-y class="list">
<view class="item" v-for="p in products" :key="p.id" @click="select(p)">
<view class="name">{{ p.name }}</view>
<view class="meta">{{ p.code }} · 库存{{ p.stock || 0 }}</view>
</view>
</scroll-view>
</view>
</template>
<script>
import { get } from '../../common/http.js'
export default {
data() { return { kw: '', products: [] } },
onLoad() { this.search() },
methods: {
async search() {
try {
const res = await get('/api/products', { kw: this.kw, page: 1, size: 50 })
this.products = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])
} catch(e) { uni.showToast({ title: '加载失败', icon: 'none' }) }
},
select(p) {
const opener = getCurrentPages()[getCurrentPages().length-2]
if (opener && opener.$vm && opener.$vm.items) {
opener.$vm.items.push({ productId: p.id, productName: p.name, quantity: 1, unitPrice: Number(p.price || 0) })
}
uni.navigateBack()
}
}
}
</script>
<style>
.page { display:flex; flex-direction: column; height: 100vh; }
.search { display:flex; gap: 12rpx; padding: 16rpx; background:#fff; }
.search input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx; }
.list { flex:1; }
.item { padding: 20rpx 24rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1; }
.name { color:#333; margin-bottom: 6rpx; }
.meta { color:#888; font-size: 24rpx; }
</style>

View File

@@ -0,0 +1,50 @@
<template>
<view class="page">
<view class="search">
<input v-model="kw" placeholder="搜索供应商名称/电话" @confirm="search" />
<button size="mini" @click="search">搜索</button>
</view>
<scroll-view scroll-y class="list">
<view class="item" v-for="s in suppliers" :key="s.id" @click="select(s)">
<view class="name">{{ s.name }}</view>
<view class="meta">{{ s.mobile || '—' }}</view>
</view>
</scroll-view>
</view>
</template>
<script>
import { get } from '../../common/http.js'
export default {
data() { return { kw: '', suppliers: [] } },
onLoad() { this.search() },
methods: {
async search() {
try {
const res = await get('/api/suppliers', { kw: this.kw, page: 1, size: 50 })
this.suppliers = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])
} catch(e) { uni.showToast({ title: '加载失败', icon: 'none' }) }
},
select(s) {
const opener = getCurrentPages()[getCurrentPages().length-2]
if (opener && opener.$vm) {
opener.$vm.order.supplierId = s.id
opener.$vm.supplierName = s.name
}
uni.navigateBack()
}
}
}
</script>
<style>
.page { display:flex; flex-direction: column; height: 100vh; }
.search { display:flex; gap: 12rpx; padding: 16rpx; background:#fff; }
.search input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx; }
.list { flex:1; }
.item { padding: 20rpx 24rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1; }
.name { color:#333; margin-bottom: 6rpx; }
.meta { color:#888; font-size: 24rpx; }
</style>

View File

@@ -1 +1 @@
{"version":3,"file":"app.js","sources":["App.vue","main.js"],"sourcesContent":["<script>\r\n\texport default {\r\n\t\tonLaunch: function() {\r\n\t\t\tconsole.log('App Launch')\r\n\t\t},\r\n\t\tonShow: function() {\r\n\t\t\tconsole.log('App Show')\r\n\t\t},\r\n\t\tonHide: function() {\r\n\t\t\tconsole.log('App Hide')\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style>\r\n\t/*每个页面公共css */\r\n</style>\r\n","import App from './App'\r\n\r\n// #ifndef VUE3\r\nimport Vue from 'vue'\r\nimport './uni.promisify.adaptor'\r\nVue.config.productionTip = false\r\nApp.mpType = 'app'\r\nconst app = new Vue({\r\n ...App\r\n})\r\napp.$mount()\r\n// #endif\r\n\r\n// #ifdef VUE3\r\nimport { createSSRApp } from 'vue'\r\nexport function createApp() {\r\n const app = createSSRApp(App)\r\n return {\r\n app\r\n }\r\n}\r\n// #endif"],"names":["uni","createSSRApp","App"],"mappings":";;;;;;AACC,MAAK,YAAU;AAAA,EACd,UAAU,WAAW;AACpBA,kBAAAA,MAAA,MAAA,OAAA,gBAAY,YAAY;AAAA,EACxB;AAAA,EACD,QAAQ,WAAW;AAClBA,kBAAAA,MAAY,MAAA,OAAA,gBAAA,UAAU;AAAA,EACtB;AAAA,EACD,QAAQ,WAAW;AAClBA,kBAAAA,MAAY,MAAA,OAAA,iBAAA,UAAU;AAAA,EACvB;AACD;ACIM,SAAS,YAAY;AAC1B,QAAM,MAAMC,cAAY,aAACC,SAAG;AAC5B,SAAO;AAAA,IACL;AAAA,EACD;AACH;;;"}
{"version":3,"file":"app.js","sources":["App.vue","main.js"],"sourcesContent":["<script>\r\n\texport default {\r\n\t\tonLaunch: function() {\r\n\t\t\tconsole.log('App Launch')\r\n\t\t},\r\n\t\tonShow: function() {\r\n\t\t\tconsole.log('App Show')\r\n\t\t},\r\n\t\tonHide: function() {\r\n\t\t\tconsole.log('App Hide')\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style>\r\n\t/*每个页面公共css */\r\n</style>\r\n","import App from './App'\r\n\r\n// #ifndef VUE3\r\nimport Vue from 'vue'\r\nimport './uni.promisify.adaptor'\r\nVue.config.productionTip = false\r\nApp.mpType = 'app'\r\nconst app = new Vue({\r\n ...App\r\n})\r\napp.$mount()\r\n// #endif\r\n\r\n// #ifdef VUE3\r\nimport { createSSRApp } from 'vue'\r\nexport function createApp() {\r\n const app = createSSRApp(App)\r\n return {\r\n app\r\n }\r\n}\r\n// #endif"],"names":["uni","createSSRApp","App"],"mappings":";;;;;;;;;;;AACC,MAAK,YAAU;AAAA,EACd,UAAU,WAAW;AACpBA,kBAAAA,MAAA,MAAA,OAAA,gBAAY,YAAY;AAAA,EACxB;AAAA,EACD,QAAQ,WAAW;AAClBA,kBAAAA,MAAY,MAAA,OAAA,gBAAA,UAAU;AAAA,EACtB;AAAA,EACD,QAAQ,WAAW;AAClBA,kBAAAA,MAAY,MAAA,OAAA,iBAAA,UAAU;AAAA,EACvB;AACD;ACIM,SAAS,YAAY;AAC1B,QAAM,MAAMC,cAAY,aAACC,SAAG;AAC5B,SAAO;AAAA,IACL;AAAA,EACD;AACH;;;"}

View File

@@ -1 +1 @@
{"version":3,"file":"assets.js","sources":["../../../../../../static/metal-bg.jpg"],"sourcesContent":["export default \"/static/metal-bg.jpg\""],"names":[],"mappings":";AAAA,MAAe,aAAA;;"}
{"version":3,"file":"assets.js","sources":["../../../../../../static/metal-bg.jpg","static/logo.png"],"sourcesContent":["export default \"/static/metal-bg.jpg\"","export default \"__VITE_ASSET__46719607__\""],"names":[],"mappings":";AAAA,MAAe,eAAA;ACAf,MAAe,aAAA;;;"}

View File

@@ -1 +1 @@
{"version":3,"file":"config.js","sources":["common/config.js"],"sourcesContent":["// 统一配置:禁止在业务代码中硬编码\r\n// 优先级:环境变量(Vite/HBuilderX 构建注入) > 本地存储 > 默认值\r\n\r\nconst envBaseUrl = (typeof process !== 'undefined' && process.env && (process.env.VITE_APP_API_BASE_URL || process.env.API_BASE_URL)) || '';\r\nconst storageBaseUrl = typeof uni !== 'undefined' ? (uni.getStorageSync('API_BASE_URL') || '') : '';\r\nconst fallbackBaseUrl = 'http://localhost:8080';\r\n\r\nexport const API_BASE_URL = (envBaseUrl || storageBaseUrl || fallbackBaseUrl).replace(/\\/$/, '');\r\n\r\nconst envShopId = (typeof process !== 'undefined' && process.env && (process.env.VITE_APP_SHOP_ID || process.env.SHOP_ID)) || '';\r\nconst storageShopId = typeof uni !== 'undefined' ? (uni.getStorageSync('SHOP_ID') || '') : '';\r\nexport const SHOP_ID = Number(envShopId || storageShopId || 1);\r\n\r\n\r\n"],"names":["uni"],"mappings":";;AAGA,MAAM,aAAc,OAAO,YAAY,eAAe,QAAQ,QAAQ,QAAQ,IAAI,yBAAyB,QAAQ,IAAI,iBAAkB;AACzI,MAAM,iBAAiB,OAAOA,cAAG,UAAK,cAAeA,cAAAA,MAAI,eAAe,cAAc,KAAK,KAAM;AACjG,MAAM,kBAAkB;AAEZ,MAAC,gBAAgB,cAAc,kBAAkB,iBAAiB,QAAQ,OAAO,EAAE;AAE/F,MAAM,YAAa,OAAO,YAAY,eAAe,QAAQ,QAAQ,QAAQ,IAAI,oBAAoB,QAAQ,IAAI,YAAa;AAC9H,MAAM,gBAAgB,OAAOA,cAAG,UAAK,cAAeA,cAAAA,MAAI,eAAe,SAAS,KAAK,KAAM;AAC/E,MAAC,UAAU,OAAO,aAAa,iBAAiB,CAAC;;;"}
{"version":3,"file":"config.js","sources":["common/config.js"],"sourcesContent":["// 统一配置:禁止在业务代码中硬编码\n// 优先级:环境变量(Vite/HBuilderX 构建注入) > 本地存储 > 默认值\n\nconst envBaseUrl = (typeof process !== 'undefined' && process.env && (process.env.VITE_APP_API_BASE_URL || process.env.API_BASE_URL)) || '';\nconst storageBaseUrl = typeof uni !== 'undefined' ? (uni.getStorageSync('API_BASE_URL') || '') : '';\nconst fallbackBaseUrl = 'http://localhost:8080';\n\nexport const API_BASE_URL = (envBaseUrl || storageBaseUrl || fallbackBaseUrl).replace(/\\/$/, '');\n\nconst envShopId = (typeof process !== 'undefined' && process.env && (process.env.VITE_APP_SHOP_ID || process.env.SHOP_ID)) || '';\nconst storageShopId = typeof uni !== 'undefined' ? (uni.getStorageSync('SHOP_ID') || '') : '';\nexport const SHOP_ID = Number(envShopId || storageShopId || 1);\n\n\n// 默认用户(可移除):\n// - 用途:开发/演示环境自动将用户固定为“张老板”id=2\n// - 开关优先级:环境变量 > 本地存储 > 默认值\n// - 生产默认关闭false开发可通过本地存储或环境变量开启\nconst envEnableDefaultUser = (typeof process !== 'undefined' && process.env && (process.env.VITE_APP_ENABLE_DEFAULT_USER || process.env.ENABLE_DEFAULT_USER)) || '';\nconst storageEnableDefaultUser = typeof uni !== 'undefined' ? (uni.getStorageSync('ENABLE_DEFAULT_USER') || '') : '';\nexport const ENABLE_DEFAULT_USER = String(envEnableDefaultUser || storageEnableDefaultUser || 'false').toLowerCase() === 'true';\n\nconst envDefaultUserId = (typeof process !== 'undefined' && process.env && (process.env.VITE_APP_DEFAULT_USER_ID || process.env.DEFAULT_USER_ID)) || '';\nconst storageDefaultUserId = typeof uni !== 'undefined' ? (uni.getStorageSync('DEFAULT_USER_ID') || '') : '';\nexport const DEFAULT_USER_ID = Number(envDefaultUserId || storageDefaultUserId || 2);\n\n\n"],"names":["uni"],"mappings":";;AAGA,MAAM,aAAc,OAAO,YAAY,eAAe,QAAQ,QAAQ,QAAQ,IAAI,yBAAyB,QAAQ,IAAI,iBAAkB;AACzI,MAAM,iBAAiB,OAAOA,cAAG,UAAK,cAAeA,cAAAA,MAAI,eAAe,cAAc,KAAK,KAAM;AACjG,MAAM,kBAAkB;AAEZ,MAAC,gBAAgB,cAAc,kBAAkB,iBAAiB,QAAQ,OAAO,EAAE;AAE/F,MAAM,YAAa,OAAO,YAAY,eAAe,QAAQ,QAAQ,QAAQ,IAAI,oBAAoB,QAAQ,IAAI,YAAa;AAC9H,MAAM,gBAAgB,OAAOA,cAAG,UAAK,cAAeA,cAAAA,MAAI,eAAe,SAAS,KAAK,KAAM;AAC/E,MAAC,UAAU,OAAO,aAAa,iBAAiB,CAAC;AAO7D,MAAM,uBAAwB,OAAO,YAAY,eAAe,QAAQ,QAAQ,QAAQ,IAAI,gCAAgC,QAAQ,IAAI,wBAAyB;AACjK,MAAM,2BAA2B,OAAOA,cAAG,UAAK,cAAeA,cAAAA,MAAI,eAAe,qBAAqB,KAAK,KAAM;AACtG,MAAC,sBAAsB,OAAO,wBAAwB,4BAA4B,OAAO,EAAE,YAAW,MAAO;AAEzH,MAAM,mBAAoB,OAAO,YAAY,eAAe,QAAQ,QAAQ,QAAQ,IAAI,4BAA4B,QAAQ,IAAI,oBAAqB;AACrJ,MAAM,uBAAuB,OAAOA,cAAG,UAAK,cAAeA,cAAAA,MAAI,eAAe,iBAAiB,KAAK,KAAM;AAC9F,MAAC,kBAAkB,OAAO,oBAAoB,wBAAwB,CAAC;;;;;"}

View File

@@ -0,0 +1 @@
{"version":3,"file":"constants.js","sources":["common/constants.js"],"sourcesContent":["// 统一常量配置:其他收入/支出分类,禁止在业务中硬编码\r\nexport const INCOME_CATEGORIES = [\r\n\t{ key: 'sale_income', label: '销售收入' },\r\n\t{ key: 'operation_income', label: '经营所得' },\r\n\t{ key: 'interest_income', label: '利息收入' },\r\n\t{ key: 'investment_income', label: '投资收入' },\r\n\t{ key: 'other_income', label: '其它收入' }\r\n]\r\n\r\nexport const EXPENSE_CATEGORIES = [\r\n\t{ key: 'operation_expense', label: '经营支出' },\r\n\t{ key: 'office_supplies', label: '办公用品' },\r\n\t{ key: 'rent', label: '房租' },\r\n\t{ key: 'interest_expense', label: '利息支出' },\r\n\t{ key: 'other_expense', label: '其它支出' }\r\n]\r\n\r\n\r\n"],"names":[],"mappings":";AACY,MAAC,oBAAoB;AAAA,EAChC,EAAE,KAAK,eAAe,OAAO,OAAQ;AAAA,EACrC,EAAE,KAAK,oBAAoB,OAAO,OAAQ;AAAA,EAC1C,EAAE,KAAK,mBAAmB,OAAO,OAAQ;AAAA,EACzC,EAAE,KAAK,qBAAqB,OAAO,OAAQ;AAAA,EAC3C,EAAE,KAAK,gBAAgB,OAAO,OAAQ;AACvC;AAEY,MAAC,qBAAqB;AAAA,EACjC,EAAE,KAAK,qBAAqB,OAAO,OAAQ;AAAA,EAC3C,EAAE,KAAK,mBAAmB,OAAO,OAAQ;AAAA,EACzC,EAAE,KAAK,QAAQ,OAAO,KAAM;AAAA,EAC5B,EAAE,KAAK,oBAAoB,OAAO,OAAQ;AAAA,EAC1C,EAAE,KAAK,iBAAiB,OAAO,OAAQ;AACxC;;;"}

View File

@@ -1 +1 @@
{"version":3,"file":"http.js","sources":["common/http.js"],"sourcesContent":["import { API_BASE_URL, SHOP_ID } from './config.js'\r\n\r\nfunction buildUrl(path) {\r\n if (!path) return API_BASE_URL\r\n if (path.startsWith('http')) return path\r\n return API_BASE_URL + (path.startsWith('/') ? path : '/' + path)\r\n}\r\n\r\nexport function get(path, params = {}) {\r\n return new Promise((resolve, reject) => {\r\n uni.request({\r\n url: buildUrl(path),\r\n method: 'GET',\r\n data: params,\r\n header: { 'X-Shop-Id': SHOP_ID },\r\n success: (res) => {\r\n const { statusCode, data } = res\r\n if (statusCode >= 200 && statusCode < 300) return resolve(data)\r\n reject(new Error('HTTP ' + statusCode))\r\n },\r\n fail: (err) => reject(err)\r\n })\r\n })\r\n}\r\n\r\n\r\n"],"names":["API_BASE_URL","uni","SHOP_ID"],"mappings":";;;AAEA,SAAS,SAAS,MAAM;AACtB,MAAI,CAAC;AAAM,WAAOA,cAAY;AAC9B,MAAI,KAAK,WAAW,MAAM;AAAG,WAAO;AACpC,SAAOA,cAAAA,gBAAgB,KAAK,WAAW,GAAG,IAAI,OAAO,MAAM;AAC7D;AAEO,SAAS,IAAI,MAAM,SAAS,IAAI;AACrC,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtCC,kBAAAA,MAAI,QAAQ;AAAA,MACV,KAAK,SAAS,IAAI;AAAA,MAClB,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,QAAQ,EAAE,aAAaC,sBAAS;AAAA,MAChC,SAAS,CAAC,QAAQ;AAChB,cAAM,EAAE,YAAY,KAAI,IAAK;AAC7B,YAAI,cAAc,OAAO,aAAa;AAAK,iBAAO,QAAQ,IAAI;AAC9D,eAAO,IAAI,MAAM,UAAU,UAAU,CAAC;AAAA,MACvC;AAAA,MACD,MAAM,CAAC,QAAQ,OAAO,GAAG;AAAA,IAC/B,CAAK;AAAA,EACL,CAAG;AACH;;"}
{"version":3,"file":"http.js","sources":["common/http.js"],"sourcesContent":["import { API_BASE_URL, SHOP_ID, ENABLE_DEFAULT_USER, DEFAULT_USER_ID } from './config.js'\n\nfunction buildUrl(path) {\n if (!path) return API_BASE_URL\n if (path.startsWith('http')) return path\n return API_BASE_URL + (path.startsWith('/') ? path : '/' + path)\n}\n\nexport function get(path, params = {}) {\n return new Promise((resolve, reject) => {\n const headers = { 'X-Shop-Id': SHOP_ID }\n if (ENABLE_DEFAULT_USER && DEFAULT_USER_ID) headers['X-User-Id'] = DEFAULT_USER_ID\n uni.request({\n url: buildUrl(path),\n method: 'GET',\n data: params,\n header: headers,\n success: (res) => {\n const { statusCode, data } = res\n if (statusCode >= 200 && statusCode < 300) return resolve(data)\n reject(new Error('HTTP ' + statusCode))\n },\n fail: (err) => reject(err)\n })\n })\n}\n\n\nexport function post(path, body = {}) {\n return new Promise((resolve, reject) => {\n const headers = { 'Content-Type': 'application/json', 'X-Shop-Id': SHOP_ID }\n if (ENABLE_DEFAULT_USER && DEFAULT_USER_ID) headers['X-User-Id'] = DEFAULT_USER_ID\n uni.request({\n url: buildUrl(path),\n method: 'POST',\n data: body,\n header: headers,\n success: (res) => {\n const { statusCode, data } = res\n if (statusCode >= 200 && statusCode < 300) return resolve(data)\n reject(new Error('HTTP ' + statusCode))\n },\n fail: (err) => reject(err)\n })\n })\n}\n\n\n"],"names":["API_BASE_URL","SHOP_ID","ENABLE_DEFAULT_USER","DEFAULT_USER_ID","uni"],"mappings":";;;AAEA,SAAS,SAAS,MAAM;AACtB,MAAI,CAAC;AAAM,WAAOA,cAAY;AAC9B,MAAI,KAAK,WAAW,MAAM;AAAG,WAAO;AACpC,SAAOA,cAAAA,gBAAgB,KAAK,WAAW,GAAG,IAAI,OAAO,MAAM;AAC7D;AAEO,SAAS,IAAI,MAAM,SAAS,IAAI;AACrC,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,UAAU,EAAE,aAAaC,sBAAS;AACxC,QAAIC,cAAmB,uBAAIC,cAAe;AAAE,cAAQ,WAAW,IAAIA,cAAe;AAClFC,kBAAAA,MAAI,QAAQ;AAAA,MACV,KAAK,SAAS,IAAI;AAAA,MAClB,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,SAAS,CAAC,QAAQ;AAChB,cAAM,EAAE,YAAY,KAAI,IAAK;AAC7B,YAAI,cAAc,OAAO,aAAa;AAAK,iBAAO,QAAQ,IAAI;AAC9D,eAAO,IAAI,MAAM,UAAU,UAAU,CAAC;AAAA,MACvC;AAAA,MACD,MAAM,CAAC,QAAQ,OAAO,GAAG;AAAA,IAC/B,CAAK;AAAA,EACL,CAAG;AACH;AAGO,SAAS,KAAK,MAAM,OAAO,IAAI;AACpC,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,UAAU,EAAE,gBAAgB,oBAAoB,aAAaH,cAAAA,QAAS;AAC5E,QAAIC,cAAmB,uBAAIC,cAAe;AAAE,cAAQ,WAAW,IAAIA,cAAe;AAClFC,kBAAAA,MAAI,QAAQ;AAAA,MACV,KAAK,SAAS,IAAI;AAAA,MAClB,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,SAAS,CAAC,QAAQ;AAChB,cAAM,EAAE,YAAY,KAAI,IAAK;AAC7B,YAAI,cAAc,OAAO,aAAa;AAAK,iBAAO,QAAQ,IAAI;AAC9D,eAAO,IAAI,MAAM,UAAU,UAAU,CAAC;AAAA,MACvC;AAAA,MACD,MAAM,CAAC,QAAQ,OAAO,GAAG;AAAA,IAC/B,CAAK;AAAA,EACL,CAAG;AACH;;;"}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"version":3,"file":"select.js","sources":["pages/account/select.vue","../../../../Downloads/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvYWNjb3VudC9zZWxlY3QudnVl"],"sourcesContent":["<template>\r\n\t<view class=\"page\">\r\n\t\t<scroll-view scroll-y class=\"list\">\r\n\t\t\t<view class=\"item\" v-for=\"a in accounts\" :key=\"a.id\" @click=\"select(a)\">\r\n\t\t\t\t<view class=\"name\">{{ a.name }}</view>\r\n\t\t\t\t<view class=\"meta\">{{ typeLabel(a.type) }} · 余额:{{ a.balance?.toFixed ? a.balance.toFixed(2) : a.balance }}</view>\r\n\t\t\t</view>\r\n\t\t</scroll-view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport { get } from '../../common/http.js'\r\n\tconst TYPE_MAP = { cash: '现金', bank: '银行', alipay: '支付宝', wechat: '微信', other: '其他' }\r\n\texport default {\r\n\t\tdata() { return { accounts: [] } },\r\n\t\tasync onLoad() {\r\n\t\t\ttry {\r\n\t\t\t\tconst res = await get('/api/accounts')\r\n\t\t\t\tthis.accounts = Array.isArray(res) ? res : (res?.list || [])\r\n\t\t\t} catch(e) { uni.showToast({ title: '加载失败', icon: 'none' }) }\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tselect(a) {\r\n\t\t\t\tconst opener = getCurrentPages()[getCurrentPages().length-2]\r\n\t\t\t\tif (opener && opener.$vm) {\r\n\t\t\t\t\topener.$vm.selectedAccountId = a.id\r\n\t\t\t\t\topener.$vm.selectedAccountName = a.name\r\n\t\t\t\t}\r\n\t\t\t\tuni.navigateBack()\r\n\t\t\t},\r\n\t\t\ttypeLabel(t) { return TYPE_MAP[t] || t }\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style>\r\n\t.page { display:flex; flex-direction: column; height: 100vh; }\r\n\t.list { flex:1; }\r\n\t.item { padding: 20rpx 24rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1; }\r\n\t.name { color:#333; margin-bottom: 6rpx; }\r\n\t.meta { color:#888; font-size: 24rpx; }\r\n</style>\r\n\r\n\r\n","import MiniProgramPage from 'C:/Users/21826/Desktop/Wj/PartsInquiry/frontend/pages/account/select.vue'\nwx.createPage(MiniProgramPage)"],"names":["get","uni"],"mappings":";;;AAaC,MAAM,WAAW,EAAE,MAAM,MAAM,MAAM,MAAM,QAAQ,OAAO,QAAQ,MAAM,OAAO,KAAK;AACpF,MAAK,YAAU;AAAA,EACd,OAAO;AAAE,WAAO,EAAE,UAAU,CAAG,EAAA;AAAA,EAAG;AAAA,EAClC,MAAM,SAAS;AACd,QAAI;AACH,YAAM,MAAM,MAAMA,YAAG,IAAC,eAAe;AACrC,WAAK,WAAW,MAAM,QAAQ,GAAG,IAAI,OAAO,2BAAK,SAAQ;aAClD,GAAG;AAAEC,oBAAAA,MAAI,UAAU,EAAE,OAAO,QAAQ,MAAM,OAAO,CAAC;AAAA,IAAE;AAAA,EAC5D;AAAA,EACD,SAAS;AAAA,IACR,OAAO,GAAG;AACT,YAAM,SAAS,gBAAiB,EAAC,gBAAe,EAAG,SAAO,CAAC;AAC3D,UAAI,UAAU,OAAO,KAAK;AACzB,eAAO,IAAI,oBAAoB,EAAE;AACjC,eAAO,IAAI,sBAAsB,EAAE;AAAA,MACpC;AACAA,oBAAAA,MAAI,aAAa;AAAA,IACjB;AAAA,IACD,UAAU,GAAG;AAAE,aAAO,SAAS,CAAC,KAAK;AAAA,IAAE;AAAA,EACxC;AACD;;;;;;;;;;;;;;;;AChCD,GAAG,WAAW,eAAe;"}

View File

@@ -0,0 +1 @@
{"version":3,"file":"select.js","sources":["pages/customer/select.vue","../../../../Downloads/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvY3VzdG9tZXIvc2VsZWN0LnZ1ZQ"],"sourcesContent":["<template>\r\n\t<view class=\"page\">\r\n\t\t<view class=\"search\">\r\n\t\t\t<input v-model=\"kw\" placeholder=\"搜索客户名称/电话\" @confirm=\"search\" />\r\n\t\t\t<button size=\"mini\" @click=\"search\">搜索</button>\r\n\t\t</view>\r\n\t\t<scroll-view scroll-y class=\"list\">\r\n\t\t\t<view class=\"item\" v-for=\"c in customers\" :key=\"c.id\" @click=\"select(c)\">\r\n\t\t\t\t<view class=\"name\">{{ c.name }}</view>\r\n\t\t\t\t<view class=\"meta\">{{ c.mobile || '—' }}</view>\r\n\t\t\t</view>\r\n\t\t</scroll-view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport { get } from '../../common/http.js'\r\n\texport default {\r\n\t\tdata() { return { kw: '', customers: [] } },\r\n\t\tonLoad() { this.search() },\r\n\t\tmethods: {\r\n\t\t\tasync search() {\r\n\t\t\t\ttry {\r\n\t\t\t\t\tconst res = await get('/api/customers', { kw: this.kw, page: 1, size: 50 })\r\n\t\t\t\t\tthis.customers = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])\r\n\t\t\t\t} catch(e) { uni.showToast({ title: '加载失败', icon: 'none' }) }\r\n\t\t\t},\r\n\t\t\tselect(c) {\r\n\t\t\t\tconst opener = getCurrentPages()[getCurrentPages().length-2]\r\n\t\t\t\tif (opener && opener.$vm) {\r\n\t\t\t\t\topener.$vm.order.customerId = c.id\r\n\t\t\t\t\topener.$vm.customerName = c.name\r\n\t\t\t\t}\r\n\t\t\t\tuni.navigateBack()\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style>\r\n\t.page { display:flex; flex-direction: column; height: 100vh; }\r\n\t.search { display:flex; gap: 12rpx; padding: 16rpx; background:#fff; }\r\n\t.search input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx; }\r\n\t.list { flex:1; }\r\n\t.item { padding: 20rpx 24rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1; }\r\n\t.name { color:#333; margin-bottom: 6rpx; }\r\n\t.meta { color:#888; font-size: 24rpx; }\r\n</style>\r\n\r\n\r\n","import MiniProgramPage from 'C:/Users/21826/Desktop/Wj/PartsInquiry/frontend/pages/customer/select.vue'\nwx.createPage(MiniProgramPage)"],"names":["get","uni"],"mappings":";;;AAiBC,MAAK,YAAU;AAAA,EACd,OAAO;AAAE,WAAO,EAAE,IAAI,IAAI,WAAW,CAAA;EAAM;AAAA,EAC3C,SAAS;AAAE,SAAK;EAAU;AAAA,EAC1B,SAAS;AAAA,IACR,MAAM,SAAS;AACd,UAAI;AACH,cAAM,MAAM,MAAMA,gBAAI,kBAAkB,EAAE,IAAI,KAAK,IAAI,MAAM,GAAG,MAAM,GAAC,CAAG;AAC1E,aAAK,YAAY,MAAM,QAAQ,2BAAK,IAAI,IAAI,IAAI,OAAQ,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAA;AAAA,eAC5E,GAAG;AAAEC,sBAAAA,MAAI,UAAU,EAAE,OAAO,QAAQ,MAAM,OAAO,CAAC;AAAA,MAAE;AAAA,IAC5D;AAAA,IACD,OAAO,GAAG;AACT,YAAM,SAAS,gBAAiB,EAAC,gBAAe,EAAG,SAAO,CAAC;AAC3D,UAAI,UAAU,OAAO,KAAK;AACzB,eAAO,IAAI,MAAM,aAAa,EAAE;AAChC,eAAO,IAAI,eAAe,EAAE;AAAA,MAC7B;AACAA,oBAAAA,MAAI,aAAa;AAAA,IAClB;AAAA,EACD;AACD;;;;;;;;;;;;;;;;;;ACnCD,GAAG,WAAW,eAAe;"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"version":3,"file":"select.js","sources":["pages/product/select.vue","../../../../Downloads/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvcHJvZHVjdC9zZWxlY3QudnVl"],"sourcesContent":["<template>\r\n\t<view class=\"page\">\r\n\t\t<view class=\"search\">\r\n\t\t\t<input v-model=\"kw\" placeholder=\"搜索商品名称/编码\" @confirm=\"search\" />\r\n\t\t\t<button size=\"mini\" @click=\"search\">搜索</button>\r\n\t\t</view>\r\n\t\t<scroll-view scroll-y class=\"list\">\r\n\t\t\t<view class=\"item\" v-for=\"p in products\" :key=\"p.id\" @click=\"select(p)\">\r\n\t\t\t\t<view class=\"name\">{{ p.name }}</view>\r\n\t\t\t\t<view class=\"meta\">{{ p.code }} · 库存:{{ p.stock || 0 }}</view>\r\n\t\t\t</view>\r\n\t\t</scroll-view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport { get } from '../../common/http.js'\r\n\texport default {\r\n\t\tdata() { return { kw: '', products: [] } },\r\n\t\tonLoad() { this.search() },\r\n\t\tmethods: {\r\n\t\t\tasync search() {\r\n\t\t\t\ttry {\r\n\t\t\t\t\tconst res = await get('/api/products', { kw: this.kw, page: 1, size: 50 })\r\n\t\t\t\t\tthis.products = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])\r\n\t\t\t\t} catch(e) { uni.showToast({ title: '加载失败', icon: 'none' }) }\r\n\t\t\t},\r\n\t\t\tselect(p) {\r\n\t\t\t\tconst opener = getCurrentPages()[getCurrentPages().length-2]\r\n\t\t\t\tif (opener && opener.$vm && opener.$vm.items) {\r\n\t\t\t\t\topener.$vm.items.push({ productId: p.id, productName: p.name, quantity: 1, unitPrice: Number(p.price || 0) })\r\n\t\t\t\t}\r\n\t\t\t\tuni.navigateBack()\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style>\r\n\t.page { display:flex; flex-direction: column; height: 100vh; }\r\n\t.search { display:flex; gap: 12rpx; padding: 16rpx; background:#fff; }\r\n\t.search input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx; }\r\n\t.list { flex:1; }\r\n\t.item { padding: 20rpx 24rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1; }\r\n\t.name { color:#333; margin-bottom: 6rpx; }\r\n\t.meta { color:#888; font-size: 24rpx; }\r\n</style>\r\n\r\n\r\n","import MiniProgramPage from 'C:/Users/21826/Desktop/Wj/PartsInquiry/frontend/pages/product/select.vue'\nwx.createPage(MiniProgramPage)"],"names":["get","uni"],"mappings":";;;AAiBC,MAAK,YAAU;AAAA,EACd,OAAO;AAAE,WAAO,EAAE,IAAI,IAAI,UAAU,CAAA;EAAM;AAAA,EAC1C,SAAS;AAAE,SAAK;EAAU;AAAA,EAC1B,SAAS;AAAA,IACR,MAAM,SAAS;AACd,UAAI;AACH,cAAM,MAAM,MAAMA,gBAAI,iBAAiB,EAAE,IAAI,KAAK,IAAI,MAAM,GAAG,MAAM,GAAC,CAAG;AACzE,aAAK,WAAW,MAAM,QAAQ,2BAAK,IAAI,IAAI,IAAI,OAAQ,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAA;AAAA,eAC3E,GAAG;AAAEC,sBAAAA,MAAI,UAAU,EAAE,OAAO,QAAQ,MAAM,OAAO,CAAC;AAAA,MAAE;AAAA,IAC5D;AAAA,IACD,OAAO,GAAG;AACT,YAAM,SAAS,gBAAiB,EAAC,gBAAe,EAAG,SAAO,CAAC;AAC3D,UAAI,UAAU,OAAO,OAAO,OAAO,IAAI,OAAO;AAC7C,eAAO,IAAI,MAAM,KAAK,EAAE,WAAW,EAAE,IAAI,aAAa,EAAE,MAAM,UAAU,GAAG,WAAW,OAAO,EAAE,SAAS,CAAC,GAAG;AAAA,MAC7G;AACAA,oBAAAA,MAAI,aAAa;AAAA,IAClB;AAAA,EACD;AACD;;;;;;;;;;;;;;;;;;;AClCD,GAAG,WAAW,eAAe;"}

View File

@@ -0,0 +1 @@
{"version":3,"file":"select.js","sources":["pages/supplier/select.vue","../../../../Downloads/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvc3VwcGxpZXIvc2VsZWN0LnZ1ZQ"],"sourcesContent":["<template>\r\n\t<view class=\"page\">\r\n\t\t<view class=\"search\">\r\n\t\t\t<input v-model=\"kw\" placeholder=\"搜索供应商名称/电话\" @confirm=\"search\" />\r\n\t\t\t<button size=\"mini\" @click=\"search\">搜索</button>\r\n\t\t</view>\r\n\t\t<scroll-view scroll-y class=\"list\">\r\n\t\t\t<view class=\"item\" v-for=\"s in suppliers\" :key=\"s.id\" @click=\"select(s)\">\r\n\t\t\t\t<view class=\"name\">{{ s.name }}</view>\r\n\t\t\t\t<view class=\"meta\">{{ s.mobile || '—' }}</view>\r\n\t\t\t</view>\r\n\t\t</scroll-view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport { get } from '../../common/http.js'\r\n\texport default {\r\n\t\tdata() { return { kw: '', suppliers: [] } },\r\n\t\tonLoad() { this.search() },\r\n\t\tmethods: {\r\n\t\t\tasync search() {\r\n\t\t\t\ttry {\r\n\t\t\t\t\tconst res = await get('/api/suppliers', { kw: this.kw, page: 1, size: 50 })\r\n\t\t\t\t\tthis.suppliers = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])\r\n\t\t\t\t} catch(e) { uni.showToast({ title: '加载失败', icon: 'none' }) }\r\n\t\t\t},\r\n\t\t\tselect(s) {\r\n\t\t\t\tconst opener = getCurrentPages()[getCurrentPages().length-2]\r\n\t\t\t\tif (opener && opener.$vm) {\r\n\t\t\t\t\topener.$vm.order.supplierId = s.id\r\n\t\t\t\t\topener.$vm.supplierName = s.name\r\n\t\t\t\t}\r\n\t\t\t\tuni.navigateBack()\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style>\r\n\t.page { display:flex; flex-direction: column; height: 100vh; }\r\n\t.search { display:flex; gap: 12rpx; padding: 16rpx; background:#fff; }\r\n\t.search input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx; }\r\n\t.list { flex:1; }\r\n\t.item { padding: 20rpx 24rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1; }\r\n\t.name { color:#333; margin-bottom: 6rpx; }\r\n\t.meta { color:#888; font-size: 24rpx; }\r\n</style>\r\n\r\n\r\n","import MiniProgramPage from 'C:/Users/21826/Desktop/Wj/PartsInquiry/frontend/pages/supplier/select.vue'\nwx.createPage(MiniProgramPage)"],"names":["get","uni"],"mappings":";;;AAiBC,MAAK,YAAU;AAAA,EACd,OAAO;AAAE,WAAO,EAAE,IAAI,IAAI,WAAW,CAAA;EAAM;AAAA,EAC3C,SAAS;AAAE,SAAK;EAAU;AAAA,EAC1B,SAAS;AAAA,IACR,MAAM,SAAS;AACd,UAAI;AACH,cAAM,MAAM,MAAMA,gBAAI,kBAAkB,EAAE,IAAI,KAAK,IAAI,MAAM,GAAG,MAAM,GAAC,CAAG;AAC1E,aAAK,YAAY,MAAM,QAAQ,2BAAK,IAAI,IAAI,IAAI,OAAQ,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAA;AAAA,eAC5E,GAAG;AAAEC,sBAAAA,MAAI,UAAU,EAAE,OAAO,QAAQ,MAAM,OAAO,CAAC;AAAA,MAAE;AAAA,IAC5D;AAAA,IACD,OAAO,GAAG;AACT,YAAM,SAAS,gBAAiB,EAAC,gBAAe,EAAG,SAAO,CAAC;AAC3D,UAAI,UAAU,OAAO,KAAK;AACzB,eAAO,IAAI,MAAM,aAAa,EAAE;AAChC,eAAO,IAAI,eAAe,EAAE;AAAA,MAC7B;AACAA,oBAAAA,MAAI,aAAa;AAAA,IAClB;AAAA,EACD;AACD;;;;;;;;;;;;;;;;;;ACnCD,GAAG,WAAW,eAAe;"}

View File

@@ -3,6 +3,11 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const common_vendor = require("./common/vendor.js");
if (!Math) {
"./pages/index/index.js";
"./pages/order/create.js";
"./pages/product/select.js";
"./pages/customer/select.js";
"./pages/supplier/select.js";
"./pages/account/select.js";
}
const _sfc_main = {
onLaunch: function() {

View File

@@ -1,6 +1,11 @@
{
"pages": [
"pages/index/index"
"pages/index/index",
"pages/order/create",
"pages/product/select",
"pages/customer/select",
"pages/supplier/select",
"pages/account/select"
],
"window": {
"navigationBarTextStyle": "black",

View File

@@ -1,4 +1,6 @@
"use strict";
const _imports_0 = "/static/metal-bg.jpg";
exports._imports_0 = _imports_0;
const _imports_0$1 = "/static/metal-bg.jpg";
const _imports_0 = "/static/logo.png";
exports._imports_0 = _imports_0$1;
exports._imports_0$1 = _imports_0;
//# sourceMappingURL=../../.sourcemap/mp-weixin/common/assets.js.map

View File

@@ -7,6 +7,14 @@ const API_BASE_URL = (envBaseUrl || storageBaseUrl || fallbackBaseUrl).replace(/
const envShopId = typeof process !== "undefined" && process.env && (process.env.VITE_APP_SHOP_ID || process.env.SHOP_ID) || "";
const storageShopId = typeof common_vendor.index !== "undefined" ? common_vendor.index.getStorageSync("SHOP_ID") || "" : "";
const SHOP_ID = Number(envShopId || storageShopId || 1);
const envEnableDefaultUser = typeof process !== "undefined" && process.env && (process.env.VITE_APP_ENABLE_DEFAULT_USER || process.env.ENABLE_DEFAULT_USER) || "";
const storageEnableDefaultUser = typeof common_vendor.index !== "undefined" ? common_vendor.index.getStorageSync("ENABLE_DEFAULT_USER") || "" : "";
const ENABLE_DEFAULT_USER = String(envEnableDefaultUser || storageEnableDefaultUser || "false").toLowerCase() === "true";
const envDefaultUserId = typeof process !== "undefined" && process.env && (process.env.VITE_APP_DEFAULT_USER_ID || process.env.DEFAULT_USER_ID) || "";
const storageDefaultUserId = typeof common_vendor.index !== "undefined" ? common_vendor.index.getStorageSync("DEFAULT_USER_ID") || "" : "";
const DEFAULT_USER_ID = Number(envDefaultUserId || storageDefaultUserId || 2);
exports.API_BASE_URL = API_BASE_URL;
exports.DEFAULT_USER_ID = DEFAULT_USER_ID;
exports.ENABLE_DEFAULT_USER = ENABLE_DEFAULT_USER;
exports.SHOP_ID = SHOP_ID;
//# sourceMappingURL=../../.sourcemap/mp-weixin/common/config.js.map

View File

@@ -0,0 +1,18 @@
"use strict";
const INCOME_CATEGORIES = [
{ key: "sale_income", label: "销售收入" },
{ key: "operation_income", label: "经营所得" },
{ key: "interest_income", label: "利息收入" },
{ key: "investment_income", label: "投资收入" },
{ key: "other_income", label: "其它收入" }
];
const EXPENSE_CATEGORIES = [
{ key: "operation_expense", label: "经营支出" },
{ key: "office_supplies", label: "办公用品" },
{ key: "rent", label: "房租" },
{ key: "interest_expense", label: "利息支出" },
{ key: "other_expense", label: "其它支出" }
];
exports.EXPENSE_CATEGORIES = EXPENSE_CATEGORIES;
exports.INCOME_CATEGORIES = INCOME_CATEGORIES;
//# sourceMappingURL=../../.sourcemap/mp-weixin/common/constants.js.map

View File

@@ -10,11 +10,34 @@ function buildUrl(path) {
}
function get(path, params = {}) {
return new Promise((resolve, reject) => {
const headers = { "X-Shop-Id": common_config.SHOP_ID };
if (common_config.ENABLE_DEFAULT_USER && common_config.DEFAULT_USER_ID)
headers["X-User-Id"] = common_config.DEFAULT_USER_ID;
common_vendor.index.request({
url: buildUrl(path),
method: "GET",
data: params,
header: { "X-Shop-Id": common_config.SHOP_ID },
header: headers,
success: (res) => {
const { statusCode, data } = res;
if (statusCode >= 200 && statusCode < 300)
return resolve(data);
reject(new Error("HTTP " + statusCode));
},
fail: (err) => reject(err)
});
});
}
function post(path, body = {}) {
return new Promise((resolve, reject) => {
const headers = { "Content-Type": "application/json", "X-Shop-Id": common_config.SHOP_ID };
if (common_config.ENABLE_DEFAULT_USER && common_config.DEFAULT_USER_ID)
headers["X-User-Id"] = common_config.DEFAULT_USER_ID;
common_vendor.index.request({
url: buildUrl(path),
method: "POST",
data: body,
header: headers,
success: (res) => {
const { statusCode, data } = res;
if (statusCode >= 200 && statusCode < 300)
@@ -26,4 +49,5 @@ function get(path, params = {}) {
});
}
exports.get = get;
exports.post = post;
//# sourceMappingURL=../../.sourcemap/mp-weixin/common/http.js.map

View File

@@ -88,6 +88,10 @@ const looseToNumber = (val) => {
const n = parseFloat(val);
return isNaN(n) ? val : n;
};
const toNumber = (val) => {
const n = isString(val) ? Number(val) : NaN;
return isNaN(n) ? val : n;
};
const toDisplayString = (val) => {
return isString(val) ? val : val == null ? "" : isArray(val) || isObject(val) && (val.toString === objectToString || !isFunction(val.toString)) ? JSON.stringify(val, replacer, 2) : String(val);
};
@@ -5094,10 +5098,32 @@ function vFor(source, renderItem) {
}
return ret;
}
function withModelModifiers(fn, { number, trim }, isComponent = false) {
if (isComponent) {
return (...args) => {
if (trim) {
args = args.map((a) => a.trim());
} else if (number) {
args = args.map(toNumber);
}
return fn(...args);
};
}
return (event) => {
const value = event.detail.value;
if (trim) {
event.detail.value = value.trim();
} else if (number) {
event.detail.value = toNumber(value);
}
return fn(event);
};
}
const o = (value, key) => vOn(value, key);
const f = (source, renderItem) => vFor(source, renderItem);
const e = (target, ...sources) => extend(target, ...sources);
const t = (val) => toDisplayString(val);
const m = (fn, modifiers, isComponent = false) => withModelModifiers(fn, modifiers, isComponent);
function createApp$1(rootComponent, rootProps = null) {
rootComponent && (rootComponent.mpType = "app");
return createVueApp(rootComponent, rootProps).use(plugin);
@@ -7870,6 +7896,7 @@ exports.createSSRApp = createSSRApp;
exports.e = e;
exports.f = f;
exports.index = index;
exports.m = m;
exports.o = o;
exports.t = t;
//# sourceMappingURL=../../.sourcemap/mp-weixin/common/vendor.js.map

View File

@@ -0,0 +1,47 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
const TYPE_MAP = { cash: "现金", bank: "银行", alipay: "支付宝", wechat: "微信", other: "其他" };
const _sfc_main = {
data() {
return { accounts: [] };
},
async onLoad() {
try {
const res = await common_http.get("/api/accounts");
this.accounts = Array.isArray(res) ? res : (res == null ? void 0 : res.list) || [];
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
}
},
methods: {
select(a) {
const opener = getCurrentPages()[getCurrentPages().length - 2];
if (opener && opener.$vm) {
opener.$vm.selectedAccountId = a.id;
opener.$vm.selectedAccountName = a.name;
}
common_vendor.index.navigateBack();
},
typeLabel(t) {
return TYPE_MAP[t] || t;
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: common_vendor.f($data.accounts, (a, k0, i0) => {
var _a;
return {
a: common_vendor.t(a.name),
b: common_vendor.t($options.typeLabel(a.type)),
c: common_vendor.t(((_a = a.balance) == null ? void 0 : _a.toFixed) ? a.balance.toFixed(2) : a.balance),
d: a.id,
e: common_vendor.o(($event) => $options.select(a), a.id)
};
})
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/account/select.js.map

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "选择账户",
"usingComponents": {}
}

View File

@@ -0,0 +1 @@
<view class="page"><scroll-view scroll-y class="list"><view wx:for="{{a}}" wx:for-item="a" wx:key="d" class="item" bindtap="{{a.e}}"><view class="name">{{a.a}}</view><view class="meta">{{a.b}} · 余额:{{a.c}}</view></view></scroll-view></view>

View File

@@ -0,0 +1,11 @@
.page { display:flex; flex-direction: column; height: 100vh;
}
.list { flex:1;
}
.item { padding: 20rpx 24rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1;
}
.name { color:#333; margin-bottom: 6rpx;
}
.meta { color:#888; font-size: 24rpx;
}

View File

@@ -0,0 +1,48 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
const _sfc_main = {
data() {
return { kw: "", customers: [] };
},
onLoad() {
this.search();
},
methods: {
async search() {
try {
const res = await common_http.get("/api/customers", { kw: this.kw, page: 1, size: 50 });
this.customers = Array.isArray(res == null ? void 0 : res.list) ? res.list : Array.isArray(res) ? res : [];
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
}
},
select(c) {
const opener = getCurrentPages()[getCurrentPages().length - 2];
if (opener && opener.$vm) {
opener.$vm.order.customerId = c.id;
opener.$vm.customerName = c.name;
}
common_vendor.index.navigateBack();
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: common_vendor.o((...args) => $options.search && $options.search(...args)),
b: $data.kw,
c: common_vendor.o(($event) => $data.kw = $event.detail.value),
d: common_vendor.o((...args) => $options.search && $options.search(...args)),
e: common_vendor.f($data.customers, (c, k0, i0) => {
return {
a: common_vendor.t(c.name),
b: common_vendor.t(c.mobile || "—"),
c: c.id,
d: common_vendor.o(($event) => $options.select(c), c.id)
};
})
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/customer/select.js.map

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "选择客户",
"usingComponents": {}
}

View File

@@ -0,0 +1 @@
<view class="page"><view class="search"><input placeholder="搜索客户名称/电话" bindconfirm="{{a}}" value="{{b}}" bindinput="{{c}}"/><button size="mini" bindtap="{{d}}">搜索</button></view><scroll-view scroll-y class="list"><view wx:for="{{e}}" wx:for-item="c" wx:key="c" class="item" bindtap="{{c.d}}"><view class="name">{{c.a}}</view><view class="meta">{{c.b}}</view></view></scroll-view></view>

View File

@@ -0,0 +1,15 @@
.page { display:flex; flex-direction: column; height: 100vh;
}
.search { display:flex; gap: 12rpx; padding: 16rpx; background:#fff;
}
.search input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx;
}
.list { flex:1;
}
.item { padding: 20rpx 24rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1;
}
.name { color:#333; margin-bottom: 6rpx;
}
.meta { color:#888; font-size: 24rpx;
}

View File

@@ -0,0 +1,135 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
function toDateString(d) {
const m = (d.getMonth() + 1).toString().padStart(2, "0");
const day = d.getDate().toString().padStart(2, "0");
return `${d.getFullYear()}-${m}-${day}`;
}
const _sfc_main = {
data() {
const today = /* @__PURE__ */ new Date();
const first = new Date(today.getFullYear(), today.getMonth(), 1);
return {
range: "month",
biz: "sale",
sub: "out",
kw: "",
begin: toDateString(first),
end: toDateString(today),
list: [],
loading: false
};
},
computed: {
totalAmount() {
return this.list.reduce((s, o) => s + Number(o.amount || 0), 0);
}
},
onLoad() {
this.reload();
},
methods: {
setRange(r) {
this.range = r;
const now = /* @__PURE__ */ new Date();
if (r === "today") {
this.begin = this.end = toDateString(now);
} else if (r === "week") {
const day = now.getDay() || 7;
const start = new Date(now);
start.setDate(now.getDate() - day + 1);
this.begin = toDateString(start);
this.end = toDateString(now);
} else if (r === "month") {
const first = new Date(now.getFullYear(), now.getMonth(), 1);
this.begin = toDateString(first);
this.end = toDateString(now);
} else if (r === "year") {
const first = new Date(now.getFullYear(), 0, 1);
this.begin = toDateString(first);
this.end = toDateString(now);
}
this.reload();
},
async reload() {
this.loading = true;
try {
const res = await common_http.get("/api/sales/orders", { begin: this.begin, end: this.end, kw: this.kw, sub: this.sub });
this.list = Array.isArray(res == null ? void 0 : res.list) ? res.list : Array.isArray(res) ? res : [];
} catch (e) {
this.list = [];
} finally {
this.loading = false;
}
},
goCreate() {
common_vendor.index.navigateTo({ url: "/pages/order/create" });
},
open(o) {
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return common_vendor.e({
a: $data.range === "custom" ? 1 : "",
b: common_vendor.o(($event) => $options.setRange("custom")),
c: $data.range === "week" ? 1 : "",
d: common_vendor.o(($event) => $options.setRange("week")),
e: $data.range === "today" ? 1 : "",
f: common_vendor.o(($event) => $options.setRange("today")),
g: $data.range === "month" ? 1 : "",
h: common_vendor.o(($event) => $options.setRange("month")),
i: $data.range === "year" ? 1 : "",
j: common_vendor.o(($event) => $options.setRange("year")),
k: $data.biz === "sale" ? 1 : "",
l: common_vendor.o(($event) => $data.biz = "sale"),
m: $data.biz === "purchase" ? 1 : "",
n: common_vendor.o(($event) => $data.biz = "purchase"),
o: $data.biz === "collection" ? 1 : "",
p: common_vendor.o(($event) => $data.biz = "collection"),
q: $data.biz === "capital" ? 1 : "",
r: common_vendor.o(($event) => $data.biz = "capital"),
s: $data.biz === "inventory" ? 1 : "",
t: common_vendor.o(($event) => $data.biz = "inventory"),
v: $data.sub === "out" ? 1 : "",
w: common_vendor.o(($event) => $data.sub = "out"),
x: $data.sub === "return" ? 1 : "",
y: common_vendor.o(($event) => $data.sub = "return"),
z: $data.sub === "receive" ? 1 : "",
A: common_vendor.o(($event) => $data.sub = "receive"),
B: common_vendor.o((...args) => $options.goCreate && $options.goCreate(...args)),
C: common_vendor.o((...args) => $options.reload && $options.reload(...args)),
D: $data.kw,
E: common_vendor.o(($event) => $data.kw = $event.detail.value),
F: $data.range === "custom"
}, $data.range === "custom" ? {
G: common_vendor.t($data.begin),
H: $data.begin,
I: common_vendor.o((e) => {
$data.begin = e.detail.value;
$options.reload();
}),
J: common_vendor.t($data.end),
K: $data.end,
L: common_vendor.o((e) => {
$data.end = e.detail.value;
$options.reload();
})
} : {}, {
M: common_vendor.t($options.totalAmount.toFixed(2)),
N: common_vendor.f($data.list, (o, k0, i0) => {
return {
a: common_vendor.t(o.orderDate),
b: common_vendor.t(o.customerName),
c: common_vendor.t(o.orderNo),
d: common_vendor.t(Number(o.amount).toFixed(2)),
e: o.id,
f: common_vendor.o(($event) => $options.open(o), o.id)
};
})
});
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/detail/index.js.map

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "明细",
"usingComponents": {}
}

View File

@@ -0,0 +1 @@
<view class="detail"><view class="filter-tabs"><view class="{{['tab', a && 'active']}}" bindtap="{{b}}">自定义</view><view class="{{['tab', c && 'active']}}" bindtap="{{d}}">本周</view><view class="{{['tab', e && 'active']}}" bindtap="{{f}}">今日</view><view class="{{['tab', g && 'active']}}" bindtap="{{h}}">本月</view><view class="{{['tab', i && 'active']}}" bindtap="{{j}}">本年</view></view><view class="biz-tabs"><view class="{{['biz', k && 'active']}}" bindtap="{{l}}">销售</view><view class="{{['biz', m && 'active']}}" bindtap="{{n}}">进货</view><view class="{{['biz', o && 'active']}}" bindtap="{{p}}">收款</view><view class="{{['biz', q && 'active']}}" bindtap="{{r}}">资金</view><view class="{{['biz', s && 'active']}}" bindtap="{{t}}">盘点</view></view><view class="card"><view class="subtabs"><view class="{{['sub', v && 'active']}}" bindtap="{{w}}">出货</view><view class="{{['sub', x && 'active']}}" bindtap="{{y}}">退货</view><view class="{{['sub', z && 'active']}}" bindtap="{{A}}">收款</view><view class="plus" bindtap="{{B}}">+</view></view><view class="search"><input placeholder="单据号/客户/名称/规格/备注" bindconfirm="{{C}}" value="{{D}}" bindinput="{{E}}"/></view><view wx:if="{{F}}" class="daterange"><picker mode="date" value="{{H}}" bindchange="{{I}}"><text>{{G}}</text></picker><text class="sep">—</text><picker mode="date" value="{{K}}" bindchange="{{L}}"><text>{{J}}</text></picker></view><view class="total">合计:¥ {{M}}</view><scroll-view scroll-y class="list"><view wx:for="{{N}}" wx:for-item="o" wx:key="e" class="row" bindtap="{{o.f}}"><view class="left"><view class="date">{{o.a}}</view><view class="name">{{o.b}}</view><view class="no">{{o.c}}</view></view><view class="right">¥ {{o.d}}</view></view></scroll-view></view></view>

View File

@@ -0,0 +1,43 @@
.detail { display:flex;
}
.filter-tabs { display:flex; gap: 24rpx; padding: 18rpx 24rpx; color:#666;
}
.filter-tabs .tab.active { color:#2aa7b6; font-weight: 700;
}
.biz-tabs { position: fixed; left:0; top: 160rpx; bottom: 120rpx; width: 120rpx; display:flex; flex-direction: column; gap: 24rpx; padding: 12rpx;
}
.biz { background:#6aa9ff; color:#fff; border-radius: 16rpx; padding: 20rpx 0; text-align:center; opacity: .85;
}
.biz.active { opacity: 1;
}
.card { margin-left: 140rpx; background:#fff; border-radius: 24rpx; padding: 16rpx;
}
.subtabs { display:flex; align-items:center; gap: 24rpx; padding: 8rpx 6rpx 12rpx;
}
.sub { color:#57c2cf; padding: 8rpx 12rpx;
}
.sub.active { border-bottom: 4rpx solid #57c2cf; font-weight:700;
}
.plus { margin-left:auto; width: 60rpx; height: 60rpx; border-radius: 30rpx; background:#2ec0d0; color:#fff; font-size: 40rpx; display:flex; align-items:center; justify-content:center;
}
.search { background:#f6f7fb; border-radius: 999rpx; padding: 14rpx 20rpx; margin: 8rpx 0 12rpx;
}
.daterange { display:flex; align-items:center; gap: 12rpx; color:#888; padding-bottom: 8rpx;
}
.daterange .sep { color:#ccc;
}
.total { color:#2ec0d0; font-weight: 800; padding: 12rpx 0; border-top: 2rpx solid #eaeaea;
}
.list { height: calc(100vh - 420rpx);
}
.row { display:flex; justify-content: space-between; align-items:center; padding: 22rpx 10rpx; border-bottom: 1rpx solid #f0f0f0;
}
.left .date { color:#999; margin-bottom: 6rpx;
}
.left .name { color:#333; margin-bottom: 6rpx;
}
.left .no { color:#bbb;
}
.right { color:#555;
}

View File

@@ -5,9 +5,7 @@ const common_assets = require("../../common/assets.js");
const _sfc_main = {
data() {
return {
todayAmount: "0.00",
monthProfit: "0.00",
stockQty: "0.00",
kpi: { todaySales: "0.00", monthSales: "0.00", monthProfit: "0.00", stockCount: "0" },
activeTab: "home",
notices: [],
loadingNotices: false,
@@ -26,9 +24,22 @@ const _sfc_main = {
};
},
onLoad() {
this.fetchMetrics();
this.fetchNotices();
},
methods: {
async fetchMetrics() {
try {
const d = await common_http.get("/api/metrics/overview");
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"
};
} catch (e) {
}
},
async fetchNotices() {
this.loadingNotices = true;
this.noticeError = "";
@@ -48,18 +59,15 @@ const _sfc_main = {
common_vendor.index.showToast({ title: item.title + "(开发中)", icon: "none" });
},
onCreateOrder() {
common_vendor.index.showToast({ title: "开单(开发中)", icon: "none" });
common_vendor.index.navigateTo({ url: "/pages/order/create" });
},
onNoticeTap(n) {
common_vendor.index.showModal({
title: "告",
title: "广告",
content: n && (n.text || n.title || n.content) || "",
showCancel: false
});
},
onNoticeList() {
common_vendor.index.showToast({ title: "公告列表(开发中)", icon: "none" });
},
onIconError(item) {
item.img = "";
}
@@ -68,14 +76,15 @@ const _sfc_main = {
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return common_vendor.e({
a: common_assets._imports_0,
b: common_vendor.t($data.todayAmount),
c: common_vendor.t($data.monthProfit),
d: common_vendor.t($data.stockQty),
e: $data.loadingNotices
b: common_vendor.t($data.kpi.todaySales),
c: common_vendor.t($data.kpi.monthSales),
d: common_vendor.t($data.kpi.monthProfit),
e: common_vendor.t($data.kpi.stockCount),
f: $data.loadingNotices
}, $data.loadingNotices ? {} : $data.noticeError ? {
g: common_vendor.t($data.noticeError)
h: common_vendor.t($data.noticeError)
} : !$data.notices.length ? {} : {
i: common_vendor.f($data.notices, (n, idx, i0) => {
j: common_vendor.f($data.notices, (n, idx, i0) => {
return common_vendor.e({
a: common_vendor.t(n.text),
b: n.tag
@@ -87,9 +96,8 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
});
})
}, {
f: $data.noticeError,
h: !$data.notices.length,
j: common_vendor.o((...args) => $options.onNoticeList && $options.onNoticeList(...args)),
g: $data.noticeError,
i: !$data.notices.length,
k: common_vendor.f($data.features, (item, k0, i0) => {
return common_vendor.e({
a: item.img
@@ -107,11 +115,15 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
}),
l: $data.activeTab === "home" ? 1 : "",
m: common_vendor.o(($event) => $data.activeTab = "home"),
n: common_vendor.o((...args) => $options.onCreateOrder && $options.onCreateOrder(...args)),
o: $data.activeTab === "detail" ? 1 : "",
p: common_vendor.o(($event) => $data.activeTab = "detail"),
q: $data.activeTab === "me" ? 1 : "",
r: common_vendor.o(($event) => $data.activeTab = "me")
n: $data.activeTab === "product" ? 1 : "",
o: common_vendor.o(($event) => $data.activeTab = "product"),
p: common_vendor.o((...args) => $options.onCreateOrder && $options.onCreateOrder(...args)),
q: $data.activeTab === "detail" ? 1 : "",
r: common_vendor.o(($event) => $data.activeTab = "detail"),
s: $data.activeTab === "report" ? 1 : "",
t: common_vendor.o(($event) => $data.activeTab = "report"),
v: $data.activeTab === "me" ? 1 : "",
w: common_vendor.o(($event) => $data.activeTab = "me")
});
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);

View File

@@ -1 +1 @@
<view class="home"><image class="home-bg" src="{{a}}" mode="aspectFill"></image><view class="hero"><view class="hero-top"><text class="brand">五金配件管家</text><view class="cta"><text class="cta-text">咨询</text></view></view><view class="kpi"><view class="kpi-item"><text class="kpi-label">今日销售额</text><text class="kpi-value">{{b}}</text></view><view class="kpi-item"><text class="kpi-label">本月利润</text><text class="kpi-value">{{c}}</text></view><view class="kpi-item"><text class="kpi-label">库存数量</text><text class="kpi-value">{{d}}</text></view></view></view><view class="notice"><view class="notice-left">告</view><view wx:if="{{e}}" class="notice-swiper" style="display:flex;align-items:center;color:#6b5a2a">加载中...</view><view wx:elif="{{f}}" class="notice-swiper" style="display:flex;align-items:center;color:#dd524d">{{g}}</view><view wx:elif="{{h}}" class="notice-swiper" style="display:flex;align-items:center;color:#6b5a2a">暂无公告</view><swiper wx:else class="notice-swiper" circular autoplay interval="4000" duration="400" vertical><swiper-item wx:for="{{i}}" wx:for-item="n" wx:key="e"><view class="notice-item" bindtap="{{n.d}}"><text class="notice-text">{{n.a}}</text><text wx:if="{{n.b}}" class="notice-tag">{{n.c}}</text></view></swiper-item></swiper><view class="notice-right" bindtap="{{j}}">更多</view></view><view class="section-title"><text class="section-text">常用功能</text></view><view class="grid-wrap"><view class="grid"><view wx:for="{{k}}" wx:for-item="item" wx:key="g" class="grid-item" bindtap="{{item.h}}"><view class="icon icon-squircle"><image wx:if="{{item.a}}" src="{{item.b}}" class="icon-img" mode="aspectFit" binderror="{{item.c}}"></image><text wx:elif="{{item.d}}" class="icon-emoji">{{item.e}}</text><view wx:else class="icon-placeholder"></view></view><text class="grid-chip">{{item.f}}</text></view></view></view><view class="bottom-bar"><view class="{{['tab', l && 'active']}}" bindtap="{{m}}"><text>首页</text></view><view class="tab primary" bindtap="{{n}}"><text>开单</text></view><view class="{{['tab', o && 'active']}}" bindtap="{{p}}"><text>明细</text></view><view class="{{['tab', q && 'active']}}" bindtap="{{r}}"><text>我的</text></view></view></view>
<view class="home"><image class="home-bg" src="{{a}}" mode="aspectFill"></image><view class="hero"><view class="hero-top"><text class="brand">五金配件管家</text><view class="cta"><text class="cta-text">咨询</text></view></view><view class="kpi"><view class="kpi-item"><text class="kpi-label">今日销售额</text><text class="kpi-value">{{b}}</text></view><view class="kpi-item"><text class="kpi-label">本月销售额</text><text class="kpi-value">{{c}}</text></view><view class="kpi-item"><text class="kpi-label">本月利润</text><text class="kpi-value">{{d}}</text></view><view class="kpi-item"><text class="kpi-label">库存商品数量</text><text class="kpi-value">{{e}}</text></view></view></view><view class="notice"><view class="notice-left">广告</view><view wx:if="{{f}}" class="notice-swiper" style="display:flex;align-items:center;color:#6b5a2a">加载中...</view><view wx:elif="{{g}}" class="notice-swiper" style="display:flex;align-items:center;color:#dd524d">{{h}}</view><view wx:elif="{{i}}" class="notice-swiper" style="display:flex;align-items:center;color:#6b5a2a">暂无公告</view><swiper wx:else class="notice-swiper" circular autoplay interval="4000" duration="400" vertical><swiper-item wx:for="{{j}}" wx:for-item="n" wx:key="e"><view class="notice-item" bindtap="{{n.d}}"><text class="notice-text">{{n.a}}</text><text wx:if="{{n.b}}" class="notice-tag">{{n.c}}</text></view></swiper-item></swiper></view><view class="section-title"><text class="section-text">常用功能</text></view><view class="grid-wrap"><view class="grid"><view wx:for="{{k}}" wx:for-item="item" wx:key="g" class="grid-item" bindtap="{{item.h}}"><view class="icon icon-squircle"><image wx:if="{{item.a}}" src="{{item.b}}" class="icon-img" mode="aspectFit" binderror="{{item.c}}"></image><text wx:elif="{{item.d}}" class="icon-emoji">{{item.e}}</text><view wx:else class="icon-placeholder"></view></view><text class="grid-chip">{{item.f}}</text></view></view></view><view class="bottom-bar"><view class="{{['tab', l && 'active']}}" bindtap="{{m}}"><text>首页</text></view><view class="{{['tab', n && 'active']}}" bindtap="{{o}}"><text>货品</text></view><view class="tab primary" bindtap="{{p}}"><text>开单</text></view><view class="{{['tab', q && 'active']}}" bindtap="{{r}}"><text>明细</text></view><view class="{{['tab', s && 'active']}}" bindtap="{{t}}"><text>报表</text></view><view class="{{['tab', v && 'active']}}" bindtap="{{w}}"><text>我的</text></view></view></view>

View File

@@ -48,8 +48,7 @@
}
.notice-tag { color: #B4880F; font-size: 22rpx; padding: 4rpx 10rpx; border-radius: 999rpx; background: rgba(215,167,46,0.18);
}
.notice-right { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; min-width: 72rpx; height: 44rpx; color: #B4880F; font-size: 26rpx; padding-left: 8rpx;
}
/* 分割标题 */
.section-title { display: flex; align-items: center; gap: 16rpx; padding: 10rpx 28rpx 0;

View File

@@ -0,0 +1,212 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
const common_constants = require("../../common/constants.js");
const common_assets = require("../../common/assets.js");
function todayString() {
const d = /* @__PURE__ */ new Date();
const m = (d.getMonth() + 1).toString().padStart(2, "0");
const day = d.getDate().toString().padStart(2, "0");
return `${d.getFullYear()}-${m}-${day}`;
}
const _sfc_main = {
data() {
return {
biz: "sale",
saleType: "out",
purchaseType: "in",
order: {
orderTime: todayString(),
customerId: null,
supplierId: null,
remark: ""
},
customerName: "",
supplierName: "",
items: [],
activeCategory: "sale_income",
trxAmount: 0,
selectedAccountId: null,
selectedAccountName: ""
};
},
computed: {
totalQuantity() {
return this.items.reduce((s, it) => s + Number(it.quantity || 0), 0);
},
totalAmount() {
return this.items.reduce((s, it) => s + Number(it.quantity || 0) * Number(it.unitPrice || 0), 0);
},
customerLabel() {
return this.customerName || "零售客户";
},
supplierLabel() {
return this.supplierName || "零散供应商";
},
incomeCategories() {
return common_constants.INCOME_CATEGORIES;
},
expenseCategories() {
return common_constants.EXPENSE_CATEGORIES;
},
accountLabel() {
return this.selectedAccountName || "现金";
},
counterpartyLabel() {
return this.customerName || this.supplierName || "—";
}
},
methods: {
switchBiz(type) {
this.biz = type;
},
onDateChange(e) {
this.order.orderTime = e.detail.value;
},
chooseCustomer() {
common_vendor.index.navigateTo({ url: "/pages/customer/select" });
},
chooseSupplier() {
common_vendor.index.navigateTo({ url: "/pages/supplier/select" });
},
chooseProduct() {
common_vendor.index.navigateTo({ url: "/pages/product/select" });
},
chooseAccount() {
common_vendor.index.navigateTo({ url: "/pages/account/select" });
},
chooseCounterparty() {
if (this.biz === "income" || this.biz === "expense") {
common_vendor.index.navigateTo({ url: "/pages/customer/select" });
}
},
recalc() {
this.$forceUpdate();
},
async submit() {
const isSaleOrPurchase = this.biz === "sale" || this.biz === "purchase";
const payload = isSaleOrPurchase ? {
type: this.biz === "sale" ? this.saleType : "purchase." + this.purchaseType,
orderTime: this.order.orderTime,
customerId: this.order.customerId,
supplierId: this.order.supplierId,
items: this.items.map((it) => ({ productId: it.productId, quantity: Number(it.quantity || 0), unitPrice: Number(it.unitPrice || 0) })),
amount: this.totalAmount
} : {
type: this.biz,
category: this.activeCategory,
counterpartyId: this.order.customerId || null,
accountId: this.selectedAccountId || null,
amount: Number(this.trxAmount || 0),
txTime: this.order.orderTime,
remark: this.order.remark
};
try {
const url = isSaleOrPurchase ? "/api/orders" : "/api/other-transactions";
await common_http.post(url, 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" });
}
},
saveAndReset() {
this.items = [];
this.trxAmount = 0;
this.order.remark = "";
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return common_vendor.e({
a: $data.biz === "sale" ? 1 : "",
b: common_vendor.o(($event) => $options.switchBiz("sale")),
c: $data.biz === "purchase" ? 1 : "",
d: common_vendor.o(($event) => $options.switchBiz("purchase")),
e: $data.biz === "income" ? 1 : "",
f: common_vendor.o(($event) => $options.switchBiz("income")),
g: $data.biz === "expense" ? 1 : "",
h: common_vendor.o(($event) => $options.switchBiz("expense")),
i: $data.biz === "sale"
}, $data.biz === "sale" ? {
j: $data.saleType === "out" ? 1 : "",
k: common_vendor.o(($event) => $data.saleType = "out"),
l: $data.saleType === "return" ? 1 : "",
m: common_vendor.o(($event) => $data.saleType = "return"),
n: $data.saleType === "collect" ? 1 : "",
o: common_vendor.o(($event) => $data.saleType = "collect")
} : $data.biz === "purchase" ? {
q: $data.purchaseType === "in" ? 1 : "",
r: common_vendor.o(($event) => $data.purchaseType = "in"),
s: $data.purchaseType === "return" ? 1 : "",
t: common_vendor.o(($event) => $data.purchaseType = "return"),
v: $data.purchaseType === "pay" ? 1 : "",
w: common_vendor.o(($event) => $data.purchaseType = "pay")
} : {}, {
p: $data.biz === "purchase",
x: common_vendor.t($data.order.orderTime),
y: $data.order.orderTime,
z: common_vendor.o((...args) => $options.onDateChange && $options.onDateChange(...args)),
A: $data.biz === "sale"
}, $data.biz === "sale" ? {
B: common_vendor.t($options.customerLabel),
C: common_vendor.o((...args) => $options.chooseCustomer && $options.chooseCustomer(...args))
} : $data.biz === "purchase" ? {
E: common_vendor.t($options.supplierLabel),
F: common_vendor.o((...args) => $options.chooseSupplier && $options.chooseSupplier(...args))
} : {}, {
D: $data.biz === "purchase",
G: $data.biz === "sale" || $data.biz === "purchase"
}, $data.biz === "sale" || $data.biz === "purchase" ? {
H: common_vendor.t($options.totalQuantity),
I: common_vendor.t($options.totalAmount.toFixed(2)),
J: common_vendor.o((...args) => $options.chooseProduct && $options.chooseProduct(...args))
} : {
K: common_vendor.f($data.biz === "income" ? $options.incomeCategories : $options.expenseCategories, (c, k0, i0) => {
return {
a: common_vendor.t(c.label),
b: c.key,
c: $data.activeCategory === c.key ? 1 : "",
d: common_vendor.o(($event) => $data.activeCategory = c.key, c.key)
};
}),
L: common_vendor.t($options.counterpartyLabel),
M: common_vendor.o((...args) => $options.chooseCounterparty && $options.chooseCounterparty(...args)),
N: common_vendor.t($options.accountLabel),
O: common_vendor.o((...args) => $options.chooseAccount && $options.chooseAccount(...args)),
P: $data.trxAmount,
Q: common_vendor.o(common_vendor.m(($event) => $data.trxAmount = $event.detail.value, {
number: true
})),
R: $data.order.remark,
S: common_vendor.o(($event) => $data.order.remark = $event.detail.value)
}, {
T: !$data.items.length
}, !$data.items.length ? {
U: common_assets._imports_0$1
} : {
V: common_vendor.f($data.items, (it, idx, i0) => {
return {
a: common_vendor.t(it.productName),
b: common_vendor.o([common_vendor.m(($event) => it.quantity = $event.detail.value, {
number: true
}), ($event) => $options.recalc()], idx),
c: it.quantity,
d: common_vendor.o([common_vendor.m(($event) => it.unitPrice = $event.detail.value, {
number: true
}), ($event) => $options.recalc()], idx),
e: it.unitPrice,
f: common_vendor.t((Number(it.quantity) * Number(it.unitPrice)).toFixed(2)),
g: idx
};
})
}, {
W: common_vendor.o((...args) => $options.saveAndReset && $options.saveAndReset(...args)),
X: 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/order/create.js.map

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "开单",
"usingComponents": {}
}

View File

@@ -0,0 +1 @@
<view class="order"><view class="tabs"><text class="{{[a && 'active']}}" bindtap="{{b}}">销售</text><text class="{{[c && 'active']}}" bindtap="{{d}}">进货</text><text class="{{[e && 'active']}}" bindtap="{{f}}">其他收入</text><text class="{{[g && 'active']}}" bindtap="{{h}}">其他支出</text></view><view wx:if="{{i}}" class="subtabs"><button class="{{['subbtn', j && 'active']}}" bindtap="{{k}}">出货</button><button class="{{['subbtn', l && 'active']}}" bindtap="{{m}}">退货</button><button class="{{['subbtn', n && 'active']}}" bindtap="{{o}}">收款</button></view><view wx:elif="{{p}}" class="subtabs"><button class="{{['subbtn', q && 'active']}}" bindtap="{{r}}">进货</button><button class="{{['subbtn', s && 'active']}}" bindtap="{{t}}">退货</button><button class="{{['subbtn', v && 'active']}}" bindtap="{{w}}">付款</button></view><picker mode="date" value="{{y}}" bindchange="{{z}}"><view class="field"><text class="label">时间</text><text class="value">{{x}}</text></view></picker><view wx:if="{{A}}" class="field" bindtap="{{C}}"><text class="label">客户</text><text class="value">{{B}}</text></view><view wx:elif="{{D}}" class="field" bindtap="{{F}}"><text class="label">供应商</text><text class="value">{{E}}</text></view><view wx:if="{{G}}"><view class="summary"><text>选中货品({{H}}</text><text>合计金额:¥ {{I}}</text></view><view class="add" bindtap="{{J}}">+</view></view><view wx:else><view class="chips"><view wx:for="{{K}}" wx:for-item="c" wx:key="b" class="{{['chip', c.c && 'active']}}" bindtap="{{c.d}}">{{c.a}}</view></view><view class="field" bindtap="{{M}}"><text class="label">往来单位</text><text class="value">{{L}}</text></view><view class="field" bindtap="{{O}}"><text class="label">结算账户</text><text class="value">{{N}}</text></view><view class="field"><text class="label">金额</text><input class="value" type="digit" placeholder="0.00" value="{{P}}" bindinput="{{Q}}"/></view><view class="textarea"><block wx:if="{{r0}}"><textarea maxlength="200" placeholder="备注最多输入200个字" value="{{R}}" bindinput="{{S}}"></textarea></block></view></view><view wx:if="{{T}}" class="empty"><image src="{{U}}" mode="widthFix" class="empty-img"></image><text class="empty-text">购物车里空空如也</text><text class="empty-sub">扫描或点击 “+” 选择商品吧</text></view><view wx:else class="list"><view wx:for="{{V}}" wx:for-item="it" wx:key="g" class="row"><view class="col name">{{it.a}}</view><view class="col qty"><input type="number" bindinput="{{it.b}}" value="{{it.c}}"/></view><view class="col price"><input type="number" bindinput="{{it.d}}" value="{{it.e}}"/></view><view class="col amount">¥ {{it.f}}</view></view></view><view class="bottom"><button class="ghost" bindtap="{{W}}">再记一笔</button><button class="primary" bindtap="{{X}}">保存</button></view></view>

View File

@@ -0,0 +1,43 @@
.order { padding-bottom: 140rpx;
}
.tabs { display: flex; justify-content: space-around; padding: 16rpx 24rpx;
}
.tabs text { color: #666;
}
.tabs text.active { color: #333; font-weight: 700;
}
.subtabs { display: flex; gap: 16rpx; padding: 0 24rpx 16rpx;
}
.subbtn { padding: 10rpx 20rpx; border-radius: 999rpx; background: #f4f4f4; color: #666;
}
.subbtn.active { background: #ffe69a; color: #3f320f;
}
.field { display:flex; justify-content: space-between; padding: 22rpx 24rpx; background: #fff; border-bottom: 1rpx solid #eee;
}
.label { color:#666;
}
.value { color:#333;
}
.summary { display:flex; justify-content: space-between; padding: 22rpx 24rpx; color:#333;
}
.add { margin: 24rpx auto; width: 120rpx; height: 120rpx; border-radius: 20rpx; background: #c7eef7; color:#16a1c4; font-size: 72rpx; display:flex; align-items:center; justify-content:center;
}
.empty { display:flex; flex-direction: column; align-items:center; padding: 60rpx 0; color:#888;
}
.empty-img { width: 220rpx; margin-bottom: 20rpx;
}
.empty-text { margin-bottom: 8rpx;
}
.list { background:#fff;
}
.row { display:grid; grid-template-columns: 1.5fr 1fr 1fr 1fr; gap: 12rpx; padding: 16rpx 12rpx; align-items:center; border-bottom: 1rpx solid #f3f3f3;
}
.col.name { padding-left: 12rpx;
}
.col.amount { text-align:right; padding-right: 12rpx; color:#333;
}
.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,48 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
const _sfc_main = {
data() {
return { kw: "", products: [] };
},
onLoad() {
this.search();
},
methods: {
async search() {
try {
const res = await common_http.get("/api/products", { kw: this.kw, page: 1, size: 50 });
this.products = Array.isArray(res == null ? void 0 : res.list) ? res.list : Array.isArray(res) ? res : [];
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
}
},
select(p) {
const opener = getCurrentPages()[getCurrentPages().length - 2];
if (opener && opener.$vm && opener.$vm.items) {
opener.$vm.items.push({ productId: p.id, productName: p.name, quantity: 1, unitPrice: Number(p.price || 0) });
}
common_vendor.index.navigateBack();
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: common_vendor.o((...args) => $options.search && $options.search(...args)),
b: $data.kw,
c: common_vendor.o(($event) => $data.kw = $event.detail.value),
d: common_vendor.o((...args) => $options.search && $options.search(...args)),
e: common_vendor.f($data.products, (p, k0, i0) => {
return {
a: common_vendor.t(p.name),
b: common_vendor.t(p.code),
c: common_vendor.t(p.stock || 0),
d: p.id,
e: common_vendor.o(($event) => $options.select(p), p.id)
};
})
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/product/select.js.map

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "选择商品",
"usingComponents": {}
}

View File

@@ -0,0 +1 @@
<view class="page"><view class="search"><input placeholder="搜索商品名称/编码" bindconfirm="{{a}}" value="{{b}}" bindinput="{{c}}"/><button size="mini" bindtap="{{d}}">搜索</button></view><scroll-view scroll-y class="list"><view wx:for="{{e}}" wx:for-item="p" wx:key="d" class="item" bindtap="{{p.e}}"><view class="name">{{p.a}}</view><view class="meta">{{p.b}} · 库存:{{p.c}}</view></view></scroll-view></view>

View File

@@ -0,0 +1,15 @@
.page { display:flex; flex-direction: column; height: 100vh;
}
.search { display:flex; gap: 12rpx; padding: 16rpx; background:#fff;
}
.search input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx;
}
.list { flex:1;
}
.item { padding: 20rpx 24rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1;
}
.name { color:#333; margin-bottom: 6rpx;
}
.meta { color:#888; font-size: 24rpx;
}

View File

@@ -0,0 +1,48 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
const _sfc_main = {
data() {
return { kw: "", suppliers: [] };
},
onLoad() {
this.search();
},
methods: {
async search() {
try {
const res = await common_http.get("/api/suppliers", { kw: this.kw, page: 1, size: 50 });
this.suppliers = Array.isArray(res == null ? void 0 : res.list) ? res.list : Array.isArray(res) ? res : [];
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
}
},
select(s) {
const opener = getCurrentPages()[getCurrentPages().length - 2];
if (opener && opener.$vm) {
opener.$vm.order.supplierId = s.id;
opener.$vm.supplierName = s.name;
}
common_vendor.index.navigateBack();
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: common_vendor.o((...args) => $options.search && $options.search(...args)),
b: $data.kw,
c: common_vendor.o(($event) => $data.kw = $event.detail.value),
d: common_vendor.o((...args) => $options.search && $options.search(...args)),
e: common_vendor.f($data.suppliers, (s, k0, i0) => {
return {
a: common_vendor.t(s.name),
b: common_vendor.t(s.mobile || "—"),
c: s.id,
d: common_vendor.o(($event) => $options.select(s), s.id)
};
})
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/supplier/select.js.map

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "选择供应商",
"usingComponents": {}
}

View File

@@ -0,0 +1 @@
<view class="page"><view class="search"><input placeholder="搜索供应商名称/电话" bindconfirm="{{a}}" value="{{b}}" bindinput="{{c}}"/><button size="mini" bindtap="{{d}}">搜索</button></view><scroll-view scroll-y class="list"><view wx:for="{{e}}" wx:for-item="s" wx:key="c" class="item" bindtap="{{s.d}}"><view class="name">{{s.a}}</view><view class="meta">{{s.b}}</view></view></scroll-view></view>

View File

@@ -0,0 +1,15 @@
.page { display:flex; flex-direction: column; height: 100vh;
}
.search { display:flex; gap: 12rpx; padding: 16rpx; background:#fff;
}
.search input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx;
}
.list { flex:1;
}
.item { padding: 20rpx 24rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1;
}
.name { color:#333; margin-bottom: 6rpx;
}
.meta { color:#888; font-size: 24rpx;
}