This commit is contained in:
2025-09-18 21:17:44 +08:00
parent e560e90970
commit bff3d0414d
49 changed files with 1063 additions and 90 deletions

View File

@@ -0,0 +1,43 @@
package com.example.demo.common;
import org.springframework.dao.DataAccessException;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice
public class GlobalExceptionHandler {
private ResponseEntity<Map<String,Object>> badRequest(String message) {
Map<String,Object> body = new HashMap<>();
body.put("message", message);
return ResponseEntity.badRequest().body(body);
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<?> handleIllegalArgument(IllegalArgumentException ex) {
return badRequest(ex.getMessage());
}
@ExceptionHandler(IllegalStateException.class)
public ResponseEntity<?> handleIllegalState(IllegalStateException ex) {
return badRequest(ex.getMessage());
}
@ExceptionHandler(DataAccessException.class)
public ResponseEntity<?> handleDataAccess(DataAccessException ex) {
return badRequest("数据库操作失败");
}
@ExceptionHandler(Exception.class)
public ResponseEntity<?> handleAny(Exception ex) {
Map<String,Object> body = new HashMap<>();
body.put("message", ex.getMessage() == null ? "Internal Server Error" : ex.getMessage());
return ResponseEntity.status(500).body(body);
}
}

View File

@@ -45,7 +45,11 @@ public class CustomerController {
body.put("priceLevel", c.getPriceLevel()); body.put("priceLevel", c.getPriceLevel());
body.put("remark", c.getRemark()); body.put("remark", c.getRemark());
body.put("address", c.getAddress()); body.put("address", c.getAddress());
body.put("receivable", new java.math.BigDecimal("0")); // 详情页如需可扩展计算 body.put("arOpening", c.getArOpening());
Long sid = (shopId == null ? null : shopId);
if (sid != null) {
body.put("receivable", customerService.computeReceivable(sid, id));
}
return ResponseEntity.ok(body); return ResponseEntity.ok(body);
} }

View File

