9.18王德鹏/1

This commit is contained in:
2025-09-18 14:22:04 +08:00
parent a8dcee7296
commit 335e21347b
90 changed files with 1618 additions and 1346 deletions

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"java.compile.nullAnalysis.mode": "automatic"
}

View File

@@ -71,3 +71,6 @@ public class AttachmentController {

View File

@@ -30,3 +30,6 @@ public class AttachmentPlaceholderProperties {

View File

@@ -19,3 +19,6 @@ public class AppDefaultsProperties {

View File

@@ -0,0 +1,118 @@
package com.example.demo.order;
import com.example.demo.common.AppDefaultsProperties;
import com.example.demo.order.dto.OrderDtos;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class OrderController {
private final OrderService orderService;
private final AppDefaultsProperties defaults;
public OrderController(OrderService orderService, AppDefaultsProperties defaults) {
this.orderService = orderService;
this.defaults = defaults;
}
@PostMapping("/orders")
public ResponseEntity<?> createOrder(@RequestHeader(name = "X-Shop-Id", required = false) Long shopId,
@RequestHeader(name = "X-User-Id", required = false) Long userId,
@RequestBody OrderDtos.CreateOrderRequest req) {
Long sid = (shopId == null ? defaults.getShopId() : shopId);
Long uid = (userId == null ? defaults.getUserId() : userId);
return ResponseEntity.ok(orderService.create(sid, uid, req));
}
@PostMapping("/payments/{biz}")
public ResponseEntity<?> createPayments(@RequestHeader(name = "X-Shop-Id", required = false) Long shopId,
@RequestHeader(name = "X-User-Id", required = false) Long userId,
@PathVariable("biz") String biz,
@RequestBody java.util.List<OrderDtos.PaymentItem> req) {
Long sid = (shopId == null ? defaults.getShopId() : shopId);
Long uid = (userId == null ? defaults.getUserId() : userId);
return ResponseEntity.ok(orderService.createPayments(sid, uid, req, biz));
}
@PostMapping("/orders/{id}/void")
public ResponseEntity<?> voidOrder(@RequestHeader(name = "X-Shop-Id", required = false) Long shopId,
@RequestHeader(name = "X-User-Id", required = false) Long userId,
@PathVariable("id") Long id,
@RequestParam("type") String type) {
Long sid = (shopId == null ? defaults.getShopId() : shopId);
Long uid = (userId == null ? defaults.getUserId() : userId);
orderService.voidOrder(sid, uid, id, type);
return ResponseEntity.ok().build();
}
@GetMapping("/orders")
public ResponseEntity<?> list(@RequestHeader(name = "X-Shop-Id", required = false) Long shopId,
@RequestParam(name = "biz", required = false) String biz,
@RequestParam(name = "type", required = false) String type,
@RequestParam(name = "kw", required = false) String kw,
@RequestParam(name = "page", defaultValue = "1") int page,
@RequestParam(name = "size", defaultValue = "20") int size,
@RequestParam(name = "startDate", required = false) String startDate,
@RequestParam(name = "endDate", required = false) String endDate) {
Long sid = (shopId == null ? defaults.getShopId() : shopId);
return ResponseEntity.ok(orderService.list(sid, biz, type, kw, Math.max(0, page-1), size, startDate, endDate));
}
// 兼容前端直接调用 /api/purchase-orders
@GetMapping("/purchase-orders")
public ResponseEntity<?> purchaseOrders(@RequestHeader(name = "X-Shop-Id", required = false) Long shopId,
@RequestParam(name = "status", required = false) String status,
@RequestParam(name = "kw", required = false) String kw,
@RequestParam(name = "page", defaultValue = "1") int page,
@RequestParam(name = "size", defaultValue = "20") int size,
@RequestParam(name = "startDate", required = false) String startDate,
@RequestParam(name = "endDate", required = false) String endDate) {
Long sid = (shopId == null ? defaults.getShopId() : shopId);
String type = ("returned".equalsIgnoreCase(status) ? "purchase.return" : "purchase.in");
return ResponseEntity.ok(orderService.list(sid, "purchase", type, kw, Math.max(0, page-1), size, startDate, endDate));
}
@GetMapping("/payments")
public ResponseEntity<?> listPayments(@RequestHeader(name = "X-Shop-Id", required = false) Long shopId,
@RequestParam(name = "direction", required = false) String direction,
@RequestParam(name = "bizType", required = false) String bizType,
@RequestParam(name = "accountId", required = false) Long accountId,
@RequestParam(name = "kw", required = false) String kw,
@RequestParam(name = "page", defaultValue = "1") int page,
@RequestParam(name = "size", defaultValue = "20") int size,
@RequestParam(name = "startDate", required = false) String startDate,
@RequestParam(name = "endDate", required = false) String endDate) {
Long sid = (shopId == null ? defaults.getShopId() : shopId);
return ResponseEntity.ok(orderService.listPayments(sid, direction, bizType, accountId, kw, Math.max(0, page-1), size, startDate, endDate));
}
@GetMapping("/other-transactions")
public ResponseEntity<?> listOtherTransactions(@RequestHeader(name = "X-Shop-Id", required = false) Long shopId,
@RequestParam(name = "type", required = false) String type,
@RequestParam(name = "accountId", required = false) Long accountId,
@RequestParam(name = "kw", required = false) String kw,
@RequestParam(name = "page", defaultValue = "1") int page,
@RequestParam(name = "size", defaultValue = "20") int size,
@RequestParam(name = "startDate", required = false) String startDate,
@RequestParam(name = "endDate", required = false) String endDate) {
Long sid = (shopId == null ? defaults.getShopId() : shopId);
return ResponseEntity.ok(orderService.listOtherTransactions(sid, type, accountId, kw, Math.max(0, page-1), size, startDate, endDate));
}
@GetMapping("/inventories/logs")
public ResponseEntity<?> listInventoryLogs(@RequestHeader(name = "X-Shop-Id", required = false) Long shopId,
@RequestParam(name = "productId", required = false) Long productId,
@RequestParam(name = "reason", required = false) String reason,
@RequestParam(name = "kw", required = false) String kw,
@RequestParam(name = "page", defaultValue = "1") int page,
@RequestParam(name = "size", defaultValue = "20") int size,
@RequestParam(name = "startDate", required = false) String startDate,
@RequestParam(name = "endDate", required = false) String endDate) {
Long sid = (shopId == null ? defaults.getShopId() : shopId);
return ResponseEntity.ok(orderService.listInventoryLogs(sid, productId, reason, kw, Math.max(0, page-1), size, startDate, endDate));
}
}

View File

@@ -0,0 +1,24 @@
package com.example.demo.order;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 简单的进程内单号生成器:前缀 + yyyyMMdd + 4位流水
* 说明:演示用,生产建议使用数据库序列或 Redis 自增确保多实例唯一。
*/
public class OrderNumberGenerator {
private static final ConcurrentHashMap<String, AtomicInteger> dateCounters = new ConcurrentHashMap<>();
private static final DateTimeFormatter DATE = DateTimeFormatter.ofPattern("yyyyMMdd");
public static String next(String prefix) {
String day = LocalDateTime.now().format(DATE);
String key = prefix + day;
int seq = dateCounters.computeIfAbsent(key, k -> new AtomicInteger(0)).incrementAndGet();
return prefix + day + String.format("%04d", seq);
}
}

View File

@@ -0,0 +1,353 @@
package com.example.demo.order;
import com.example.demo.order.dto.OrderDtos;
import com.example.demo.product.entity.Inventory;
import com.example.demo.product.repo.InventoryRepository;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Service
public class OrderService {
private final InventoryRepository inventoryRepository;
private final JdbcTemplate jdbcTemplate;
public OrderService(InventoryRepository inventoryRepository,
JdbcTemplate jdbcTemplate) {
this.inventoryRepository = inventoryRepository;
this.jdbcTemplate = jdbcTemplate;
}
@Transactional
public Object create(Long shopId, Long userId, OrderDtos.CreateOrderRequest req) {
String type = req.type == null ? "" : req.type;
boolean isSaleOut = "sale.out".equals(type) || "out".equals(type) || "sale".equals(type);
boolean isPurchaseIn = "purchase.in".equals(type) || "in".equals(type);
boolean isSaleReturn = "sale.return".equals(type);
boolean isPurchaseReturn = "purchase.return".equals(type);
boolean isSaleCollect = "sale.collect".equals(type);
boolean isPurchasePay = "purchase.pay".equals(type);
if (isSaleCollect || isPurchasePay) {
java.util.List<OrderDtos.PaymentItem> payments = req.payments == null ? java.util.List.of() : req.payments;
return createPayments(shopId, userId, payments, isSaleCollect ? "sale" : "purchase");
}
if (!(isSaleOut || isPurchaseIn || isSaleReturn || isPurchaseReturn)) throw new IllegalArgumentException("不支持的type");
if (req.items == null || req.items.isEmpty()) throw new IllegalArgumentException("明细为空");
// 后端重算金额
final BigDecimal[] totalRef = new BigDecimal[]{BigDecimal.ZERO};
for (OrderDtos.Item it : req.items) {
BigDecimal qty = n(it.quantity);
BigDecimal price = n(it.unitPrice);
BigDecimal dr = n(it.discountRate);
BigDecimal line = qty.multiply(price).multiply(BigDecimal.ONE.subtract(dr.divide(new BigDecimal("100"))));
totalRef[0] = totalRef[0].add(scale2(line));
}
// 库存变动(保存即 approved
LocalDateTime now = nowUtc();
for (OrderDtos.Item it : req.items) {
Long pid = it.productId;
Inventory inv = inventoryRepository.findById(pid).orElseGet(Inventory::new);
inv.setProductId(pid);
inv.setShopId(shopId);
inv.setUserId(userId);
BigDecimal cur = n(inv.getQuantity());
BigDecimal delta = BigDecimal.ZERO;
if (isSaleOut) delta = n(it.quantity).negate();
if (isPurchaseIn) delta = n(it.quantity);
if (isSaleReturn) delta = n(it.quantity); // 退货入库
if (isPurchaseReturn) delta = n(it.quantity).negate(); // 退货出库
BigDecimal next = cur.add(delta);
if (isSaleOut && next.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalStateException("库存不足");
}
inv.setQuantity(next);
inv.setUpdatedAt(now);
inventoryRepository.save(inv);
// 写入库存流水(可选金额)
String imSql = "INSERT INTO inventory_movements (shop_id,user_id,product_id,source_type,source_id,qty_delta,amount_delta,reason,tx_time,remark,created_at) VALUES (?,?,?,?,?,?,?,?,?,NULL,NOW())";
String sourceType = isSaleOut ? "sale" : (isPurchaseIn ? "purchase" : (isSaleReturn ? "sale_return" : "purchase_return"));
jdbcTemplate.update(imSql, shopId, userId, pid, sourceType, null, delta, null, null, java.sql.Timestamp.from(now.atZone(java.time.ZoneOffset.UTC).toInstant()));
}
String prefix = isSaleOut || isSaleReturn ? (isSaleReturn ? "SR" : "SO") : (isPurchaseReturn ? "PR" : "PO");
String orderNo = OrderNumberGenerator.next(prefix);
// 持久化订单头与明细(简化使用 JDBC
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"));
// insert head
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) " +
"VALUES (?,?,?,?,?,?, 'approved', ?, 0, ?, NOW(), NOW())";
Long customerId = req.customerId;
Long supplierId = req.supplierId;
jdbcTemplate.update(con -> {
java.sql.PreparedStatement ps = con.prepareStatement(headSql, new String[]{"id"});
ps.setLong(1, shopId);
ps.setLong(2, userId);
if (headTable.startsWith("sales")) {
ps.setObject(3, customerId, java.sql.Types.BIGINT);
ps.setObject(4, null);
} else if (headTable.startsWith("purchase")) {
ps.setObject(3, null);
ps.setObject(4, supplierId, java.sql.Types.BIGINT);
} else {
ps.setObject(3, null);
ps.setObject(4, null);
}
ps.setString(5, orderNo);
ps.setTimestamp(6, java.sql.Timestamp.from(now.atZone(java.time.ZoneOffset.UTC).toInstant()));
ps.setBigDecimal(7, scale2(totalRef[0]));
ps.setString(8, req.remark);
return ps;
}, kh);
Number orderKey = kh.getKey();
Long orderId = (orderKey == null ? null : orderKey.longValue());
// insert items
String itemSql = "INSERT INTO " + itemTable + " (order_id,product_id,quantity,unit_price,discount_rate,amount) VALUES (?,?,?,?,?,?)";
for (OrderDtos.Item it : req.items) {
BigDecimal qty = n(it.quantity);
BigDecimal price = n(it.unitPrice);
BigDecimal dr = n(it.discountRate);
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);
}
return new OrderDtos.CreateOrderResponse(orderId, orderNo);
}
@Transactional
public OrderDtos.CreatePaymentsResponse createPayments(Long shopId, Long userId, java.util.List<OrderDtos.PaymentItem> req, String bizType) {
ensureDefaultAccounts(shopId, userId);
List<Long> ids = new ArrayList<>();
if (req == null) return new OrderDtos.CreatePaymentsResponse(ids);
String direction = "sale".equals(bizType) ? "in" : "out";
for (OrderDtos.PaymentItem p : req) {
Long accountId = resolveAccountId(shopId, userId, p.method);
KeyHolder kh = new GeneratedKeyHolder();
String sql = "INSERT INTO payments (shop_id,user_id,biz_type,biz_id,account_id,direction,amount,pay_time,remark,created_at) " +
"VALUES (?,?,?,?,?,?,?,NOW(),NULL,NOW())";
jdbcTemplate.update(con -> {
java.sql.PreparedStatement ps = con.prepareStatement(sql, new String[]{"id"});
ps.setLong(1, shopId);
ps.setLong(2, userId);
ps.setString(3, bizType);
if (p.orderId == null) ps.setNull(4, java.sql.Types.BIGINT); else ps.setLong(4, p.orderId);
ps.setLong(5, accountId);
ps.setString(6, direction);
ps.setBigDecimal(7, n(p.amount));
return ps;
}, kh);
Number payKey = kh.getKey();
Long pid = (payKey == null ? null : payKey.longValue());
if (pid != null) ids.add(pid);
// 若挂单,累加已付
if (p.orderId != null) {
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);
}
}
return new OrderDtos.CreatePaymentsResponse(ids);
}
@Transactional
public void voidOrder(Long shopId, Long userId, Long id, String type) {
// type: sale.out / purchase.in / sale.return / purchase.return
String headTable;
String itemTable;
boolean revertIncrease; // true 表示作废时库存应减少原先增加false 表示应增加(原先减少)
if ("sale.out".equals(type)) { headTable = "sales_orders"; itemTable = "sales_order_items"; revertIncrease = false; }
else if ("purchase.in".equals(type)) { headTable = "purchase_orders"; itemTable = "purchase_order_items"; revertIncrease = true; }
else if ("sale.return".equals(type)) { headTable = "sales_return_orders"; itemTable = "sales_return_order_items"; revertIncrease = true; }
else if ("purchase.return".equals(type)) { headTable = "purchase_return_orders"; itemTable = "purchase_return_order_items"; revertIncrease = false; }
else throw new IllegalArgumentException("不支持的type");
// 查询明细
List<java.util.Map<String,Object>> rows = jdbcTemplate.queryForList("SELECT product_id, quantity FROM " + itemTable + " WHERE order_id = ?", id);
// 回滚库存
LocalDateTime now = nowUtc();
for (java.util.Map<String,Object> r : rows) {
Long pid = ((Number)r.get("product_id")).longValue();
java.math.BigDecimal qty = new java.math.BigDecimal(r.get("quantity").toString());
Inventory inv = inventoryRepository.findById(pid).orElseGet(Inventory::new);
inv.setProductId(pid);
inv.setShopId(shopId);
inv.setUserId(userId);
java.math.BigDecimal delta = revertIncrease ? qty.negate() : qty; // 与创建时相反
inv.setQuantity(n(inv.getQuantity()).add(delta));
inv.setUpdatedAt(now);
inventoryRepository.save(inv);
}
// 更新状态
jdbcTemplate.update("UPDATE " + headTable + " SET status='void' WHERE id = ?", id);
}
public java.util.Map<String,Object> list(Long shopId, String biz, String type, String kw, int page, int size, String startDate, String endDate) {
String headTable;
if ("sale".equals(biz)) {
headTable = ("sale.return".equals(type) ? "sales_return_orders" : "sales_orders");
} else if ("purchase".equals(biz)) {
headTable = ("purchase.return".equals(type) ? "purchase_orders" : "purchase_orders");
} else {
// 若未传,默认销售出货
headTable = "sales_orders";
}
StringBuilder sql = new StringBuilder("SELECT id, order_no, order_time, amount FROM " + headTable + " WHERE shop_id=?");
java.util.List<Object> ps = new java.util.ArrayList<>();
ps.add(shopId);
if ("purchase".equals(biz) && "purchase.return".equals(type)) {
sql.append(" AND status='returned'");
}
if (kw != null && !kw.isBlank()) {
sql.append(" AND (order_no LIKE ?)");
ps.add('%' + kw + '%');
}
if (startDate != null && !startDate.isBlank()) { sql.append(" AND order_time>=?"); ps.add(java.sql.Timestamp.valueOf(startDate + " 00:00:00")); }
if (endDate != null && !endDate.isBlank()) { sql.append(" AND order_time<=?"); ps.add(java.sql.Timestamp.valueOf(endDate + " 23:59:59")); }
sql.append(" ORDER BY order_time DESC LIMIT ? OFFSET ?");
ps.add(size);
ps.add(page * size);
java.util.List<java.util.Map<String,Object>> list = jdbcTemplate.queryForList(sql.toString(), ps.toArray());
// 汇总
StringBuilder sumSql = new StringBuilder("SELECT COALESCE(SUM(amount),0) FROM " + headTable + " WHERE shop_id=?");
java.util.List<Object> sumPs = new java.util.ArrayList<>();
sumPs.add(shopId);
if ("purchase".equals(biz) && "purchase.return".equals(type)) { sumSql.append(" AND status='returned'"); }
if (kw != null && !kw.isBlank()) { sumSql.append(" AND (order_no LIKE ?)"); sumPs.add('%' + kw + '%'); }
if (startDate != null && !startDate.isBlank()) { sumSql.append(" AND order_time>=?"); sumPs.add(java.sql.Timestamp.valueOf(startDate + " 00:00:00")); }
if (endDate != null && !endDate.isBlank()) { sumSql.append(" AND order_time<=?"); sumPs.add(java.sql.Timestamp.valueOf(endDate + " 23:59:59")); }
java.math.BigDecimal total = jdbcTemplate.queryForObject(sumSql.toString(), java.math.BigDecimal.class, sumPs.toArray());
java.util.Map<String,Object> resp = new java.util.HashMap<>();
resp.put("list", list);
resp.put("totalAmount", total == null ? java.math.BigDecimal.ZERO : total);
return resp;
}
public java.util.Map<String,Object> listPayments(Long shopId, String direction, String bizType, Long accountId, String kw, int page, int size, String startDate, String endDate) {
StringBuilder sql = new StringBuilder("SELECT id, biz_type, account_id, direction, amount, pay_time FROM payments WHERE shop_id=?");
java.util.List<Object> ps = new java.util.ArrayList<>();
ps.add(shopId);
if (direction != null && !direction.isBlank()) { sql.append(" AND direction=?"); ps.add(direction); }
if (bizType != null && !bizType.isBlank()) { sql.append(" AND biz_type=?"); ps.add(bizType); }
if (accountId != null) { sql.append(" AND account_id=?"); ps.add(accountId); }
if (kw != null && !kw.isBlank()) { sql.append(" AND (CAST(id AS CHAR) LIKE ?)"); ps.add('%'+kw+'%'); }
if (startDate != null && !startDate.isBlank()) { sql.append(" AND pay_time>=?"); ps.add(java.sql.Timestamp.valueOf(startDate + " 00:00:00")); }
if (endDate != null && !endDate.isBlank()) { sql.append(" AND pay_time<=?"); ps.add(java.sql.Timestamp.valueOf(endDate + " 23:59:59")); }
sql.append(" ORDER BY pay_time DESC LIMIT ? OFFSET ?");
ps.add(size); ps.add(page * size);
java.util.List<java.util.Map<String,Object>> list = jdbcTemplate.queryForList(sql.toString(), ps.toArray());
StringBuilder sumSql = new StringBuilder("SELECT COALESCE(SUM(amount),0) FROM payments WHERE shop_id=?");
java.util.List<Object> sumPs = new java.util.ArrayList<>(); sumPs.add(shopId);
if (direction != null && !direction.isBlank()) { sumSql.append(" AND direction=?"); sumPs.add(direction); }
if (bizType != null && !bizType.isBlank()) { sumSql.append(" AND biz_type=?"); sumPs.add(bizType); }
if (accountId != null) { sumSql.append(" AND account_id=?"); sumPs.add(accountId); }
if (kw != null && !kw.isBlank()) { sumSql.append(" AND (CAST(id AS CHAR) LIKE ?)"); sumPs.add('%'+kw+'%'); }
if (startDate != null && !startDate.isBlank()) { sumSql.append(" AND pay_time>=?"); sumPs.add(java.sql.Timestamp.valueOf(startDate + " 00:00:00")); }
if (endDate != null && !endDate.isBlank()) { sumSql.append(" AND pay_time<=?"); sumPs.add(java.sql.Timestamp.valueOf(endDate + " 23:59:59")); }
java.math.BigDecimal total = jdbcTemplate.queryForObject(sumSql.toString(), java.math.BigDecimal.class, sumPs.toArray());
java.util.Map<String,Object> resp = new java.util.HashMap<>(); resp.put("list", list); resp.put("totalAmount", total == null ? java.math.BigDecimal.ZERO : total); return resp;
}
public java.util.Map<String,Object> listOtherTransactions(Long shopId, String type, Long accountId, String kw, int page, int size, String startDate, String endDate) {
StringBuilder sql = new StringBuilder("SELECT id, `type`, account_id, amount, tx_time, remark FROM other_transactions WHERE shop_id=?");
java.util.List<Object> ps = new java.util.ArrayList<>(); ps.add(shopId);
if (type != null && !type.isBlank()) { sql.append(" AND `type`=?"); ps.add(type); }
if (accountId != null) { sql.append(" AND account_id=?"); ps.add(accountId); }
if (kw != null && !kw.isBlank()) { sql.append(" AND (remark LIKE ? OR category LIKE ?)"); ps.add('%'+kw+'%'); ps.add('%'+kw+'%'); }
if (startDate != null && !startDate.isBlank()) { sql.append(" AND tx_time>=?"); ps.add(java.sql.Timestamp.valueOf(startDate + " 00:00:00")); }
if (endDate != null && !endDate.isBlank()) { sql.append(" AND tx_time<=?"); ps.add(java.sql.Timestamp.valueOf(endDate + " 23:59:59")); }
sql.append(" ORDER BY tx_time DESC LIMIT ? OFFSET ?"); ps.add(size); ps.add(page * size);
java.util.List<java.util.Map<String,Object>> list = jdbcTemplate.queryForList(sql.toString(), ps.toArray());
StringBuilder sumSql = new StringBuilder("SELECT COALESCE(SUM(amount),0) FROM other_transactions WHERE shop_id=?");
java.util.List<Object> sumPs = new java.util.ArrayList<>(); sumPs.add(shopId);
if (type != null && !type.isBlank()) { sumSql.append(" AND `type`=?"); sumPs.add(type); }
if (accountId != null) { sumSql.append(" AND account_id=?"); sumPs.add(accountId); }
if (kw != null && !kw.isBlank()) { sumSql.append(" AND (remark LIKE ? OR category LIKE ?)"); sumPs.add('%'+kw+'%'); sumPs.add('%'+kw+'%'); }
if (startDate != null && !startDate.isBlank()) { sumSql.append(" AND tx_time>=?"); sumPs.add(java.sql.Timestamp.valueOf(startDate + " 00:00:00")); }
if (endDate != null && !endDate.isBlank()) { sumSql.append(" AND tx_time<=?"); sumPs.add(java.sql.Timestamp.valueOf(endDate + " 23:59:59")); }
java.math.BigDecimal total = jdbcTemplate.queryForObject(sumSql.toString(), java.math.BigDecimal.class, sumPs.toArray());
java.util.Map<String,Object> resp = new java.util.HashMap<>(); resp.put("list", list); resp.put("totalAmount", total == null ? java.math.BigDecimal.ZERO : total); return resp;
}
public java.util.Map<String,Object> listInventoryLogs(Long shopId, Long productId, String reason, String kw, int page, int size, String startDate, String endDate) {
StringBuilder sql = new StringBuilder("SELECT id, product_id, qty_delta, amount_delta, reason, tx_time FROM inventory_movements WHERE shop_id=?");
java.util.List<Object> ps = new java.util.ArrayList<>(); ps.add(shopId);
if (productId != null) { sql.append(" AND product_id=?"); ps.add(productId); }
if (reason != null && !reason.isBlank()) { sql.append(" AND reason=?"); ps.add(reason); }
if (kw != null && !kw.isBlank()) { sql.append(" AND (remark LIKE ? OR source_type LIKE ?)"); ps.add('%'+kw+'%'); ps.add('%'+kw+'%'); }
if (startDate != null && !startDate.isBlank()) { sql.append(" AND tx_time>=?"); ps.add(java.sql.Timestamp.valueOf(startDate + " 00:00:00")); }
if (endDate != null && !endDate.isBlank()) { sql.append(" AND tx_time<=?"); ps.add(java.sql.Timestamp.valueOf(endDate + " 23:59:59")); }
sql.append(" ORDER BY tx_time DESC LIMIT ? OFFSET ?"); ps.add(size); ps.add(page * size);
java.util.List<java.util.Map<String,Object>> list = jdbcTemplate.queryForList(sql.toString(), ps.toArray());
StringBuilder sumSql = new StringBuilder("SELECT COALESCE(SUM(COALESCE(amount_delta,0)),0) FROM inventory_movements WHERE shop_id=?");
java.util.List<Object> sumPs = new java.util.ArrayList<>(); sumPs.add(shopId);
if (productId != null) { sumSql.append(" AND product_id=?"); sumPs.add(productId); }
if (reason != null && !reason.isBlank()) { sumSql.append(" AND reason=?"); sumPs.add(reason); }
if (kw != null && !kw.isBlank()) { sumSql.append(" AND (remark LIKE ? OR source_type LIKE ?)"); sumPs.add('%'+kw+'%'); sumPs.add('%'+kw+'%'); }
if (startDate != null && !startDate.isBlank()) { sumSql.append(" AND tx_time>=?"); sumPs.add(java.sql.Timestamp.valueOf(startDate + " 00:00:00")); }
if (endDate != null && !endDate.isBlank()) { sumSql.append(" AND tx_time<=?"); sumPs.add(java.sql.Timestamp.valueOf(endDate + " 23:59:59")); }
java.math.BigDecimal total = jdbcTemplate.queryForObject(sumSql.toString(), java.math.BigDecimal.class, sumPs.toArray());
java.util.Map<String,Object> resp = new java.util.HashMap<>(); resp.put("list", list); resp.put("totalAmount", total == null ? java.math.BigDecimal.ZERO : total); return resp;
}
private static BigDecimal n(BigDecimal v) { return v == null ? BigDecimal.ZERO : v; }
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 void ensureDefaultAccounts(Long shopId, Long userId) {
// 为 cash/bank/wechat 分别确保存在一条账户记录;按 type→name 顺序检查,避免同名唯一冲突
ensureAccount(shopId, userId, "cash", "现金");
ensureAccount(shopId, userId, "bank", "银行存款");
ensureAccount(shopId, userId, "wechat", "微信");
}
private void ensureAccount(Long shopId, Long userId, String type, String name) {
List<Long> byType = jdbcTemplate.query("SELECT id FROM accounts WHERE shop_id=? AND type=? LIMIT 1", (rs,rn)->rs.getLong(1), shopId, type);
if (!byType.isEmpty()) return;
List<Long> byName = jdbcTemplate.query("SELECT id FROM accounts WHERE shop_id=? AND name=? LIMIT 1", (rs,rn)->rs.getLong(1), shopId, name);
if (!byName.isEmpty()) return; // 已有同名则直接复用,无需再插
jdbcTemplate.update("INSERT INTO accounts (shop_id,user_id,name,type,balance,status,created_at,updated_at) VALUES (?,?,?,?,0,1,NOW(),NOW())",
shopId, userId, name, type);
}
private Long resolveAccountId(Long shopId, Long userId, String method) {
String type = "cash";
if ("bank".equalsIgnoreCase(method)) type = "bank";
if ("wechat".equalsIgnoreCase(method)) type = "wechat";
String name = "现金";
if ("bank".equals(type)) name = "银行存款"; else if ("wechat".equals(type)) name = "微信";
// 先按 type 查
List<Long> byType = jdbcTemplate.query("SELECT id FROM accounts WHERE shop_id=? AND type=? LIMIT 1", (rs,rn)->rs.getLong(1), shopId, type);
if (!byType.isEmpty()) return byType.get(0);
// 再按 name 查,避免同名唯一冲突
List<Long> byName = jdbcTemplate.query("SELECT id FROM accounts WHERE shop_id=? AND name=? LIMIT 1", (rs,rn)->rs.getLong(1), shopId, name);
if (!byName.isEmpty()) return byName.get(0);
// 都没有再插入
jdbcTemplate.update("INSERT INTO accounts (shop_id,user_id,name,type,balance,status,created_at,updated_at) VALUES (?,?,?,?,0,1,NOW(),NOW())",
shopId, userId, name, type);
// 插入后按 type 读取
List<Long> recheck = jdbcTemplate.query("SELECT id FROM accounts WHERE shop_id=? AND type=? LIMIT 1", (rs,rn)->rs.getLong(1), shopId, type);
if (!recheck.isEmpty()) return recheck.get(0);
throw new IllegalStateException("账户映射失败: " + method);
}
}

View File

@@ -0,0 +1,44 @@
package com.example.demo.order.dto;
import java.math.BigDecimal;
import java.util.List;
public class OrderDtos {
public static class CreateOrderRequest {
public String type; // sale.out / sale.return / sale.collect / purchase.in / purchase.return / purchase.pay
public String orderTime; // ISO8601 或 yyyy-MM-dd
public Long customerId; // 可空
public Long supplierId; // 可空
public List<Item> items; // 出入库时必填
public List<PaymentItem> payments; // 收款/付款时必填
public BigDecimal amount; // 前端提供,后端将重算覆盖
public String remark;
}
public static class Item {
public Long productId;
public BigDecimal quantity;
public BigDecimal unitPrice;
public BigDecimal discountRate; // 可空,缺省 0
}
public static class PaymentItem {
public String method; // cash/bank/wechat
public BigDecimal amount;
public Long orderId; // 可选:若挂单则带上
}
public static class CreateOrderResponse {
public Long id;
public String orderNo;
public CreateOrderResponse(Long id, String orderNo) { this.id = id; this.orderNo = orderNo; }
}
public static class CreatePaymentsResponse {
public java.util.List<Long> paymentIds;
public CreatePaymentsResponse(java.util.List<Long> ids) { this.paymentIds = ids; }
}
}

View File