@@ -45,7 +45,7 @@ public class CustomerService {
public Long create(Long shopId, Long userId, CustomerDtos.CreateOrUpdateCustomerRequest req) { public Long create(Long shopId, Long userId, CustomerDtos.CreateOrUpdateCustomerRequest req) {
Customer c = new Customer(); Customer c = new Customer();
c.setShopId(shopId); c.setUserId(userId); c.setShopId(shopId); c.setUserId(userId);
c.setName(req.name); c.setLevel(req.level); c.setPriceLevel(nullToDefaultPriceLevel(req.priceLevel)); c.setName(req.name); c.setLevel(req.level); c.setPriceLevel(cnPriceLevelOrDefault(req.priceLevel));
c.setContactName(req.contactName); c.setMobile(req.mobile); c.setPhone(req.phone); c.setAddress(req.address); c.setContactName(req.contactName); c.setMobile(req.mobile); c.setPhone(req.phone); c.setAddress(req.address);
c.setStatus(1); c.setStatus(1);
c.setArOpening(req.arOpening == null ? BigDecimal.ZERO : req.arOpening); c.setArOpening(req.arOpening == null ? BigDecimal.ZERO : req.arOpening);
@@ -60,7 +60,7 @@ public class CustomerService {
public void update(Long id, Long shopId, Long userId, CustomerDtos.CreateOrUpdateCustomerRequest req) { public void update(Long id, Long shopId, Long userId, CustomerDtos.CreateOrUpdateCustomerRequest req) {
Customer c = customerRepository.findById(id).orElseThrow(); Customer c = customerRepository.findById(id).orElseThrow();
if (!c.getShopId().equals(shopId)) throw new IllegalArgumentException("跨店铺修改"); if (!c.getShopId().equals(shopId)) throw new IllegalArgumentException("跨店铺修改");
c.setName(req.name); c.setLevel(req.level); c.setPriceLevel(nullToDefaultPriceLevel(req.priceLevel)); c.setName(req.name); c.setLevel(req.level); c.setPriceLevel(cnPriceLevelOrDefault(req.priceLevel));
c.setContactName(req.contactName); c.setMobile(req.mobile); c.setPhone(req.phone); c.setAddress(req.address); c.setContactName(req.contactName); c.setMobile(req.mobile); c.setPhone(req.phone); c.setAddress(req.address);
if (req.arOpening != null) c.setArOpening(req.arOpening); if (req.arOpening != null) c.setArOpening(req.arOpening);
c.setRemark(req.remark); c.setRemark(req.remark);
@@ -68,7 +68,20 @@ public class CustomerService {
customerRepository.save(c); customerRepository.save(c);
} }
private String nullToDefaultPriceLevel(String v) { return (v == null || v.isBlank()) ? "retail" : v; } private String cnPriceLevelOrDefault(String v) {
if (v == null || v.isBlank()) return "零售价";
// 兼容历史英文值
if ("retail".equalsIgnoreCase(v)) return "零售价";
if ("wholesale".equalsIgnoreCase(v) || "distribution".equalsIgnoreCase(v)) return "批发价";
if ("big_client".equalsIgnoreCase(v)) return "大单报价";
return v; // 已是中文
}
public BigDecimal computeReceivable(Long shopId, Long customerId) {
Optional<Customer> oc = customerRepository.findById(customerId);
BigDecimal opening = oc.map(Customer::getArOpening).orElse(BigDecimal.ZERO);
return calcReceivable(shopId, customerId, opening);
}
private BigDecimal calcReceivable(Long shopId, Long customerId, BigDecimal opening) { private BigDecimal calcReceivable(Long shopId, Long customerId, BigDecimal opening) {
BigDecimal open = opening == null ? BigDecimal.ZERO : opening; BigDecimal open = opening == null ? BigDecimal.ZERO : opening;

View File

@@ -91,43 +91,66 @@ public class OrderService {
String headTable = isSaleOut ? "sales_orders" : (isPurchaseIn ? "purchase_orders" : (isSaleReturn ? "sales_return_orders" : "purchase_return_orders")); String headTable = isSaleOut ? "sales_orders" : (isPurchaseIn ? "purchase_orders" : (isSaleReturn ? "sales_return_orders" : "purchase_return_orders"));
String itemTable = isSaleOut ? "sales_order_items" : (isPurchaseIn ? "purchase_order_items" : (isSaleReturn ? "sales_return_order_items" : "purchase_return_order_items")); String itemTable = isSaleOut ? "sales_order_items" : (isPurchaseIn ? "purchase_order_items" : (isSaleReturn ? "sales_return_order_items" : "purchase_return_order_items"));
// insert head // insert head(按表结构分别处理 sales*/purchase* 的 customer_id/supplier_id 列)
KeyHolder kh = new GeneratedKeyHolder(); KeyHolder kh = new GeneratedKeyHolder();
String headSql = "INSERT INTO " + headTable + " (shop_id,user_id,customer_id,supplier_id,order_no,order_time,status,amount,paid_amount,remark,created_at,updated_at) " + boolean isSalesHead = headTable.startsWith("sales");
"VALUES (?,?,?,?,?,?, 'approved', ?, 0, ?, NOW(), NOW())"; boolean isPurchaseHead = headTable.startsWith("purchase");
String headSql;
if (isSalesHead) {
headSql = "INSERT INTO " + headTable + " (shop_id,user_id,customer_id,order_no,order_time,status,amount,paid_amount,remark,created_at,updated_at) " +
"VALUES (?,?,?,?,?, 'approved', ?, 0, ?, NOW(), NOW())";
} else if (isPurchaseHead) {
headSql = "INSERT INTO " + headTable + " (shop_id,user_id,supplier_id,order_no,order_time,status,amount,paid_amount,remark,created_at,updated_at) " +
"VALUES (?,?,?,?,?, 'approved', ?, 0, ?, NOW(), NOW())";
} else {
// 理论不会到这里,兜底为 sales 结构
headSql = "INSERT INTO " + headTable + " (shop_id,user_id,order_no,order_time,status,amount,paid_amount,remark,created_at,updated_at) " +
"VALUES (?,?,?,?,'approved', ?, 0, ?, NOW(), NOW())";
}
Long customerId = req.customerId; Long customerId = req.customerId;
Long supplierId = req.supplierId; Long supplierId = req.supplierId;
jdbcTemplate.update(con -> { jdbcTemplate.update(con -> {
java.sql.PreparedStatement ps = con.prepareStatement(headSql, new String[]{"id"}); java.sql.PreparedStatement ps = con.prepareStatement(headSql, new String[]{"id"});
ps.setLong(1, shopId); int idx = 1;
ps.setLong(2, userId); ps.setLong(idx++, shopId);
if (headTable.startsWith("sales")) { ps.setLong(idx++, userId);
ps.setObject(3, customerId, java.sql.Types.BIGINT); if (isSalesHead) {
ps.setObject(4, null); ps.setObject(idx++, customerId, java.sql.Types.BIGINT);
} else if (headTable.startsWith("purchase")) { } else if (isPurchaseHead) {
ps.setObject(3, null); ps.setObject(idx++, supplierId, java.sql.Types.BIGINT);
ps.setObject(4, supplierId, java.sql.Types.BIGINT);
} else {
ps.setObject(3, null);
ps.setObject(4, null);
} }
ps.setString(5, orderNo); ps.setString(idx++, orderNo);
ps.setTimestamp(6, java.sql.Timestamp.from(now.atZone(java.time.ZoneOffset.UTC).toInstant())); ps.setTimestamp(idx++, java.sql.Timestamp.from(now.atZone(java.time.ZoneOffset.UTC).toInstant()));
ps.setBigDecimal(7, scale2(totalRef[0])); ps.setBigDecimal(idx++, scale2(totalRef[0]));
ps.setString(8, req.remark); ps.setString(idx, req.remark);
return ps; return ps;
}, kh); }, kh);
Number orderKey = kh.getKey(); Number orderKey = kh.getKey();
Long orderId = (orderKey == null ? null : orderKey.longValue()); Long orderId = (orderKey == null ? null : orderKey.longValue());
// insert items // insert items(销售类有折扣列,采购类无折扣列)
String itemSql = "INSERT INTO " + itemTable + " (order_id,product_id,quantity,unit_price,discount_rate,amount) VALUES (?,?,?,?,?,?)"; boolean itemsHasDiscount = isSaleOut || isSaleReturn;
String itemSql = itemsHasDiscount
? ("INSERT INTO " + itemTable + " (order_id,product_id,quantity,unit_price,discount_rate,amount) VALUES (?,?,?,?,?,?)")
: ("INSERT INTO " + itemTable + " (order_id,product_id,quantity,unit_price,amount) VALUES (?,?,?,?,?)");
for (OrderDtos.Item it : req.items) { for (OrderDtos.Item it : req.items) {
BigDecimal qty = n(it.quantity); BigDecimal qty = n(it.quantity);
BigDecimal price = n(it.unitPrice); BigDecimal price = n(it.unitPrice);
BigDecimal dr = n(it.discountRate); BigDecimal dr = n(it.discountRate);
BigDecimal line = scale2(qty.multiply(price).multiply(BigDecimal.ONE.subtract(dr.divide(new BigDecimal("100"))))); BigDecimal line = scale2(qty.multiply(price).multiply(BigDecimal.ONE.subtract(dr.divide(new BigDecimal("100")))));
jdbcTemplate.update(itemSql, orderId, it.productId, qty, price, dr, line); if (itemsHasDiscount) {
jdbcTemplate.update(itemSql, orderId, it.productId, qty, price, dr, line);
} else {
jdbcTemplate.update(itemSql, orderId, it.productId, qty, price, line);
}
}
// 维护供应商应付:采购入库增加,应付=+amount采购退货减少应付=-amount
if (isPurchaseHead && supplierId != null) {
java.math.BigDecimal delta = scale2(totalRef[0]);
if (isPurchaseReturn) delta = delta.negate();
adjustSupplierPayable(supplierId, delta);
} }
return new OrderDtos.CreateOrderResponse(orderId, orderNo); return new OrderDtos.CreateOrderResponse(orderId, orderNo);
@@ -163,6 +186,14 @@ public class OrderService {
if (p.orderId != null) { if (p.orderId != null) {
String table = "sale".equals(bizType) ? "sales_orders" : "purchase_orders"; String table = "sale".equals(bizType) ? "sales_orders" : "purchase_orders";
jdbcTemplate.update("UPDATE " + table + " SET paid_amount = paid_amount + ? WHERE id = ?", n(p.amount), p.orderId); jdbcTemplate.update("UPDATE " + table + " SET paid_amount = paid_amount + ? WHERE id = ?", n(p.amount), p.orderId);
// 采购付款联动应付:应付 -= 付款金额
if ("purchase".equals(bizType)) {
java.util.List<Long> sids = jdbcTemplate.query("SELECT supplier_id FROM purchase_orders WHERE id=?", (rs,rn)-> rs.getLong(1), p.orderId);
if (!sids.isEmpty() && sids.get(0) != null) {
adjustSupplierPayable(sids.get(0), n(p.amount).negate());
}
}
} }
} }
return new OrderDtos.CreatePaymentsResponse(ids); return new OrderDtos.CreatePaymentsResponse(ids);
@@ -198,6 +229,21 @@ public class OrderService {
} }
// 更新状态 // 更新状态
jdbcTemplate.update("UPDATE " + headTable + " SET status='void' WHERE id = ?", id); jdbcTemplate.update("UPDATE " + headTable + " SET status='void' WHERE id = ?", id);
// 采购单作废回滚应付
if ("purchase.out".equals(type) || "purchase.in".equals(type) || "purchase.return".equals(type)) {
boolean purchaseIn = "purchase.in".equals(type);
boolean purchaseRet = "purchase.return".equals(type);
if (purchaseIn || purchaseRet) {
java.util.List<java.util.Map<String,Object>> h = jdbcTemplate.queryForList("SELECT supplier_id, amount FROM " + headTable + " WHERE id=?", id);
if (!h.isEmpty()) {
Long sid = ((Number)h.get(0).get("supplier_id")).longValue();
java.math.BigDecimal amt = new java.math.BigDecimal(h.get(0).get("amount").toString());
java.math.BigDecimal delta = purchaseIn ? amt.negate() : amt; // 入库作废应付减少;退货作废应付增加
adjustSupplierPayable(sid, delta);
}
}
}
} }
public java.util.Map<String,Object> list(Long shopId, String biz, String type, String kw, int page, int size, String startDate, String endDate) { public java.util.Map<String,Object> list(Long shopId, String biz, String type, String kw, int page, int size, String startDate, String endDate) {
@@ -312,6 +358,11 @@ public class OrderService {
private static BigDecimal scale2(BigDecimal v) { return v.setScale(2, java.math.RoundingMode.HALF_UP); } private static BigDecimal scale2(BigDecimal v) { return v.setScale(2, java.math.RoundingMode.HALF_UP); }
private static LocalDateTime nowUtc() { return LocalDateTime.now(java.time.Clock.systemUTC()); } private static LocalDateTime nowUtc() { return LocalDateTime.now(java.time.Clock.systemUTC()); }
private void adjustSupplierPayable(Long supplierId, java.math.BigDecimal delta) {
if (supplierId == null || delta == null) return;
jdbcTemplate.update("UPDATE suppliers SET ap_payable = ap_payable + ? WHERE id = ?", delta, supplierId);
}
private void ensureDefaultAccounts(Long shopId, Long userId) { private void ensureDefaultAccounts(Long shopId, Long userId) {
// 为 cash/bank/wechat 分别确保存在一条账户记录;按 type→name 顺序检查,避免同名唯一冲突 // 为 cash/bank/wechat 分别确保存在一条账户记录;按 type→name 顺序检查,避免同名唯一冲突
ensureAccount(shopId, userId, "cash", "现金"); ensureAccount(shopId, userId, "cash", "现金");

View File

@@ -0,0 +1,55 @@
package com.example.demo.supplier.controller;
import com.example.demo.common.AppDefaultsProperties;
import com.example.demo.supplier.dto.SupplierDtos;
import com.example.demo.supplier.service.SupplierService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/suppliers")
public class SupplierController {
private final SupplierService supplierService;
private final AppDefaultsProperties defaults;
public SupplierController(SupplierService supplierService, AppDefaultsProperties defaults) {
this.supplierService = supplierService;
this.defaults = defaults;
}
@GetMapping
public ResponseEntity<?> search(@RequestHeader(name = "X-Shop-Id", required = false) Long shopId,
@RequestParam(name = "kw", required = false) String kw,
@RequestParam(name = "debtOnly", required = false, defaultValue = "false") boolean debtOnly,
@RequestParam(name = "page", defaultValue = "1") int page,
@RequestParam(name = "size", defaultValue = "50") int size) {
Long sid = (shopId == null ? defaults.getShopId() : shopId);
return ResponseEntity.ok(supplierService.search(sid, kw, debtOnly, Math.max(0, page-1), size));
}
@PostMapping
public ResponseEntity<?> create(@RequestHeader(name = "X-Shop-Id", required = false) Long shopId,
@RequestHeader(name = "X-User-Id", required = false) Long userId,
@RequestBody SupplierDtos.CreateOrUpdateSupplierRequest req) {
Long sid = (shopId == null ? defaults.getShopId() : shopId);
Long uid = (userId == null ? defaults.getUserId() : userId);
Long id = supplierService.create(sid, uid, req);
java.util.Map<String,Object> body = new java.util.HashMap<>();
body.put("id", id);
return ResponseEntity.ok(body);
}
@PutMapping("/{id}")
public ResponseEntity<?> update(@PathVariable("id") Long id,
@RequestHeader(name = "X-Shop-Id", required = false) Long shopId,
@RequestHeader(name = "X-User-Id", required = false) Long userId,
@RequestBody SupplierDtos.CreateOrUpdateSupplierRequest req) {
Long sid = (shopId == null ? defaults.getShopId() : shopId);
Long uid = (userId == null ? defaults.getUserId() : userId);
supplierService.update(id, sid, uid, req);
return ResponseEntity.ok().build();
}
}

View File

@@ -0,0 +1,49 @@
package com.example.demo.supplier.dto;
public class SupplierDtos {
public static class CreateOrUpdateSupplierRequest {
public String name;
public String contactName;
public String mobile;
public String phone;
public String address;
public java.math.BigDecimal apOpening;
public java.math.BigDecimal apPayable;
public String remark;
}
public static class SupplierListItem {
public Long id;
public String name;
public String contactName;
public String mobile;
public String phone;
public String address;
public java.math.BigDecimal apOpening;
public java.math.BigDecimal apPayable;
public String remark;
public SupplierListItem(Long id,
String name,
String contactName,
String mobile,
String phone,
String address,
java.math.BigDecimal apOpening,
java.math.BigDecimal apPayable,
String remark) {
this.id = id;
this.name = name;
this.contactName = contactName;
this.mobile = mobile;
this.phone = phone;
this.address = address;
this.apOpening = apOpening;
this.apPayable = apPayable;
this.remark = remark;
}
}
}

View File

@@ -0,0 +1,96 @@
package com.example.demo.supplier.service;
import com.example.demo.supplier.dto.SupplierDtos;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
public class SupplierService {
private final JdbcTemplate jdbcTemplate;
public SupplierService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public Map<String, Object> search(Long shopId, String kw, boolean debtOnly, int page, int size) {
StringBuilder sql = new StringBuilder("SELECT id,name,contact_name,mobile,phone,address,ap_opening,ap_payable,remark FROM suppliers WHERE shop_id=? AND status=1");
java.util.List<Object> ps = new java.util.ArrayList<>();
ps.add(shopId);
if (kw != null && !kw.isBlank()) {
sql.append(" AND (name LIKE ? OR phone LIKE ? OR mobile LIKE ?)");
String like = '%'+kw+'%';
ps.add(like); ps.add(like); ps.add(like);
}
if (debtOnly) {
sql.append(" AND ap_payable > 0");
}
sql.append(" ORDER BY id DESC LIMIT ? OFFSET ?");
ps.add(size); ps.add(Math.max(0, page) * size);
List<Map<String,Object>> rows = jdbcTemplate.queryForList(sql.toString(), ps.toArray());
List<SupplierDtos.SupplierListItem> items = rows.stream().map(r -> new SupplierDtos.SupplierListItem(
((Number)r.get("id")).longValue(),
(String) r.get("name"),
(String) r.get("contact_name"),
(String) r.get("mobile"),
(String) r.get("phone"),
(String) r.get("address"),
toBig((java.lang.Number) r.get("ap_opening")),
toBig((java.lang.Number) r.get("ap_payable")),
(String) r.get("remark")
)).collect(Collectors.toList());
java.util.Map<String,Object> resp = new java.util.HashMap<>();
resp.put("list", items);
return resp;
}
@Transactional
public Long create(Long shopId, Long userId, SupplierDtos.CreateOrUpdateSupplierRequest req) {
BigDecimal open = nz(req.apOpening);
BigDecimal payable = nz(req.apPayable);
String sql = "INSERT INTO suppliers (shop_id,user_id,name,contact_name,mobile,phone,address,status,ap_opening,ap_payable,remark,created_at,updated_at) " +
"VALUES (?,?,?,?,?,?,?,?,?,?,?,NOW(),NOW())";
org.springframework.jdbc.support.GeneratedKeyHolder kh = new org.springframework.jdbc.support.GeneratedKeyHolder();
jdbcTemplate.update(con -> {
java.sql.PreparedStatement ps = con.prepareStatement(sql, new String[]{"id"});
int i = 1;
ps.setLong(i++, shopId);
ps.setLong(i++, userId);
ps.setString(i++, req.name);
ps.setString(i++, req.contactName);
ps.setString(i++, req.mobile);
ps.setString(i++, req.phone);
ps.setString(i++, req.address);
ps.setInt(i++, 1);
ps.setBigDecimal(i++, open);
ps.setBigDecimal(i++, payable);
ps.setString(i, req.remark);
return ps;
}, kh);
Number key = kh.getKey();
return key == null ? null : key.longValue();
}
@Transactional
public void update(Long id, Long shopId, Long userId, SupplierDtos.CreateOrUpdateSupplierRequest req) {
String sql = "UPDATE suppliers SET name=?, contact_name=?, mobile=?, phone=?, address=?, ap_opening=?, ap_payable=?, remark=?, updated_at=NOW() WHERE id=? AND shop_id=?";
jdbcTemplate.update(sql, req.name, req.contactName, req.mobile, req.phone, req.address, nz(req.apOpening), nz(req.apPayable), req.remark, id, shopId);
}
public java.util.Optional<java.util.Map<String,Object>> findById(Long id) {
List<Map<String,Object>> rows = jdbcTemplate.queryForList("SELECT id,name,contact_name,mobile,phone,address,ap_opening,ap_payable,remark FROM suppliers WHERE id=?", id);
if (rows.isEmpty()) return java.util.Optional.empty();
return java.util.Optional.of(rows.get(0));
}
private static BigDecimal toBig(Number n) { return n == null ? BigDecimal.ZERO : new BigDecimal(n.toString()); }
private static BigDecimal nz(BigDecimal v) { return v == null ? BigDecimal.ZERO : v.setScale(2, java.math.RoundingMode.HALF_UP); }
}

View File

@@ -223,7 +223,7 @@
| mobile | VARCHAR(32) | YES | | 手机 | | mobile | VARCHAR(32) | YES | | 手机 |
| level | VARCHAR(32) | YES | | 客户等级标签 | | level | VARCHAR(32) | YES | | 客户等级标签 |
| contact_name | VARCHAR(64) | YES | | 联系人 | | contact_name | VARCHAR(64) | YES | | 联系人 |
| price_level | ENUM('retail','distribution','wholesale','big_client') | NOT NULL | retail | 默认售价列 | | price_level | ENUM('零售价','批发价','大单报价') | NOT NULL | 零售价 | 默认售价列(中文存储) |
| status | TINYINT UNSIGNED | NOT NULL | 1 | | | status | TINYINT UNSIGNED | NOT NULL | 1 | |
| ar_opening | DECIMAL(18,2) | NOT NULL | 0.00 | 期初应收 | | ar_opening | DECIMAL(18,2) | NOT NULL | 0.00 | 期初应收 |
| remark | VARCHAR(255) | YES | | | | remark | VARCHAR(255) | YES | | |
@@ -241,14 +241,19 @@
| shop_id | BIGINT UNSIGNED | NOT NULL | | | | shop_id | BIGINT UNSIGNED | NOT NULL | | |
| user_id | BIGINT UNSIGNED | NOT NULL | | | | user_id | BIGINT UNSIGNED | NOT NULL | | |
| name | VARCHAR(120) | NOT NULL | | | | name | VARCHAR(120) | NOT NULL | | |
| phone | VARCHAR(32) | YES | | | | contact_name | VARCHAR(64) | YES | | 联系人 |
| mobile | VARCHAR(32) | YES | | 手机 |
| phone | VARCHAR(32) | YES | | 电话 |
| address | VARCHAR(255) | YES | | 经营地址 |
| status | TINYINT UNSIGNED | NOT NULL | 1 | | | status | TINYINT UNSIGNED | NOT NULL | 1 | |
| ap_opening | DECIMAL(18,2) | NOT NULL | 0.00 | 期初应付 |
| ap_payable | DECIMAL(18,2) | NOT NULL | 0.00 | 当前应付(实时维护) |
| remark | VARCHAR(255) | YES | | | | remark | VARCHAR(255) | YES | | |
| created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | | | created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
| updated_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | | | updated_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
| deleted_at | DATETIME | YES | | | | deleted_at | DATETIME | YES | | |
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_suppliers_shop` (`shop_id`) - KEY: `idx_suppliers_phone` (`phone`) **Indexes**: - PRIMARY KEY: `id` - KEY: `idx_suppliers_shop` (`shop_id`) - KEY: `idx_suppliers_phone` (`phone`) - KEY: `idx_suppliers_mobile` (`mobile`)
**Foreign Keys**: - `fk_suppliers_shop`: `shop_id``shops(id)` - `fk_suppliers_user`: `user_id``users(id)` **Foreign Keys**: - `fk_suppliers_shop`: `shop_id``shops(id)` - `fk_suppliers_user`: `user_id``users(id)`
### accounts ### accounts

View File

@@ -84,12 +84,26 @@ paths:
$ref: '#/components/schemas/Account' $ref: '#/components/schemas/Account'
/api/suppliers: /api/suppliers:
get: get:
summary: 供应商搜索(❌ Partially Implemented summary: 供应商搜索(✅ Fully Implemented
parameters: parameters:
- in: query - in: query
name: kw name: kw
schema: schema:
type: string type: string
- in: query
name: debtOnly
schema:
type: boolean
- in: query
name: page
schema:
type: integer
default: 1
- in: query
name: size
schema:
type: integer
default: 50
responses: responses:
'200': '200':
description: 成功 description: 成功
@@ -106,6 +120,32 @@ paths:
type: array type: array
items: items:
$ref: '#/components/schemas/Supplier' $ref: '#/components/schemas/Supplier'
post:
summary: 新建供应商(✅ Fully Implemented
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateSupplierRequest'
responses:
'200': { description: 成功 }
/api/suppliers/{id}:
put:
summary: 更新供应商(✅ Fully Implemented
parameters:
- in: path
name: id
required: true
schema: { type: integer, format: int64 }
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateSupplierRequest'
responses:
'200': { description: 成功 }
/api/other-transactions: /api/other-transactions:
post: post:
summary: 新建其他收入/支出(❌ Partially Implemented summary: 新建其他收入/支出(❌ Partially Implemented
@@ -735,13 +775,26 @@ components:
Supplier: Supplier:
type: object type: object
properties: properties:
id: id: { type: integer, format: int64 }
type: integer name: { type: string }
format: int64 contactName: { type: string, nullable: true }
name: mobile: { type: string, nullable: true }
type: string phone: { type: string, nullable: true }
mobile: address: { type: string, nullable: true }
type: string apOpening: { type: number }
apPayable: { type: number }
remark: { type: string, nullable: true }
CreateSupplierRequest:
type: object
properties:
name: { type: string }
contactName: { type: string, nullable: true }
mobile: { type: string, nullable: true }
phone: { type: string, nullable: true }
address: { type: string, nullable: true }
apOpening: { type: number, nullable: true }
apPayable: { type: number, nullable: true }
remark: { type: string, nullable: true }
CreateOtherTransactionRequest: CreateOtherTransactionRequest:
type: object type: object
properties: properties:
@@ -860,7 +913,7 @@ components:
mobile: { type: string } mobile: { type: string }
phone: { type: string } phone: { type: string }
level: { type: string } level: { type: string }
priceLevel: { type: string, enum: [retail, distribution, wholesale, big_client] } priceLevel: { type: string, enum: [零售价, 批发价, 大单报价] }
remark: { type: string } remark: { type: string }
receivable: { type: number } receivable: { type: number }
CreateCustomerRequest: CreateCustomerRequest:
@@ -868,7 +921,7 @@ components:
properties: properties:
name: { type: string } name: { type: string }
level: { type: string, nullable: true } level: { type: string, nullable: true }
priceLevel: { type: string, enum: [retail, distribution, wholesale, big_client] } priceLevel: { type: string, enum: [零售价, 批发价, 大单报价] }
contactName: { type: string, nullable: true } contactName: { type: string, nullable: true }
mobile: { type: string, nullable: true } mobile: { type: string, nullable: true }
phone: { type: string, nullable: true } phone: { type: string, nullable: true }

View File

@@ -12,8 +12,10 @@ function requestWithFallback(options, candidates, idx, resolve, reject) {
uni.request({ ...options, url, success: (res) => { uni.request({ ...options, url, success: (res) => {
const { statusCode, data } = res const { statusCode, data } = res
if (statusCode >= 200 && statusCode < 300) return resolve(data) if (statusCode >= 200 && statusCode < 300) return resolve(data)
const msg = (data && (data.message || data.error || data.msg)) || ('HTTP ' + statusCode)
uni.showToast({ title: msg, icon: 'none' })
if (idx + 1 < candidates.length) return requestWithFallback(options, candidates, idx + 1, resolve, reject) if (idx + 1 < candidates.length) return requestWithFallback(options, candidates, idx + 1, resolve, reject)
reject(new Error('HTTP ' + statusCode)) reject(new Error(msg))
}, fail: (err) => { }, fail: (err) => {
if (idx + 1 < candidates.length) return requestWithFallback(options, candidates, idx + 1, resolve, reject) if (idx + 1 < candidates.length) return requestWithFallback(options, candidates, idx + 1, resolve, reject)
reject(err) reject(err)

View File

@@ -60,12 +60,24 @@
"navigationBarTitleText": "新增客户" "navigationBarTitleText": "新增客户"
} }
}, },
{
"path": "pages/customer/detail",
"style": {
"navigationBarTitleText": "客户详情"
}
},
{ {
"path": "pages/supplier/select", "path": "pages/supplier/select",
"style": { "style": {
"navigationBarTitleText": "选择供应商" "navigationBarTitleText": "选择供应商"
} }
}, },
{
"path": "pages/supplier/form",
"style": {
"navigationBarTitleText": "新增/编辑供应商"
}
},
{ {
"path": "pages/account/select", "path": "pages/account/select",
"style": { "style": {

View File

@@ -0,0 +1,86 @@
<template>
<view class="page">
<view class="card">
<view class="row"><text class="label">名称</text><text v-if="!editing" class="value">{{ d.name }}</text><input v-else class="value-input" v-model="form.name" placeholder="必填" /></view>
<view class="row"><text class="label">联系人</text><text v-if="!editing" class="value">{{ d.contactName || '—' }}</text><input v-else class="value-input" v-model="form.contactName" placeholder="可选" /></view>
<view class="row"><text class="label">手机</text><text v-if="!editing" class="value">{{ d.mobile || '—' }}</text><input v-else class="value-input" v-model="form.mobile" placeholder="可选" /></view>
<view class="row"><text class="label">电话</text><text v-if="!editing" class="value">{{ d.phone || '—' }}</text><input v-else class="value-input" v-model="form.phone" placeholder="可选(座机)" /></view>
<view class="row"><text class="label">地址</text><text v-if="!editing" class="value">{{ d.address || '—' }}</text><input v-else class="value-input" v-model="form.address" placeholder="可选" /></view>
<view class="row"><text class="label">等级</text><text v-if="!editing" class="value">{{ d.level || '—' }}</text><input v-else class="value-input" v-model="form.level" placeholder="可选,如 VIP/A/B" /></view>
<view class="row"><text class="label">售价档位</text>
<text v-if="!editing" class="value">{{ d.priceLevel }}</text>
<picker v-else :range="priceLabels" :value="priceIdx" @change="onPriceChange"><view class="value">{{ priceLabels[priceIdx] }}</view></picker>
</view>
<view class="row"><text class="label">初始应收</text><text v-if="!editing" class="value">¥ {{ Number(d.arOpening||0).toFixed(2) }}</text><input v-else class="value-input" type="digit" v-model.number="form.arOpening" placeholder="0.00" /></view>
<view class="row"><text class="label">当前应收</text><text class="value emp">¥ {{ (Number(d.receivable||0)).toFixed(2) }}</text></view>
<view class="row"><text class="label">备注</text><text v-if="!editing" class="value">{{ d.remark || '—' }}</text><input v-else class="value-input" v-model="form.remark" placeholder="—" /></view>
</view>
<view class="bottom">
<button class="ghost" @click="toggleEdit">{{ editing ? '取消' : '编辑' }}</button>
<button class="primary" v-if="editing" @click="save">保存</button>
<button class="primary" v-else @click="choose">选择此客户</button>
</view>
</view>
</template>
<script>
import { get, put } from '../../common/http.js'
export default {
data(){ return { id: null, d: {}, editing: false, form: { name:'', contactName:'', mobile:'', phone:'', address:'', level:'', priceLevel:'零售价', arOpening:0, remark:'' }, priceLevels:['零售价','批发价','大单报价'], priceLabels:['零售价','批发价','大单报价'], priceIdx:0 } },
onLoad(q){ if (q && q.id) { this.id = Number(q.id); this.fetch() } },
methods: {
async fetch(){
try {
this.d = await get(`/api/customers/${this.id}`)
// 初始化表单
this.form = {
name: this.d.name || '', contactName: this.d.contactName || '', mobile: this.d.mobile || '', phone: this.d.phone || '',
address: this.d.address || '', level: this.d.level || '', priceLevel: this.d.priceLevel || 'retail', arOpening: Number(this.d.arOpening||0), remark: this.d.remark || ''
}
const idx = this.priceLevels.indexOf(this.form.priceLevel); this.priceIdx = idx >= 0 ? idx : 0
} catch(e){ uni.showToast({ title:'加载失败', icon:'none' }) }
},
toggleEdit(){ this.editing = !this.editing },
onPriceChange(e){ this.priceIdx = Number(e.detail.value); this.form.priceLevel = this.priceLevels[this.priceIdx] },
choose(){
const pages = getCurrentPages()
let targetIdx = -1
for (let i = pages.length - 2; i >= 0; i--) {
const vm = pages[i] && pages[i].$vm ? pages[i].$vm : null
if (vm && vm.order) { vm.order.customerId = this.d.id; vm.customerName = this.d.name; targetIdx = i; break }
}
if (targetIdx >= 0) {
const delta = (pages.length - 1) - targetIdx
uni.navigateBack({ delta })
} else {
uni.navigateBack()
}
},
async save(){
if (!this.form.name) return uni.showToast({ title:'请填写客户名称', icon:'none' })
try {
await put(`/api/customers/${this.id}`, this.form)
uni.showToast({ title:'已保存', icon:'success' })
this.editing = false
await this.fetch()
} catch(e) { uni.showToast({ title: e?.message || '保存失败', icon:'none' }) }
}
}
}
</script>
<style>
.page { padding-bottom: 140rpx; }
.card { background:#fff; margin: 16rpx; padding: 12rpx 16rpx; border-radius: 16rpx; }
.row { display:flex; justify-content: space-between; padding: 18rpx 8rpx; border-bottom: 1rpx solid #f3f3f3; }
.row:last-child { border-bottom: 0; }
.label { color:#666; }
.value { color:#333; max-width: 60%; text-align: right; }
.value-input { color:#333; text-align: right; flex: 1; }
.emp { color:#107e9b; font-weight: 700; }
.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); display:flex; gap: 12rpx; }
.primary { flex:1; background: linear-gradient(135deg, #A0E4FF 0%, #17A2C4 100%); color:#fff; border-radius: 999rpx; padding: 20rpx 0; }
.ghost { flex:1; background:#fff; color:#107e9b; border: 2rpx solid #A0E4FF; border-radius: 999rpx; padding: 18rpx 0; }
</style>

View File

@@ -2,12 +2,12 @@
<view class="page"> <view class="page">
<view class="field"><text class="label">客户名称</text><input class="value" v-model="form.name" placeholder="必填" /></view> <view class="field"><text class="label">客户名称</text><input class="value" v-model="form.name" placeholder="必填" /></view>
<view class="field"><text class="label">客户等级</text><input class="value" v-model="form.level" placeholder="可选,如 VIP/A/B" /></view> <view class="field"><text class="label">客户等级</text><input class="value" v-model="form.level" placeholder="可选,如 VIP/A/B" /></view>
<view class="field"> <view class="field">
<text class="label">售价档位</text> <text class="label">售价档位</text>
<picker :range="priceLevels" :value="priceIdx" @change="onPriceChange"> <picker :range="priceLabels" :value="priceIdx" @change="onPriceChange">
<view class="value">{{ priceLevels[priceIdx] }}</view> <view class="value">{{ priceLabels[priceIdx] }}</view>
</picker> </picker>
</view> </view>
<view class="field"><text class="label">联系人</text><input class="value" v-model="form.contactName" placeholder="可选" /></view> <view class="field"><text class="label">联系人</text><input class="value" v-model="form.contactName" placeholder="可选" /></view>
<view class="field"><text class="label">手机</text><input class="value" v-model="form.mobile" placeholder="可选" /></view> <view class="field"><text class="label">手机</text><input class="value" v-model="form.mobile" placeholder="可选" /></view>
<view class="field"><text class="label">电话</text><input class="value" v-model="form.phone" placeholder="可选(座机)" /></view> <view class="field"><text class="label">电话</text><input class="value" v-model="form.phone" placeholder="可选(座机)" /></view>
@@ -26,13 +26,14 @@ export default {
return { return {
id: null, id: null,
form: { name:'', level:'', priceLevel:'retail', contactName:'', mobile:'', phone:'', address:'', arOpening:0, remark:'' }, form: { name:'', level:'', priceLevel:'retail', contactName:'', mobile:'', phone:'', address:'', arOpening:0, remark:'' },
priceLevels: ['retail','distribution','wholesale','big_client'], priceLevels: ['零售价','批发价','大单报价'],
priceLabels: ['零售价','批发价','大单报价'],
priceIdx: 0 priceIdx: 0
} }
}, },
onLoad(query) { if (query && query.id) { this.id = Number(query.id) } }, onLoad(query) { if (query && query.id) { this.id = Number(query.id) } },
methods: { methods: {
onPriceChange(e){ this.priceIdx = Number(e.detail.value); this.form.priceLevel = this.priceLevels[this.priceIdx] }, onPriceChange(e){ this.priceIdx = Number(e.detail.value); this.form.priceLevel = this.priceLevels[this.priceIdx] },
async save() { async save() {
if (!this.form.name) return uni.showToast({ title:'请填写客户名称', icon:'none' }) if (!this.form.name) return uni.showToast({ title:'请填写客户名称', icon:'none' })
try { try {

View File

@@ -6,7 +6,7 @@
<button size="mini" :type="debtOnly ? 'primary' : 'default'" @click="toggleDebtOnly">只看欠款</button> <button size="mini" :type="debtOnly ? 'primary' : 'default'" @click="toggleDebtOnly">只看欠款</button>
</view> </view>
<scroll-view scroll-y class="list"> <scroll-view scroll-y class="list">
<view class="item" v-for="c in customers" :key="c.id" @click="select(c)"> <view class="item" v-for="c in customers" :key="c.id" @click="openDetail(c)">
<view class="name">{{ c.name }}</view> <view class="name">{{ c.name }}</view>
<view class="meta"> <view class="meta">
{{ c.mobile || '—' }} {{ c.mobile || '—' }}
@@ -25,6 +25,7 @@
export default { export default {
data() { return { kw: '', debtOnly: false, customers: [] } }, data() { return { kw: '', debtOnly: false, customers: [] } },
onLoad() { this.search() }, onLoad() { this.search() },
onShow() { this.search() },
methods: { methods: {
toggleDebtOnly() { this.debtOnly = !this.debtOnly; this.search() }, toggleDebtOnly() { this.debtOnly = !this.debtOnly; this.search() },
async search() { async search() {
@@ -35,13 +36,17 @@
}, },
createCustomer() { uni.navigateTo({ url: '/pages/customer/form' }) }, createCustomer() { uni.navigateTo({ url: '/pages/customer/form' }) },
select(c) { select(c) {
const opener = getCurrentPages()[getCurrentPages().length-2] const pages = getCurrentPages()
if (opener && opener.$vm) { const prev = pages.length >= 2 ? pages[pages.length - 2] : null
opener.$vm.order.customerId = c.id const vm = prev && prev.$vm ? prev.$vm : null
opener.$vm.customerName = c.name if (vm && vm.order) {
vm.order.customerId = c.id
vm.customerName = c.name
} }
uni.navigateBack() uni.navigateBack()
} }
,
openDetail(c) { uni.navigateTo({ url: '/pages/customer/detail?id=' + c.id }) }
} }
} }
</script> </script>

View File

@@ -156,6 +156,10 @@
uni.navigateTo({ url: '/pages/customer/select' }) uni.navigateTo({ url: '/pages/customer/select' })
return return
} }
if (item.key === 'supplier') {
uni.navigateTo({ url: '/pages/supplier/select' })
return
}
uni.showToast({ title: item.title + '(开发中)', icon: 'none' }) uni.showToast({ title: item.title + '(开发中)', icon: 'none' })
}, },
goProduct() { goProduct() {

View File

@@ -124,7 +124,7 @@
<input type="number" v-model.number="it.quantity" @input="recalc()" /> <input type="number" v-model.number="it.quantity" @input="recalc()" />
</view> </view>
<view class="col price"> <view class="col price">
<input type="number" v-model.number="it.unitPrice" @input="recalc()" /> <input type="number" v-model.number="it.unitPrice" @input="onPriceInput(it)" />
</view> </view>
<view class="col amount">¥ {{ (Number(it.quantity)*Number(it.unitPrice)).toFixed(2) }}</view> <view class="col amount">¥ {{ (Number(it.quantity)*Number(it.unitPrice)).toFixed(2) }}</view>
</view> </view>
@@ -139,7 +139,7 @@
</template> </template>
<script> <script>
import { post } from '../../common/http.js' import { get, post } from '../../common/http.js'
import { INCOME_CATEGORIES, EXPENSE_CATEGORIES } from '../../common/constants.js' import { INCOME_CATEGORIES, EXPENSE_CATEGORIES } from '../../common/constants.js'
function todayString() { function todayString() {
@@ -162,6 +162,9 @@
remark: '' remark: ''
}, },
customerName: '', customerName: '',
customerPriceLevel: '零售价',
_lastCustomerId: null,
_priceCache: {},
supplierName: '', supplierName: '',
items: [], items: [],
activeCategory: 'sale_income', activeCategory: 'sale_income',
@@ -192,7 +195,46 @@
return Number(p.cash||0) + Number(p.bank||0) + Number(p.wechat||0) return Number(p.cash||0) + Number(p.bank||0) + Number(p.wechat||0)
} }
}, },
onShow() {
if (this.biz === 'sale') {
if (this.order.customerId && this.order.customerId !== this._lastCustomerId) {
this.loadCustomerLevel(this.order.customerId).then(() => {
this._lastCustomerId = this.order.customerId
for (const it of this.items) { if (it && (it._autoPrice || !it.unitPrice)) this.autoPriceItem(it) }
})
}
for (const it of this.items) { if (it && !it.unitPrice) this.autoPriceItem(it) }
}
},
methods: { methods: {
async loadCustomerLevel(customerId) {
try {
const d = await get(`/api/customers/${customerId}`)
this.customerPriceLevel = d && d.priceLevel ? d.priceLevel : '零售价'
} catch(e) { this.customerPriceLevel = '零售价' }
},
priceFieldForLevel() {
const lvl = this.customerPriceLevel || '零售价'
if (lvl === '批发价') return 'wholesalePrice'
if (lvl === '大单报价') return 'bigClientPrice'
return 'retailPrice'
},
async autoPriceItem(it) {
if (this.biz !== 'sale') return
if (!it || !it.productId) return
const pid = it.productId
let detail = this._priceCache[pid]
if (!detail) {
try { detail = await get(`/api/products/${pid}`); this._priceCache[pid] = detail } catch(e) { return }
}
const field = this.priceFieldForLevel()
let price = Number(detail && detail[field] != null ? detail[field] : 0)
if (!price && field !== 'retailPrice') { price = Number(detail && detail.retailPrice != null ? detail.retailPrice : 0) }
it.unitPrice = price
it._autoPrice = true
this.recalc()
},
onPriceInput(it) { if (it) { it._autoPrice = false; this.recalc() } },
switchBiz(type) { this.biz = type }, switchBiz(type) { this.biz = type },
onDateChange(e) { this.order.orderTime = e.detail.value }, onDateChange(e) { this.order.orderTime = e.detail.value },
chooseCustomer() { chooseCustomer() {
@@ -214,6 +256,12 @@
const isSaleOrPurchase = (this.biz==='sale' || this.biz==='purchase') const isSaleOrPurchase = (this.biz==='sale' || this.biz==='purchase')
const isCollectOrPay = (this.biz==='sale' && this.saleType==='collect') || (this.biz==='purchase' && this.purchaseType==='pay') const isCollectOrPay = (this.biz==='sale' && this.saleType==='collect') || (this.biz==='purchase' && this.purchaseType==='pay')
const saleTypeValue = this.biz==='sale' ? ('sale.' + this.saleType) : ('purchase.' + this.purchaseType) const saleTypeValue = this.biz==='sale' ? ('sale.' + this.saleType) : ('purchase.' + this.purchaseType)
// 前置校验:销售/进货 出入库要求有明细
if (isSaleOrPurchase && !isCollectOrPay) {
if (!this.items.length) { uni.showToast({ title: '请先选择商品', icon: 'none' }); return }
const invalid = this.items.find(it => !it.productId || Number(it.quantity||0) <= 0)
if (invalid) { uni.showToast({ title: '数量需大于0', icon: 'none' }); return }
}
const payload = isSaleOrPurchase ? (isCollectOrPay ? [ const payload = isSaleOrPurchase ? (isCollectOrPay ? [
{ method: 'cash', amount: Number(this.payments.cash||0) }, { method: 'cash', amount: Number(this.payments.cash||0) },
{ method: 'bank', amount: Number(this.payments.bank||0) }, { method: 'bank', amount: Number(this.payments.bank||0) },

View File

@@ -28,7 +28,8 @@
select(p) { select(p) {
const opener = getCurrentPages()[getCurrentPages().length-2] const opener = getCurrentPages()[getCurrentPages().length-2]
if (opener && opener.$vm && opener.$vm.items) { if (opener && opener.$vm && opener.$vm.items) {
opener.$vm.items.push({ productId: p.id, productName: p.name, quantity: 1, unitPrice: Number(p.price || 0) }) const initPrice = Number((p.retailPrice != null ? p.retailPrice : (p.price || 0)))
opener.$vm.items.push({ productId: p.id, productName: p.name, quantity: 1, unitPrice: initPrice, _autoPrice: true })
} }
uni.navigateBack() uni.navigateBack()
} }

View File

@@ -0,0 +1,50 @@
<template>
<view class="page">
<view class="field"><text class="label">供应商名称</text><input class="value" v-model="form.name" placeholder="必填" /></view>
<view class="field"><text class="label">联系人</text><input class="value" v-model="form.contactName" placeholder="可选" /></view>
<view class="field"><text class="label">手机</text><input class="value" v-model="form.mobile" placeholder="可选" /></view>
<view class="field"><text class="label">电话</text><input class="value" v-model="form.phone" placeholder="可选(座机)" /></view>
<view class="field"><text class="label">经营地址</text><input class="value" v-model="form.address" placeholder="可选" /></view>
<view class="field"><text class="label">初始应付款</text><input class="value" type="digit" v-model.number="form.apOpening" placeholder="默认 0.00" /></view>
<view class="field"><text class="label">应付款</text><input class="value" type="digit" v-model.number="form.apPayable" placeholder="默认 0.00" /></view>
<view class="textarea"><textarea v-model="form.remark" maxlength="200" placeholder="备注最多200字"></textarea></view>
<view class="bottom"><button class="primary" @click="save">保存</button></view>
</view>
</template>
<script>
import { post, put } from '../../common/http.js'
export default {
data() {
return {
id: null,
form: { name:'', contactName:'', mobile:'', phone:'', address:'', apOpening:0, apPayable:0, remark:'' }
}
},
onLoad(query) { if (query && query.id) { this.id = Number(query.id) } },
methods: {
async save() {
if (!this.form.name) return uni.showToast({ title:'请填写供应商名称', icon:'none' })
try {
if (this.id) await put(`/api/suppliers/${this.id}`, this.form)
else await post('/api/suppliers', this.form)
uni.showToast({ title:'保存成功', icon:'success' })
setTimeout(() => uni.navigateBack(), 500)
} catch(e) { uni.showToast({ title: e?.message || '保存失败', icon:'none' }) }
}
}
}
</script>
<style>
.page { padding-bottom: 140rpx; }
.field { display:flex; justify-content: space-between; padding: 22rpx 24rpx; background:#fff; border-bottom:1rpx solid #eee; }
.label { color:#666; }
.value { color:#333; text-align: right; flex: 1; }
.textarea { padding: 16rpx 24rpx; background:#fff; margin-top: 12rpx; }
.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, #A0E4FF 0%, #17A2C4 100%); color:#fff; border-radius: 999rpx; padding: 20rpx 0; }
</style>

View File

@@ -3,28 +3,37 @@
<view class="search"> <view class="search">
<input v-model="kw" placeholder="搜索供应商名称/电话" @confirm="search" /> <input v-model="kw" placeholder="搜索供应商名称/电话" @confirm="search" />
<button size="mini" @click="search">搜索</button> <button size="mini" @click="search">搜索</button>
<button size="mini" :type="debtOnly ? 'primary' : 'default'" @click="toggleDebtOnly">只看欠款</button>
</view> </view>
<scroll-view scroll-y class="list"> <scroll-view scroll-y class="list">
<view class="item" v-for="s in suppliers" :key="s.id" @click="select(s)"> <view class="item" v-for="s in suppliers" :key="s.id" @click="select(s)">
<view class="name">{{ s.name }}</view> <view class="name">{{ s.name }}</view>
<view class="meta">{{ s.mobile || '—' }}</view> <view class="meta">
{{ s.mobile || '—' }}
<text v-if="typeof s.apPayable === 'number'">应付¥ {{ Number(s.apPayable).toFixed(2) }}</text>
</view>
</view> </view>
</scroll-view> </scroll-view>
<view class="bottom">
<button class="primary" @click="createSupplier">新增供应商</button>
</view>
</view> </view>
</template> </template>
<script> <script>
import { get } from '../../common/http.js' import { get } from '../../common/http.js'
export default { export default {
data() { return { kw: '', suppliers: [] } }, data() { return { kw: '', debtOnly: false, suppliers: [] } },
onLoad() { this.search() }, onLoad() { this.search() },
methods: { methods: {
toggleDebtOnly() { this.debtOnly = !this.debtOnly; this.search() },
async search() { async search() {
try { try {
const res = await get('/api/suppliers', { kw: this.kw, page: 1, size: 50 }) const res = await get('/api/suppliers', { kw: this.kw, debtOnly: this.debtOnly, page: 1, size: 50 })
this.suppliers = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : []) this.suppliers = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])
} catch(e) { uni.showToast({ title: '加载失败', icon: 'none' }) } } catch(e) { uni.showToast({ title: '加载失败', icon: 'none' }) }
}, },
createSupplier() { uni.navigateTo({ url: '/pages/supplier/form' }) },
select(s) { select(s) {
const opener = getCurrentPages()[getCurrentPages().length-2] const opener = getCurrentPages()[getCurrentPages().length-2]
if (opener && opener.$vm) { if (opener && opener.$vm) {

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\r\n\r\n// 规范化 WebSocket 关闭码(仅微信小程序)\r\n// #ifdef MP-WEIXIN\r\nif (typeof uni !== 'undefined' && typeof uni.connectSocket === 'function') {\r\n const _connectSocket = uni.connectSocket\r\n uni.connectSocket = function(options) {\r\n const task = _connectSocket.call(this, options)\r\n if (task && typeof task.close === 'function') {\r\n const _close = task.close\r\n task.close = function(params = {}) {\r\n if (params && typeof params === 'object') {\r\n const codeNum = Number(params.code)\r\n const isValid = codeNum === 1000 || (codeNum >= 3000 && codeNum <= 4999)\r\n if (!isValid) {\r\n params.code = 1000\r\n if (!params.reason) params.reason = 'normalized from invalid close code'\r\n }\r\n }\r\n return _close.call(this, params)\r\n }\r\n }\r\n return task\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;AAKA,IAAI,OAAOF,cAAG,UAAK,eAAe,OAAOA,cAAAA,MAAI,kBAAkB,YAAY;AACzE,QAAM,iBAAiBA,cAAAA,MAAI;AAC3BA,sBAAI,gBAAgB,SAAS,SAAS;AACpC,UAAM,OAAO,eAAe,KAAK,MAAM,OAAO;AAC9C,QAAI,QAAQ,OAAO,KAAK,UAAU,YAAY;AAC5C,YAAM,SAAS,KAAK;AACpB,WAAK,QAAQ,SAAS,SAAS,IAAI;AACjC,YAAI,UAAU,OAAO,WAAW,UAAU;AACxC,gBAAM,UAAU,OAAO,OAAO,IAAI;AAClC,gBAAM,UAAU,YAAY,OAAS,WAAW,OAAQ,WAAW;AACnE,cAAI,CAAC,SAAS;AACZ,mBAAO,OAAO;AACd,gBAAI,CAAC,OAAO;AAAQ,qBAAO,SAAS;AAAA,UACrC;AAAA,QACF;AACD,eAAO,OAAO,KAAK,MAAM,MAAM;AAAA,MAChC;AAAA,IACF;AACD,WAAO;AAAA,EACR;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\r\n\r\n// 规范化 WebSocket 关闭码(仅微信小程序)\r\n// #ifdef MP-WEIXIN\r\nif (typeof uni !== 'undefined' && typeof uni.connectSocket === 'function') {\r\n const _connectSocket = uni.connectSocket\r\n uni.connectSocket = function(options) {\r\n const task = _connectSocket.call(this, options)\r\n if (task && typeof task.close === 'function') {\r\n const _close = task.close\r\n task.close = function(params = {}) {\r\n if (params && typeof params === 'object') {\r\n const codeNum = Number(params.code)\r\n const isValid = codeNum === 1000 || (codeNum >= 3000 && codeNum <= 4999)\r\n if (!isValid) {\r\n params.code = 1000\r\n if (!params.reason) params.reason = 'normalized from invalid close code'\r\n }\r\n }\r\n return _close.call(this, params)\r\n }\r\n }\r\n return task\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;AAKA,IAAI,OAAOF,cAAG,UAAK,eAAe,OAAOA,cAAAA,MAAI,kBAAkB,YAAY;AACzE,QAAM,iBAAiBA,cAAAA,MAAI;AAC3BA,sBAAI,gBAAgB,SAAS,SAAS;AACpC,UAAM,OAAO,eAAe,KAAK,MAAM,OAAO;AAC9C,QAAI,QAAQ,OAAO,KAAK,UAAU,YAAY;AAC5C,YAAM,SAAS,KAAK;AACpB,WAAK,QAAQ,SAAS,SAAS,IAAI;AACjC,YAAI,UAAU,OAAO,WAAW,UAAU;AACxC,gBAAM,UAAU,OAAO,OAAO,IAAI;AAClC,gBAAM,UAAU,YAAY,OAAS,WAAW,OAAQ,WAAW;AACnE,cAAI,CAAC,SAAS;AACZ,mBAAO,OAAO;AACd,gBAAI,CAAC,OAAO;AAAQ,qBAAO,SAAS;AAAA,UACrC;AAAA,QACF;AACD,eAAO,OAAO,KAAK,MAAM,MAAM;AAAA,MAChC;AAAA,IACF;AACD,WAAO;AAAA,EACR;AACH;;;"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":3,"file":"form.js","sources":["pages/customer/form.vue","../../../../Downloads/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvY3VzdG9tZXIvZm9ybS52dWU"],"sourcesContent":["<template>\r\n\t<view class=\"page\">\r\n\t\t<view class=\"field\"><text class=\"label\">客户名称</text><input class=\"value\" v-model=\"form.name\" placeholder=\"必填\" /></view>\r\n\t\t<view class=\"field\"><text class=\"label\">客户等级</text><input class=\"value\" v-model=\"form.level\" placeholder=\"可选,如 VIP/A/B\" /></view>\r\n\t\t<view class=\"field\">\r\n\t\t\t<text class=\"label\">售价档位</text>\r\n\t\t\t<picker :range=\"priceLevels\" :value=\"priceIdx\" @change=\"onPriceChange\">\r\n\t\t\t\t<view class=\"value\">{{ priceLevels[priceIdx] }}</view>\r\n\t\t\t</picker>\r\n\t\t</view>\r\n\t\t<view class=\"field\"><text class=\"label\">联系人</text><input class=\"value\" v-model=\"form.contactName\" placeholder=\"可选\" /></view>\r\n\t\t<view class=\"field\"><text class=\"label\">手机</text><input class=\"value\" v-model=\"form.mobile\" placeholder=\"可选\" /></view>\r\n\t\t<view class=\"field\"><text class=\"label\">电话</text><input class=\"value\" v-model=\"form.phone\" placeholder=\"可选(座机)\" /></view>\r\n\t\t<view class=\"field\"><text class=\"label\">送货地址</text><input class=\"value\" v-model=\"form.address\" placeholder=\"可选\" /></view>\r\n\t\t<view class=\"field\"><text class=\"label\">初始应收</text><input class=\"value\" type=\"digit\" v-model.number=\"form.arOpening\" placeholder=\"默认 0.00\" /></view>\r\n\t\t<view class=\"textarea\"><textarea v-model=\"form.remark\" maxlength=\"200\" placeholder=\"备注最多200字\"></textarea></view>\r\n\r\n\t\t<view class=\"bottom\"><button class=\"primary\" @click=\"save\">保存</button></view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\nimport { post, put } from '../../common/http.js'\r\nexport default {\r\n\tdata() {\r\n\t\treturn {\r\n\t\t\tid: null,\r\n\t\t\tform: { name:'', level:'', priceLevel:'retail', contactName:'', mobile:'', phone:'', address:'', arOpening:0, remark:'' },\r\n\t\t\tpriceLevels: ['retail','distribution','wholesale','big_client'],\r\n\t\t\tpriceIdx: 0\r\n\t\t}\r\n\t},\r\n\tonLoad(query) { if (query && query.id) { this.id = Number(query.id) } },\r\n\tmethods: {\r\n\t\tonPriceChange(e){ this.priceIdx = Number(e.detail.value); this.form.priceLevel = this.priceLevels[this.priceIdx] },\r\n\t\tasync save() {\r\n\t\t\tif (!this.form.name) return uni.showToast({ title:'请填写客户名称', icon:'none' })\r\n\t\t\ttry {\r\n\t\t\t\tif (this.id) await put(`/api/customers/${this.id}`, this.form)\r\n\t\t\t\telse await post('/api/customers', this.form)\r\n\t\t\t\tuni.showToast({ title:'保存成功', icon:'success' })\r\n\t\t\t\tsetTimeout(() => uni.navigateBack(), 500)\r\n\t\t\t} catch(e) { uni.showToast({ title: e?.message || '保存失败', icon:'none' }) }\r\n\t\t}\r\n\t}\r\n}\r\n</script>\r\n\r\n<style>\r\n.page { padding-bottom: 140rpx; }\r\n.field { display:flex; justify-content: space-between; padding: 22rpx 24rpx; background:#fff; border-bottom:1rpx solid #eee; }\r\n.label { color:#666; }\r\n.value { color:#333; text-align: right; flex: 1; }\r\n.textarea { padding: 16rpx 24rpx; background:#fff; margin-top: 12rpx; }\r\n.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); }\r\n.primary { width: 100%; background: linear-gradient(135deg, #A0E4FF 0%, #17A2C4 100%); color:#fff; border-radius: 999rpx; padding: 20rpx 0; }\r\n</style>\r\n\r\n\r\n","import MiniProgramPage from 'C:/Users/21826/Desktop/Wj/PartsInquiry/frontend/pages/customer/form.vue'\nwx.createPage(MiniProgramPage)"],"names":["uni","put","post"],"mappings":";;;AAuBA,MAAK,YAAU;AAAA,EACd,OAAO;AACN,WAAO;AAAA,MACN,IAAI;AAAA,MACJ,MAAM,EAAE,MAAK,IAAI,OAAM,IAAI,YAAW,UAAU,aAAY,IAAI,QAAO,IAAI,OAAM,IAAI,SAAQ,IAAI,WAAU,GAAG,QAAO,GAAI;AAAA,MACzH,aAAa,CAAC,UAAS,gBAAe,aAAY,YAAY;AAAA,MAC9D,UAAU;AAAA,IACX;AAAA,EACA;AAAA,EACD,OAAO,OAAO;AAAE,QAAI,SAAS,MAAM,IAAI;AAAE,WAAK,KAAK,OAAO,MAAM,EAAE;AAAA,IAAE;AAAA,EAAG;AAAA,EACvE,SAAS;AAAA,IACR,cAAc,GAAE;AAAE,WAAK,WAAW,OAAO,EAAE,OAAO,KAAK;AAAG,WAAK,KAAK,aAAa,KAAK,YAAY,KAAK,QAAQ;AAAA,IAAG;AAAA,IAClH,MAAM,OAAO;AACZ,UAAI,CAAC,KAAK,KAAK;AAAM,eAAOA,cAAG,MAAC,UAAU,EAAE,OAAM,WAAW,MAAK,QAAQ;AAC1E,UAAI;AACH,YAAI,KAAK;AAAI,gBAAMC,YAAAA,IAAI,kBAAkB,KAAK,EAAE,IAAI,KAAK,IAAI;AAAA;AACxD,gBAAMC,iBAAK,kBAAkB,KAAK,IAAI;AAC3CF,sBAAG,MAAC,UAAU,EAAE,OAAM,QAAQ,MAAK,WAAW;AAC9C,mBAAW,MAAMA,cAAAA,MAAI,aAAY,GAAI,GAAG;AAAA,MACzC,SAAQ,GAAG;AAAEA,sBAAG,MAAC,UAAU,EAAE,QAAO,uBAAG,YAAW,QAAQ,MAAK,OAAQ,CAAA;AAAA,MAAE;AAAA,IAC1E;AAAA,EACD;AACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC5CA,GAAG,WAAW,eAAe;"} {"version":3,"file":"form.js","sources":["pages/customer/form.vue","../../../../Downloads/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvY3VzdG9tZXIvZm9ybS52dWU"],"sourcesContent":["<template>\r\n\t<view class=\"page\">\r\n\t\t<view class=\"field\"><text class=\"label\">客户名称</text><input class=\"value\" v-model=\"form.name\" placeholder=\"必填\" /></view>\r\n\t\t<view class=\"field\"><text class=\"label\">客户等级</text><input class=\"value\" v-model=\"form.level\" placeholder=\"可选,如 VIP/A/B\" /></view>\r\n <view class=\"field\">\r\n <text class=\"label\">售价档位</text>\r\n <picker :range=\"priceLabels\" :value=\"priceIdx\" @change=\"onPriceChange\">\r\n <view class=\"value\">{{ priceLabels[priceIdx] }}</view>\r\n </picker>\r\n </view>\r\n\t\t<view class=\"field\"><text class=\"label\">联系人</text><input class=\"value\" v-model=\"form.contactName\" placeholder=\"可选\" /></view>\r\n\t\t<view class=\"field\"><text class=\"label\">手机</text><input class=\"value\" v-model=\"form.mobile\" placeholder=\"可选\" /></view>\r\n\t\t<view class=\"field\"><text class=\"label\">电话</text><input class=\"value\" v-model=\"form.phone\" placeholder=\"可选(座机)\" /></view>\r\n\t\t<view class=\"field\"><text class=\"label\">送货地址</text><input class=\"value\" v-model=\"form.address\" placeholder=\"可选\" /></view>\r\n\t\t<view class=\"field\"><text class=\"label\">初始应收</text><input class=\"value\" type=\"digit\" v-model.number=\"form.arOpening\" placeholder=\"默认 0.00\" /></view>\r\n\t\t<view class=\"textarea\"><textarea v-model=\"form.remark\" maxlength=\"200\" placeholder=\"备注最多200字\"></textarea></view>\r\n\r\n\t\t<view class=\"bottom\"><button class=\"primary\" @click=\"save\">保存</button></view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\nimport { post, put } from '../../common/http.js'\r\nexport default {\r\n\tdata() {\r\n\t\treturn {\r\n\t\t\tid: null,\r\n\t\t\tform: { name:'', level:'', priceLevel:'retail', contactName:'', mobile:'', phone:'', address:'', arOpening:0, remark:'' },\r\n priceLevels: ['零售价','批发价','大单报价'],\r\n priceLabels: ['零售价','批发价','大单报价'],\r\n\t\t\tpriceIdx: 0\r\n\t\t}\r\n\t},\r\n\tonLoad(query) { if (query && query.id) { this.id = Number(query.id) } },\r\n\tmethods: {\r\n onPriceChange(e){ this.priceIdx = Number(e.detail.value); this.form.priceLevel = this.priceLevels[this.priceIdx] },\r\n\t\tasync save() {\r\n\t\t\tif (!this.form.name) return uni.showToast({ title:'请填写客户名称', icon:'none' })\r\n\t\t\ttry {\r\n\t\t\t\tif (this.id) await put(`/api/customers/${this.id}`, this.form)\r\n\t\t\t\telse await post('/api/customers', this.form)\r\n\t\t\t\tuni.showToast({ title:'保存成功', icon:'success' })\r\n\t\t\t\tsetTimeout(() => uni.navigateBack(), 500)\r\n\t\t\t} catch(e) { uni.showToast({ title: e?.message || '保存失败', icon:'none' }) }\r\n\t\t}\r\n\t}\r\n}\r\n</script>\r\n\r\n<style>\r\n.page { padding-bottom: 140rpx; }\r\n.field { display:flex; justify-content: space-between; padding: 22rpx 24rpx; background:#fff; border-bottom:1rpx solid #eee; }\r\n.label { color:#666; }\r\n.value { color:#333; text-align: right; flex: 1; }\r\n.textarea { padding: 16rpx 24rpx; background:#fff; margin-top: 12rpx; }\r\n.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); }\r\n.primary { width: 100%; background: linear-gradient(135deg, #A0E4FF 0%, #17A2C4 100%); color:#fff; border-radius: 999rpx; padding: 20rpx 0; }\r\n</style>\r\n\r\n\r\n","import MiniProgramPage from 'C:/Users/21826/Desktop/Wj/PartsInquiry/frontend/pages/customer/form.vue'\nwx.createPage(MiniProgramPage)"],"names":["uni","put","post"],"mappings":";;;AAuBA,MAAK,YAAU;AAAA,EACd,OAAO;AACN,WAAO;AAAA,MACN,IAAI;AAAA,MACJ,MAAM,EAAE,MAAK,IAAI,OAAM,IAAI,YAAW,UAAU,aAAY,IAAI,QAAO,IAAI,OAAM,IAAI,SAAQ,IAAI,WAAU,GAAG,QAAO,GAAI;AAAA,MACtH,aAAa,CAAC,OAAM,OAAM,MAAM;AAAA,MAChC,aAAa,CAAC,OAAM,OAAM,MAAM;AAAA,MACnC,UAAU;AAAA,IACX;AAAA,EACA;AAAA,EACD,OAAO,OAAO;AAAE,QAAI,SAAS,MAAM,IAAI;AAAE,WAAK,KAAK,OAAO,MAAM,EAAE;AAAA,IAAE;AAAA,EAAG;AAAA,EACvE,SAAS;AAAA,IACN,cAAc,GAAE;AAAE,WAAK,WAAW,OAAO,EAAE,OAAO,KAAK;AAAG,WAAK,KAAK,aAAa,KAAK,YAAY,KAAK,QAAQ;AAAA,IAAG;AAAA,IACpH,MAAM,OAAO;AACZ,UAAI,CAAC,KAAK,KAAK;AAAM,eAAOA,cAAG,MAAC,UAAU,EAAE,OAAM,WAAW,MAAK,QAAQ;AAC1E,UAAI;AACH,YAAI,KAAK;AAAI,gBAAMC,YAAAA,IAAI,kBAAkB,KAAK,EAAE,IAAI,KAAK,IAAI;AAAA;AACxD,gBAAMC,iBAAK,kBAAkB,KAAK,IAAI;AAC3CF,sBAAG,MAAC,UAAU,EAAE,OAAM,QAAQ,MAAK,WAAW;AAC9C,mBAAW,MAAMA,cAAAA,MAAI,aAAY,GAAI,GAAG;AAAA,MACzC,SAAQ,GAAG;AAAEA,sBAAG,MAAC,UAAU,EAAE,QAAO,uBAAG,YAAW,QAAQ,MAAK,OAAQ,CAAA;AAAA,MAAE;AAAA,IAC1E;AAAA,EACD;AACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC7CA,GAAG,WAAW,eAAe;"}

View File

@@ -1 +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\t<button size=\"mini\" :type=\"debtOnly ? 'primary' : 'default'\" @click=\"toggleDebtOnly\">只看欠款</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\">\r\n\t\t\t\t\t{{ c.mobile || '—' }}\r\n\t\t\t\t\t<text v-if=\"typeof c.receivable === 'number'\">|应收:¥ {{ Number(c.receivable).toFixed(2) }}</text>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t</scroll-view>\r\n\t\t<view class=\"bottom\">\r\n\t\t\t<button class=\"primary\" @click=\"createCustomer\">新增客户</button>\r\n\t\t</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: '', debtOnly: false, customers: [] } },\r\n\t\tonLoad() { this.search() },\r\n\t\tmethods: {\r\n\t\t\ttoggleDebtOnly() { this.debtOnly = !this.debtOnly; this.search() },\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, debtOnly: this.debtOnly, 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\tcreateCustomer() { uni.navigateTo({ url: '/pages/customer/form' }) },\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; align-items:center; }\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\t.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); }\r\n\t.primary { width: 100%; background: linear-gradient(135deg, #A0E4FF 0%, #17A2C4 100%); color:#fff; border-radius: 999rpx; padding: 20rpx 0; }\r\n</style>\r\n\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":";;;AAwBC,MAAK,YAAU;AAAA,EACd,OAAO;AAAE,WAAO,EAAE,IAAI,IAAI,UAAU,OAAO,WAAW,CAAA;EAAM;AAAA,EAC5D,SAAS;AAAE,SAAK;EAAU;AAAA,EAC1B,SAAS;AAAA,IACR,iBAAiB;AAAE,WAAK,WAAW,CAAC,KAAK;AAAU,WAAK;IAAU;AAAA,IAClE,MAAM,SAAS;AACd,UAAI;AACH,cAAM,MAAM,MAAMA,YAAG,IAAC,kBAAkB,EAAE,IAAI,KAAK,IAAI,UAAU,KAAK,UAAU,MAAM,GAAG,MAAM,IAAI;AACnG,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,iBAAiB;AAAEA,oBAAAA,MAAI,WAAW,EAAE,KAAK,uBAAqB,CAAG;AAAA,IAAG;AAAA,IACpE,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;;;;;;;;;;;;;;;;;;;;;;;;;AC5CD,GAAG,WAAW,eAAe;"} {"version":3,"file":"select.js","sources":["pages/customer/select.vue","../../../../Downloads/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvY3VzdG9tZXIvc2VsZWN0LnZ1ZQ"],"sourcesContent":["<template>\n\t<view class=\"page\">\n\t\t<view class=\"search\">\n\t\t\t<input v-model=\"kw\" placeholder=\"搜索客户名称/电话\" @confirm=\"search\" />\n\t\t\t<button size=\"mini\" @click=\"search\">搜索</button>\n\t\t\t<button size=\"mini\" :type=\"debtOnly ? 'primary' : 'default'\" @click=\"toggleDebtOnly\">只看欠款</button>\n\t\t</view>\n\t\t<scroll-view scroll-y class=\"list\">\n\t\t\t<view class=\"item\" v-for=\"c in customers\" :key=\"c.id\" @click=\"openDetail(c)\">\n\t\t\t\t<view class=\"name\">{{ c.name }}</view>\n\t\t\t\t<view class=\"meta\">\n\t\t\t\t\t{{ c.mobile || '—' }}\n\t\t\t\t\t<text v-if=\"typeof c.receivable === 'number'\">|应收:¥ {{ Number(c.receivable).toFixed(2) }}</text>\n\t\t\t\t</view>\n\t\t\t</view>\n\t\t</scroll-view>\n\t\t<view class=\"bottom\">\n\t\t\t<button class=\"primary\" @click=\"createCustomer\">新增客户</button>\n\t\t</view>\n\t</view>\n</template>\n\n<script>\n\timport { get } from '../../common/http.js'\n\texport default {\n\t\tdata() { return { kw: '', debtOnly: false, customers: [] } },\n\t\tonLoad() { this.search() },\n\t\tonShow() { this.search() },\n\t\tmethods: {\n\t\t\ttoggleDebtOnly() { this.debtOnly = !this.debtOnly; this.search() },\n\t\t\tasync search() {\n\t\t\t\ttry {\n\t\t\t\t\tconst res = await get('/api/customers', { kw: this.kw, debtOnly: this.debtOnly, page: 1, size: 50 })\n\t\t\t\t\tthis.customers = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])\n\t\t\t\t} catch(e) { uni.showToast({ title: '加载失败', icon: 'none' }) }\n\t\t\t},\n\t\t\tcreateCustomer() { uni.navigateTo({ url: '/pages/customer/form' }) },\n\t\t\tselect(c) {\n\t\t\t\tconst pages = getCurrentPages()\n\t\t\t\tconst prev = pages.length >= 2 ? pages[pages.length - 2] : null\n\t\t\t\tconst vm = prev && prev.$vm ? prev.$vm : null\n\t\t\t\tif (vm && vm.order) {\n\t\t\t\t\tvm.order.customerId = c.id\n\t\t\t\t\tvm.customerName = c.name\n\t\t\t\t}\n\t\t\t\tuni.navigateBack()\n\t\t\t}\n\t\t\t,\n\t\t\topenDetail(c) { uni.navigateTo({ url: '/pages/customer/detail?id=' + c.id }) }\n\t\t}\n\t}\n</script>\n\n<style>\n\t.page { display:flex; flex-direction: column; height: 100vh; }\n\t.search { display:flex; gap: 12rpx; padding: 16rpx; background:#fff; align-items:center; }\n\t.search input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx; }\n\t.list { flex:1; }\n\t.item { padding: 20rpx 24rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1; }\n\t.name { color:#333; margin-bottom: 6rpx; }\n\t.meta { color:#888; font-size: 24rpx; }\n\t.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); }\n\t.primary { width: 100%; background: linear-gradient(135deg, #A0E4FF 0%, #17A2C4 100%); color:#fff; border-radius: 999rpx; padding: 20rpx 0; }\n</style>\n\n\n\n","import MiniProgramPage from 'C:/Users/21826/Desktop/Wj/PartsInquiry/frontend/pages/customer/select.vue'\nwx.createPage(MiniProgramPage)"],"names":["get","uni"],"mappings":";;;AAwBC,MAAK,YAAU;AAAA,EACd,OAAO;AAAE,WAAO,EAAE,IAAI,IAAI,UAAU,OAAO,WAAW,CAAA;EAAM;AAAA,EAC5D,SAAS;AAAE,SAAK;EAAU;AAAA,EAC1B,SAAS;AAAE,SAAK;EAAU;AAAA,EAC1B,SAAS;AAAA,IACR,iBAAiB;AAAE,WAAK,WAAW,CAAC,KAAK;AAAU,WAAK;IAAU;AAAA,IAClE,MAAM,SAAS;AACd,UAAI;AACH,cAAM,MAAM,MAAMA,YAAG,IAAC,kBAAkB,EAAE,IAAI,KAAK,IAAI,UAAU,KAAK,UAAU,MAAM,GAAG,MAAM,IAAI;AACnG,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,iBAAiB;AAAEA,oBAAAA,MAAI,WAAW,EAAE,KAAK,uBAAqB,CAAG;AAAA,IAAG;AAAA,IACpE,OAAO,GAAG;AACT,YAAM,QAAQ,gBAAgB;AAC9B,YAAM,OAAO,MAAM,UAAU,IAAI,MAAM,MAAM,SAAS,CAAC,IAAI;AAC3D,YAAM,KAAK,QAAQ,KAAK,MAAM,KAAK,MAAM;AACzC,UAAI,MAAM,GAAG,OAAO;AACnB,WAAG,MAAM,aAAa,EAAE;AACxB,WAAG,eAAe,EAAE;AAAA,MACrB;AACAA,oBAAAA,MAAI,aAAa;AAAA,IAClB;AAAA,IAEA,WAAW,GAAG;AAAEA,oBAAAA,MAAI,WAAW,EAAE,KAAK,+BAA+B,EAAE,GAAC,CAAG;AAAA,IAAE;AAAA,EAC9E;AACD;;;;;;;;;;;;;;;;;;;;;;;;;ACjDD,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

@@ -1 +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.brand||'') + ' ' + (p.model||'') + ' ' + (p.spec||'') }} · 库存:{{ 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\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;"} {"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.brand||'') + ' ' + (p.model||'') + ' ' + (p.spec||'') }} · 库存:{{ 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\tconst initPrice = Number((p.retailPrice != null ? p.retailPrice : (p.price || 0)))\r\n\t\t\t\t\topener.$vm.items.push({ productId: p.id, productName: p.name, quantity: 1, unitPrice: initPrice, _autoPrice: true })\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\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,cAAM,YAAY,OAAQ,EAAE,eAAe,OAAO,EAAE,cAAe,EAAE,SAAS,CAAG;AACjF,eAAO,IAAI,MAAM,KAAK,EAAE,WAAW,EAAE,IAAI,aAAa,EAAE,MAAM,UAAU,GAAG,WAAW,WAAW,YAAY,MAAM;AAAA,MACpH;AACAA,oBAAAA,MAAI,aAAa;AAAA,IAClB;AAAA,EACD;AACD;;;;;;;;;;;;;;;;;;;ACnCD,GAAG,WAAW,eAAe;"}

View File

@@ -0,0 +1 @@
{"version":3,"file":"form.js","sources":["pages/supplier/form.vue","../../../../Downloads/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvc3VwcGxpZXIvZm9ybS52dWU"],"sourcesContent":["<template>\r\n\t<view class=\"page\">\r\n\t\t<view class=\"field\"><text class=\"label\">供应商名称</text><input class=\"value\" v-model=\"form.name\" placeholder=\"必填\" /></view>\r\n\t\t<view class=\"field\"><text class=\"label\">联系人</text><input class=\"value\" v-model=\"form.contactName\" placeholder=\"可选\" /></view>\r\n\t\t<view class=\"field\"><text class=\"label\">手机</text><input class=\"value\" v-model=\"form.mobile\" placeholder=\"可选\" /></view>\r\n\t\t<view class=\"field\"><text class=\"label\">电话</text><input class=\"value\" v-model=\"form.phone\" placeholder=\"可选(座机)\" /></view>\r\n\t\t<view class=\"field\"><text class=\"label\">经营地址</text><input class=\"value\" v-model=\"form.address\" placeholder=\"可选\" /></view>\r\n\t\t<view class=\"field\"><text class=\"label\">初始应付款</text><input class=\"value\" type=\"digit\" v-model.number=\"form.apOpening\" placeholder=\"默认 0.00\" /></view>\r\n\t\t<view class=\"field\"><text class=\"label\">应付款</text><input class=\"value\" type=\"digit\" v-model.number=\"form.apPayable\" placeholder=\"默认 0.00\" /></view>\r\n\t\t<view class=\"textarea\"><textarea v-model=\"form.remark\" maxlength=\"200\" placeholder=\"备注最多200字\"></textarea></view>\r\n\r\n\t\t<view class=\"bottom\"><button class=\"primary\" @click=\"save\">保存</button></view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\nimport { post, put } from '../../common/http.js'\r\nexport default {\r\n\tdata() {\r\n\t\treturn {\r\n\t\t\tid: null,\r\n\t\t\tform: { name:'', contactName:'', mobile:'', phone:'', address:'', apOpening:0, apPayable:0, remark:'' }\r\n\t\t}\r\n\t},\r\n\tonLoad(query) { if (query && query.id) { this.id = Number(query.id) } },\r\n\tmethods: {\r\n\t\tasync save() {\r\n\t\t\tif (!this.form.name) return uni.showToast({ title:'请填写供应商名称', icon:'none' })\r\n\t\t\ttry {\r\n\t\t\t\tif (this.id) await put(`/api/suppliers/${this.id}`, this.form)\r\n\t\t\t\telse await post('/api/suppliers', this.form)\r\n\t\t\t\tuni.showToast({ title:'保存成功', icon:'success' })\r\n\t\t\t\tsetTimeout(() => uni.navigateBack(), 500)\r\n\t\t\t} catch(e) { uni.showToast({ title: e?.message || '保存失败', icon:'none' }) }\r\n\t\t}\r\n\t}\r\n}\r\n</script>\r\n\r\n<style>\r\n.page { padding-bottom: 140rpx; }\r\n.field { display:flex; justify-content: space-between; padding: 22rpx 24rpx; background:#fff; border-bottom:1rpx solid #eee; }\r\n.label { color:#666; }\r\n.value { color:#333; text-align: right; flex: 1; }\r\n.textarea { padding: 16rpx 24rpx; background:#fff; margin-top: 12rpx; }\r\n.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); }\r\n.primary { width: 100%; background: linear-gradient(135deg, #A0E4FF 0%, #17A2C4 100%); color:#fff; border-radius: 999rpx; padding: 20rpx 0; }\r\n</style>\r\n\r\n\r\n","import MiniProgramPage from 'C:/Users/21826/Desktop/Wj/PartsInquiry/frontend/pages/supplier/form.vue'\nwx.createPage(MiniProgramPage)"],"names":["uni","put","post"],"mappings":";;;AAiBA,MAAK,YAAU;AAAA,EACd,OAAO;AACN,WAAO;AAAA,MACN,IAAI;AAAA,MACJ,MAAM,EAAE,MAAK,IAAI,aAAY,IAAI,QAAO,IAAI,OAAM,IAAI,SAAQ,IAAI,WAAU,GAAG,WAAU,GAAG,QAAO,GAAG;AAAA,IACvG;AAAA,EACA;AAAA,EACD,OAAO,OAAO;AAAE,QAAI,SAAS,MAAM,IAAI;AAAE,WAAK,KAAK,OAAO,MAAM,EAAE;AAAA,IAAE;AAAA,EAAG;AAAA,EACvE,SAAS;AAAA,IACR,MAAM,OAAO;AACZ,UAAI,CAAC,KAAK,KAAK;AAAM,eAAOA,cAAG,MAAC,UAAU,EAAE,OAAM,YAAY,MAAK,QAAQ;AAC3E,UAAI;AACH,YAAI,KAAK;AAAI,gBAAMC,YAAAA,IAAI,kBAAkB,KAAK,EAAE,IAAI,KAAK,IAAI;AAAA;AACxD,gBAAMC,iBAAK,kBAAkB,KAAK,IAAI;AAC3CF,sBAAG,MAAC,UAAU,EAAE,OAAM,QAAQ,MAAK,WAAW;AAC9C,mBAAW,MAAMA,cAAAA,MAAI,aAAY,GAAI,GAAG;AAAA,MACzC,SAAQ,GAAG;AAAEA,sBAAG,MAAC,UAAU,EAAE,QAAO,uBAAG,YAAW,QAAQ,MAAK,OAAQ,CAAA;AAAA,MAAE;AAAA,IAC1E;AAAA,EACD;AACD;;;;;;;;;;;;;;;;;;;;;;;;;;;ACnCA,GAAG,WAAW,eAAe;"}

View File

@@ -1 +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\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;"} {"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\t<button size=\"mini\" :type=\"debtOnly ? 'primary' : 'default'\" @click=\"toggleDebtOnly\">只看欠款</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\">\r\n\t\t\t\t\t{{ s.mobile || '—' }}\r\n\t\t\t\t\t<text v-if=\"typeof s.apPayable === 'number'\">|应付:¥ {{ Number(s.apPayable).toFixed(2) }}</text>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t</scroll-view>\r\n\t\t<view class=\"bottom\">\r\n\t\t\t<button class=\"primary\" @click=\"createSupplier\">新增供应商</button>\r\n\t\t</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 data() { return { kw: '', debtOnly: false, suppliers: [] } },\r\n\t\tonLoad() { this.search() },\r\n\t\tmethods: {\r\n\t\t\ttoggleDebtOnly() { this.debtOnly = !this.debtOnly; this.search() },\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, debtOnly: this.debtOnly, 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\tcreateSupplier() { uni.navigateTo({ url: '/pages/supplier/form' }) },\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\r\n","import MiniProgramPage from 'C:/Users/21826/Desktop/Wj/PartsInquiry/frontend/pages/supplier/select.vue'\nwx.createPage(MiniProgramPage)"],"names":["get","uni"],"mappings":";;;AAwBC,MAAK,YAAU;AAAA,EACZ,OAAO;AAAE,WAAO,EAAE,IAAI,IAAI,UAAU,OAAO,WAAW,CAAA;EAAM;AAAA,EAC9D,SAAS;AAAE,SAAK;EAAU;AAAA,EAC1B,SAAS;AAAA,IACR,iBAAiB;AAAE,WAAK,WAAW,CAAC,KAAK;AAAU,WAAK;IAAU;AAAA,IAClE,MAAM,SAAS;AACd,UAAI;AACH,cAAM,MAAM,MAAMA,YAAG,IAAC,kBAAkB,EAAE,IAAI,KAAK,IAAI,UAAU,KAAK,UAAU,MAAM,GAAG,MAAM,IAAI;AACnG,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,iBAAiB;AAAEA,oBAAAA,MAAI,WAAW,EAAE,KAAK,uBAAqB,CAAG;AAAA,IAAG;AAAA,IACpE,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;;;;;;;;;;;;;;;;;;;;;;;;;AC5CD,GAAG,WAAW,eAAe;"}

View File

@@ -12,7 +12,9 @@ if (!Math) {
"./pages/product/settings.js"; "./pages/product/settings.js";
"./pages/customer/select.js"; "./pages/customer/select.js";
"./pages/customer/form.js"; "./pages/customer/form.js";
"./pages/customer/detail.js";
"./pages/supplier/select.js"; "./pages/supplier/select.js";
"./pages/supplier/form.js";
"./pages/account/select.js"; "./pages/account/select.js";
"./pages/detail/index.js"; "./pages/detail/index.js";
} }

View File

@@ -10,7 +10,9 @@
"pages/product/settings", "pages/product/settings",
"pages/customer/select", "pages/customer/select",
"pages/customer/form", "pages/customer/form",
"pages/customer/detail",
"pages/supplier/select", "pages/supplier/select",
"pages/supplier/form",
"pages/account/select", "pages/account/select",
"pages/detail/index" "pages/detail/index"
], ],

View File

@@ -15,9 +15,11 @@ function requestWithFallback(options, candidates, idx, resolve, reject) {
const { statusCode, data } = res; const { statusCode, data } = res;
if (statusCode >= 200 && statusCode < 300) if (statusCode >= 200 && statusCode < 300)
return resolve(data); return resolve(data);
const msg = data && (data.message || data.error || data.msg) || "HTTP " + statusCode;
common_vendor.index.showToast({ title: msg, icon: "none" });
if (idx + 1 < candidates.length) if (idx + 1 < candidates.length)
return requestWithFallback(options, candidates, idx + 1, resolve, reject); return requestWithFallback(options, candidates, idx + 1, resolve, reject);
reject(new Error("HTTP " + statusCode)); reject(new Error(msg));
}, fail: (err) => { }, fail: (err) => {
if (idx + 1 < candidates.length) if (idx + 1 < candidates.length)
return requestWithFallback(options, candidates, idx + 1, resolve, reject); return requestWithFallback(options, candidates, idx + 1, resolve, reject);

View File

@@ -0,0 +1,156 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
const _sfc_main = {
data() {
return { id: null, d: {}, editing: false, form: { name: "", contactName: "", mobile: "", phone: "", address: "", level: "", priceLevel: "零售价", arOpening: 0, remark: "" }, priceLevels: ["零售价", "批发价", "大单报价"], priceLabels: ["零售价", "批发价", "大单报价"], priceIdx: 0 };
},
onLoad(q) {
if (q && q.id) {
this.id = Number(q.id);
this.fetch();
}
},
methods: {
async fetch() {
try {
this.d = await common_http.get(`/api/customers/${this.id}`);
this.form = {
name: this.d.name || "",
contactName: this.d.contactName || "",
mobile: this.d.mobile || "",
phone: this.d.phone || "",
address: this.d.address || "",
level: this.d.level || "",
priceLevel: this.d.priceLevel || "retail",
arOpening: Number(this.d.arOpening || 0),
remark: this.d.remark || ""
};
const idx = this.priceLevels.indexOf(this.form.priceLevel);
this.priceIdx = idx >= 0 ? idx : 0;
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
}
},
toggleEdit() {
this.editing = !this.editing;
},
onPriceChange(e) {
this.priceIdx = Number(e.detail.value);
this.form.priceLevel = this.priceLevels[this.priceIdx];
},
choose() {
const pages = getCurrentPages();
let targetIdx = -1;
for (let i = pages.length - 2; i >= 0; i--) {
const vm = pages[i] && pages[i].$vm ? pages[i].$vm : null;
if (vm && vm.order) {
vm.order.customerId = this.d.id;
vm.customerName = this.d.name;
targetIdx = i;
break;
}
}
if (targetIdx >= 0) {
const delta = pages.length - 1 - targetIdx;
common_vendor.index.navigateBack({ delta });
} else {
common_vendor.index.navigateBack();
}
},
async save() {
if (!this.form.name)
return common_vendor.index.showToast({ title: "请填写客户名称", icon: "none" });
try {
await common_http.put(`/api/customers/${this.id}`, this.form);
common_vendor.index.showToast({ title: "已保存", icon: "success" });
this.editing = false;
await this.fetch();
} catch (e) {
common_vendor.index.showToast({ title: (e == null ? void 0 : e.message) || "保存失败", icon: "none" });
}
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return common_vendor.e({
a: !$data.editing
}, !$data.editing ? {
b: common_vendor.t($data.d.name)
} : {
c: $data.form.name,
d: common_vendor.o(($event) => $data.form.name = $event.detail.value)
}, {
e: !$data.editing
}, !$data.editing ? {
f: common_vendor.t($data.d.contactName || "—")
} : {
g: $data.form.contactName,
h: common_vendor.o(($event) => $data.form.contactName = $event.detail.value)
}, {
i: !$data.editing
}, !$data.editing ? {
j: common_vendor.t($data.d.mobile || "—")
} : {
k: $data.form.mobile,
l: common_vendor.o(($event) => $data.form.mobile = $event.detail.value)
}, {
m: !$data.editing
}, !$data.editing ? {
n: common_vendor.t($data.d.phone || "—")
} : {
o: $data.form.phone,
p: common_vendor.o(($event) => $data.form.phone = $event.detail.value)
}, {
q: !$data.editing
}, !$data.editing ? {
r: common_vendor.t($data.d.address || "—")
} : {
s: $data.form.address,
t: common_vendor.o(($event) => $data.form.address = $event.detail.value)
}, {
v: !$data.editing
}, !$data.editing ? {
w: common_vendor.t($data.d.level || "—")
} : {
x: $data.form.level,
y: common_vendor.o(($event) => $data.form.level = $event.detail.value)
}, {
z: !$data.editing
}, !$data.editing ? {
A: common_vendor.t($data.d.priceLevel)
} : {
B: common_vendor.t($data.priceLabels[$data.priceIdx]),
C: $data.priceLabels,
D: $data.priceIdx,
E: common_vendor.o((...args) => $options.onPriceChange && $options.onPriceChange(...args))
}, {
F: !$data.editing
}, !$data.editing ? {
G: common_vendor.t(Number($data.d.arOpening || 0).toFixed(2))
} : {
H: $data.form.arOpening,
I: common_vendor.o(common_vendor.m(($event) => $data.form.arOpening = $event.detail.value, {
number: true
}))
}, {
J: common_vendor.t(Number($data.d.receivable || 0).toFixed(2)),
K: !$data.editing
}, !$data.editing ? {
L: common_vendor.t($data.d.remark || "—")
} : {
M: $data.form.remark,
N: common_vendor.o(($event) => $data.form.remark = $event.detail.value)
}, {
O: common_vendor.t($data.editing ? "取消" : "编辑"),
P: common_vendor.o((...args) => $options.toggleEdit && $options.toggleEdit(...args)),
Q: $data.editing
}, $data.editing ? {
R: common_vendor.o((...args) => $options.save && $options.save(...args))
} : {
S: common_vendor.o((...args) => $options.choose && $options.choose(...args))
});
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/customer/detail.js.map

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "客户详情",
"usingComponents": {}
}

View File

@@ -0,0 +1 @@
<view class="page"><view class="card"><view class="row"><text class="label">名称</text><text wx:if="{{a}}" class="value">{{b}}</text><input wx:else class="value-input" placeholder="必填" value="{{c}}" bindinput="{{d}}"/></view><view class="row"><text class="label">联系人</text><text wx:if="{{e}}" class="value">{{f}}</text><input wx:else class="value-input" placeholder="可选" value="{{g}}" bindinput="{{h}}"/></view><view class="row"><text class="label">手机</text><text wx:if="{{i}}" class="value">{{j}}</text><input wx:else class="value-input" placeholder="可选" value="{{k}}" bindinput="{{l}}"/></view><view class="row"><text class="label">电话</text><text wx:if="{{m}}" class="value">{{n}}</text><input wx:else class="value-input" placeholder="可选(座机)" value="{{o}}" bindinput="{{p}}"/></view><view class="row"><text class="label">地址</text><text wx:if="{{q}}" class="value">{{r}}</text><input wx:else class="value-input" placeholder="可选" value="{{s}}" bindinput="{{t}}"/></view><view class="row"><text class="label">等级</text><text wx:if="{{v}}" class="value">{{w}}</text><input wx:else class="value-input" placeholder="可选,如 VIP/A/B" value="{{x}}" bindinput="{{y}}"/></view><view class="row"><text class="label">售价档位</text><text wx:if="{{z}}" class="value">{{A}}</text><picker wx:else range="{{C}}" value="{{D}}" bindchange="{{E}}"><view class="value">{{B}}</view></picker></view><view class="row"><text class="label">初始应收</text><text wx:if="{{F}}" class="value">¥ {{G}}</text><input wx:else class="value-input" type="digit" placeholder="0.00" value="{{H}}" bindinput="{{I}}"/></view><view class="row"><text class="label">当前应收</text><text class="value emp">¥ {{J}}</text></view><view class="row"><text class="label">备注</text><text wx:if="{{K}}" class="value">{{L}}</text><input wx:else class="value-input" placeholder="—" value="{{M}}" bindinput="{{N}}"/></view></view><view class="bottom"><button class="ghost" bindtap="{{P}}">{{O}}</button><button wx:if="{{Q}}" class="primary" bindtap="{{R}}">保存</button><button wx:else class="primary" bindtap="{{S}}">选择此客户</button></view></view>

View File

@@ -0,0 +1,23 @@
.page { padding-bottom: 140rpx;
}
.card { background:#fff; margin: 16rpx; padding: 12rpx 16rpx; border-radius: 16rpx;
}
.row { display:flex; justify-content: space-between; padding: 18rpx 8rpx; border-bottom: 1rpx solid #f3f3f3;
}
.row:last-child { border-bottom: 0;
}
.label { color:#666;
}
.value { color:#333; max-width: 60%; text-align: right;
}
.value-input { color:#333; text-align: right; flex: 1;
}
.emp { color:#107e9b; font-weight: 700;
}
.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); display:flex; gap: 12rpx;
}
.primary { flex:1; background: linear-gradient(135deg, #A0E4FF 0%, #17A2C4 100%); color:#fff; border-radius: 999rpx; padding: 20rpx 0;
}
.ghost { flex:1; background:#fff; color:#107e9b; border: 2rpx solid #A0E4FF; border-radius: 999rpx; padding: 18rpx 0;
}

View File

@@ -6,7 +6,8 @@ const _sfc_main = {
return { return {
id: null, id: null,
form: { name: "", level: "", priceLevel: "retail", contactName: "", mobile: "", phone: "", address: "", arOpening: 0, remark: "" }, form: { name: "", level: "", priceLevel: "retail", contactName: "", mobile: "", phone: "", address: "", arOpening: 0, remark: "" },
priceLevels: ["retail", "distribution", "wholesale", "big_client"], priceLevels: ["零售价", "批发价", "大单报价"],
priceLabels: ["零售价", "批发价", "大单报价"],
priceIdx: 0 priceIdx: 0
}; };
}, },
@@ -42,8 +43,8 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
b: common_vendor.o(($event) => $data.form.name = $event.detail.value), b: common_vendor.o(($event) => $data.form.name = $event.detail.value),
c: $data.form.level, c: $data.form.level,
d: common_vendor.o(($event) => $data.form.level = $event.detail.value), d: common_vendor.o(($event) => $data.form.level = $event.detail.value),
e: common_vendor.t($data.priceLevels[$data.priceIdx]), e: common_vendor.t($data.priceLabels[$data.priceIdx]),
f: $data.priceLevels, f: $data.priceLabels,
g: $data.priceIdx, g: $data.priceIdx,
h: common_vendor.o((...args) => $options.onPriceChange && $options.onPriceChange(...args)), h: common_vendor.o((...args) => $options.onPriceChange && $options.onPriceChange(...args)),
i: $data.form.contactName, i: $data.form.contactName,

View File

@@ -8,6 +8,9 @@ const _sfc_main = {
onLoad() { onLoad() {
this.search(); this.search();
}, },
onShow() {
this.search();
},
methods: { methods: {
toggleDebtOnly() { toggleDebtOnly() {
this.debtOnly = !this.debtOnly; this.debtOnly = !this.debtOnly;
@@ -25,12 +28,17 @@ const _sfc_main = {
common_vendor.index.navigateTo({ url: "/pages/customer/form" }); common_vendor.index.navigateTo({ url: "/pages/customer/form" });
}, },
select(c) { select(c) {
const opener = getCurrentPages()[getCurrentPages().length - 2]; const pages = getCurrentPages();
if (opener && opener.$vm) { const prev = pages.length >= 2 ? pages[pages.length - 2] : null;
opener.$vm.order.customerId = c.id; const vm = prev && prev.$vm ? prev.$vm : null;
opener.$vm.customerName = c.name; if (vm && vm.order) {
vm.order.customerId = c.id;
vm.customerName = c.name;
} }
common_vendor.index.navigateBack(); common_vendor.index.navigateBack();
},
openDetail(c) {
common_vendor.index.navigateTo({ url: "/pages/customer/detail?id=" + c.id });
} }
} }
}; };
@@ -51,7 +59,7 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
d: common_vendor.t(Number(c.receivable).toFixed(2)) d: common_vendor.t(Number(c.receivable).toFixed(2))
} : {}, { } : {}, {
e: c.id, e: c.id,
f: common_vendor.o(($event) => $options.select(c), c.id) f: common_vendor.o(($event) => $options.openDetail(c), c.id)
}); });
}), }),
h: common_vendor.o((...args) => $options.createCustomer && $options.createCustomer(...args)) h: common_vendor.o((...args) => $options.createCustomer && $options.createCustomer(...args))

View File

@@ -67,6 +67,10 @@ const _sfc_main = {
common_vendor.index.navigateTo({ url: "/pages/customer/select" }); common_vendor.index.navigateTo({ url: "/pages/customer/select" });
return; return;
} }
if (item.key === "supplier") {
common_vendor.index.navigateTo({ url: "/pages/supplier/select" });
return;
}
common_vendor.index.showToast({ title: item.title + "(开发中)", icon: "none" }); common_vendor.index.showToast({ title: item.title + "(开发中)", icon: "none" });
}, },
goProduct() { goProduct() {
@@ -79,7 +83,7 @@ const _sfc_main = {
goDetail() { goDetail() {
this.activeTab = "detail"; this.activeTab = "detail";
try { try {
common_vendor.index.__f__("log", "at pages/index/index.vue:170", "[index] goDetail → /pages/detail/index"); common_vendor.index.__f__("log", "at pages/index/index.vue:174", "[index] goDetail → /pages/detail/index");
} catch (e) { } catch (e) {
} }
common_vendor.index.navigateTo({ url: "/pages/detail/index" }); common_vendor.index.navigateTo({ url: "/pages/detail/index" });

View File

@@ -22,6 +22,9 @@ const _sfc_main = {
remark: "" remark: ""
}, },
customerName: "", customerName: "",
customerPriceLevel: "零售价",
_lastCustomerId: null,
_priceCache: {},
supplierName: "", supplierName: "",
items: [], items: [],
activeCategory: "sale_income", activeCategory: "sale_income",
@@ -64,7 +67,70 @@ const _sfc_main = {
return Number(p.cash || 0) + Number(p.bank || 0) + Number(p.wechat || 0); return Number(p.cash || 0) + Number(p.bank || 0) + Number(p.wechat || 0);
} }
}, },
onShow() {
if (this.biz === "sale") {
if (this.order.customerId && this.order.customerId !== this._lastCustomerId) {
this.loadCustomerLevel(this.order.customerId).then(() => {
this._lastCustomerId = this.order.customerId;
for (const it of this.items) {
if (it && (it._autoPrice || !it.unitPrice))
this.autoPriceItem(it);
}
});
}
for (const it of this.items) {
if (it && !it.unitPrice)
this.autoPriceItem(it);
}
}
},
methods: { methods: {
async loadCustomerLevel(customerId) {
try {
const d = await common_http.get(`/api/customers/${customerId}`);
this.customerPriceLevel = d && d.priceLevel ? d.priceLevel : "零售价";
} catch (e) {
this.customerPriceLevel = "零售价";
}
},
priceFieldForLevel() {
const lvl = this.customerPriceLevel || "零售价";
if (lvl === "批发价")
return "wholesalePrice";
if (lvl === "大单报价")
return "bigClientPrice";
return "retailPrice";
},
async autoPriceItem(it) {
if (this.biz !== "sale")
return;
if (!it || !it.productId)
return;
const pid = it.productId;
let detail = this._priceCache[pid];
if (!detail) {
try {
detail = await common_http.get(`/api/products/${pid}`);
this._priceCache[pid] = detail;
} catch (e) {
return;
}
}
const field = this.priceFieldForLevel();
let price = Number(detail && detail[field] != null ? detail[field] : 0);
if (!price && field !== "retailPrice") {
price = Number(detail && detail.retailPrice != null ? detail.retailPrice : 0);
}
it.unitPrice = price;
it._autoPrice = true;
this.recalc();
},
onPriceInput(it) {
if (it) {
it._autoPrice = false;
this.recalc();
}
},
switchBiz(type) { switchBiz(type) {
this.biz = type; this.biz = type;
}, },
@@ -98,6 +164,17 @@ const _sfc_main = {
const isSaleOrPurchase = this.biz === "sale" || this.biz === "purchase"; const isSaleOrPurchase = this.biz === "sale" || this.biz === "purchase";
const isCollectOrPay = this.biz === "sale" && this.saleType === "collect" || this.biz === "purchase" && this.purchaseType === "pay"; const isCollectOrPay = this.biz === "sale" && this.saleType === "collect" || this.biz === "purchase" && this.purchaseType === "pay";
const saleTypeValue = this.biz === "sale" ? "sale." + this.saleType : "purchase." + this.purchaseType; const saleTypeValue = this.biz === "sale" ? "sale." + this.saleType : "purchase." + this.purchaseType;
if (isSaleOrPurchase && !isCollectOrPay) {
if (!this.items.length) {
common_vendor.index.showToast({ title: "请先选择商品", icon: "none" });
return;
}
const invalid = this.items.find((it) => !it.productId || Number(it.quantity || 0) <= 0);
if (invalid) {
common_vendor.index.showToast({ title: "数量需大于0", icon: "none" });
return;
}
}
const payload = isSaleOrPurchase ? isCollectOrPay ? [ const payload = isSaleOrPurchase ? isCollectOrPay ? [
{ method: "cash", amount: Number(this.payments.cash || 0) }, { method: "cash", amount: Number(this.payments.cash || 0) },
{ method: "bank", amount: Number(this.payments.bank || 0) }, { method: "bank", amount: Number(this.payments.bank || 0) },
@@ -244,7 +321,7 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
c: it.quantity, c: it.quantity,
d: common_vendor.o([common_vendor.m(($event) => it.unitPrice = $event.detail.value, { d: common_vendor.o([common_vendor.m(($event) => it.unitPrice = $event.detail.value, {
number: true number: true
}), ($event) => $options.recalc()], idx), }), ($event) => $options.onPriceInput(it)], idx),
e: it.unitPrice, e: it.unitPrice,
f: common_vendor.t((Number(it.quantity) * Number(it.unitPrice)).toFixed(2)), f: common_vendor.t((Number(it.quantity) * Number(it.unitPrice)).toFixed(2)),
g: idx g: idx

View File

@@ -20,7 +20,8 @@ const _sfc_main = {
select(p) { select(p) {
const opener = getCurrentPages()[getCurrentPages().length - 2]; const opener = getCurrentPages()[getCurrentPages().length - 2];
if (opener && opener.$vm && opener.$vm.items) { if (opener && opener.$vm && opener.$vm.items) {
opener.$vm.items.push({ productId: p.id, productName: p.name, quantity: 1, unitPrice: Number(p.price || 0) }); const initPrice = Number(p.retailPrice != null ? p.retailPrice : p.price || 0);
opener.$vm.items.push({ productId: p.id, productName: p.name, quantity: 1, unitPrice: initPrice, _autoPrice: true });
} }
common_vendor.index.navigateBack(); common_vendor.index.navigateBack();
} }

View File

@@ -0,0 +1,60 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
const _sfc_main = {
data() {
return {
id: null,
form: { name: "", contactName: "", mobile: "", phone: "", address: "", apOpening: 0, apPayable: 0, remark: "" }
};
},
onLoad(query) {
if (query && query.id) {
this.id = Number(query.id);
}
},
methods: {
async save() {
if (!this.form.name)
return common_vendor.index.showToast({ title: "请填写供应商名称", icon: "none" });
try {
if (this.id)
await common_http.put(`/api/suppliers/${this.id}`, this.form);
else
await common_http.post("/api/suppliers", this.form);
common_vendor.index.showToast({ title: "保存成功", icon: "success" });
setTimeout(() => common_vendor.index.navigateBack(), 500);
} catch (e) {
common_vendor.index.showToast({ title: (e == null ? void 0 : e.message) || "保存失败", icon: "none" });
}
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: $data.form.name,
b: common_vendor.o(($event) => $data.form.name = $event.detail.value),
c: $data.form.contactName,
d: common_vendor.o(($event) => $data.form.contactName = $event.detail.value),
e: $data.form.mobile,
f: common_vendor.o(($event) => $data.form.mobile = $event.detail.value),
g: $data.form.phone,
h: common_vendor.o(($event) => $data.form.phone = $event.detail.value),
i: $data.form.address,
j: common_vendor.o(($event) => $data.form.address = $event.detail.value),
k: $data.form.apOpening,
l: common_vendor.o(common_vendor.m(($event) => $data.form.apOpening = $event.detail.value, {
number: true
})),
m: $data.form.apPayable,
n: common_vendor.o(common_vendor.m(($event) => $data.form.apPayable = $event.detail.value, {
number: true
})),
o: $data.form.remark,
p: common_vendor.o(($event) => $data.form.remark = $event.detail.value),
q: common_vendor.o((...args) => $options.save && $options.save(...args))
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/supplier/form.js.map

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "新增/编辑供应商",
"usingComponents": {}
}

View File

@@ -0,0 +1 @@
<view class="page"><view class="field"><text class="label">供应商名称</text><input class="value" placeholder="必填" value="{{a}}" bindinput="{{b}}"/></view><view class="field"><text class="label">联系人</text><input class="value" placeholder="可选" value="{{c}}" bindinput="{{d}}"/></view><view class="field"><text class="label">手机</text><input class="value" placeholder="可选" value="{{e}}" bindinput="{{f}}"/></view><view class="field"><text class="label">电话</text><input class="value" placeholder="可选(座机)" value="{{g}}" bindinput="{{h}}"/></view><view class="field"><text class="label">经营地址</text><input class="value" placeholder="可选" value="{{i}}" bindinput="{{j}}"/></view><view class="field"><text class="label">初始应付款</text><input class="value" type="digit" placeholder="默认 0.00" value="{{k}}" bindinput="{{l}}"/></view><view class="field"><text class="label">应付款</text><input class="value" type="digit" placeholder="默认 0.00" value="{{m}}" bindinput="{{n}}"/></view><view class="textarea"><block wx:if="{{r0}}"><textarea maxlength="200" placeholder="备注最多200字" value="{{o}}" bindinput="{{p}}"></textarea></block></view><view class="bottom"><button class="primary" bindtap="{{q}}">保存</button></view></view>

View File

@@ -0,0 +1,15 @@
.page { padding-bottom: 140rpx;
}
.field { display:flex; justify-content: space-between; padding: 22rpx 24rpx; background:#fff; border-bottom:1rpx solid #eee;
}
.label { color:#666;
}
.value { color:#333; text-align: right; flex: 1;
}
.textarea { padding: 16rpx 24rpx; background:#fff; margin-top: 12rpx;
}
.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, #A0E4FF 0%, #17A2C4 100%); color:#fff; border-radius: 999rpx; padding: 20rpx 0;
}

View File

@@ -3,20 +3,27 @@ const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js"); const common_http = require("../../common/http.js");
const _sfc_main = { const _sfc_main = {
data() { data() {
return { kw: "", suppliers: [] }; return { kw: "", debtOnly: false, suppliers: [] };
}, },
onLoad() { onLoad() {
this.search(); this.search();
}, },
methods: { methods: {
toggleDebtOnly() {
this.debtOnly = !this.debtOnly;
this.search();
},
async search() { async search() {
try { try {
const res = await common_http.get("/api/suppliers", { kw: this.kw, page: 1, size: 50 }); const res = await common_http.get("/api/suppliers", { kw: this.kw, debtOnly: this.debtOnly, page: 1, size: 50 });
this.suppliers = Array.isArray(res == null ? void 0 : res.list) ? res.list : Array.isArray(res) ? res : []; this.suppliers = Array.isArray(res == null ? void 0 : res.list) ? res.list : Array.isArray(res) ? res : [];
} catch (e) { } catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" }); common_vendor.index.showToast({ title: "加载失败", icon: "none" });
} }
}, },
createSupplier() {
common_vendor.index.navigateTo({ url: "/pages/supplier/form" });
},
select(s) { select(s) {
const opener = getCurrentPages()[getCurrentPages().length - 2]; const opener = getCurrentPages()[getCurrentPages().length - 2];
if (opener && opener.$vm) { if (opener && opener.$vm) {
@@ -33,14 +40,21 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
b: $data.kw, b: $data.kw,
c: common_vendor.o(($event) => $data.kw = $event.detail.value), c: common_vendor.o(($event) => $data.kw = $event.detail.value),
d: common_vendor.o((...args) => $options.search && $options.search(...args)), d: common_vendor.o((...args) => $options.search && $options.search(...args)),
e: common_vendor.f($data.suppliers, (s, k0, i0) => { e: $data.debtOnly ? "primary" : "default",
return { f: common_vendor.o((...args) => $options.toggleDebtOnly && $options.toggleDebtOnly(...args)),
g: common_vendor.f($data.suppliers, (s, k0, i0) => {
return common_vendor.e({
a: common_vendor.t(s.name), a: common_vendor.t(s.name),
b: common_vendor.t(s.mobile || "—"), b: common_vendor.t(s.mobile || "—"),
c: s.id, c: typeof s.apPayable === "number"
d: common_vendor.o(($event) => $options.select(s), s.id) }, typeof s.apPayable === "number" ? {
}; d: common_vendor.t(Number(s.apPayable).toFixed(2))
}) } : {}, {
e: s.id,
f: common_vendor.o(($event) => $options.select(s), s.id)
});
}),
h: common_vendor.o((...args) => $options.createSupplier && $options.createSupplier(...args))
}; };
} }
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]); const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);

View File

@@ -1 +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 class="page"><view class="search"><input placeholder="搜索供应商名称/电话" bindconfirm="{{a}}" value="{{b}}" bindinput="{{c}}"/><button size="mini" bindtap="{{d}}">搜索</button><button size="mini" type="{{e}}" bindtap="{{f}}">只看欠款</button></view><scroll-view scroll-y class="list"><view wx:for="{{g}}" wx:for-item="s" wx:key="e" class="item" bindtap="{{s.f}}"><view class="name">{{s.a}}</view><view class="meta">{{s.b}} <text wx:if="{{s.c}}">|应付:¥ {{s.d}}</text></view></view></scroll-view><view class="bottom"><button class="primary" bindtap="{{h}}">新增供应商</button></view></view>

View File

@@ -21,11 +21,20 @@
3.开单(未全完成) 3.开单(未全完成)
9.18王德鹏2 9.18王德鹏2
前端1.客户功能 前端1.客户功能
后端1.客户功能√(未全完成) 后端1.客户功能√(未全完成)
数据库1.客户表更改 数据库1.客户表更改
9.18王德鹏3
前端1.供应商功能√
后端1.客户功能√
2.供应商功能√
3.开单功能(销售进货)√
※※我前端都只做了小程序的,就没看过安卓端,还需要做双端适配※※
前端问题:
1.都只做了小程序的,就没看过安卓端,还需要做双端适配
2.图标