@@ -31,7 +31,6 @@ public class ProductDtos {
public BigDecimal stock;
public BigDecimal purchasePrice;
public BigDecimal retailPrice;
public BigDecimal distributionPrice;
public BigDecimal wholesalePrice;
public BigDecimal bigClientPrice;
public List<Image> images;
@@ -61,7 +60,6 @@ public class ProductDtos {
public static class Prices {
public BigDecimal purchasePrice;
public BigDecimal retailPrice;
public BigDecimal distributionPrice;
public BigDecimal wholesalePrice;
public BigDecimal bigClientPrice;
}
@@ -70,3 +68,6 @@ public class ProductDtos {

View File

@@ -39,3 +39,6 @@ public class Inventory {

View File

@@ -98,3 +98,6 @@ public class Product {

View File

@@ -56,3 +56,6 @@ public class ProductCategory {

View File

@@ -41,3 +41,6 @@ public class ProductImage {

View File

@@ -59,3 +59,6 @@ public class ProductPrice {

View File

@@ -46,3 +46,6 @@ public class ProductUnit {

View File

@@ -15,3 +15,6 @@ public interface CategoryRepository extends JpaRepository<ProductCategory, Long>

View File

@@ -9,3 +9,6 @@ public interface InventoryRepository extends JpaRepository<Inventory, Long> {

View File

@@ -13,3 +13,6 @@ public interface ProductImageRepository extends JpaRepository<ProductImage, Long

View File

@@ -9,3 +9,6 @@ public interface ProductPriceRepository extends JpaRepository<ProductPrice, Long

View File

@@ -23,3 +23,6 @@ public interface ProductRepository extends JpaRepository<Product, Long> {

View File

@@ -14,3 +14,6 @@ public interface UnitRepository extends JpaRepository<com.example.demo.product.e

View File

@@ -10,7 +10,9 @@ import com.example.demo.product.repo.ProductImageRepository;
import com.example.demo.product.repo.ProductPriceRepository;
import com.example.demo.product.repo.ProductRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -27,35 +29,66 @@ public class ProductService {
private final ProductPriceRepository priceRepository;
private final InventoryRepository inventoryRepository;
private final ProductImageRepository imageRepository;
private final JdbcTemplate jdbcTemplate;
public ProductService(ProductRepository productRepository,
ProductPriceRepository priceRepository,
InventoryRepository inventoryRepository,
ProductImageRepository imageRepository) {
ProductImageRepository imageRepository,
JdbcTemplate jdbcTemplate) {
this.productRepository = productRepository;
this.priceRepository = priceRepository;
this.inventoryRepository = inventoryRepository;
this.imageRepository = imageRepository;
this.jdbcTemplate = jdbcTemplate;
}
public Page<ProductDtos.ProductListItem> search(Long shopId, String kw, Long categoryId, int page, int size) {
Page<Product> p = productRepository.search(shopId, kw, categoryId, PageRequest.of(page, size));
return p.map(prod -> {
ProductDtos.ProductListItem it = new ProductDtos.ProductListItem();
it.id = prod.getId();
it.name = prod.getName();
it.brand = prod.getBrand();
it.model = prod.getModel();
it.spec = prod.getSpec();
// stock
inventoryRepository.findById(prod.getId()).ifPresent(inv -> it.stock = inv.getQuantity());
// price
priceRepository.findById(prod.getId()).ifPresent(pr -> it.retailPrice = pr.getRetailPrice());
// cover
List<ProductImage> imgs = imageRepository.findByProductIdOrderBySortOrderAscIdAsc(prod.getId());
it.cover = imgs.isEmpty() ? null : imgs.get(0).getUrl();
return it;
});
try {
Page<Product> p = productRepository.search(shopId, kw, categoryId, PageRequest.of(page, size));
return p.map(prod -> {
ProductDtos.ProductListItem it = new ProductDtos.ProductListItem();
it.id = prod.getId();
it.name = prod.getName();
it.brand = prod.getBrand();
it.model = prod.getModel();
it.spec = prod.getSpec();
inventoryRepository.findById(prod.getId()).ifPresent(inv -> it.stock = inv.getQuantity());
priceRepository.findById(prod.getId()).ifPresent(pr -> it.retailPrice = pr.getRetailPrice());
List<ProductImage> imgs = imageRepository.findByProductIdOrderBySortOrderAscIdAsc(prod.getId());
it.cover = imgs.isEmpty() ? null : imgs.get(0).getUrl();
return it;
});
} catch (Exception e) {
// 安全回退为 JDBC 查询,保障功能可用
StringBuilder sql = new StringBuilder("SELECT p.id,p.name,p.brand,p.model,p.spec,\n" +
"(SELECT i.quantity FROM inventories i WHERE i.product_id=p.id) AS stock,\n" +
"(SELECT pr.retail_price FROM product_prices pr WHERE pr.product_id=p.id) AS retail_price,\n" +
"(SELECT img.url FROM product_images img WHERE img.product_id=p.id ORDER BY img.sort_order, img.id LIMIT 1) AS cover\n" +
"FROM products p WHERE p.shop_id=? AND p.deleted_at IS NULL");
List<Object> ps = new ArrayList<>();
ps.add(shopId);
if (kw != null && !kw.isBlank()) { sql.append(" AND (p.name LIKE ? OR p.brand LIKE ? OR p.model LIKE ? OR p.spec LIKE ? OR p.barcode LIKE ?)");
String like = "%" + kw + "%"; ps.add(like); ps.add(like); ps.add(like); ps.add(like); ps.add(like); }
if (categoryId != null) { sql.append(" AND p.category_id=?"); ps.add(categoryId); }
sql.append(" ORDER BY p.id DESC LIMIT ? OFFSET ?");
ps.add(size); ps.add(page * size);
List<ProductDtos.ProductListItem> list = jdbcTemplate.query(sql.toString(), (rs,rn) -> {
ProductDtos.ProductListItem it = new ProductDtos.ProductListItem();
it.id = rs.getLong("id");
it.name = rs.getString("name");
it.brand = rs.getString("brand");
it.model = rs.getString("model");
it.spec = rs.getString("spec");
java.math.BigDecimal st = (java.math.BigDecimal) rs.getObject("stock");
it.stock = st;
java.math.BigDecimal rp = (java.math.BigDecimal) rs.getObject("retail_price");
it.retailPrice = rp;
it.cover = rs.getString("cover");
return it;
}, ps.toArray());
return new PageImpl<>(list, PageRequest.of(page, size), list.size());
}
}
public Optional<ProductDtos.ProductDetail> findDetail(Long id) {
@@ -78,7 +111,6 @@ public class ProductService {
priceRepository.findById(p.getId()).ifPresent(pr -> {
d.purchasePrice = pr.getPurchasePrice();
d.retailPrice = pr.getRetailPrice();
d.distributionPrice = pr.getDistributionPrice();
d.wholesalePrice = pr.getWholesalePrice();
d.bigClientPrice = pr.getBigClientPrice();
});
@@ -171,12 +203,6 @@ public class ProductService {
pr.setUserId(userId);
pr.setPurchasePrice(nvl(prices.purchasePrice, BigDecimal.ZERO));
pr.setRetailPrice(nvl(prices.retailPrice, BigDecimal.ZERO));
// 前端不再传分销价:仅当入参提供时更新;新建记录若未提供则置 0
if (prices.distributionPrice != null) {
pr.setDistributionPrice(prices.distributionPrice);
} else if (existed.isEmpty()) {
pr.setDistributionPrice(BigDecimal.ZERO);
}
pr.setWholesalePrice(nvl(prices.wholesalePrice, BigDecimal.ZERO));
pr.setBigClientPrice(nvl(prices.bigClientPrice, BigDecimal.ZERO));
pr.setUpdatedAt(now);

View File

@@ -1,9 +1,14 @@
spring.application.name=demo
# 数据源配置(通过环境变量注入,避免硬编码)
spring.datasource.url=${SPRING_DATASOURCE_URL}
spring.datasource.username=${SPRING_DATASOURCE_USERNAME}
spring.datasource.password=${SPRING_DATASOURCE_PASSWORD}
# 正确的配置
# 格式为: jdbc:mysql://<主机名>:<端口号>/<数据库名>?参数
# 默认附带 MySQL 8 推荐参数,避免握手/时区/编码问题
spring.datasource.url=${DB_URL:jdbc:mysql://mysql.tonaspace.com:3306/partsinquiry?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8}
# 用户名和密码直接写值
spring.datasource.username=${DB_USER:root}
spring.datasource.password=${DB_PASSWORD:TONA1234}
# JPA 基本配置
spring.jpa.hibernate.ddl-auto=none

View File

@@ -192,7 +192,6 @@
| user_id | BIGINT UNSIGNED | NOT NULL | | |
| purchase_price | DECIMAL(18,2) | NOT NULL | 0.00 | |
| retail_price | DECIMAL(18,2) | NOT NULL | 0.00 | |
| distribution_price | DECIMAL(18,2) | NOT NULL | 0.00 | |
| wholesale_price | DECIMAL(18,2) | NOT NULL | 0.00 | |
| big_client_price | DECIMAL(18,2) | NOT NULL | 0.00 | |
| updated_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
@@ -308,7 +307,7 @@
| supplier_id | BIGINT UNSIGNED | YES | | |
| order_no | VARCHAR(32) | NOT NULL | | |
| order_time | DATETIME | NOT NULL | | |
| status | ENUM('draft','approved','void') | NOT NULL | draft | |
| status | ENUM('draft','approved','void','returned') | NOT NULL | draft | |
| amount | DECIMAL(18,2) | NOT NULL | 0.00 | 应付合计 |
| paid_amount | DECIMAL(18,2) | NOT NULL | 0.00 | 已付合计 |
| remark | VARCHAR(255) | YES | | |
@@ -399,6 +398,25 @@
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_notices_shop` (`shop_id`,`status`,`is_pinned`,`created_at`) - KEY: `idx_notices_time` (`starts_at`,`ends_at`)
**Foreign Keys**: - `fk_notices_shop`: `shop_id``shops(id)` ON UPDATE NO ACTION ON DELETE NO ACTION - `fk_notices_user`: `user_id``users(id)` ON UPDATE NO ACTION ON DELETE NO ACTION
### inventory_movements
| Column Name | Data Type | Nullable | Default | Comment |
| ----------- | --------- | -------- | ------- | ------- |
| id | BIGINT UNSIGNED | NOT NULL | AUTO_INCREMENT | |
| shop_id | BIGINT UNSIGNED | NOT NULL | | |
| user_id | BIGINT UNSIGNED | NOT NULL | | |
| product_id | BIGINT UNSIGNED | NOT NULL | | |
| source_type | VARCHAR(32) | NOT NULL | | sale/purchase/sale_return/purchase_return/adjust |
| source_id | BIGINT UNSIGNED | YES | | 关联单据ID |
| qty_delta | DECIMAL(18,3) | NOT NULL | | 数量增减(正加负减) |
| amount_delta | DECIMAL(18,2) | YES | | 金额变动(可空) |
| reason | VARCHAR(64) | YES | | |
| tx_time | DATETIME | NOT NULL | | |
| remark | VARCHAR(255) | YES | | |
| created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_im_shop_time` (`shop_id`,`tx_time`) - KEY: `idx_im_product` (`product_id`)
**Foreign Keys**: - `fk_im_shop`: `shop_id``shops(id)` - `fk_im_user`: `user_id``users(id)` - `fk_im_product`: `product_id``products(id)`
### 附:演示种子数据(非完整,仅用于联调验证)
- 演示店铺演示店A用户 3全部店长 owner
- 商品域基础单位3条、类别2条、全局SKU2条、商品2条含别名/价格/库存/图片)
@@ -406,3 +424,70 @@
- 单据销售单1含明细2与进货单1含明细2、收付款各1、其他收支2
- 审核与公告part_submissions 1、attachments 1、notices 2、新增 wechat 身份与会话各1
### sales_return_orders
| Column Name | Data Type | Nullable | Default | Comment |
| ----------- | --------- | -------- | ------- | ------- |
| id | BIGINT UNSIGNED | NOT NULL | AUTO_INCREMENT | |
| shop_id | BIGINT UNSIGNED | NOT NULL | | |
| user_id | BIGINT UNSIGNED | NOT NULL | | |
| customer_id | BIGINT UNSIGNED | YES | | |
| order_no | VARCHAR(32) | NOT NULL | | |
| order_time | DATETIME | NOT NULL | | |
| status | ENUM('approved','void') | NOT NULL | approved | |
| amount | DECIMAL(18,2) | NOT NULL | 0.00 | |
| paid_amount | DECIMAL(18,2) | NOT NULL | 0.00 | |
| remark | VARCHAR(255) | YES | | |
| created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
| updated_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
| deleted_at | DATETIME | YES | | |
**Indexes**: - PRIMARY KEY: `id` - UNIQUE: `ux_sr_order_no` (`shop_id`,`order_no`) - KEY: `idx_sr_shop_time` (`shop_id`,`order_time`)
**Foreign Keys**: - `fk_sr_shop`: `shop_id``shops(id)` - `fk_sr_user`: `user_id``users(id)` - `fk_sr_customer`: `customer_id``customers(id)`
### sales_return_order_items
| Column Name | Data Type | Nullable | Default | Comment |
| ----------- | --------- | -------- | ------- | ------- |
| id | BIGINT UNSIGNED | NOT NULL | AUTO_INCREMENT | |
| order_id | BIGINT UNSIGNED | NOT NULL | | |
| product_id | BIGINT UNSIGNED | NOT NULL | | |
| quantity | DECIMAL(18,3) | NOT NULL | | |
| unit_price | DECIMAL(18,2) | NOT NULL | | |
| discount_rate | DECIMAL(5,2) | NOT NULL | 0.00 | |
| amount | DECIMAL(18,2) | NOT NULL | | |
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_sroi_order` (`order_id`) - KEY: `idx_sroi_product` (`product_id`)
**Foreign Keys**: - `fk_sroi_order`: `order_id``sales_return_orders(id)` ON DELETE CASCADE - `fk_sroi_product`: `product_id``products(id)`
### purchase_return_orders
| Column Name | Data Type | Nullable | Default | Comment |
| ----------- | --------- | -------- | ------- | ------- |
| id | BIGINT UNSIGNED | NOT NULL | AUTO_INCREMENT | |
| shop_id | BIGINT UNSIGNED | NOT NULL | | |
| user_id | BIGINT UNSIGNED | NOT NULL | | |
| supplier_id | BIGINT UNSIGNED | YES | | |
| order_no | VARCHAR(32) | NOT NULL | | |
| order_time | DATETIME | NOT NULL | | |
| status | ENUM('approved','void') | NOT NULL | approved | |
| amount | DECIMAL(18,2) | NOT NULL | 0.00 | |
| paid_amount | DECIMAL(18,2) | NOT NULL | 0.00 | |
| remark | VARCHAR(255) | YES | | |
| created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
| updated_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
| deleted_at | DATETIME | YES | | |
**Indexes**: - PRIMARY KEY: `id` - UNIQUE: `ux_pr_order_no` (`shop_id`,`order_no`) - KEY: `idx_pr_shop_time` (`shop_id`,`order_time`)
**Foreign Keys**: - `fk_pr_shop`: `shop_id``shops(id)` - `fk_pr_user`: `user_id``users(id)` - `fk_pr_supplier`: `supplier_id``suppliers(id)`
### purchase_return_order_items
| Column Name | Data Type | Nullable | Default | Comment |
| ----------- | --------- | -------- | ------- | ------- |
| id | BIGINT UNSIGNED | NOT NULL | AUTO_INCREMENT | |
| order_id | BIGINT UNSIGNED | NOT NULL | | |
| product_id | BIGINT UNSIGNED | NOT NULL | | |
| quantity | DECIMAL(18,3) | NOT NULL | | |
| unit_price | DECIMAL(18,2) | NOT NULL | | |
| amount | DECIMAL(18,2) | NOT NULL | | |
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_proi_order` (`order_id`) - KEY: `idx_proi_product` (`product_id`)
**Foreign Keys**: - `fk_proi_order`: `order_id``purchase_return_orders(id)` ON DELETE CASCADE - `fk_proi_product`: `product_id``products(id)`

View File

@@ -370,8 +370,8 @@ paths:
$ref: '#/components/schemas/Customer'
/api/orders:
post:
summary: 新建单据(❌ Partially Implemented
description: 前端开单页已提交 payload后端待实现
summary: 新建单据(✅ Fully Implemented
description: 销售/进货出入库与退货:保存即 approved后端重算金额并联动库存
requestBody:
required: true
content:
@@ -391,6 +391,224 @@ paths:
format: int64
orderNo:
type: string
/api/orders:
get:
summary: 单据列表查询(❌ Partially Implemented
description: 支持按时间范围与关键字筛选;参数 biz=sale|purchasetype=out|in|return返回 {list:[]}。
parameters:
- in: query
name: biz
schema: { type: string }
- in: query
name: type
schema: { type: string }
- in: query
name: kw
schema: { type: string }
- in: query
name: page
schema: { type: integer, default: 1 }
- in: query
name: size
schema: { type: integer, default: 20 }
- in: query
name: startDate
schema: { type: string, format: date }
- in: query
name: endDate
schema: { type: string, format: date }
/api/payments/{biz}:
post:
summary: 创建收款/付款(✅ Fully Implemented
description: biz=sale|purchase根据 payments 写入多条记录,可选挂单并累加订单已付金额。
parameters:
- in: path
name: biz
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/PaymentItem'
responses:
'200':
description: 成功
content:
application/json:
schema:
type: object
properties:
paymentIds:
type: array
items: { type: integer, format: int64 }
description: 支持按时间范围与关键字筛选;返回 {list:[]}。前端已接入,后端待实现。
parameters:
- in: query
name: kw
schema: { type: string }
- in: query
name: page
schema: { type: integer, default: 1 }
- in: query
name: size
schema: { type: integer, default: 20 }
- in: query
name: startDate
schema: { type: string, format: date }
- in: query
name: endDate
schema: { type: string, format: date }
responses:
'200':
description: 成功
content:
application/json:
schema:
type: object
properties:
list:
type: array
items:
type: object
properties:
id: { type: integer, format: int64 }
orderNo: { type: string }
orderTime: { type: string, format: date-time }
amount: { type: number }
customerName: { type: string }
/api/purchase-orders:
get:
summary: 进货单列表查询(❌ Partially Implemented
parameters:
- in: query
name: kw
schema: { type: string }
- in: query
name: page
schema: { type: integer, default: 1 }
- in: query
name: size
schema: { type: integer, default: 20 }
- in: query
name: startDate
schema: { type: string, format: date }
- in: query
name: endDate
schema: { type: string, format: date }
- in: query
name: status
schema:
type: string
enum: [draft, approved, void, returned]
responses:
'200':
description: 成功
content:
application/json:
schema:
type: object
properties:
list:
type: array
items:
type: object
properties:
id: { type: integer, format: int64 }
orderNo: { type: string }
orderTime: { type: string, format: date-time }
amount: { type: number }
supplierName: { type: string }
/api/payments:
get:
summary: 收付款流水列表(❌ Partially Implemented
parameters:
- in: query
name: kw
schema: { type: string }
- in: query
name: page
schema: { type: integer, default: 1 }
- in: query
name: size
schema: { type: integer, default: 20 }
- in: query
name: startDate
schema: { type: string, format: date }
- in: query
name: endDate
schema: { type: string, format: date }
responses:
'200':
description: 成功
content:
application/json:
schema:
type: object
properties:
list:
type: array
items:
type: object
properties:
id: { type: integer, format: int64 }
bizType: { type: string }
direction: { type: string }
payTime: { type: string, format: date-time }
amount: { type: number }
accountName: { type: string }
/api/inventories/logs:
get:
summary: 库存/盘点流水列表(❌ Partially Implemented
parameters:
- in: query
name: kw
schema: { type: string }
- in: query
name: page
schema: { type: integer, default: 1 }
- in: query
name: size
schema: { type: integer, default: 20 }
- in: query
name: startDate
schema: { type: string, format: date }
- in: query
name: endDate
schema: { type: string, format: date }
- in: query
name: productId
schema: { type: integer, format: int64 }
- in: query
name: reason
schema: { type: string }
responses:
'200':
description: 成功
content:
application/json:
schema:
type: object
properties:
list:
type: array
items:
type: object
properties:
id: { type: integer, format: int64 }
bizType: { type: string }
txTime: { type: string, format: date-time }
amount: { type: number }
remark: { type: string }
productId: { type: integer, format: int64 }
qtyDelta: { type: number }
amountDelta: { type: number, nullable: true }
/api/attachments:
post:
summary: 上传附件(✅ Fully Implemented占位图方案
@@ -633,14 +851,18 @@ components:
properties:
type:
type: string
description: 'sale.out/sale.return/sale.collect/purchase/income/expense 等'
description: 'sale.out/sale.return/sale.collect/purchase.in/purchase.return/purchase.pay'
orderTime:
type: string
format: date
format: date-time
customerId:
type: integer
format: int64
nullable: true
supplierId:
type: integer
format: int64
nullable: true
items:
type: array
items:
@@ -653,6 +875,18 @@ components:
type: number
unitPrice:
type: number
discountRate:
type: number
amount:
type: number
payments:
type: array
items:
$ref: '#/components/schemas/PaymentItem'
PaymentItem:
type: object
properties:
method: { type: string, enum: [cash, bank, wechat] }
amount: { type: number }
orderId: { type: integer, format: int64, nullable: true }

View File

@@ -162,3 +162,6 @@ export default {

View File

@@ -19,4 +19,29 @@ export function createApp() {
app
}
}
// #endif
// 规范化 WebSocket 关闭码(仅微信小程序)
// #ifdef MP-WEIXIN
if (typeof uni !== 'undefined' && typeof uni.connectSocket === 'function') {
const _connectSocket = uni.connectSocket
uni.connectSocket = function(options) {
const task = _connectSocket.call(this, options)
if (task && typeof task.close === 'function') {
const _close = task.close
task.close = function(params = {}) {
if (params && typeof params === 'object') {
const codeNum = Number(params.code)
const isValid = codeNum === 1000 || (codeNum >= 3000 && codeNum <= 4999)
if (!isValid) {
params.code = 1000
if (!params.reason) params.reason = 'normalized from invalid close code'
}
}
return _close.call(this, params)
}
}
return task
}
}
// #endif

View File

@@ -65,6 +65,12 @@
"style": {
"navigationBarTitleText": "选择账户"
}
},
{
"path": "pages/detail/index",
"style": {
"navigationBarTitleText": "明细"
}
}
],
"globalStyle": {

View File

@@ -0,0 +1,162 @@
<template>
<view class="page">
<!-- 顶部时间维度筛选 -->
<view class="seg">
<view :class="['seg-item', range==='custom' && 'active']" @click="switchRange('custom')">自定义</view>
<view :class="['seg-item', range==='week' && 'active']" @click="switchRange('week')">本周</view>
<view :class="['seg-item', range==='today' && 'active']" @click="switchRange('today')">今日</view>
<view :class="['seg-item', range==='month' && 'active']" @click="switchRange('month')">本月</view>
<view :class="['seg-item', range==='year' && 'active']" @click="switchRange('year')">本年</view>
</view>
<!-- 业务类型侧边切换销售/进货/收款/资金/盘点 -->
<view class="content">
<view class="biz-tabs">
<view v-for="b in bizList" :key="b.key" :class="['biz', biz===b.key && 'active']" @click="switchBiz(b.key)">{{ b.name }}</view>
</view>
<view class="panel">
<!-- 搜索框与期间显示总额 -->
<view class="toolbar">
<view class="search">
<input class="search-input" v-model.trim="query.kw" :placeholder="placeholder" @confirm="reload" />
</view>
<view class="period">{{ periodLabel }}</view>
<button size="mini" @click="reload">查询</button>
</view>
<view class="total">合计¥{{ totalAmount.toFixed(2) }}</view>
<!-- 列表 -->
<scroll-view scroll-y class="list" @scrolltolower="loadMore">
<block v-if="items.length">
<view class="item" v-for="it in items" :key="it.id" @click="openDetail(it)">
<view class="item-left">
<view class="date">{{ formatDate(it.orderTime || it.txTime || it.createdAt) }}</view>
<view class="name">{{ it.customerName || it.supplierName || it.accountName || it.remark || '-' }}</view>
<view class="no">{{ it.orderNo || it.code || it.id }}</view>
</view>
<view class="amount">¥ {{ (it.amount || 0).toFixed(2) }}</view>
<view class="arrow"></view>
</view>
</block>
<view v-else class="empty">暂无数据</view>
</scroll-view>
<!-- 右下角新增按钮根据业务类型跳转对应开单页或创建页 -->
<view class="fab" @click="onCreate"></view>
</view>
</view>
</view>
</template>
<script>
import { get } from '../../common/http.js'
const API_OF = {
sale: '/api/orders',
purchase: '/api/purchase-orders',
collect: '/api/payments',
fund: '/api/other-transactions',
stock: '/api/inventories/logs'
}
export default {
data() {
return {
biz: 'sale',
bizList: [
{ key: 'sale', name: '出货' },
{ key: 'purchase', name: '进货' },
{ key: 'collect', name: '收款' },
{ key: 'fund', name: '资金' },
{ key: 'stock', name: '盘点' }
],
range: 'month',
query: { kw: '' },
items: [],
page: 1,
size: 20,
finished: false,
loading: false,
startDate: '',
endDate: ''
}
},
computed: {
placeholder() { return '单据号/客户名称/品名规格/备注' },
periodLabel() { return this.startDate && this.endDate ? `${this.startDate}~${this.endDate}` : '' },
totalAmount() { return this.items.reduce((s, it) => s + Number(it.amount || 0), 0) }
},
onLoad() {
try { console.log('[detail] onLoad route = pages/detail/index') } catch(e){}
this.computeRange()
this.reload()
},
methods: {
switchBiz(k) { if (this.biz === k) return; this.biz = k; this.reload() },
switchRange(r) { this.range = r; this.computeRange(); this.reload() },
computeRange() {
const now = new Date()
const pad = n => String(n).padStart(2, '0')
const fmt = d => `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`
let start = now, end = now
if (this.range === 'today') { start = end = now }
else if (this.range === 'week') { const day = now.getDay() || 7; start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - day + 1); end = now }
else if (this.range === 'month') { start = new Date(now.getFullYear(), now.getMonth(), 1); end = new Date(now.getFullYear(), now.getMonth() + 1, 0) }
else if (this.range === 'year') { start = new Date(now.getFullYear(), 0, 1); end = new Date(now.getFullYear(), 11, 31) }
else { start = new Date(now.getFullYear(), now.getMonth(), 1); end = new Date(now.getFullYear(), now.getMonth() + 1, 0) }
this.startDate = fmt(start); this.endDate = fmt(end)
},
reload() { this.items = []; this.page = 1; this.finished = false; this.loadMore() },
async loadMore() {
if (this.loading || this.finished) return
this.loading = true
try {
const path = API_OF[this.biz] || '/api/orders'
const params = { kw: this.query.kw, page: this.page, size: this.size, startDate: this.startDate, endDate: this.endDate, biz: this.biz }
if (this.biz === 'sale') params.type = 'out'
const res = await get(path, params)
const list = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])
this.items = this.items.concat(list)
if (list.length < this.size) this.finished = true
this.page += 1
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
} finally { this.loading = false }
},
formatDate(s) { if (!s) return ''; try { const d = new Date(s); const pad = n => String(n).padStart(2, '0'); return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}` } catch (_) { return String(s).slice(0,10) } },
onCreate() { if (this.biz === 'sale') { uni.navigateTo({ url: '/pages/order/create' }); return } uni.showToast({ title: '该类型创建页待实现', icon: 'none' }) },
openDetail(it) { uni.showToast({ title: '详情开发中', icon: 'none' }) }
}
}
</script>
<style>
.page { display:flex; flex-direction: column; height: 100vh; }
.seg { display:flex; background:#fff; }
.seg-item { flex:1; padding: 22rpx 0; text-align:center; color:#666; }
.seg-item.active { color:#18b566; font-weight: 600; }
.content { display:flex; flex:1; min-height: 0; }
.biz-tabs { width: 120rpx; background:#eef6ff; display:flex; flex-direction: column; }
.biz { flex:0 0 120rpx; display:flex; align-items:center; justify-content:center; color:#4aa3d6; }
.biz.active { background:#3ac1c9; color:#fff; border-radius: 0 16rpx 16rpx 0; }
.panel { flex:1; display:flex; flex-direction: column; background:#fff; margin: 16rpx; border-radius: 16rpx; padding: 12rpx; }
.toolbar { display:flex; align-items: center; gap: 12rpx; padding: 8rpx 6rpx; }
.search { flex:1; }
.search-input { width:100%; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx; }
.period { color:#999; font-size: 24rpx; padding: 0 6rpx; }
.total { color:#18b566; font-weight: 700; padding: 6rpx 6rpx 12rpx; }
.list { flex:1; }
.item { display:flex; align-items:center; padding: 20rpx 10rpx; border-bottom: 1rpx solid #f1f1f1; }
.item-left { flex:1; }
.date { color:#999; font-size: 24rpx; }
.name { color:#333; margin: 4rpx 0; font-weight: 600; }
.no { color:#bbb; font-size: 22rpx; }
.amount { color:#333; font-weight: 700; }
.arrow { color:#ccc; font-size: 40rpx; margin-left: 8rpx; }
.empty { height: 50vh; display:flex; align-items:center; justify-content:center; color:#999; }
.fab { position: fixed; right: 30rpx; bottom: 120rpx; width: 100rpx; height: 100rpx; background:#18b566; color:#fff; border-radius: 50rpx; text-align:center; line-height: 100rpx; font-size: 48rpx; box-shadow: 0 8rpx 20rpx rgba(0,0,0,0.15); }
</style>

View File

@@ -75,7 +75,7 @@
<view class="tab primary" @click="onCreateOrder">
<text>开单</text>
</view>
<view class="tab" :class="{ active: activeTab==='detail' }" @click="activeTab='detail'">
<view class="tab" :class="{ active: activeTab==='detail' }" @click="goDetail">
<text>明细</text>
</view>
<view class="tab" :class="{ active: activeTab==='report' }" @click="activeTab='report'">
@@ -161,6 +161,11 @@
onCreateOrder() {
uni.navigateTo({ url: '/pages/order/create' })
},
goDetail() {
this.activeTab = 'detail'
try { console.log('[index] goDetail → /pages/detail/index') } catch(e){}
uni.navigateTo({ url: '/pages/detail/index' })
},
onNoticeTap(n) {
uni.showModal({
title: '广告',
@@ -325,6 +330,7 @@
background: rgba(255,255,255,0.85);
box-shadow: 0 -6rpx 18rpx rgba(0,0,0,0.08);
backdrop-filter: blur(10rpx);
z-index: 9999;
}
.tab { flex: 1; text-align: center; color: #8a7535; font-size: 26rpx; }

View File

@@ -36,8 +36,48 @@
<text class="value">{{ supplierLabel }}</text>
</view>
<!-- 已选商品与合计销售/进货 -->
<view v-if="biz==='sale' || biz==='purchase'">
<!-- 销售/进货收款/付款 专用页面 -->
<view v-if="(biz==='sale' && saleType==='collect') || (biz==='purchase' && purchaseType==='pay')">
<!-- 客户 / 供应商 -->
<view class="field" v-if="biz==='sale'" @click="chooseCustomer">
<text class="label">客户</text>
<text class="value">{{ customerLabel }}</text>
</view>
<view class="field" v-else @click="chooseSupplier">
<text class="label">供应商</text>
<text class="value">{{ supplierLabel }}</text>
</view>
<!-- 三种收付款方式 -->
<view class="field pay-row">
<text class="label">现金</text>
<input class="pay-input" type="digit" v-model.number="payments.cash" placeholder="0.00" @input="recalcPay()"/>
</view>
<view class="field pay-row">
<text class="label">银行存款</text>
<input class="pay-input" type="digit" v-model.number="payments.bank" placeholder="0.00" @input="recalcPay()"/>
</view>
<view class="field pay-row">
<text class="label">微信</text>
<input class="pay-input" type="digit" v-model.number="payments.wechat" placeholder="0.00" @input="recalcPay()"/>
</view>
<view class="collapse-trigger" @click="showMore = !showMore">{{ showMore ? '收起' : '' }}</view>
<!-- 备注与日期 -->
<view class="textarea">
<view class="amount-badge">总金额{{ payTotal.toFixed(2) }}</view>
<textarea v-model="order.remark" maxlength="200" placeholder="备注最多输入200个字"></textarea>
<view class="date-mini">
<picker mode="date" :value="order.orderTime" @change="onDateChange">
<text>{{ order.orderTime }}</text>
</picker>
</view>
</view>
</view>
<!-- 已选商品与合计销售/进货 出入库 -->
<view v-else-if="biz==='sale' || biz==='purchase'">
<view class="summary">
<text>选中货品{{ totalQuantity }}</text>
<text>合计金额¥ {{ totalAmount.toFixed(2) }}</text>
@@ -127,7 +167,10 @@
activeCategory: 'sale_income',
trxAmount: 0,
selectedAccountId: null,
selectedAccountName: ''
selectedAccountName: '',
// 收款/付款输入
payments: { cash: 0, bank: 0, wechat: 0 },
showMore: false
}
},
computed: {
@@ -141,11 +184,16 @@
supplierLabel() { return this.supplierName || '零散供应商' },
incomeCategories() { return INCOME_CATEGORIES },
expenseCategories() { return EXPENSE_CATEGORIES },
accountLabel() { return this.selectedAccountName || '现金' },
counterpartyLabel() { return this.customerName || this.supplierName || '—' }
accountLabel() { return this.selectedAccountName || '现金' },
counterpartyLabel() { return this.customerName || this.supplierName || '—' },
// 收款/付款合计
payTotal() {
const p = this.payments || { cash:0, bank:0, wechat:0 }
return Number(p.cash||0) + Number(p.bank||0) + Number(p.wechat||0)
}
},
methods: {
switchBiz(type) { this.biz = type },
switchBiz(type) { this.biz = type },
onDateChange(e) { this.order.orderTime = e.detail.value },
chooseCustomer() {
uni.navigateTo({ url: '/pages/customer/select' })
@@ -160,17 +208,24 @@
uni.navigateTo({ url: '/pages/customer/select' })
}
},
recalc() { this.$forceUpdate() },
recalc() { this.$forceUpdate() },
recalcPay() { this.$forceUpdate() },
async submit() {
const isSaleOrPurchase = (this.biz==='sale' || this.biz==='purchase')
const payload = isSaleOrPurchase ? {
type: this.biz === 'sale' ? (this.saleType) : ('purchase.' + this.purchaseType),
orderTime: this.order.orderTime,
customerId: this.order.customerId,
supplierId: this.order.supplierId,
items: this.items.map(it => ({ productId: it.productId, quantity: Number(it.quantity||0), unitPrice: Number(it.unitPrice||0) })),
amount: this.totalAmount
} : {
const isSaleOrPurchase = (this.biz==='sale' || this.biz==='purchase')
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 payload = isSaleOrPurchase ? (isCollectOrPay ? [
{ method: 'cash', amount: Number(this.payments.cash||0) },
{ method: 'bank', amount: Number(this.payments.bank||0) },
{ method: 'wechat', amount: Number(this.payments.wechat||0) }
].filter(p => p.amount>0) : {
type: saleTypeValue,
orderTime: this.order.orderTime,
customerId: this.order.customerId,
supplierId: this.order.supplierId,
items: this.items.map(it => ({ productId: it.productId, quantity: Number(it.quantity||0), unitPrice: Number(it.unitPrice||0) })),
amount: this.totalAmount
}) : {
type: this.biz,
category: this.activeCategory,
counterpartyId: this.order.customerId || null,
@@ -179,8 +234,8 @@
txTime: this.order.orderTime,
remark: this.order.remark
}
try {
const url = isSaleOrPurchase ? '/api/orders' : '/api/other-transactions'
try {
const url = isSaleOrPurchase ? (isCollectOrPay ? (`/api/payments/${this.biz}`) : '/api/orders') : '/api/other-transactions'
await post(url, payload)
uni.showToast({ title: '已保存', icon: 'success' })
setTimeout(() => { uni.navigateBack() }, 600)
@@ -188,10 +243,11 @@
uni.showToast({ title: e && e.message || '保存失败', icon: 'none' })
}
},
saveAndReset() {
saveAndReset() {
this.items = []
this.trxAmount = 0
this.order.remark = ''
this.payments = { cash: 0, bank: 0, wechat: 0 }
}
}
}
@@ -219,6 +275,11 @@
.col.amount { text-align:right; padding-right: 12rpx; color:#333; }
.bottom { position: fixed; left:0; right:0; bottom:0; background:#fff; padding: 16rpx 24rpx calc(env(safe-area-inset-bottom) + 16rpx); box-shadow: 0 -4rpx 12rpx rgba(0,0,0,0.06); }
.primary { width: 100%; background: linear-gradient(135deg, #FFE69A 0%, #F4CF62 45%, #D7A72E 100%); color:#493c1b; border-radius: 999rpx; padding: 20rpx 0; font-weight:800; }
/* 收款/付款页样式 */
.pay-row .pay-input { text-align: right; color:#333; }
.textarea { position: relative; padding: 16rpx 24rpx; background:#fff; border-top: 1rpx solid #eee; }
.amount-badge { position: absolute; right: 24rpx; top: -36rpx; background: #d1f0ff; color:#107e9b; padding: 8rpx 16rpx; border-radius: 12rpx; font-size: 24rpx; }
.date-mini { position: absolute; right: 24rpx; bottom: 20rpx; color:#666; font-size: 24rpx; }
</style>

View File

@@ -65,3 +65,6 @@ export default {

View File

@@ -53,6 +53,10 @@ export default {
this.fetchCategories()
this.reload()
},
onShow() {
// 从创建/编辑页返回时,确保刷新最新列表
this.reload()
},
computed: {
categoryNames() { return this.categories.map(c => c.name) },
categoryLabel() {
@@ -131,3 +135,6 @@ export default {

View File

@@ -7,7 +7,7 @@
<scroll-view scroll-y class="list">
<view class="item" v-for="p in products" :key="p.id" @click="select(p)">
<view class="name">{{ p.name }}</view>
<view class="meta">{{ p.code }} · 库存{{ p.stock || 0 }}</view>
<view class="meta">{{ (p.brand||'') + ' ' + (p.model||'') + ' ' + (p.spec||'') }} · 库存{{ p.stock ?? 0 }}</view>
</view>
</scroll-view>
</view>

View File

@@ -43,3 +43,6 @@ export default {

View File

@@ -65,3 +65,6 @@ export default {

View File

View File

@@ -1,11 +0,0 @@
var __getOwnPropNames = Object.getOwnPropertyNames;
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var require_app_css = __commonJS({
"app.css.js"(exports) {
const _style_0 = {};
exports.styles = [_style_0];
}
});
export default require_app_css();

View File

@@ -1,2 +0,0 @@
Promise.resolve("./app.css.js").then(() => {
});

View File

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

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":"categories.js","sources":["pages/product/categories.vue","../../../../Downloads/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvcHJvZHVjdC9jYXRlZ29yaWVzLnZ1ZQ"],"sourcesContent":["<template>\r\n\t<view class=\"page\">\r\n\t\t<view class=\"toolbar\">\r\n\t\t\t<input v-model.trim=\"name\" placeholder=\"新类别名称\" />\r\n\t\t\t<button size=\"mini\" @click=\"create\">新增</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 list\" :key=\"c.id\">\r\n\t\t\t\t<input v-model.trim=\"c.name\" />\r\n\t\t\t\t<view class=\"ops\">\r\n\t\t\t\t\t<button size=\"mini\" @click=\"update(c)\">保存</button>\r\n\t\t\t\t\t<button size=\"mini\" type=\"warn\" @click=\"remove(c)\">删除</button>\r\n\t\t\t\t</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\nimport { get, post, put, del } from '../../common/http.js'\r\n\r\nexport default {\r\n\tdata() {\r\n\t\treturn { name: '', list: [] }\r\n\t},\r\n\tonLoad() { this.reload() },\r\n\tmethods: {\r\n\t\tasync reload() {\r\n\t\t\ttry {\r\n\t\t\t\tconst res = await get('/api/product-categories')\r\n\t\t\t\tthis.list = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])\r\n\t\t\t} catch (_) {}\r\n\t\t},\r\n\t\tasync create() {\r\n\t\t\tif (!this.name) return\r\n\t\t\tawait post('/api/product-categories', { name: this.name })\r\n\t\t\tthis.name = ''\r\n\t\t\tthis.reload()\r\n\t\t},\r\n\t\tasync update(c) {\r\n\t\t\tawait put('/api/product-categories/' + c.id, { name: c.name })\r\n\t\t\tuni.showToast({ title: '已保存', icon: 'success' })\r\n\t\t},\r\n\t\tasync remove(c) {\r\n\t\t\tuni.showModal({ content: '确定删除该类别?', success: async (r) => {\r\n\t\t\t\tif (!r.confirm) return\r\n\t\t\t\tawait del('/api/product-categories/' + c.id)\r\n\t\t\t\tthis.reload()\r\n\t\t\t}})\r\n\t\t}\r\n\t}\r\n}\r\n</script>\r\n\r\n<style>\r\n.page { display:flex; flex-direction: column; height: 100vh; }\r\n.toolbar { display:flex; gap: 12rpx; padding: 16rpx; background:#fff; }\r\n.toolbar input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx; }\r\n.list { flex:1; }\r\n.item { display:flex; gap: 12rpx; align-items:center; padding: 16rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1; }\r\n.item input { flex:1; background:#f7f7f7; border-radius: 10rpx; padding: 12rpx; }\r\n.ops { display:flex; gap: 10rpx; }\r\n</style>\r\n\r\n\r\n\r\n\r\n","import MiniProgramPage from 'C:/Users/21826/Desktop/Wj/PartsInquiry/frontend/pages/product/categories.vue'\nwx.createPage(MiniProgramPage)"],"names":["get","post","put","uni","del"],"mappings":";;;AAqBA,MAAK,YAAU;AAAA,EACd,OAAO;AACN,WAAO,EAAE,MAAM,IAAI,MAAM,CAAA,EAAG;AAAA,EAC5B;AAAA,EACD,SAAS;AAAE,SAAK;EAAU;AAAA,EAC1B,SAAS;AAAA,IACR,MAAM,SAAS;AACd,UAAI;AACH,cAAM,MAAM,MAAMA,YAAG,IAAC,yBAAyB;AAC/C,aAAK,OAAO,MAAM,QAAQ,2BAAK,IAAI,IAAI,IAAI,OAAQ,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAA;AAAA,eACtE,GAAG;AAAA,MAAC;AAAA,IACb;AAAA,IACD,MAAM,SAAS;AACd,UAAI,CAAC,KAAK;AAAM;AAChB,YAAMC,YAAAA,KAAK,2BAA2B,EAAE,MAAM,KAAK,KAAG,CAAG;AACzD,WAAK,OAAO;AACZ,WAAK,OAAO;AAAA,IACZ;AAAA,IACD,MAAM,OAAO,GAAG;AACf,YAAMC,YAAG,IAAC,6BAA6B,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM;AAC7DC,oBAAG,MAAC,UAAU,EAAE,OAAO,OAAO,MAAM,WAAW;AAAA,IAC/C;AAAA,IACD,MAAM,OAAO,GAAG;AACfA,oBAAG,MAAC,UAAU,EAAE,SAAS,YAAY,SAAS,OAAO,MAAM;AAC1D,YAAI,CAAC,EAAE;AAAS;AAChB,cAAMC,gBAAI,6BAA6B,EAAE,EAAE;AAC3C,aAAK,OAAO;AAAA,MACb,EAAC,CAAC;AAAA,IACH;AAAA,EACD;AACD;;;;;;;;;;;;;;;;;;;;;;AClDA,GAAG,WAAW,eAAe;"}
{"version":3,"file":"categories.js","sources":["pages/product/categories.vue","../../../../Downloads/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvcHJvZHVjdC9jYXRlZ29yaWVzLnZ1ZQ"],"sourcesContent":["<template>\r\n\t<view class=\"page\">\r\n\t\t<view class=\"toolbar\">\r\n\t\t\t<input v-model.trim=\"name\" placeholder=\"新类别名称\" />\r\n\t\t\t<button size=\"mini\" @click=\"create\">新增</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 list\" :key=\"c.id\">\r\n\t\t\t\t<input v-model.trim=\"c.name\" />\r\n\t\t\t\t<view class=\"ops\">\r\n\t\t\t\t\t<button size=\"mini\" @click=\"update(c)\">保存</button>\r\n\t\t\t\t\t<button size=\"mini\" type=\"warn\" @click=\"remove(c)\">删除</button>\r\n\t\t\t\t</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\nimport { get, post, put, del } from '../../common/http.js'\r\n\r\nexport default {\r\n\tdata() {\r\n\t\treturn { name: '', list: [] }\r\n\t},\r\n\tonLoad() { this.reload() },\r\n\tmethods: {\r\n\t\tasync reload() {\r\n\t\t\ttry {\r\n\t\t\t\tconst res = await get('/api/product-categories')\r\n\t\t\t\tthis.list = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])\r\n\t\t\t} catch (_) {}\r\n\t\t},\r\n\t\tasync create() {\r\n\t\t\tif (!this.name) return\r\n\t\t\tawait post('/api/product-categories', { name: this.name })\r\n\t\t\tthis.name = ''\r\n\t\t\tthis.reload()\r\n\t\t},\r\n\t\tasync update(c) {\r\n\t\t\tawait put('/api/product-categories/' + c.id, { name: c.name })\r\n\t\t\tuni.showToast({ title: '已保存', icon: 'success' })\r\n\t\t},\r\n\t\tasync remove(c) {\r\n\t\t\tuni.showModal({ content: '确定删除该类别?', success: async (r) => {\r\n\t\t\t\tif (!r.confirm) return\r\n\t\t\t\tawait del('/api/product-categories/' + c.id)\r\n\t\t\t\tthis.reload()\r\n\t\t\t}})\r\n\t\t}\r\n\t}\r\n}\r\n</script>\r\n\r\n<style>\r\n.page { display:flex; flex-direction: column; height: 100vh; }\r\n.toolbar { display:flex; gap: 12rpx; padding: 16rpx; background:#fff; }\r\n.toolbar input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx; }\r\n.list { flex:1; }\r\n.item { display:flex; gap: 12rpx; align-items:center; padding: 16rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1; }\r\n.item input { flex:1; background:#f7f7f7; border-radius: 10rpx; padding: 12rpx; }\r\n.ops { display:flex; gap: 10rpx; }\r\n</style>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n","import MiniProgramPage from 'C:/Users/21826/Desktop/Wj/PartsInquiry/frontend/pages/product/categories.vue'\nwx.createPage(MiniProgramPage)"],"names":["get","post","put","uni","del"],"mappings":";;;AAqBA,MAAK,YAAU;AAAA,EACd,OAAO;AACN,WAAO,EAAE,MAAM,IAAI,MAAM,CAAA,EAAG;AAAA,EAC5B;AAAA,EACD,SAAS;AAAE,SAAK;EAAU;AAAA,EAC1B,SAAS;AAAA,IACR,MAAM,SAAS;AACd,UAAI;AACH,cAAM,MAAM,MAAMA,YAAG,IAAC,yBAAyB;AAC/C,aAAK,OAAO,MAAM,QAAQ,2BAAK,IAAI,IAAI,IAAI,OAAQ,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAA;AAAA,eACtE,GAAG;AAAA,MAAC;AAAA,IACb;AAAA,IACD,MAAM,SAAS;AACd,UAAI,CAAC,KAAK;AAAM;AAChB,YAAMC,YAAAA,KAAK,2BAA2B,EAAE,MAAM,KAAK,KAAG,CAAG;AACzD,WAAK,OAAO;AACZ,WAAK,OAAO;AAAA,IACZ;AAAA,IACD,MAAM,OAAO,GAAG;AACf,YAAMC,YAAG,IAAC,6BAA6B,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM;AAC7DC,oBAAG,MAAC,UAAU,EAAE,OAAO,OAAO,MAAM,WAAW;AAAA,IAC/C;AAAA,IACD,MAAM,OAAO,GAAG;AACfA,oBAAG,MAAC,UAAU,EAAE,SAAS,YAAY,SAAS,OAAO,MAAM;AAC1D,YAAI,CAAC,EAAE;AAAS;AAChB,cAAMC,gBAAI,6BAA6B,EAAE,EAAE;AAC3C,aAAK,OAAO;AAAA,MACb,EAAC,CAAC;AAAA,IACH;AAAA,EACD;AACD;;;;;;;;;;;;;;;;;;;;;;AClDA,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

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.code }} · 库存:{{ p.stock || 0 }}</view>\r\n\t\t\t</view>\r\n\t\t</scroll-view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport { get } from '../../common/http.js'\r\n\texport default {\r\n\t\tdata() { return { kw: '', products: [] } },\r\n\t\tonLoad() { this.search() },\r\n\t\tmethods: {\r\n\t\t\tasync search() {\r\n\t\t\t\ttry {\r\n\t\t\t\t\tconst res = await get('/api/products', { kw: this.kw, page: 1, size: 50 })\r\n\t\t\t\t\tthis.products = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])\r\n\t\t\t\t} catch(e) { uni.showToast({ title: '加载失败', icon: 'none' }) }\r\n\t\t\t},\r\n\t\t\tselect(p) {\r\n\t\t\t\tconst opener = getCurrentPages()[getCurrentPages().length-2]\r\n\t\t\t\tif (opener && opener.$vm && opener.$vm.items) {\r\n\t\t\t\t\topener.$vm.items.push({ productId: p.id, productName: p.name, quantity: 1, unitPrice: Number(p.price || 0) })\r\n\t\t\t\t}\r\n\t\t\t\tuni.navigateBack()\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style>\r\n\t.page { display:flex; flex-direction: column; height: 100vh; }\r\n\t.search { display:flex; gap: 12rpx; padding: 16rpx; background:#fff; }\r\n\t.search input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx; }\r\n\t.list { flex:1; }\r\n\t.item { padding: 20rpx 24rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1; }\r\n\t.name { color:#333; margin-bottom: 6rpx; }\r\n\t.meta { color:#888; font-size: 24rpx; }\r\n</style>\r\n\r\n\r\n\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\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;"}

View File

@@ -1 +1 @@
{"version":3,"file":"settings.js","sources":["pages/product/settings.vue","../../../../Downloads/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvcHJvZHVjdC9zZXR0aW5ncy52dWU"],"sourcesContent":["<template>\r\n\t<view class=\"page\">\r\n\t\t<view class=\"item\">\r\n\t\t\t<text>隐藏零库存商品</text>\r\n\t\t\t<switch :checked=\"settings.hideZeroStock\" @change=\"(e)=>update('hideZeroStock', e.detail.value)\" />\r\n\t\t</view>\r\n\t\t<view class=\"item\">\r\n\t\t\t<text>隐藏进货价</text>\r\n\t\t\t<switch :checked=\"settings.hidePurchasePrice\" @change=\"(e)=>update('hidePurchasePrice', e.detail.value)\" />\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\nimport { get, put } from '../../common/http.js'\r\n\r\nexport default {\r\n\tdata() {\r\n\t\treturn { settings: { hideZeroStock: false, hidePurchasePrice: false } }\r\n\t},\r\n\tonLoad() { this.load() },\r\n\tmethods: {\r\n\t\tasync load() {\r\n\t\t\ttry {\r\n\t\t\t\tconst res = await get('/api/product-settings')\r\n\t\t\t\tthis.settings = { hideZeroStock: !!res?.hideZeroStock, hidePurchasePrice: !!res?.hidePurchasePrice }\r\n\t\t\t} catch (_) {}\r\n\t\t},\r\n\t\tasync update(key, val) {\r\n\t\t\tconst next = { ...this.settings, [key]: val }\r\n\t\t\tthis.settings = next\r\n\t\t\ttry { await put('/api/product-settings', next) } catch (_) {}\r\n\t\t}\r\n\t}\r\n}\r\n</script>\r\n\r\n<style>\r\n.page { background:#fff; }\r\n.item { display:flex; justify-content: space-between; align-items:center; padding: 20rpx; border-bottom: 1rpx solid #f1f1f1; }\r\n</style>\r\n\r\n\r\n\r\n\r\n","import MiniProgramPage from 'C:/Users/21826/Desktop/Wj/PartsInquiry/frontend/pages/product/settings.vue'\nwx.createPage(MiniProgramPage)"],"names":["get","put"],"mappings":";;;AAgBA,MAAK,YAAU;AAAA,EACd,OAAO;AACN,WAAO,EAAE,UAAU,EAAE,eAAe,OAAO,mBAAmB,QAAQ;AAAA,EACtE;AAAA,EACD,SAAS;AAAE,SAAK;EAAQ;AAAA,EACxB,SAAS;AAAA,IACR,MAAM,OAAO;AACZ,UAAI;AACH,cAAM,MAAM,MAAMA,YAAG,IAAC,uBAAuB;AAC7C,aAAK,WAAW,EAAE,eAAe,CAAC,EAAC,2BAAK,gBAAe,mBAAmB,CAAC,EAAC,2BAAK,mBAAkB;AAAA,eAC3F,GAAG;AAAA,MAAC;AAAA,IACb;AAAA,IACD,MAAM,OAAO,KAAK,KAAK;AACtB,YAAM,OAAO,EAAE,GAAG,KAAK,UAAU,CAAC,GAAG,GAAG,IAAI;AAC5C,WAAK,WAAW;AAChB,UAAI;AAAE,cAAMC,YAAAA,IAAI,yBAAyB,IAAI;AAAA,MAAI,SAAO,GAAG;AAAA,MAAC;AAAA,IAC7D;AAAA,EACD;AACD;;;;;;;;;;ACjCA,GAAG,WAAW,eAAe;"}
{"version":3,"file":"settings.js","sources":["pages/product/settings.vue","../../../../Downloads/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvcHJvZHVjdC9zZXR0aW5ncy52dWU"],"sourcesContent":["<template>\r\n\t<view class=\"page\">\r\n\t\t<view class=\"item\">\r\n\t\t\t<text>隐藏零库存商品</text>\r\n\t\t\t<switch :checked=\"settings.hideZeroStock\" @change=\"(e)=>update('hideZeroStock', e.detail.value)\" />\r\n\t\t</view>\r\n\t\t<view class=\"item\">\r\n\t\t\t<text>隐藏进货价</text>\r\n\t\t\t<switch :checked=\"settings.hidePurchasePrice\" @change=\"(e)=>update('hidePurchasePrice', e.detail.value)\" />\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\nimport { get, put } from '../../common/http.js'\r\n\r\nexport default {\r\n\tdata() {\r\n\t\treturn { settings: { hideZeroStock: false, hidePurchasePrice: false } }\r\n\t},\r\n\tonLoad() { this.load() },\r\n\tmethods: {\r\n\t\tasync load() {\r\n\t\t\ttry {\r\n\t\t\t\tconst res = await get('/api/product-settings')\r\n\t\t\t\tthis.settings = { hideZeroStock: !!res?.hideZeroStock, hidePurchasePrice: !!res?.hidePurchasePrice }\r\n\t\t\t} catch (_) {}\r\n\t\t},\r\n\t\tasync update(key, val) {\r\n\t\t\tconst next = { ...this.settings, [key]: val }\r\n\t\t\tthis.settings = next\r\n\t\t\ttry { await put('/api/product-settings', next) } catch (_) {}\r\n\t\t}\r\n\t}\r\n}\r\n</script>\r\n\r\n<style>\r\n.page { background:#fff; }\r\n.item { display:flex; justify-content: space-between; align-items:center; padding: 20rpx; border-bottom: 1rpx solid #f1f1f1; }\r\n</style>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n","import MiniProgramPage from 'C:/Users/21826/Desktop/Wj/PartsInquiry/frontend/pages/product/settings.vue'\nwx.createPage(MiniProgramPage)"],"names":["get","put"],"mappings":";;;AAgBA,MAAK,YAAU;AAAA,EACd,OAAO;AACN,WAAO,EAAE,UAAU,EAAE,eAAe,OAAO,mBAAmB,QAAQ;AAAA,EACtE;AAAA,EACD,SAAS;AAAE,SAAK;EAAQ;AAAA,EACxB,SAAS;AAAA,IACR,MAAM,OAAO;AACZ,UAAI;AACH,cAAM,MAAM,MAAMA,YAAG,IAAC,uBAAuB;AAC7C,aAAK,WAAW,EAAE,eAAe,CAAC,EAAC,2BAAK,gBAAe,mBAAmB,CAAC,EAAC,2BAAK,mBAAkB;AAAA,eAC3F,GAAG;AAAA,MAAC;AAAA,IACb;AAAA,IACD,MAAM,OAAO,KAAK,KAAK;AACtB,YAAM,OAAO,EAAE,GAAG,KAAK,UAAU,CAAC,GAAG,GAAG,IAAI;AAC5C,WAAK,WAAW;AAChB,UAAI;AAAE,cAAMC,YAAAA,IAAI,yBAAyB,IAAI;AAAA,MAAI,SAAO,GAAG;AAAA,MAAC;AAAA,IAC7D;AAAA,EACD;AACD;;;;;;;;;;ACjCA,GAAG,WAAW,eAAe;"}

View File

@@ -1 +1 @@
{"version":3,"file":"units.js","sources":["pages/product/units.vue","../../../../Downloads/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvcHJvZHVjdC91bml0cy52dWU"],"sourcesContent":["<template>\r\n\t<view class=\"page\">\r\n\t\t<view class=\"toolbar\">\r\n\t\t\t<input v-model.trim=\"name\" placeholder=\"新单位名称\" />\r\n\t\t\t<button size=\"mini\" @click=\"create\">新增</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=\"u in list\" :key=\"u.id\">\r\n\t\t\t\t<input v-model.trim=\"u.name\" />\r\n\t\t\t\t<view class=\"ops\">\r\n\t\t\t\t\t<button size=\"mini\" @click=\"update(u)\">保存</button>\r\n\t\t\t\t\t<button size=\"mini\" type=\"warn\" @click=\"remove(u)\">删除</button>\r\n\t\t\t\t</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\nimport { get, post, put, del } from '../../common/http.js'\r\n\r\nexport default {\r\n\tdata() {\r\n\t\treturn { name: '', list: [] }\r\n\t},\r\n\tonLoad() { this.reload() },\r\n\tmethods: {\r\n\t\tasync reload() {\r\n\t\t\ttry {\r\n\t\t\t\tconst res = await get('/api/product-units')\r\n\t\t\t\tthis.list = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])\r\n\t\t\t} catch (_) {}\r\n\t\t},\r\n\t\tasync create() {\r\n\t\t\tif (!this.name) return\r\n\t\t\tawait post('/api/product-units', { name: this.name })\r\n\t\t\tthis.name = ''\r\n\t\t\tthis.reload()\r\n\t\t},\r\n\t\tasync update(u) {\r\n\t\t\tawait put('/api/product-units/' + u.id, { name: u.name })\r\n\t\t\tuni.showToast({ title: '已保存', icon: 'success' })\r\n\t\t},\r\n\t\tasync remove(u) {\r\n\t\t\tuni.showModal({ content: '确定删除该单位?', success: async (r) => {\r\n\t\t\t\tif (!r.confirm) return\r\n\t\t\t\tawait del('/api/product-units/' + u.id)\r\n\t\t\t\tthis.reload()\r\n\t\t\t}})\r\n\t\t}\r\n\t}\r\n}\r\n</script>\r\n\r\n<style>\r\n.page { display:flex; flex-direction: column; height: 100vh; }\r\n.toolbar { display:flex; gap: 12rpx; padding: 16rpx; background:#fff; }\r\n.toolbar input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx; }\r\n.list { flex:1; }\r\n.item { display:flex; gap: 12rpx; align-items:center; padding: 16rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1; }\r\n.item input { flex:1; background:#f7f7f7; border-radius: 10rpx; padding: 12rpx; }\r\n.ops { display:flex; gap: 10rpx; }\r\n</style>\r\n\r\n\r\n\r\n\r\n","import MiniProgramPage from 'C:/Users/21826/Desktop/Wj/PartsInquiry/frontend/pages/product/units.vue'\nwx.createPage(MiniProgramPage)"],"names":["get","post","put","uni","del"],"mappings":";;;AAqBA,MAAK,YAAU;AAAA,EACd,OAAO;AACN,WAAO,EAAE,MAAM,IAAI,MAAM,CAAA,EAAG;AAAA,EAC5B;AAAA,EACD,SAAS;AAAE,SAAK;EAAU;AAAA,EAC1B,SAAS;AAAA,IACR,MAAM,SAAS;AACd,UAAI;AACH,cAAM,MAAM,MAAMA,YAAG,IAAC,oBAAoB;AAC1C,aAAK,OAAO,MAAM,QAAQ,2BAAK,IAAI,IAAI,IAAI,OAAQ,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAA;AAAA,eACtE,GAAG;AAAA,MAAC;AAAA,IACb;AAAA,IACD,MAAM,SAAS;AACd,UAAI,CAAC,KAAK;AAAM;AAChB,YAAMC,YAAAA,KAAK,sBAAsB,EAAE,MAAM,KAAK,KAAG,CAAG;AACpD,WAAK,OAAO;AACZ,WAAK,OAAO;AAAA,IACZ;AAAA,IACD,MAAM,OAAO,GAAG;AACf,YAAMC,YAAG,IAAC,wBAAwB,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM;AACxDC,oBAAG,MAAC,UAAU,EAAE,OAAO,OAAO,MAAM,WAAW;AAAA,IAC/C;AAAA,IACD,MAAM,OAAO,GAAG;AACfA,oBAAG,MAAC,UAAU,EAAE,SAAS,YAAY,SAAS,OAAO,MAAM;AAC1D,YAAI,CAAC,EAAE;AAAS;AAChB,cAAMC,gBAAI,wBAAwB,EAAE,EAAE;AACtC,aAAK,OAAO;AAAA,MACb,EAAC,CAAC;AAAA,IACH;AAAA,EACD;AACD;;;;;;;;;;;;;;;;;;;;;;AClDA,GAAG,WAAW,eAAe;"}
{"version":3,"file":"units.js","sources":["pages/product/units.vue","../../../../Downloads/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvcHJvZHVjdC91bml0cy52dWU"],"sourcesContent":["<template>\r\n\t<view class=\"page\">\r\n\t\t<view class=\"toolbar\">\r\n\t\t\t<input v-model.trim=\"name\" placeholder=\"新单位名称\" />\r\n\t\t\t<button size=\"mini\" @click=\"create\">新增</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=\"u in list\" :key=\"u.id\">\r\n\t\t\t\t<input v-model.trim=\"u.name\" />\r\n\t\t\t\t<view class=\"ops\">\r\n\t\t\t\t\t<button size=\"mini\" @click=\"update(u)\">保存</button>\r\n\t\t\t\t\t<button size=\"mini\" type=\"warn\" @click=\"remove(u)\">删除</button>\r\n\t\t\t\t</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\nimport { get, post, put, del } from '../../common/http.js'\r\n\r\nexport default {\r\n\tdata() {\r\n\t\treturn { name: '', list: [] }\r\n\t},\r\n\tonLoad() { this.reload() },\r\n\tmethods: {\r\n\t\tasync reload() {\r\n\t\t\ttry {\r\n\t\t\t\tconst res = await get('/api/product-units')\r\n\t\t\t\tthis.list = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])\r\n\t\t\t} catch (_) {}\r\n\t\t},\r\n\t\tasync create() {\r\n\t\t\tif (!this.name) return\r\n\t\t\tawait post('/api/product-units', { name: this.name })\r\n\t\t\tthis.name = ''\r\n\t\t\tthis.reload()\r\n\t\t},\r\n\t\tasync update(u) {\r\n\t\t\tawait put('/api/product-units/' + u.id, { name: u.name })\r\n\t\t\tuni.showToast({ title: '已保存', icon: 'success' })\r\n\t\t},\r\n\t\tasync remove(u) {\r\n\t\t\tuni.showModal({ content: '确定删除该单位?', success: async (r) => {\r\n\t\t\t\tif (!r.confirm) return\r\n\t\t\t\tawait del('/api/product-units/' + u.id)\r\n\t\t\t\tthis.reload()\r\n\t\t\t}})\r\n\t\t}\r\n\t}\r\n}\r\n</script>\r\n\r\n<style>\r\n.page { display:flex; flex-direction: column; height: 100vh; }\r\n.toolbar { display:flex; gap: 12rpx; padding: 16rpx; background:#fff; }\r\n.toolbar input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx; }\r\n.list { flex:1; }\r\n.item { display:flex; gap: 12rpx; align-items:center; padding: 16rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1; }\r\n.item input { flex:1; background:#f7f7f7; border-radius: 10rpx; padding: 12rpx; }\r\n.ops { display:flex; gap: 10rpx; }\r\n</style>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n","import MiniProgramPage from 'C:/Users/21826/Desktop/Wj/PartsInquiry/frontend/pages/product/units.vue'\nwx.createPage(MiniProgramPage)"],"names":["get","post","put","uni","del"],"mappings":";;;AAqBA,MAAK,YAAU;AAAA,EACd,OAAO;AACN,WAAO,EAAE,MAAM,IAAI,MAAM,CAAA,EAAG;AAAA,EAC5B;AAAA,EACD,SAAS;AAAE,SAAK;EAAU;AAAA,EAC1B,SAAS;AAAA,IACR,MAAM,SAAS;AACd,UAAI;AACH,cAAM,MAAM,MAAMA,YAAG,IAAC,oBAAoB;AAC1C,aAAK,OAAO,MAAM,QAAQ,2BAAK,IAAI,IAAI,IAAI,OAAQ,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAA;AAAA,eACtE,GAAG;AAAA,MAAC;AAAA,IACb;AAAA,IACD,MAAM,SAAS;AACd,UAAI,CAAC,KAAK;AAAM;AAChB,YAAMC,YAAAA,KAAK,sBAAsB,EAAE,MAAM,KAAK,KAAG,CAAG;AACpD,WAAK,OAAO;AACZ,WAAK,OAAO;AAAA,IACZ;AAAA,IACD,MAAM,OAAO,GAAG;AACf,YAAMC,YAAG,IAAC,wBAAwB,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM;AACxDC,oBAAG,MAAC,UAAU,EAAE,OAAO,OAAO,MAAM,WAAW;AAAA,IAC/C;AAAA,IACD,MAAM,OAAO,GAAG;AACfA,oBAAG,MAAC,UAAU,EAAE,SAAS,YAAY,SAAS,OAAO,MAAM;AAC1D,YAAI,CAAC,EAAE;AAAS;AAChB,cAAMC,gBAAI,wBAAwB,EAAE,EAAE;AACtC,aAAK,OAAO;AAAA,MACb,EAAC,CAAC;AAAA,IACH;AAAA,EACD;AACD;;;;;;;;;;;;;;;;;;;;;;AClDA,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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -1,24 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>View</title>
<link rel="icon" href="data:,">
<link rel="stylesheet" href="app.css" />
<script>var __uniConfig = {"globalStyle":{},"darkmode":false}</script>
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
</head>
<body>
<div id="app"></div>
<script src="uni-app-view.umd.js"></script>
</body>
</html>

View File

@@ -1,11 +0,0 @@
;(function(){
let u=void 0,isReady=false,onReadyCallbacks=[],isServiceReady=false,onServiceReadyCallbacks=[];
const __uniConfig = {"pages":[],"globalStyle":{"backgroundColor":"#F8F8F8","navigationBar":{"backgroundColor":"#F8F8F8","titleText":"五金配件管家","type":"default","titleColor":"#000000"},"isNVue":false},"nvue":{"compiler":"uni-app","styleCompiler":"uni-app","flex-direction":"column"},"renderer":"auto","appname":"林林林","splashscreen":{"alwaysShowBeforeRender":true,"autoclose":true},"compilerVersion":"4.76","entryPagePath":"pages/index/index","entryPageQuery":"","realEntryPagePath":"","networkTimeout":{"request":60000,"connectSocket":60000,"uploadFile":60000,"downloadFile":60000},"locales":{},"darkmode":false,"themeConfig":{}};
const __uniRoutes = [{"path":"pages/index/index","meta":{"isQuit":true,"isEntry":true,"navigationBar":{"titleText":"五金配件管家","type":"default"},"isNVue":false}}].map(uniRoute=>(uniRoute.meta.route=uniRoute.path,__uniConfig.pages.push(uniRoute.path),uniRoute.path='/'+uniRoute.path,uniRoute));
__uniConfig.styles=[];//styles
__uniConfig.onReady=function(callback){if(__uniConfig.ready){callback()}else{onReadyCallbacks.push(callback)}};Object.defineProperty(__uniConfig,"ready",{get:function(){return isReady},set:function(val){isReady=val;if(!isReady){return}const callbacks=onReadyCallbacks.slice(0);onReadyCallbacks.length=0;callbacks.forEach(function(callback){callback()})}});
__uniConfig.onServiceReady=function(callback){if(__uniConfig.serviceReady){callback()}else{onServiceReadyCallbacks.push(callback)}};Object.defineProperty(__uniConfig,"serviceReady",{get:function(){return isServiceReady},set:function(val){isServiceReady=val;if(!isServiceReady){return}const callbacks=onServiceReadyCallbacks.slice(0);onServiceReadyCallbacks.length=0;callbacks.forEach(function(callback){callback()})}});
service.register("uni-app-config",{create(a,b,c){if(!__uniConfig.viewport){var d=b.weex.config.env.scale,e=b.weex.config.env.deviceWidth,f=Math.ceil(e/d);Object.assign(__uniConfig,{viewport:f,defaultFontSize:16})}return{instance:{__uniConfig:__uniConfig,__uniRoutes:__uniRoutes,global:u,window:u,document:u,frames:u,self:u,location:u,navigator:u,localStorage:u,history:u,Caches:u,screen:u,alert:u,confirm:u,prompt:u,fetch:u,XMLHttpRequest:u,WebSocket:u,webkit:u,print:u}}}});
})();

View File

@@ -1 +0,0 @@
(function(){})();

View File

@@ -1,320 +0,0 @@
if (typeof Promise !== "undefined" && !Promise.prototype.finally) {
Promise.prototype.finally = function(callback) {
const promise = this.constructor;
return this.then(
(value) => promise.resolve(callback()).then(() => value),
(reason) => promise.resolve(callback()).then(() => {
throw reason;
})
);
};
}
;
if (typeof uni !== "undefined" && uni && uni.requireGlobal) {
const global = uni.requireGlobal();
ArrayBuffer = global.ArrayBuffer;
Int8Array = global.Int8Array;
Uint8Array = global.Uint8Array;
Uint8ClampedArray = global.Uint8ClampedArray;
Int16Array = global.Int16Array;
Uint16Array = global.Uint16Array;
Int32Array = global.Int32Array;
Uint32Array = global.Uint32Array;
Float32Array = global.Float32Array;
Float64Array = global.Float64Array;
BigInt64Array = global.BigInt64Array;
BigUint64Array = global.BigUint64Array;
}
;
if (uni.restoreGlobal) {
uni.restoreGlobal(Vue, weex, plus, setTimeout, clearTimeout, setInterval, clearInterval);
}
(function(vue) {
"use strict";
const _imports_0 = "/static/metal-bg.jpg";
const _export_sfc = (sfc, props) => {
const target = sfc.__vccOpts || sfc;
for (const [key, val] of props) {
target[key] = val;
}
return target;
};
const _sfc_main$1 = {
data() {
return {
todayAmount: "0.00",
monthProfit: "0.00",
stockQty: "0.00",
activeTab: "home",
notices: [
{ text: "选材精工:不锈钢、合金钢,耐腐蚀更耐用", tag: "品质" },
{ text: "表面工艺:电镀锌/镀镍/发黑处理,性能均衡", tag: "工艺" },
{ text: "库存齐全:螺丝、螺母、垫圈、膨胀螺栓等现货", tag: "库存" },
{ text: "企业采购支持:批量优惠,次日发货", tag: "服务" }
],
features: [
{ key: "customer", title: "客户", img: "/static/icons/customer.png", emoji: "👥" },
{ key: "sale", title: "销售", img: "/static/icons/sale.png", emoji: "💰" },
{ key: "account", title: "账户", img: "/static/icons/account.png", emoji: "💳" },
{ key: "supplier", title: "供应商", img: "/static/icons/supplier.png", emoji: "🚚" },
{ key: "purchase", title: "进货", img: "/static/icons/purchase.png", emoji: "🛒" },
{ key: "otherPay", title: "其他支出", img: "/static/icons/other-pay.png", emoji: "💸" },
{ key: "vip", title: "VIP会员", img: "/static/icons/vip.png", emoji: "👑" },
{ key: "report", title: "报表", img: "/static/icons/report.png", emoji: "📊" },
{ key: "more", title: "更多", img: "/static/icons/more.png", emoji: "⋯" }
]
};
},
methods: {
onFeatureTap(item) {
uni.showToast({ title: item.title + "(开发中)", icon: "none" });
},
onCreateOrder() {
uni.showToast({ title: "开单(开发中)", icon: "none" });
},
onNoticeTap(n) {
uni.showModal({
title: "公告",
content: n.text,
showCancel: false
});
},
onNoticeList() {
uni.showToast({ title: "公告列表(开发中)", icon: "none" });
},
onIconError(item) {
item.img = "";
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return vue.openBlock(), vue.createElementBlock("view", { class: "home" }, [
vue.createElementVNode("image", {
class: "home-bg",
src: _imports_0,
mode: "aspectFill"
}),
vue.createCommentVNode(" 顶部统计卡片 "),
vue.createElementVNode("view", { class: "hero" }, [
vue.createElementVNode("view", { class: "hero-top" }, [
vue.createElementVNode("text", { class: "brand" }, "五金配件管家"),
vue.createElementVNode("view", { class: "cta" }, [
vue.createElementVNode("text", { class: "cta-text" }, "咨询")
])
]),
vue.createElementVNode("view", { class: "kpi" }, [
vue.createElementVNode("view", { class: "kpi-item" }, [
vue.createElementVNode("text", { class: "kpi-label" }, "今日销售额"),
vue.createElementVNode(
"text",
{ class: "kpi-value" },
vue.toDisplayString($data.todayAmount),
1
/* TEXT */
)
]),
vue.createElementVNode("view", { class: "kpi-item" }, [
vue.createElementVNode("text", { class: "kpi-label" }, "本月利润"),
vue.createElementVNode(
"text",
{ class: "kpi-value" },
vue.toDisplayString($data.monthProfit),
1
/* TEXT */
)
]),
vue.createElementVNode("view", { class: "kpi-item" }, [
vue.createElementVNode("text", { class: "kpi-label" }, "库存数量"),
vue.createElementVNode(
"text",
{ class: "kpi-value" },
vue.toDisplayString($data.stockQty),
1
/* TEXT */
)
])
])
]),
vue.createCommentVNode(" 公告栏:自动轮播,可点击查看详情 "),
vue.createElementVNode("view", { class: "notice" }, [
vue.createElementVNode("view", { class: "notice-left" }, "公告"),
vue.createElementVNode("swiper", {
class: "notice-swiper",
circular: "",
autoplay: "",
interval: "4000",
duration: "400",
vertical: ""
}, [
(vue.openBlock(true), vue.createElementBlock(
vue.Fragment,
null,
vue.renderList($data.notices, (n, idx) => {
return vue.openBlock(), vue.createElementBlock("swiper-item", { key: idx }, [
vue.createElementVNode("view", {
class: "notice-item",
onClick: ($event) => $options.onNoticeTap(n)
}, [
vue.createElementVNode(
"text",
{ class: "notice-text" },
vue.toDisplayString(n.text),
1
/* TEXT */
),
n.tag ? (vue.openBlock(), vue.createElementBlock(
"text",
{
key: 0,
class: "notice-tag"
},
vue.toDisplayString(n.tag),
1
/* TEXT */
)) : vue.createCommentVNode("v-if", true)
], 8, ["onClick"])
]);
}),
128
/* KEYED_FRAGMENT */
))
]),
vue.createElementVNode("view", {
class: "notice-right",
onClick: _cache[0] || (_cache[0] = (...args) => $options.onNoticeList && $options.onNoticeList(...args))
}, "更多")
]),
vue.createCommentVNode(" 分割标题:产品与功能 "),
vue.createElementVNode("view", { class: "section-title" }, [
vue.createElementVNode("text", { class: "section-text" }, "常用功能")
]),
vue.createCommentVNode(" 功能九宫格(玻璃容器 + 圆角方形图标) "),
vue.createElementVNode("view", { class: "grid-wrap" }, [
vue.createElementVNode("view", { class: "grid" }, [
(vue.openBlock(true), vue.createElementBlock(
vue.Fragment,
null,
vue.renderList($data.features, (item) => {
return vue.openBlock(), vue.createElementBlock("view", {
class: "grid-item",
key: item.key,
onClick: ($event) => $options.onFeatureTap(item)
}, [
vue.createElementVNode("view", { class: "icon icon-squircle" }, [
item.img ? (vue.openBlock(), vue.createElementBlock("image", {
key: 0,
src: item.img,
class: "icon-img",
mode: "aspectFit",
onError: ($event) => $options.onIconError(item)
}, null, 40, ["src", "onError"])) : item.emoji ? (vue.openBlock(), vue.createElementBlock(
"text",
{
key: 1,
class: "icon-emoji"
},
vue.toDisplayString(item.emoji),
1
/* TEXT */
)) : (vue.openBlock(), vue.createElementBlock("view", {
key: 2,
class: "icon-placeholder"
}))
]),
vue.createElementVNode(
"text",
{ class: "grid-chip" },
vue.toDisplayString(item.title),
1
/* TEXT */
)
], 8, ["onClick"]);
}),
128
/* KEYED_FRAGMENT */
))
])
]),
vue.createCommentVNode(" 底部操作条 "),
vue.createElementVNode("view", { class: "bottom-bar" }, [
vue.createElementVNode(
"view",
{
class: vue.normalizeClass(["tab", { active: $data.activeTab === "home" }]),
onClick: _cache[1] || (_cache[1] = ($event) => $data.activeTab = "home")
},
[
vue.createElementVNode("text", null, "首页")
],
2
/* CLASS */
),
vue.createElementVNode("view", {
class: "tab primary",
onClick: _cache[2] || (_cache[2] = (...args) => $options.onCreateOrder && $options.onCreateOrder(...args))
}, [
vue.createElementVNode("text", null, "开单")
]),
vue.createElementVNode(
"view",
{
class: vue.normalizeClass(["tab", { active: $data.activeTab === "detail" }]),
onClick: _cache[3] || (_cache[3] = ($event) => $data.activeTab = "detail")
},
[
vue.createElementVNode("text", null, "明细")
],
2
/* CLASS */
),
vue.createElementVNode(
"view",
{
class: vue.normalizeClass(["tab", { active: $data.activeTab === "me" }]),
onClick: _cache[4] || (_cache[4] = ($event) => $data.activeTab = "me")
},
[
vue.createElementVNode("text", null, "我的")
],
2
/* CLASS */
)
])
]);
}
const PagesIndexIndex = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["render", _sfc_render], ["__file", "D:/wx/PartsInquiry/frontend/pages/index/index.vue"]]);
__definePage("pages/index/index", PagesIndexIndex);
function formatAppLog(type, filename, ...args) {
if (uni.__log__) {
uni.__log__(type, filename, ...args);
} else {
console[type].apply(console, [...args, filename]);
}
}
const _sfc_main = {
onLaunch: function() {
formatAppLog("log", "at App.vue:4", "App Launch");
},
onShow: function() {
formatAppLog("log", "at App.vue:7", "App Show");
},
onHide: function() {
formatAppLog("log", "at App.vue:10", "App Hide");
}
};
const App = /* @__PURE__ */ _export_sfc(_sfc_main, [["__file", "D:/wx/PartsInquiry/frontend/App.vue"]]);
function createApp() {
const app = vue.createVueApp(App);
return {
app
};
}
const { app: __app__, Vuex: __Vuex__, Pinia: __Pinia__ } = createApp();
uni.Vuex = __Vuex__;
uni.Pinia = __Pinia__;
__app__.provide("__globalStyles", __uniConfig.styles);
__app__._component.mpType = "app";
__app__._component.render = () => {
};
__app__.mount("#app");
})(Vue);

File diff suppressed because one or more lines are too long

View File

@@ -1,107 +0,0 @@
{
"@platforms": [
"android",
"iPhone",
"iPad"
],
"id": "",
"name": "林林林",
"version": {
"name": "1.0.0",
"code": "100"
},
"description": "",
"developer": {
"name": "",
"email": "",
"url": ""
},
"permissions": {
"UniNView": {
"description": "UniNView原生渲染"
}
},
"plus": {
"useragent": {
"value": "uni-app",
"concatenate": true
},
"splashscreen": {
"target": "id:1",
"autoclose": true,
"waiting": true,
"delay": 0
},
"popGesture": "close",
"launchwebview": {
"render": "always",
"id": "1",
"kernel": "WKWebview"
},
"usingComponents": true,
"nvueStyleCompiler": "uni-app",
"compilerVersion": 3,
"distribute": {
"google": {
"permissions": [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
"apple": {},
"plugins": {
"audio": {
"mp3": {
"description": "Android平台录音支持MP3格式文件"
}
}
}
},
"statusbar": {
"immersed": "supportedDevice",
"style": "dark",
"background": "#F8F8F8"
},
"uniStatistics": {
"enable": false
},
"allowsInlineMediaPlayback": true,
"uni-app": {
"control": "uni-v3",
"vueVersion": "3",
"compilerVersion": "4.76",
"nvueCompiler": "uni-app",
"renderer": "auto",
"nvue": {
"flex-direction": "column"
},
"nvueLaunchMode": "normal",
"webView": {
"minUserAgentVersion": "49.0"
}
}
},
"app-harmony": {
"useragent": {
"value": "uni-app",
"concatenate": true
},
"uniStatistics": {
"enable": false
}
},
"launch_path": "__uniappview.html"
}

View File

@@ -1,185 +0,0 @@
.home {
padding-bottom: 4.375rem;
position: relative;
/* 明亮奢华背景:金属拉丝纹理覆盖层 + 柔和浅色渐变 */
background:
repeating-linear-gradient(0deg, rgba(180,180,180,0.12) 0, rgba(180,180,180,0.12) 0.0625rem, rgba(255,255,255,0.0) 0.0625rem, rgba(255,255,255,0.0) 0.3125rem),
linear-gradient(180deg, rgba(255, 255, 255, 0.75) 0%, rgba(255, 255, 255, 0.55) 40%, rgba(255, 255, 255, 0.35) 100%);
min-height: 100vh;
}
.home-bg {
position: fixed;
left: 0; top: 0; right: 0; bottom: 0;
width: 100%; height: 100%;
pointer-events: none;
z-index: -1;
}
/* 公告栏 */
.notice {
margin: 0 0.75rem 0.75rem;
padding: 0.625rem 0.6875rem;
border-radius: 0.625rem;
background: rgba(255,255,255,0.78);
-webkit-backdrop-filter: blur(0.375rem);
backdrop-filter: blur(0.375rem);
border: 0.0625rem solid rgba(203, 166, 61, 0.28);
display: flex;
align-items: center;
gap: 0.5rem;
}
.notice-left {
flex: 0 0 auto;
display: inline-flex; align-items: center; justify-content: center;
min-width: 3rem; height: 1.375rem;
padding: 0 0.5rem;
border-radius: 31.21875rem;
background: linear-gradient(135deg, #FFE69A, #F4CF62);
color: #3f320f;
font-size: 0.75rem;
font-weight: 800;
}
.notice-swiper { height: 2.25rem; flex: 1;
}
.notice-item { display: flex; align-items: center; gap: 0.375rem; min-height: 2.25rem;
}
.notice-text { color: #4b3e19; font-size: 0.875rem; line-height: 1.125rem; font-weight: 600; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.notice-tag { color: #B4880F; font-size: 0.6875rem; padding: 0.125rem 0.3125rem; border-radius: 31.21875rem; background: rgba(215,167,46,0.18);
}
.notice-right { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; min-width: 2.25rem; height: 1.375rem; color: #B4880F; font-size: 0.8125rem; padding-left: 0.25rem;
}
/* 分割标题 */
.section-title { display: flex; align-items: center; gap: 0.5rem; padding: 0.3125rem 0.875rem 0;
}
.section-title::before { content: ''; display: block; width: 0.25rem; height: 0.875rem; border-radius: 0.25rem; background: linear-gradient(180deg, #FFE69A, #D7A72E);
}
.section-text { color: #6b5a2a; font-size: 0.875rem; font-weight: 700; letter-spacing: 0.03125rem;
}
/* 顶部英雄区:浅色玻璃卡片,带金色描边与柔和阴影 */
.hero {
margin: 0.75rem;
padding: 1rem;
border-radius: 0.875rem;
background: rgba(255, 255, 255, 0.65);
-webkit-backdrop-filter: blur(0.4375rem);
backdrop-filter: blur(0.4375rem);
border: 0.0625rem solid rgba(203, 166, 61, 0.35);
box-shadow: 0 0.375rem 0.875rem rgba(0, 0, 0, 0.10), 0 0 0 0.0625rem rgba(255,255,255,0.60) inset;
color: #473c22;
}
.hero-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.625rem;
}
.brand {
font-size: 1.3125rem;
font-weight: 700;
letter-spacing: 0.0625rem;
color: #B4880F; /* 金色标题 */
}
.cta {
padding: 0.3125rem 0.6875rem;
border-radius: 31.21875rem;
background: linear-gradient(135deg, rgba(255, 220, 128, 0.65), rgba(255, 240, 190, 0.65));
border: 0.0625rem solid rgba(203, 166, 61, 0.45);
box-shadow: 0 0.1875rem 0.4375rem rgba(203, 166, 61, 0.25);
}
.cta-text { color: #5a4712; font-size: 0.8125rem;
}
.kpi { display: flex;
}
.kpi-item { flex: 1;
}
.kpi-label { opacity: 0.9; font-size: 0.75rem; color: #6b5a2a;
}
.kpi-value { display: block; margin-top: 0.375rem; font-size: 1.4375rem; font-weight: 800; color: #B4880F;
}
/* 功能容器:整体玻璃面板,增强融入感 */
.grid-wrap {
margin: 0 0.625rem 1rem;
padding: 0.875rem 0.625rem 0.375rem;
border-radius: 0.75rem;
background: rgba(255,255,255,0.55);
-webkit-backdrop-filter: blur(0.3125rem);
backdrop-filter: blur(0.3125rem);
border: 0.0625rem solid rgba(203,166,61,0.22);
box-shadow: 0 0.25rem 0.5625rem rgba(0,0,0,0.06);
}
/* 功能九宫格 */
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-row-gap: 2rem;
grid-column-gap: 1.125rem;
padding: 1.25rem 0.875rem 0.875rem;
}
.grid-item { display: flex; flex-direction: column; align-items: center; text-align: center;
}
.icon { display: flex; align-items: center; justify-content: center; color: #6b5a2a; position: relative;
}
.icon-squircle {
width: 4.125rem; height: 4.125rem;
border-radius: 0.875rem;
background: linear-gradient(145deg, rgba(255,255,255,0.92), rgba(255,255,255,0.70));
-webkit-backdrop-filter: blur(0.375rem);
backdrop-filter: blur(0.375rem);
border: 0.0625rem solid rgba(203,166,61,0.22);
box-shadow: 0 0.3125rem 0.75rem rgba(0,0,0,0.10), 0 0 0 0.0625rem rgba(255,255,255,0.65) inset;
overflow: hidden;
}
.icon-squircle::before { content: ''; position: absolute; left: -30%; top: -40%; width: 160%; height: 70%; background: linear-gradient( to bottom, rgba(255,255,255,0.9), rgba(255,255,255,0.0) ); transform: rotate(12deg);
}
.icon-img { width: 3rem; height: 3rem;
}
.icon-emoji { font-size: 1.875rem; line-height: 1;
}
.icon-placeholder { width: 2.625rem; height: 2.625rem; border-radius: 0.5625rem; background:
linear-gradient(135deg, rgba(212,175,55,0.18), rgba(255,255,255,0.0)),
repeating-linear-gradient(90deg, rgba(180,150,60,0.35) 0, rgba(180,150,60,0.35) 0.25rem, transparent 0.25rem, transparent 0.5rem),
repeating-linear-gradient(0deg, rgba(180,150,60,0.20) 0, rgba(180,150,60,0.20) 0.25rem, transparent 0.25rem, transparent 0.5rem);
box-shadow: inset 0 0 0 0.0625rem rgba(203,166,61,0.28);
}
.icon-text { font-size: 1.4375rem; font-weight: 700;
}
.grid-title { display: none;
}
.grid-chip { margin-top: 0.4375rem; padding: 0.1875rem 0.4375rem; border-radius: 31.21875rem; background: rgba(215,167,46,0.16); color: #5a4a1f; font-size: 0.6875rem;
}
/* 底部操作条:浅色半透明 + 金色主按钮 */
.bottom-bar {
position: fixed;
left: 0; right: 0; bottom: 0;
display: flex;
align-items: center;
justify-content: space-around;
padding: 0.4375rem 0.5625rem calc(env(safe-area-inset-bottom) + 0.4375rem);
background: rgba(255,255,255,0.85);
box-shadow: 0 -0.1875rem 0.5625rem rgba(0,0,0,0.08);
-webkit-backdrop-filter: blur(0.3125rem);
backdrop-filter: blur(0.3125rem);
}
.tab { flex: 1; text-align: center; color: #8a7535; font-size: 0.8125rem;
}
.tab.active { color: #B4880F;
}
.tab.primary {
flex: 0 0 auto;
min-width: 5.625rem;
margin: 0 0.5625rem;
padding: 0.5625rem 1rem;
background: linear-gradient(135deg, #FFE69A 0%, #F4CF62 45%, #D7A72E 100%);
color: #493c1b;
border-radius: 31.21875rem;
font-size: 0.9375rem;
font-weight: 800;
box-shadow: 0 0.3125rem 0.6875rem rgba(215,167,46,0.25), 0 0 0 0.0625rem rgba(255,255,255,0.70) inset;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -13,6 +13,7 @@ if (!Math) {
"./pages/customer/select.js";
"./pages/supplier/select.js";
"./pages/account/select.js";
"./pages/detail/index.js";
}
const _sfc_main = {
onLaunch: function() {
@@ -31,6 +32,28 @@ function createApp() {
app
};
}
if (typeof common_vendor.index !== "undefined" && typeof common_vendor.index.connectSocket === "function") {
const _connectSocket = common_vendor.index.connectSocket;
common_vendor.index.connectSocket = function(options) {
const task = _connectSocket.call(this, options);
if (task && typeof task.close === "function") {
const _close = task.close;
task.close = function(params = {}) {
if (params && typeof params === "object") {
const codeNum = Number(params.code);
const isValid = codeNum === 1e3 || codeNum >= 3e3 && codeNum <= 4999;
if (!isValid) {
params.code = 1e3;
if (!params.reason)
params.reason = "normalized from invalid close code";
}
}
return _close.call(this, params);
};
}
return task;
};
}
createApp().app.mount("#app");
exports.createApp = createApp;
//# sourceMappingURL=../.sourcemap/mp-weixin/app.js.map

View File

@@ -10,7 +10,8 @@
"pages/product/settings",
"pages/customer/select",
"pages/supplier/select",
"pages/account/select"
"pages/account/select",
"pages/detail/index"
],
"window": {
"navigationBarTextStyle": "black",

View File

@@ -85,12 +85,12 @@ const def = (obj, key, value) => {
});
};
const looseToNumber = (val) => {
const n = parseFloat(val);
return isNaN(n) ? val : n;
const n2 = parseFloat(val);
return isNaN(n2) ? val : n2;
};
const toNumber = (val) => {
const n = isString(val) ? Number(val) : NaN;
return isNaN(n) ? val : n;
const n2 = isString(val) ? Number(val) : NaN;
return isNaN(n2) ? val : n2;
};
function normalizeStyle(value) {
if (isArray(value)) {
@@ -122,6 +122,26 @@ function parseStringStyle(cssText) {
});
return ret;
}
function normalizeClass(value) {
let res = "";
if (isString(value)) {
res = value;
} else if (isArray(value)) {
for (let i = 0; i < value.length; i++) {
const normalized = normalizeClass(value[i]);
if (normalized) {
res += normalized + " ";
}
}
} else if (isObject(value)) {
for (const name in value) {
if (value[name]) {
res += name + " ";
}
}
}
return res.trim();
}
const toDisplayString = (val) => {
return isString(val) ? val : val == null ? "" : isArray(val) || isObject(val) && (val.toString === objectToString || !isFunction(val.toString)) ? JSON.stringify(val, replacer, 2) : String(val);
};
@@ -5224,6 +5244,7 @@ const o = (value, key) => vOn(value, key);
const f = (source, renderItem) => vFor(source, renderItem);
const s = (value) => stringifyStyle(value);
const e = (target, ...sources) => extend(target, ...sources);
const n = (value) => normalizeClass(value);
const t = (val) => toDisplayString(val);
const p = (props) => renderProps(props);
const m = (fn, modifiers, isComponent = false) => withModelModifiers(fn, modifiers, isComponent);
@@ -5817,8 +5838,8 @@ const $once = defineSyncApi(API_ONCE, (name, callback) => {
const $off = defineSyncApi(API_OFF, (name, callback) => {
if (!isArray(name))
name = name ? [name] : [];
name.forEach((n) => {
eventBus.off(n, callback);
name.forEach((n2) => {
eventBus.off(n2, callback);
});
}, OffProtocol);
const $emit = defineSyncApi(API_EMIT, (name, ...args) => {
@@ -7046,9 +7067,9 @@ function isConsoleWritable() {
return isWritable;
}
function initRuntimeSocketService() {
const hosts = "198.18.0.1,192.168.31.193,127.0.0.1";
const hosts = "198.18.0.1,192.168.31.192,127.0.0.1";
const port = "8090";
const id = "mp-weixin_UjOtWQ";
const id = "mp-weixin_eSBEHk";
const lazy = typeof swan !== "undefined";
let restoreError = lazy ? () => {
} : initOnError();
@@ -8000,6 +8021,7 @@ exports.e = e;
exports.f = f;
exports.index = index;
exports.m = m;
exports.n = n;
exports.o = o;
exports.p = p;
exports.resolveComponent = resolveComponent;

View File

@@ -1,133 +1,184 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
function toDateString(d) {
const m = (d.getMonth() + 1).toString().padStart(2, "0");
const day = d.getDate().toString().padStart(2, "0");
return `${d.getFullYear()}-${m}-${day}`;
}
const API_OF = {
sale: "/api/orders",
purchase: "/api/purchase-orders",
collect: "/api/payments",
fund: "/api/other-transactions",
stock: "/api/inventories/logs"
};
const _sfc_main = {
data() {
const today = /* @__PURE__ */ new Date();
const first = new Date(today.getFullYear(), today.getMonth(), 1);
return {
range: "month",
biz: "sale",
sub: "out",
kw: "",
begin: toDateString(first),
end: toDateString(today),
list: [],
loading: false
bizList: [
{ key: "sale", name: "出货" },
{ key: "purchase", name: "进货" },
{ key: "collect", name: "收款" },
{ key: "fund", name: "资金" },
{ key: "stock", name: "盘点" }
],
range: "month",
query: { kw: "" },
items: [],
page: 1,
size: 20,
finished: false,
loading: false,
startDate: "",
endDate: ""
};
},
computed: {
placeholder() {
return "单据号/客户名称/品名规格/备注";
},
periodLabel() {
return this.startDate && this.endDate ? `${this.startDate}~${this.endDate}` : "";
},
totalAmount() {
return this.list.reduce((s, o) => s + Number(o.amount || 0), 0);
return this.items.reduce((s, it) => s + Number(it.amount || 0), 0);
}
},
onLoad() {
try {
common_vendor.index.__f__("log", "at pages/detail/index.vue:92", "[detail] onLoad route = pages/detail/index");
} catch (e) {
}
this.computeRange();
this.reload();
},
methods: {
setRange(r) {
this.range = r;
const now = /* @__PURE__ */ new Date();
if (r === "today") {
this.begin = this.end = toDateString(now);
} else if (r === "week") {
const day = now.getDay() || 7;
const start = new Date(now);
start.setDate(now.getDate() - day + 1);
this.begin = toDateString(start);
this.end = toDateString(now);
} else if (r === "month") {
const first = new Date(now.getFullYear(), now.getMonth(), 1);
this.begin = toDateString(first);
this.end = toDateString(now);
} else if (r === "year") {
const first = new Date(now.getFullYear(), 0, 1);
this.begin = toDateString(first);
this.end = toDateString(now);
}
switchBiz(k) {
if (this.biz === k)
return;
this.biz = k;
this.reload();
},
async reload() {
switchRange(r) {
this.range = r;
this.computeRange();
this.reload();
},
computeRange() {
const now = /* @__PURE__ */ new Date();
const pad = (n) => String(n).padStart(2, "0");
const fmt = (d) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
let start = now, end = now;
if (this.range === "today") {
start = end = now;
} else if (this.range === "week") {
const day = now.getDay() || 7;
start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - day + 1);
end = now;
} else if (this.range === "month") {
start = new Date(now.getFullYear(), now.getMonth(), 1);
end = new Date(now.getFullYear(), now.getMonth() + 1, 0);
} else if (this.range === "year") {
start = new Date(now.getFullYear(), 0, 1);
end = new Date(now.getFullYear(), 11, 31);
} else {
start = new Date(now.getFullYear(), now.getMonth(), 1);
end = new Date(now.getFullYear(), now.getMonth() + 1, 0);
}
this.startDate = fmt(start);
this.endDate = fmt(end);
},
reload() {
this.items = [];
this.page = 1;
this.finished = false;
this.loadMore();
},
async loadMore() {
if (this.loading || this.finished)
return;
this.loading = true;
try {
const res = await common_http.get("/api/sales/orders", { begin: this.begin, end: this.end, kw: this.kw, sub: this.sub });
this.list = Array.isArray(res == null ? void 0 : res.list) ? res.list : Array.isArray(res) ? res : [];
const path = API_OF[this.biz] || "/api/orders";
const params = { kw: this.query.kw, page: this.page, size: this.size, startDate: this.startDate, endDate: this.endDate, biz: this.biz };
if (this.biz === "sale")
params.type = "out";
const res = await common_http.get(path, params);
const list = Array.isArray(res == null ? void 0 : res.list) ? res.list : Array.isArray(res) ? res : [];
this.items = this.items.concat(list);
if (list.length < this.size)
this.finished = true;
this.page += 1;
} catch (e) {
this.list = [];
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
} finally {
this.loading = false;
}
},
goCreate() {
common_vendor.index.navigateTo({ url: "/pages/order/create" });
formatDate(s) {
if (!s)
return "";
try {
const d = new Date(s);
const pad = (n) => String(n).padStart(2, "0");
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
} catch (_) {
return String(s).slice(0, 10);
}
},
open(o) {
onCreate() {
if (this.biz === "sale") {
common_vendor.index.navigateTo({ url: "/pages/order/create" });
return;
}
common_vendor.index.showToast({ title: "该类型创建页待实现", icon: "none" });
},
openDetail(it) {
common_vendor.index.showToast({ title: "详情开发中", icon: "none" });
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return common_vendor.e({
a: $data.range === "custom" ? 1 : "",
b: common_vendor.o(($event) => $options.setRange("custom")),
c: $data.range === "week" ? 1 : "",
d: common_vendor.o(($event) => $options.setRange("week")),
e: $data.range === "today" ? 1 : "",
f: common_vendor.o(($event) => $options.setRange("today")),
g: $data.range === "month" ? 1 : "",
h: common_vendor.o(($event) => $options.setRange("month")),
i: $data.range === "year" ? 1 : "",
j: common_vendor.o(($event) => $options.setRange("year")),
k: $data.biz === "sale" ? 1 : "",
l: common_vendor.o(($event) => $data.biz = "sale"),
m: $data.biz === "purchase" ? 1 : "",
n: common_vendor.o(($event) => $data.biz = "purchase"),
o: $data.biz === "collection" ? 1 : "",
p: common_vendor.o(($event) => $data.biz = "collection"),
q: $data.biz === "capital" ? 1 : "",
r: common_vendor.o(($event) => $data.biz = "capital"),
s: $data.biz === "inventory" ? 1 : "",
t: common_vendor.o(($event) => $data.biz = "inventory"),
v: $data.sub === "out" ? 1 : "",
w: common_vendor.o(($event) => $data.sub = "out"),
x: $data.sub === "return" ? 1 : "",
y: common_vendor.o(($event) => $data.sub = "return"),
z: $data.sub === "receive" ? 1 : "",
A: common_vendor.o(($event) => $data.sub = "receive"),
B: common_vendor.o((...args) => $options.goCreate && $options.goCreate(...args)),
C: common_vendor.o((...args) => $options.reload && $options.reload(...args)),
D: $data.kw,
E: common_vendor.o(($event) => $data.kw = $event.detail.value),
F: $data.range === "custom"
}, $data.range === "custom" ? {
G: common_vendor.t($data.begin),
H: $data.begin,
I: common_vendor.o((e) => {
$data.begin = e.detail.value;
$options.reload();
}),
J: common_vendor.t($data.end),
K: $data.end,
L: common_vendor.o((e) => {
$data.end = e.detail.value;
$options.reload();
})
} : {}, {
M: common_vendor.t($options.totalAmount.toFixed(2)),
N: common_vendor.f($data.list, (o, k0, i0) => {
a: common_vendor.n($data.range === "custom" && "active"),
b: common_vendor.o(($event) => $options.switchRange("custom")),
c: common_vendor.n($data.range === "week" && "active"),
d: common_vendor.o(($event) => $options.switchRange("week")),
e: common_vendor.n($data.range === "today" && "active"),
f: common_vendor.o(($event) => $options.switchRange("today")),
g: common_vendor.n($data.range === "month" && "active"),
h: common_vendor.o(($event) => $options.switchRange("month")),
i: common_vendor.n($data.range === "year" && "active"),
j: common_vendor.o(($event) => $options.switchRange("year")),
k: common_vendor.f($data.bizList, (b, k0, i0) => {
return {
a: common_vendor.t(o.orderDate),
b: common_vendor.t(o.customerName),
c: common_vendor.t(o.orderNo),
d: common_vendor.t(Number(o.amount).toFixed(2)),
e: o.id,
f: common_vendor.o(($event) => $options.open(o), o.id)
a: common_vendor.t(b.name),
b: b.key,
c: common_vendor.n($data.biz === b.key && "active"),
d: common_vendor.o(($event) => $options.switchBiz(b.key), b.key)
};
}),
l: $options.placeholder,
m: common_vendor.o((...args) => $options.reload && $options.reload(...args)),
n: $data.query.kw,
o: common_vendor.o(common_vendor.m(($event) => $data.query.kw = $event.detail.value, {
trim: true
})),
p: common_vendor.t($options.periodLabel),
q: common_vendor.o((...args) => $options.reload && $options.reload(...args)),
r: common_vendor.t($options.totalAmount.toFixed(2)),
s: $data.items.length
}, $data.items.length ? {
t: common_vendor.f($data.items, (it, k0, i0) => {
return {
a: common_vendor.t($options.formatDate(it.orderTime || it.txTime || it.createdAt)),
b: common_vendor.t(it.customerName || it.supplierName || it.accountName || it.remark || "-"),
c: common_vendor.t(it.orderNo || it.code || it.id),
d: common_vendor.t((it.amount || 0).toFixed(2)),
e: it.id,
f: common_vendor.o(($event) => $options.openDetail(it), it.id)
};
})
} : {}, {
v: common_vendor.o((...args) => $options.loadMore && $options.loadMore(...args)),
w: common_vendor.o((...args) => $options.onCreate && $options.onCreate(...args))
});
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);

View File

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

View File

@@ -1,43 +1,49 @@
.detail { display:flex;
.page { display:flex; flex-direction: column; height: 100vh;
}
.filter-tabs { display:flex; gap: 24rpx; padding: 18rpx 24rpx; color:#666;
.seg { display:flex; background:#fff;
}
.filter-tabs .tab.active { color:#2aa7b6; font-weight: 700;
.seg-item { flex:1; padding: 22rpx 0; text-align:center; color:#666;
}
.biz-tabs { position: fixed; left:0; top: 160rpx; bottom: 120rpx; width: 120rpx; display:flex; flex-direction: column; gap: 24rpx; padding: 12rpx;
.seg-item.active { color:#18b566; font-weight: 600;
}
.biz { background:#6aa9ff; color:#fff; border-radius: 16rpx; padding: 20rpx 0; text-align:center; opacity: .85;
.content { display:flex; flex:1; min-height: 0;
}
.biz.active { opacity: 1;
.biz-tabs { width: 120rpx; background:#eef6ff; display:flex; flex-direction: column;
}
.card { margin-left: 140rpx; background:#fff; border-radius: 24rpx; padding: 16rpx;
.biz { flex:0 0 120rpx; display:flex; align-items:center; justify-content:center; color:#4aa3d6;
}
.subtabs { display:flex; align-items:center; gap: 24rpx; padding: 8rpx 6rpx 12rpx;
.biz.active { background:#3ac1c9; color:#fff; border-radius: 0 16rpx 16rpx 0;
}
.sub { color:#57c2cf; padding: 8rpx 12rpx;
.panel { flex:1; display:flex; flex-direction: column; background:#fff; margin: 16rpx; border-radius: 16rpx; padding: 12rpx;
}
.sub.active { border-bottom: 4rpx solid #57c2cf; font-weight:700;
.toolbar { display:flex; align-items: center; gap: 12rpx; padding: 8rpx 6rpx;
}
.plus { margin-left:auto; width: 60rpx; height: 60rpx; border-radius: 30rpx; background:#2ec0d0; color:#fff; font-size: 40rpx; display:flex; align-items:center; justify-content:center;
.search { flex:1;
}
.search { background:#f6f7fb; border-radius: 999rpx; padding: 14rpx 20rpx; margin: 8rpx 0 12rpx;
.search-input { width:100%; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx;
}
.daterange { display:flex; align-items:center; gap: 12rpx; color:#888; padding-bottom: 8rpx;
.period { color:#999; font-size: 24rpx; padding: 0 6rpx;
}
.daterange .sep { color:#ccc;
.total { color:#18b566; font-weight: 700; padding: 6rpx 6rpx 12rpx;
}
.total { color:#2ec0d0; font-weight: 800; padding: 12rpx 0; border-top: 2rpx solid #eaeaea;
.list { flex:1;
}
.list { height: calc(100vh - 420rpx);
.item { display:flex; align-items:center; padding: 20rpx 10rpx; border-bottom: 1rpx solid #f1f1f1;
}
.row { display:flex; justify-content: space-between; align-items:center; padding: 22rpx 10rpx; border-bottom: 1rpx solid #f0f0f0;
.item-left { flex:1;
}
.left .date { color:#999; margin-bottom: 6rpx;
.date { color:#999; font-size: 24rpx;
}
.left .name { color:#333; margin-bottom: 6rpx;
.name { color:#333; margin: 4rpx 0; font-weight: 600;
}
.left .no { color:#bbb;
.no { color:#bbb; font-size: 22rpx;
}
.right { color:#555;
.amount { color:#333; font-weight: 700;
}
.arrow { color:#ccc; font-size: 40rpx; margin-left: 8rpx;
}
.empty { height: 50vh; display:flex; align-items:center; justify-content:center; color:#999;
}
.fab { position: fixed; right: 30rpx; bottom: 120rpx; width: 100rpx; height: 100rpx; background:#18b566; color:#fff; border-radius: 50rpx; text-align:center; line-height: 100rpx; font-size: 48rpx; box-shadow: 0 8rpx 20rpx rgba(0,0,0,0.15);
}

View File

@@ -72,6 +72,14 @@ const _sfc_main = {
onCreateOrder() {
common_vendor.index.navigateTo({ url: "/pages/order/create" });
},
goDetail() {
this.activeTab = "detail";
try {
common_vendor.index.__f__("log", "at pages/index/index.vue:166", "[index] goDetail → /pages/detail/index");
} catch (e) {
}
common_vendor.index.navigateTo({ url: "/pages/detail/index" });
},
onNoticeTap(n) {
common_vendor.index.showModal({
title: "广告",
@@ -130,7 +138,7 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
o: common_vendor.o((...args) => $options.goProduct && $options.goProduct(...args)),
p: common_vendor.o((...args) => $options.onCreateOrder && $options.onCreateOrder(...args)),
q: $data.activeTab === "detail" ? 1 : "",
r: common_vendor.o(($event) => $data.activeTab = "detail"),
r: common_vendor.o((...args) => $options.goDetail && $options.goDetail(...args)),
s: $data.activeTab === "report" ? 1 : "",
t: common_vendor.o(($event) => $data.activeTab = "report"),
v: $data.activeTab === "me" ? 1 : "",

View File

@@ -165,6 +165,7 @@
box-shadow: 0 -6rpx 18rpx rgba(0,0,0,0.08);
-webkit-backdrop-filter: blur(10rpx);
backdrop-filter: blur(10rpx);
z-index: 9999;
}
.tab { flex: 1; text-align: center; color: #8a7535; font-size: 26rpx;
}

View File

@@ -27,7 +27,10 @@ const _sfc_main = {
activeCategory: "sale_income",
trxAmount: 0,
selectedAccountId: null,
selectedAccountName: ""
selectedAccountName: "",
// 收款/付款输入
payments: { cash: 0, bank: 0, wechat: 0 },
showMore: false
};
},
computed: {
@@ -54,6 +57,11 @@ const _sfc_main = {
},
counterpartyLabel() {
return this.customerName || this.supplierName || "—";
},
// 收款/付款合计
payTotal() {
const p = this.payments || { cash: 0, bank: 0, wechat: 0 };
return Number(p.cash || 0) + Number(p.bank || 0) + Number(p.wechat || 0);
}
},
methods: {
@@ -83,10 +91,19 @@ const _sfc_main = {
recalc() {
this.$forceUpdate();
},
recalcPay() {
this.$forceUpdate();
},
async submit() {
const isSaleOrPurchase = this.biz === "sale" || this.biz === "purchase";
const payload = isSaleOrPurchase ? {
type: this.biz === "sale" ? this.saleType : "purchase." + this.purchaseType,
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 payload = isSaleOrPurchase ? isCollectOrPay ? [
{ method: "cash", amount: Number(this.payments.cash || 0) },
{ method: "bank", amount: Number(this.payments.bank || 0) },
{ method: "wechat", amount: Number(this.payments.wechat || 0) }
].filter((p) => p.amount > 0) : {
type: saleTypeValue,
orderTime: this.order.orderTime,
customerId: this.order.customerId,
supplierId: this.order.supplierId,
@@ -102,7 +119,7 @@ const _sfc_main = {
remark: this.order.remark
};
try {
const url = isSaleOrPurchase ? "/api/orders" : "/api/other-transactions";
const url = isSaleOrPurchase ? isCollectOrPay ? `/api/payments/${this.biz}` : "/api/orders" : "/api/other-transactions";
await common_http.post(url, payload);
common_vendor.index.showToast({ title: "已保存", icon: "success" });
setTimeout(() => {
@@ -116,6 +133,7 @@ const _sfc_main = {
this.items = [];
this.trxAmount = 0;
this.order.remark = "";
this.payments = { cash: 0, bank: 0, wechat: 0 };
}
}
};
@@ -158,13 +176,42 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
F: common_vendor.o((...args) => $options.chooseSupplier && $options.chooseSupplier(...args))
} : {}, {
D: $data.biz === "purchase",
G: $data.biz === "sale" || $data.biz === "purchase"
}, $data.biz === "sale" || $data.biz === "purchase" ? {
H: common_vendor.t($options.totalQuantity),
I: common_vendor.t($options.totalAmount.toFixed(2)),
J: common_vendor.o((...args) => $options.chooseProduct && $options.chooseProduct(...args))
G: $data.biz === "sale" && $data.saleType === "collect" || $data.biz === "purchase" && $data.purchaseType === "pay"
}, $data.biz === "sale" && $data.saleType === "collect" || $data.biz === "purchase" && $data.purchaseType === "pay" ? common_vendor.e({
H: $data.biz === "sale"
}, $data.biz === "sale" ? {
I: common_vendor.t($options.customerLabel),
J: common_vendor.o((...args) => $options.chooseCustomer && $options.chooseCustomer(...args))
} : {
K: common_vendor.f($data.biz === "income" ? $options.incomeCategories : $options.expenseCategories, (c, k0, i0) => {
K: common_vendor.t($options.supplierLabel),
L: common_vendor.o((...args) => $options.chooseSupplier && $options.chooseSupplier(...args))
}, {
M: common_vendor.o([common_vendor.m(($event) => $data.payments.cash = $event.detail.value, {
number: true
}), ($event) => $options.recalcPay()]),
N: $data.payments.cash,
O: common_vendor.o([common_vendor.m(($event) => $data.payments.bank = $event.detail.value, {
number: true
}), ($event) => $options.recalcPay()]),
P: $data.payments.bank,
Q: common_vendor.o([common_vendor.m(($event) => $data.payments.wechat = $event.detail.value, {
number: true
}), ($event) => $options.recalcPay()]),
R: $data.payments.wechat,
S: common_vendor.t($data.showMore ? "收起" : ""),
T: common_vendor.o(($event) => $data.showMore = !$data.showMore),
U: common_vendor.t($options.payTotal.toFixed(2)),
V: $data.order.remark,
W: common_vendor.o(($event) => $data.order.remark = $event.detail.value),
X: common_vendor.t($data.order.orderTime),
Y: $data.order.orderTime,
Z: common_vendor.o((...args) => $options.onDateChange && $options.onDateChange(...args))
}) : $data.biz === "sale" || $data.biz === "purchase" ? {
ab: common_vendor.t($options.totalQuantity),
ac: common_vendor.t($options.totalAmount.toFixed(2)),
ad: common_vendor.o((...args) => $options.chooseProduct && $options.chooseProduct(...args))
} : {
ae: common_vendor.f($data.biz === "income" ? $options.incomeCategories : $options.expenseCategories, (c, k0, i0) => {
return {
a: common_vendor.t(c.label),
b: c.key,
@@ -172,22 +219,23 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
d: common_vendor.o(($event) => $data.activeCategory = c.key, c.key)
};
}),
L: common_vendor.t($options.counterpartyLabel),
M: common_vendor.o((...args) => $options.chooseCounterparty && $options.chooseCounterparty(...args)),
N: common_vendor.t($options.accountLabel),
O: common_vendor.o((...args) => $options.chooseAccount && $options.chooseAccount(...args)),
P: $data.trxAmount,
Q: common_vendor.o(common_vendor.m(($event) => $data.trxAmount = $event.detail.value, {
af: common_vendor.t($options.counterpartyLabel),
ag: common_vendor.o((...args) => $options.chooseCounterparty && $options.chooseCounterparty(...args)),
ah: common_vendor.t($options.accountLabel),
ai: common_vendor.o((...args) => $options.chooseAccount && $options.chooseAccount(...args)),
aj: $data.trxAmount,
ak: common_vendor.o(common_vendor.m(($event) => $data.trxAmount = $event.detail.value, {
number: true
})),
R: $data.order.remark,
S: common_vendor.o(($event) => $data.order.remark = $event.detail.value)
al: $data.order.remark,
am: common_vendor.o(($event) => $data.order.remark = $event.detail.value)
}, {
T: !$data.items.length
aa: $data.biz === "sale" || $data.biz === "purchase",
an: !$data.items.length
}, !$data.items.length ? {
U: common_assets._imports_0$1
ao: common_assets._imports_0$1
} : {
V: common_vendor.f($data.items, (it, idx, i0) => {
ap: common_vendor.f($data.items, (it, idx, i0) => {
return {
a: common_vendor.t(it.productName),
b: common_vendor.o([common_vendor.m(($event) => it.quantity = $event.detail.value, {
@@ -203,8 +251,8 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
};
})
}, {
W: common_vendor.o((...args) => $options.saveAndReset && $options.saveAndReset(...args)),
X: common_vendor.o((...args) => $options.submit && $options.submit(...args))
aq: common_vendor.o((...args) => $options.saveAndReset && $options.saveAndReset(...args)),
ar: common_vendor.o((...args) => $options.submit && $options.submit(...args))
});
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);

View File

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

View File

@@ -40,4 +40,13 @@
.bottom { position: fixed; left:0; right:0; bottom:0; background:#fff; padding: 16rpx 24rpx calc(env(safe-area-inset-bottom) + 16rpx); box-shadow: 0 -4rpx 12rpx rgba(0,0,0,0.06);
}
.primary { width: 100%; background: linear-gradient(135deg, #FFE69A 0%, #F4CF62 45%, #D7A72E 100%); color:#493c1b; border-radius: 999rpx; padding: 20rpx 0; font-weight:800;
}
/* 收款/付款页样式 */
.pay-row .pay-input { text-align: right; color:#333;
}
.textarea { position: relative; padding: 16rpx 24rpx; background:#fff; border-top: 1rpx solid #eee;
}
.amount-badge { position: absolute; right: 24rpx; top: -36rpx; background: #d1f0ff; color:#107e9b; padding: 8rpx 16rpx; border-radius: 12rpx; font-size: 24rpx;
}
.date-mini { position: absolute; right: 24rpx; bottom: 20rpx; color:#666; font-size: 24rpx;
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,6 +16,9 @@ const _sfc_main = {
this.fetchCategories();
this.reload();
},
onShow() {
this.reload();
},
computed: {
categoryNames() {
return this.categories.map((c) => c.name);

View File

@@ -35,8 +35,8 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
e: common_vendor.f($data.products, (p, k0, i0) => {
return {
a: common_vendor.t(p.name),
b: common_vendor.t(p.code),
c: common_vendor.t(p.stock || 0),
b: common_vendor.t((p.brand || "") + " " + (p.model || "") + " " + (p.spec || "")),
c: common_vendor.t(p.stock ?? 0),
d: p.id,
e: common_vendor.o(($event) => $options.select(p), p.id)
};

View File

@@ -1,5 +0,0 @@
{
"setting": {
"urlCheck": false
}
}

View File

@@ -12,7 +12,14 @@
前端1.货品功能√
后端1.首页核心数据√
2.所有图片用占位图backend\picture代替
3.货品功能√(问题:打开以定义的商品其内容不能回调/不能保存商品分类【可能因为数据库】
3.货品功能√(还有问题)
9.18王德鹏1
前端1.明细功能√
后端1.数据库驱动改为硬编码
2.货品功能√
3.开单(未全完成)
数据库:
※※我前端都只做了小程序的,就没看过安卓端,还需要做双端适配※※