后端:公告√
注意数据库新建notice表
This commit is contained in:
566
backend/db/db.sql
Normal file
566
backend/db/db.sql
Normal file
@@ -0,0 +1,566 @@
|
||||
-- =====================================================================
|
||||
-- 配件查询 App 数据库结构(MySQL 8.0)
|
||||
-- 依据:/doc/requirements.md、/doc/functional_spec.md、/doc/architecture.md
|
||||
-- 注意:不在此文件中创建数据库与用户,请在外部以环境配置完成
|
||||
-- 字符集统一为 utf8mb4,排序规则 utf8mb4_0900_ai_ci
|
||||
-- =====================================================================
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
SET time_zone = '+00:00';
|
||||
SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- =====================================================================
|
||||
-- 基础:租户与用户
|
||||
-- =====================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS shops (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '店铺/租户ID',
|
||||
name VARCHAR(100) NOT NULL COMMENT '店铺名称',
|
||||
status TINYINT UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态:1启用 0停用',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
deleted_at DATETIME NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_shops_status (status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='店铺/租户';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '用户ID',
|
||||
shop_id BIGINT UNSIGNED NOT NULL COMMENT '所属店铺',
|
||||
phone VARCHAR(32) NULL COMMENT '手机号',
|
||||
name VARCHAR(64) NOT NULL COMMENT '姓名',
|
||||
role VARCHAR(32) NOT NULL DEFAULT 'staff' COMMENT '角色:owner/staff/finance/...',
|
||||
password_hash VARCHAR(255) NULL COMMENT '密码哈希(若采用短信登录可为空)',
|
||||
status TINYINT UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态:1启用 0停用',
|
||||
is_owner TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否店主',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
deleted_at DATETIME NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY ux_users_shop_phone (shop_id, phone),
|
||||
KEY idx_users_shop (shop_id),
|
||||
CONSTRAINT fk_users_shop FOREIGN KEY (shop_id) REFERENCES shops(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户';
|
||||
|
||||
-- 第三方身份映射(微信)
|
||||
CREATE TABLE IF NOT EXISTS user_identities (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
shop_id BIGINT UNSIGNED NOT NULL,
|
||||
user_id BIGINT UNSIGNED NOT NULL,
|
||||
provider ENUM('wechat_mp','wechat_app') NOT NULL COMMENT '身份提供方:小程序/APP',
|
||||
openid VARCHAR(64) NOT NULL,
|
||||
unionid VARCHAR(64) NULL,
|
||||
nickname VARCHAR(64) NULL,
|
||||
avatar_url VARCHAR(512) NULL,
|
||||
last_login_at DATETIME NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY ux_identity_provider_openid (provider, openid),
|
||||
UNIQUE KEY ux_identity_unionid (unionid),
|
||||
KEY idx_identity_user (user_id),
|
||||
KEY idx_identity_shop (shop_id),
|
||||
CONSTRAINT fk_identity_shop FOREIGN KEY (shop_id) REFERENCES shops(id),
|
||||
CONSTRAINT fk_identity_user FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='第三方身份映射(微信)';
|
||||
|
||||
-- 微信会话(小程序/APP 临时会话)
|
||||
CREATE TABLE IF NOT EXISTS wechat_sessions (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
provider ENUM('wechat_mp','wechat_app') NOT NULL,
|
||||
openid VARCHAR(64) NOT NULL,
|
||||
session_key VARCHAR(128) NOT NULL,
|
||||
expires_at DATETIME NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY ux_wechat_session (provider, openid),
|
||||
KEY idx_wechat_session_expires (expires_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='微信会话(临时)';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS system_parameters (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
shop_id BIGINT UNSIGNED NOT NULL,
|
||||
user_id BIGINT UNSIGNED NOT NULL COMMENT '创建/最后修改人',
|
||||
`key` VARCHAR(64) NOT NULL COMMENT '参数键',
|
||||
`value` JSON NOT NULL COMMENT '参数值(JSON)',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY ux_sysparams_shop_key (shop_id, `key`),
|
||||
KEY idx_sysparams_shop (shop_id),
|
||||
CONSTRAINT fk_sysparams_shop FOREIGN KEY (shop_id) REFERENCES shops(id),
|
||||
CONSTRAINT fk_sysparams_user FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='系统参数(租户级)';
|
||||
|
||||
-- =====================================================================
|
||||
-- 货品域(含价格/库存/图片/别名)
|
||||
-- =====================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS product_categories (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
shop_id BIGINT UNSIGNED NOT NULL,
|
||||
user_id BIGINT UNSIGNED NOT NULL,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
parent_id BIGINT UNSIGNED NULL,
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
deleted_at DATETIME NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY ux_categories_shop_name (shop_id, name),
|
||||
KEY idx_categories_shop (shop_id),
|
||||
KEY idx_categories_parent (parent_id),
|
||||
CONSTRAINT fk_categories_shop FOREIGN KEY (shop_id) REFERENCES shops(id),
|
||||
CONSTRAINT fk_categories_user FOREIGN KEY (user_id) REFERENCES users(id),
|
||||
CONSTRAINT fk_categories_parent FOREIGN KEY (parent_id) REFERENCES product_categories(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='商品类别';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS product_units (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
shop_id BIGINT UNSIGNED NOT NULL,
|
||||
user_id BIGINT UNSIGNED NOT NULL,
|
||||
name VARCHAR(16) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
deleted_at DATETIME NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY ux_units_shop_name (shop_id, name),
|
||||
KEY idx_units_shop (shop_id),
|
||||
CONSTRAINT fk_units_shop FOREIGN KEY (shop_id) REFERENCES shops(id),
|
||||
CONSTRAINT fk_units_user FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='商品单位';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS global_skus (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
name VARCHAR(120) NOT NULL COMMENT 'SKU名称',
|
||||
brand VARCHAR(64) NULL,
|
||||
model VARCHAR(64) NULL,
|
||||
spec VARCHAR(128) NULL,
|
||||
barcode VARCHAR(32) NULL,
|
||||
unit_id BIGINT UNSIGNED NULL,
|
||||
tags JSON NULL,
|
||||
status ENUM('published','offline') NOT NULL DEFAULT 'published',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
deleted_at DATETIME NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY ux_global_skus_barcode (barcode),
|
||||
KEY idx_global_skus_brand_model (brand, model),
|
||||
CONSTRAINT fk_globalsku_unit FOREIGN KEY (unit_id) REFERENCES product_units(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='全局SKU(众包)';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS products (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
shop_id BIGINT UNSIGNED NOT NULL,
|
||||
user_id BIGINT UNSIGNED NOT NULL,
|
||||
name VARCHAR(120) NOT NULL,
|
||||
category_id BIGINT UNSIGNED NULL,
|
||||
unit_id BIGINT UNSIGNED NOT NULL,
|
||||
brand VARCHAR(64) NULL,
|
||||
model VARCHAR(64) NULL,
|
||||
spec VARCHAR(128) NULL,
|
||||
origin VARCHAR(64) NULL,
|
||||
barcode VARCHAR(32) NULL,
|
||||
alias VARCHAR(120) NULL,
|
||||
description TEXT NULL,
|
||||
global_sku_id BIGINT UNSIGNED NULL,
|
||||
safe_min DECIMAL(18,3) NULL,
|
||||
safe_max DECIMAL(18,3) NULL,
|
||||
search_text TEXT NULL COMMENT '供全文检索的聚合字段(名称/品牌/型号/规格/别名)',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
deleted_at DATETIME NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY ux_products_shop_barcode (shop_id, barcode),
|
||||
KEY idx_products_shop (shop_id),
|
||||
KEY idx_products_category (category_id),
|
||||
KEY idx_products_unit (unit_id),
|
||||
FULLTEXT KEY ft_products_search (name, brand, model, spec, search_text),
|
||||
CONSTRAINT fk_products_shop FOREIGN KEY (shop_id) REFERENCES shops(id),
|
||||
CONSTRAINT fk_products_user FOREIGN KEY (user_id) REFERENCES users(id),
|
||||
CONSTRAINT fk_products_category FOREIGN KEY (category_id) REFERENCES product_categories(id),
|
||||
CONSTRAINT fk_products_unit FOREIGN KEY (unit_id) REFERENCES product_units(id),
|
||||
CONSTRAINT fk_products_globalsku FOREIGN KEY (global_sku_id) REFERENCES global_skus(id),
|
||||
CONSTRAINT ck_products_safe_range CHECK (safe_min IS NULL OR safe_max IS NULL OR safe_min <= safe_max)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='商品';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS product_aliases (
|
||||
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,
|
||||
alias VARCHAR(120) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
deleted_at DATETIME NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY ux_product_alias (product_id, alias),
|
||||
KEY idx_product_alias_product (product_id),
|
||||
CONSTRAINT fk_alias_shop FOREIGN KEY (shop_id) REFERENCES shops(id),
|
||||
CONSTRAINT fk_alias_user FOREIGN KEY (user_id) REFERENCES users(id),
|
||||
CONSTRAINT fk_alias_product FOREIGN KEY (product_id) REFERENCES products(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='商品别名';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS product_prices (
|
||||
product_id BIGINT UNSIGNED NOT NULL,
|
||||
shop_id BIGINT UNSIGNED NOT NULL,
|
||||
user_id BIGINT UNSIGNED NOT NULL,
|
||||
purchase_price DECIMAL(18,2) NOT NULL DEFAULT 0,
|
||||
retail_price DECIMAL(18,2) NOT NULL DEFAULT 0,
|
||||
distribution_price DECIMAL(18,2) NOT NULL DEFAULT 0,
|
||||
wholesale_price DECIMAL(18,2) NOT NULL DEFAULT 0,
|
||||
big_client_price DECIMAL(18,2) NOT NULL DEFAULT 0,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (product_id),
|
||||
KEY idx_prices_shop (shop_id),
|
||||
CONSTRAINT fk_prices_product FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_prices_shop FOREIGN KEY (shop_id) REFERENCES shops(id),
|
||||
CONSTRAINT fk_prices_user FOREIGN KEY (user_id) REFERENCES users(id),
|
||||
CONSTRAINT ck_prices_non_negative CHECK (
|
||||
purchase_price >= 0 AND retail_price >= 0 AND distribution_price >= 0 AND wholesale_price >= 0 AND big_client_price >= 0
|
||||
)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='商品价格(含四列销售价)';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS inventories (
|
||||
product_id BIGINT UNSIGNED NOT NULL,
|
||||
shop_id BIGINT UNSIGNED NOT NULL,
|
||||
user_id BIGINT UNSIGNED NOT NULL,
|
||||
quantity DECIMAL(18,3) NOT NULL DEFAULT 0,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (product_id),
|
||||
KEY idx_inventories_shop (shop_id),
|
||||
CONSTRAINT fk_inv_product FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_inv_shop FOREIGN KEY (shop_id) REFERENCES shops(id),
|
||||
CONSTRAINT fk_inv_user FOREIGN KEY (user_id) REFERENCES users(id),
|
||||
CONSTRAINT ck_inv_qty_non_negative CHECK (quantity >= 0)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='库存';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS product_images (
|
||||
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,
|
||||
url VARCHAR(512) NOT NULL,
|
||||
hash VARCHAR(64) NULL COMMENT '内容哈希(去重)',
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY ux_product_image_hash (product_id, hash),
|
||||
KEY idx_product_images_product (product_id),
|
||||
CONSTRAINT fk_pimg_shop FOREIGN KEY (shop_id) REFERENCES shops(id),
|
||||
CONSTRAINT fk_pimg_user FOREIGN KEY (user_id) REFERENCES users(id),
|
||||
CONSTRAINT fk_pimg_product FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='商品图片';
|
||||
|
||||
-- =====================================================================
|
||||
-- 往来单位与账户
|
||||
-- =====================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS customers (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
shop_id BIGINT UNSIGNED NOT NULL,
|
||||
user_id BIGINT UNSIGNED NOT NULL,
|
||||
name VARCHAR(120) NOT NULL,
|
||||
phone VARCHAR(32) NULL,
|
||||
level VARCHAR(32) NULL COMMENT '客户等级标签',
|
||||
price_level ENUM('retail','distribution','wholesale','big_client') NOT NULL DEFAULT 'retail' COMMENT '默认售价列',
|
||||
status TINYINT UNSIGNED NOT NULL DEFAULT 1,
|
||||
remark VARCHAR(255) NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
deleted_at DATETIME NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_customers_shop (shop_id),
|
||||
KEY idx_customers_phone (phone),
|
||||
CONSTRAINT fk_customers_shop FOREIGN KEY (shop_id) REFERENCES shops(id),
|
||||
CONSTRAINT fk_customers_user FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='客户';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS suppliers (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
shop_id BIGINT UNSIGNED NOT NULL,
|
||||
user_id BIGINT UNSIGNED NOT NULL,
|
||||
name VARCHAR(120) NOT NULL,
|
||||
phone VARCHAR(32) NULL,
|
||||
status TINYINT UNSIGNED NOT NULL DEFAULT 1,
|
||||
remark VARCHAR(255) NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
deleted_at DATETIME NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_suppliers_shop (shop_id),
|
||||
KEY idx_suppliers_phone (phone),
|
||||
CONSTRAINT fk_suppliers_shop FOREIGN KEY (shop_id) REFERENCES shops(id),
|
||||
CONSTRAINT fk_suppliers_user FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='供应商';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS accounts (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
shop_id BIGINT UNSIGNED NOT NULL,
|
||||
user_id BIGINT UNSIGNED NOT NULL,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
`type` ENUM('cash','bank','alipay','wechat','other') NOT NULL DEFAULT 'cash',
|
||||
balance DECIMAL(18,2) NOT NULL DEFAULT 0,
|
||||
status TINYINT UNSIGNED NOT NULL DEFAULT 1,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
deleted_at DATETIME NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY ux_accounts_shop_name (shop_id, name),
|
||||
KEY idx_accounts_shop (shop_id),
|
||||
CONSTRAINT fk_accounts_shop FOREIGN KEY (shop_id) REFERENCES shops(id),
|
||||
CONSTRAINT fk_accounts_user FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='结算账户';
|
||||
|
||||
-- =====================================================================
|
||||
-- 单据域(销售/进货/其他收支/流水)
|
||||
-- =====================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS sales_orders (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
shop_id BIGINT UNSIGNED NOT NULL,
|
||||
user_id BIGINT UNSIGNED NOT NULL COMMENT '创建人',
|
||||
customer_id BIGINT UNSIGNED NULL,
|
||||
order_no VARCHAR(32) NOT NULL,
|
||||
order_time DATETIME NOT NULL,
|
||||
status ENUM('draft','approved','returned','void') NOT NULL DEFAULT 'draft',
|
||||
amount DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '应收合计',
|
||||
paid_amount DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '已收合计',
|
||||
remark VARCHAR(255) NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
deleted_at DATETIME NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY ux_sales_order_no (shop_id, order_no),
|
||||
KEY idx_sales_shop_time (shop_id, order_time),
|
||||
KEY idx_sales_customer (customer_id),
|
||||
CONSTRAINT fk_sales_shop FOREIGN KEY (shop_id) REFERENCES shops(id),
|
||||
CONSTRAINT fk_sales_user FOREIGN KEY (user_id) REFERENCES users(id),
|
||||
CONSTRAINT fk_sales_customer FOREIGN KEY (customer_id) REFERENCES customers(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='销售单';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS sales_order_items (
|
||||
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 DEFAULT 0 COMMENT '折扣百分比0-100',
|
||||
amount DECIMAL(18,2) NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_soi_order (order_id),
|
||||
KEY idx_soi_product (product_id),
|
||||
CONSTRAINT fk_soi_order FOREIGN KEY (order_id) REFERENCES sales_orders(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_soi_product FOREIGN KEY (product_id) REFERENCES products(id),
|
||||
CONSTRAINT ck_soi_qty CHECK (quantity > 0),
|
||||
CONSTRAINT ck_soi_price CHECK (unit_price >= 0),
|
||||
CONSTRAINT ck_soi_discount CHECK (discount_rate >= 0 AND discount_rate <= 100)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='销售单明细';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS purchase_orders (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
shop_id BIGINT UNSIGNED NOT NULL,
|
||||
user_id BIGINT UNSIGNED NOT NULL,
|
||||
supplier_id BIGINT UNSIGNED NULL,
|
||||
order_no VARCHAR(32) NOT NULL,
|
||||
order_time DATETIME NOT NULL,
|
||||
status ENUM('draft','approved','void') NOT NULL DEFAULT 'draft',
|
||||
amount DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '应付合计',
|
||||
paid_amount DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '已付合计',
|
||||
remark VARCHAR(255) NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
deleted_at DATETIME NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY ux_purchase_order_no (shop_id, order_no),
|
||||
KEY idx_purchase_shop_time (shop_id, order_time),
|
||||
KEY idx_purchase_supplier (supplier_id),
|
||||
CONSTRAINT fk_purchase_shop FOREIGN KEY (shop_id) REFERENCES shops(id),
|
||||
CONSTRAINT fk_purchase_user FOREIGN KEY (user_id) REFERENCES users(id),
|
||||
CONSTRAINT fk_purchase_supplier FOREIGN KEY (supplier_id) REFERENCES suppliers(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='进货单';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS purchase_order_items (
|
||||
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,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_poi_order (order_id),
|
||||
KEY idx_poi_product (product_id),
|
||||
CONSTRAINT fk_poi_order FOREIGN KEY (order_id) REFERENCES purchase_orders(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_poi_product FOREIGN KEY (product_id) REFERENCES products(id),
|
||||
CONSTRAINT ck_poi_qty CHECK (quantity > 0),
|
||||
CONSTRAINT ck_poi_price CHECK (unit_price >= 0)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='进货单明细';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS payments (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
shop_id BIGINT UNSIGNED NOT NULL,
|
||||
user_id BIGINT UNSIGNED NOT NULL,
|
||||
biz_type ENUM('sale','purchase','other') NOT NULL,
|
||||
biz_id BIGINT UNSIGNED NULL COMMENT '业务表ID:sales_orders/purchase_orders/other_transactions',
|
||||
account_id BIGINT UNSIGNED NOT NULL,
|
||||
direction ENUM('in','out') NOT NULL COMMENT '收款/付款',
|
||||
amount DECIMAL(18,2) NOT NULL,
|
||||
pay_time DATETIME NOT NULL,
|
||||
remark VARCHAR(255) NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_payments_shop_time (shop_id, pay_time),
|
||||
KEY idx_payments_biz (biz_type, biz_id),
|
||||
CONSTRAINT fk_payments_shop FOREIGN KEY (shop_id) REFERENCES shops(id),
|
||||
CONSTRAINT fk_payments_user FOREIGN KEY (user_id) REFERENCES users(id),
|
||||
CONSTRAINT fk_payments_account FOREIGN KEY (account_id) REFERENCES accounts(id),
|
||||
CONSTRAINT ck_payments_amount CHECK (amount > 0)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='收付款记录';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS other_transactions (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
shop_id BIGINT UNSIGNED NOT NULL,
|
||||
user_id BIGINT UNSIGNED NOT NULL,
|
||||
`type` ENUM('income','expense') NOT NULL,
|
||||
category VARCHAR(64) NOT NULL,
|
||||
counterparty_type VARCHAR(32) NULL COMMENT 'customer/supplier/other',
|
||||
counterparty_id BIGINT UNSIGNED NULL,
|
||||
account_id BIGINT UNSIGNED NOT NULL,
|
||||
amount DECIMAL(18,2) NOT NULL,
|
||||
tx_time DATETIME NOT NULL,
|
||||
remark VARCHAR(255) NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
deleted_at DATETIME NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_ot_shop_time (shop_id, tx_time),
|
||||
KEY idx_ot_account (account_id),
|
||||
CONSTRAINT fk_ot_shop FOREIGN KEY (shop_id) REFERENCES shops(id),
|
||||
CONSTRAINT fk_ot_user FOREIGN KEY (user_id) REFERENCES users(id),
|
||||
CONSTRAINT fk_ot_account FOREIGN KEY (account_id) REFERENCES accounts(id),
|
||||
CONSTRAINT ck_ot_amount CHECK (amount > 0)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='其他收入/支出';
|
||||
|
||||
-- =====================================================================
|
||||
-- 配件查询与审核、附件
|
||||
-- =====================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS part_submissions (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
shop_id BIGINT UNSIGNED NOT NULL,
|
||||
user_id BIGINT UNSIGNED NOT NULL,
|
||||
model_unique VARCHAR(128) NOT NULL COMMENT '型号(唯一)',
|
||||
brand VARCHAR(64) NULL,
|
||||
spec VARCHAR(128) NULL,
|
||||
size VARCHAR(64) NULL,
|
||||
aperture VARCHAR(64) NULL,
|
||||
compatible TEXT NULL COMMENT '适配信息',
|
||||
status ENUM('draft','pending','rejected','published') NOT NULL DEFAULT 'pending',
|
||||
reason VARCHAR(255) NULL COMMENT '驳回原因',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
deleted_at DATETIME NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY ux_part_model_unique (model_unique),
|
||||
KEY idx_part_submissions_shop (shop_id),
|
||||
CONSTRAINT fk_part_shop FOREIGN KEY (shop_id) REFERENCES shops(id),
|
||||
CONSTRAINT fk_part_user FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='配件数据提交(审核)';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS attachments (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
shop_id BIGINT UNSIGNED NULL COMMENT '全局资源可空,本地资源属于租户',
|
||||
user_id BIGINT UNSIGNED NULL,
|
||||
owner_type VARCHAR(32) NOT NULL COMMENT '资源归属类型:product/part_submission/global_sku/...',
|
||||
owner_id BIGINT UNSIGNED NOT NULL,
|
||||
url VARCHAR(512) NOT NULL,
|
||||
hash VARCHAR(64) NULL,
|
||||
meta JSON NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY ux_attachments_hash (hash),
|
||||
KEY idx_attachments_owner (owner_type, owner_id),
|
||||
CONSTRAINT fk_att_shop FOREIGN KEY (shop_id) REFERENCES shops(id),
|
||||
CONSTRAINT fk_att_user FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='通用附件(图片等)';
|
||||
|
||||
-- =====================================================================
|
||||
-- 触发器:维护 products.search_text 聚合字段
|
||||
-- =====================================================================
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_products_ai;
|
||||
DELIMITER $$
|
||||
CREATE TRIGGER trg_products_ai AFTER INSERT ON products
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE products
|
||||
SET search_text = CONCAT_WS(' ', NEW.name, NEW.brand, NEW.model, NEW.spec)
|
||||
WHERE id = NEW.id;
|
||||
END $$
|
||||
DELIMITER ;
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_products_au;
|
||||
DELIMITER $$
|
||||
CREATE TRIGGER trg_products_au BEFORE UPDATE ON products
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
SET NEW.search_text = CONCAT_WS(' ', NEW.name, NEW.brand, NEW.model, NEW.spec);
|
||||
END $$
|
||||
DELIMITER ;
|
||||
|
||||
-- 当别名变化时重建 search_text(名称/品牌/型号/规格 + 所有别名)
|
||||
DROP TRIGGER IF EXISTS trg_palias_ai;
|
||||
DELIMITER $$
|
||||
CREATE TRIGGER trg_palias_ai AFTER INSERT ON product_aliases
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE products p
|
||||
JOIN (
|
||||
SELECT pa.product_id, GROUP_CONCAT(pa.alias SEPARATOR ' ') AS aliases
|
||||
FROM product_aliases pa
|
||||
WHERE pa.product_id = NEW.product_id AND pa.deleted_at IS NULL
|
||||
GROUP BY pa.product_id
|
||||
) a ON a.product_id = p.id
|
||||
SET p.search_text = CONCAT_WS(' ', p.name, p.brand, p.model, p.spec, a.aliases)
|
||||
WHERE p.id = NEW.product_id;
|
||||
END $$
|
||||
DELIMITER ;
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_palias_au;
|
||||
DELIMITER $$
|
||||
CREATE TRIGGER trg_palias_au AFTER UPDATE ON product_aliases
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE products p
|
||||
JOIN (
|
||||
SELECT pa.product_id, GROUP_CONCAT(pa.alias SEPARATOR ' ') AS aliases
|
||||
FROM product_aliases pa
|
||||
WHERE pa.product_id = NEW.product_id AND pa.deleted_at IS NULL
|
||||
GROUP BY pa.product_id
|
||||
) a ON a.product_id = p.id
|
||||
SET p.search_text = CONCAT_WS(' ', p.name, p.brand, p.model, p.spec, a.aliases)
|
||||
WHERE p.id = NEW.product_id;
|
||||
END $$
|
||||
DELIMITER ;
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_palias_ad;
|
||||
DELIMITER $$
|
||||
CREATE TRIGGER trg_palias_ad AFTER DELETE ON product_aliases
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE products p
|
||||
LEFT JOIN (
|
||||
SELECT pa.product_id, GROUP_CONCAT(pa.alias SEPARATOR ' ') AS aliases
|
||||
FROM product_aliases pa
|
||||
WHERE pa.product_id = OLD.product_id AND pa.deleted_at IS NULL
|
||||
GROUP BY pa.product_id
|
||||
) a ON a.product_id = p.id
|
||||
SET p.search_text = CONCAT_WS(' ', p.name, p.brand, p.model, p.spec, COALESCE(a.aliases, ''))
|
||||
WHERE p.id = OLD.product_id;
|
||||
END $$
|
||||
DELIMITER ;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
|
||||
39
backend/doc/同步文档.md
Normal file
39
backend/doc/同步文档.md
Normal file
@@ -0,0 +1,39 @@
|
||||
## 前后端数据库状态说明
|
||||
|
||||
**更新日期**: 2025-09-16
|
||||
|
||||
### 概要
|
||||
- 数据库已落地:已在远程 MySQL `mysql.tonaspace.com` 的 `partsinquiry` 库完成初始化(表结构与触发器已创建)。
|
||||
- 已生成根目录文档:`/doc/database_documentation.md` 已同步线上结构(字段、索引、外键、触发器)。
|
||||
- 后端代码仍未配置数据源依赖与连接,前端无本地结构化存储方案。
|
||||
|
||||
### 已建库与连接信息(用于部署/联调)
|
||||
- Address: `mysql.tonaspace.com`
|
||||
- Database: `partsinquiry`
|
||||
- User: `root`
|
||||
- 说明:所有结构变更均通过 MysqlMCP 执行并已落地到线上库。
|
||||
|
||||
### 后端(Spring Boot)数据库状态
|
||||
- 依赖:`pom.xml` 未包含 `spring-boot-starter-web`、`spring-boot-starter-data-jpa`、`mysql-connector-j` 等数据库相关依赖。
|
||||
- 配置:`src/main/resources/application.properties` 仅有 `spring.application.name=demo`;未配置 `spring.datasource.*`、`spring.jpa.*`。
|
||||
- 数据模型:`src/main/java` 未发现 `@Entity`、Repository、Service;存在 `backend/db/db.sql` 脚本,已执行至远程库。
|
||||
- 迁移:未发现 Flyway/Liquibase 配置与脚本(当前通过 MysqlMCP 手工执行)。
|
||||
- 结论:数据库已初始化,但后端未配置运行时数据源与接口,暂不可用。
|
||||
|
||||
### 前端(uni-app)数据库状态
|
||||
- 数据持久化:未见 IndexedDB/WebSQL/SQLite/云数据库使用;页面数据为内置静态数据。
|
||||
- 本地存储:未见 `uni.setStorage`/`uni.getStorage` 的集中封装或结构化键空间设计。
|
||||
- 结论:前端当前不涉及本地数据库或结构化存储方案。
|
||||
|
||||
### 风险与影响
|
||||
- 后端未配置数据源与接口,应用无法读写远端库(虽已建表)。
|
||||
- 无接口契约,前后端仍无法联调涉及数据库的功能。
|
||||
|
||||
### 建议的后续行动(不自动执行)
|
||||
- 在后端引入依赖:`spring-boot-starter-web`、`spring-boot-starter-data-jpa`、`mysql-connector-j`。
|
||||
- 配置数据源:使用环境变量注入 `SPRING_DATASOURCE_URL`、`SPRING_DATASOURCE_USERNAME`、`SPRING_DATASOURCE_PASSWORD` 等,指向上述远程库。
|
||||
- 引入迁移工具(Flyway/Liquibase)管理 DDL;后续所有变更继续通过 MysqlMCP 执行,并同步 `/doc/database_documentation.md`。
|
||||
- 增加健康检查与基础 CRUD 接口;在 `/doc/openapi.yaml` 按规范登记并标注实现状态(❌/✅)。
|
||||
|
||||
|
||||
|
||||
@@ -35,6 +35,25 @@
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Web MVC -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JPA for MySQL access -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- MySQL Driver -->
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
|
||||
82
backend/src/main/java/com/example/demo/notice/Notice.java
Normal file
82
backend/src/main/java/com/example/demo/notice/Notice.java
Normal file
@@ -0,0 +1,82 @@
|
||||
package com.example.demo.notice;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "notices")
|
||||
public class Notice {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "shop_id", nullable = false)
|
||||
private Long shopId;
|
||||
|
||||
@Column(name = "user_id", nullable = false)
|
||||
private Long userId;
|
||||
|
||||
@Column(name = "title", nullable = false, length = 120)
|
||||
private String title;
|
||||
|
||||
@Column(name = "content", nullable = false, length = 500)
|
||||
private String content;
|
||||
|
||||
@Column(name = "tag", length = 32)
|
||||
private String tag;
|
||||
|
||||
@Column(name = "is_pinned", nullable = false)
|
||||
private Boolean pinned = false;
|
||||
|
||||
@Column(name = "starts_at")
|
||||
private LocalDateTime startsAt;
|
||||
|
||||
@Column(name = "ends_at")
|
||||
private LocalDateTime endsAt;
|
||||
|
||||
@Convert(converter = NoticeStatusConverter.class)
|
||||
@Column(name = "status", nullable = false, length = 16)
|
||||
private NoticeStatus status = NoticeStatus.PUBLISHED;
|
||||
|
||||
@Column(name = "created_at", nullable = false, updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column(name = "updated_at", nullable = false)
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
this.createdAt = now;
|
||||
this.updatedAt = now;
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public Long getId() { return id; }
|
||||
public Long getShopId() { return shopId; }
|
||||
public void setShopId(Long shopId) { this.shopId = shopId; }
|
||||
public Long getUserId() { return userId; }
|
||||
public void setUserId(Long userId) { this.userId = userId; }
|
||||
public String getTitle() { return title; }
|
||||
public void setTitle(String title) { this.title = title; }
|
||||
public String getContent() { return content; }
|
||||
public void setContent(String content) { this.content = content; }
|
||||
public String getTag() { return tag; }
|
||||
public void setTag(String tag) { this.tag = tag; }
|
||||
public Boolean getPinned() { return pinned; }
|
||||
public void setPinned(Boolean pinned) { this.pinned = pinned; }
|
||||
public LocalDateTime getStartsAt() { return startsAt; }
|
||||
public void setStartsAt(LocalDateTime startsAt) { this.startsAt = startsAt; }
|
||||
public LocalDateTime getEndsAt() { return endsAt; }
|
||||
public void setEndsAt(LocalDateTime endsAt) { this.endsAt = endsAt; }
|
||||
public NoticeStatus getStatus() { return status; }
|
||||
public void setStatus(NoticeStatus status) { this.status = status; }
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
public LocalDateTime getUpdatedAt() { return updatedAt; }
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.example.demo.notice;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/notices")
|
||||
public class NoticeController {
|
||||
|
||||
private final NoticeService noticeService;
|
||||
|
||||
public NoticeController(NoticeService noticeService) {
|
||||
this.noticeService = noticeService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 简化:通过请求头 X-Shop-Id 传递当前店铺ID。
|
||||
*/
|
||||
@GetMapping
|
||||
public ResponseEntity<List<Notice>> list(@RequestHeader(name = "X-Shop-Id", required = false) Long shopId) {
|
||||
Long sid = (shopId == null ? 1L : shopId);
|
||||
return ResponseEntity.ok(noticeService.listActive(sid));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.example.demo.notice;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface NoticeRepository extends JpaRepository<Notice, Long> {
|
||||
|
||||
@Query("SELECT n FROM Notice n WHERE n.shopId = :shopId AND n.status = :status " +
|
||||
"AND (n.startsAt IS NULL OR n.startsAt <= CURRENT_TIMESTAMP) AND (n.endsAt IS NULL OR n.endsAt >= CURRENT_TIMESTAMP) " +
|
||||
"ORDER BY n.pinned DESC, n.createdAt DESC")
|
||||
List<Notice> findActiveNotices(@Param("shopId") Long shopId, @Param("status") NoticeStatus status);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.example.demo.notice;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class NoticeService {
|
||||
private final NoticeRepository noticeRepository;
|
||||
|
||||
public NoticeService(NoticeRepository noticeRepository) {
|
||||
this.noticeRepository = noticeRepository;
|
||||
}
|
||||
|
||||
public List<Notice> listActive(Long shopId) {
|
||||
return noticeRepository.findActiveNotices(shopId, NoticeStatus.PUBLISHED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.example.demo.notice;
|
||||
|
||||
/**
|
||||
* 公告状态。
|
||||
*/
|
||||
public enum NoticeStatus {
|
||||
DRAFT,
|
||||
PUBLISHED,
|
||||
OFFLINE
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.example.demo.notice;
|
||||
|
||||
import jakarta.persistence.AttributeConverter;
|
||||
import jakarta.persistence.Converter;
|
||||
|
||||
@Converter(autoApply = false)
|
||||
public class NoticeStatusConverter implements AttributeConverter<NoticeStatus, String> {
|
||||
@Override
|
||||
public String convertToDatabaseColumn(NoticeStatus attribute) {
|
||||
if (attribute == null) return null;
|
||||
return switch (attribute) {
|
||||
case DRAFT -> "draft";
|
||||
case PUBLISHED -> "published";
|
||||
case OFFLINE -> "offline";
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public NoticeStatus convertToEntityAttribute(String dbData) {
|
||||
if (dbData == null) return null;
|
||||
return switch (dbData) {
|
||||
case "draft" -> NoticeStatus.DRAFT;
|
||||
case "published" -> NoticeStatus.PUBLISHED;
|
||||
case "offline" -> NoticeStatus.OFFLINE;
|
||||
default -> NoticeStatus.PUBLISHED;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1,25 @@
|
||||
spring.application.name=demo
|
||||
|
||||
# 数据源配置(通过环境变量注入,避免硬编码)
|
||||
spring.datasource.url=${SPRING_DATASOURCE_URL}
|
||||
spring.datasource.username=${SPRING_DATASOURCE_USERNAME}
|
||||
spring.datasource.password=${SPRING_DATASOURCE_PASSWORD}
|
||||
|
||||
# JPA 基本配置
|
||||
spring.jpa.hibernate.ddl-auto=none
|
||||
spring.jpa.open-in-view=false
|
||||
spring.jpa.show-sql=false
|
||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
|
||||
|
||||
# CORS 简单放开(如需跨域)
|
||||
spring.web.cors.allowed-origins=${CORS_ALLOWED_ORIGINS:*}
|
||||
spring.web.cors.allowed-methods=GET,POST,PUT,DELETE,OPTIONS
|
||||
spring.web.cors.allowed-headers=*
|
||||
|
||||
# Hikari 连接池保活(避免云数据库空闲断开)
|
||||
spring.datasource.hikari.maximum-pool-size=10
|
||||
spring.datasource.hikari.minimum-idle=2
|
||||
spring.datasource.hikari.idle-timeout=300000
|
||||
spring.datasource.hikari.max-lifetime=600000
|
||||
spring.datasource.hikari.keepalive-time=300000
|
||||
spring.datasource.hikari.connection-timeout=30000
|
||||
|
||||
401
doc/database_documentation.md
Normal file
401
doc/database_documentation.md
Normal file
@@ -0,0 +1,401 @@
|
||||
## partsinquiry 数据库文档
|
||||
|
||||
更新日期:2025-09-16
|
||||
|
||||
说明:本文件根据远程库 mysql.tonaspace.com 中 `partsinquiry` 的实际结构生成,字段/索引/外键信息以线上为准。
|
||||
|
||||
### shops
|
||||
| Column Name | Data Type | Nullable | Default | Comment |
|
||||
| ----------- | --------- | -------- | ------- | ------- |
|
||||
| id | BIGINT UNSIGNED | NOT NULL | AUTO_INCREMENT | 店铺/租户ID |
|
||||
| name | VARCHAR(100) | NOT NULL | | 店铺名称 |
|
||||
| status | TINYINT UNSIGNED | NOT NULL | 1 | 状态:1启用 0停用 |
|
||||
| created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| updated_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| deleted_at | DATETIME | YES | | |
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_shops_status` (`status`)
|
||||
|
||||
### users
|
||||
| Column Name | Data Type | Nullable | Default | Comment |
|
||||
| ----------- | --------- | -------- | ------- | ------- |
|
||||
| id | BIGINT UNSIGNED | NOT NULL | AUTO_INCREMENT | 用户ID |
|
||||
| shop_id | BIGINT UNSIGNED | NOT NULL | | 所属店铺 |
|
||||
| phone | VARCHAR(32) | YES | | 手机号 |
|
||||
| name | VARCHAR(64) | NOT NULL | | 姓名 |
|
||||
| role | VARCHAR(32) | NOT NULL | staff | 角色:owner/staff/finance/... |
|
||||
| password_hash | VARCHAR(255) | YES | | 密码哈希(若采用短信登录可为空) |
|
||||
| status | TINYINT UNSIGNED | NOT NULL | 1 | 状态:1启用 0停用 |
|
||||
| is_owner | TINYINT(1) | NOT NULL | 0 | 是否店主 |
|
||||
| created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| updated_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| deleted_at | DATETIME | YES | | |
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_users_shop` (`shop_id`) - UNIQUE: `ux_users_shop_phone` (`shop_id`,`phone`)
|
||||
**Foreign Keys**: - `fk_users_shop`: `shop_id` → `shops(id)` ON UPDATE NO ACTION ON DELETE NO ACTION
|
||||
|
||||
### user_identities
|
||||
| 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 | | |
|
||||
| provider | ENUM('wechat_mp','wechat_app') | NOT NULL | | 身份提供方:小程序/APP |
|
||||
| openid | VARCHAR(64) | NOT NULL | | |
|
||||
| unionid | VARCHAR(64) | YES | | |
|
||||
| nickname | VARCHAR(64) | YES | | |
|
||||
| avatar_url | VARCHAR(512) | YES | | |
|
||||
| last_login_at | DATETIME | YES | | |
|
||||
| created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| updated_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_identity_shop` (`shop_id`) - KEY: `idx_identity_user` (`user_id`) - UNIQUE: `ux_identity_provider_openid` (`provider`,`openid`) - UNIQUE: `ux_identity_unionid` (`unionid`)
|
||||
**Foreign Keys**: - `fk_identity_shop`: `shop_id` → `shops(id)` ON UPDATE NO ACTION ON DELETE NO ACTION - `fk_identity_user`: `user_id` → `users(id)` ON UPDATE NO ACTION ON DELETE NO ACTION
|
||||
|
||||
### wechat_sessions
|
||||
| Column Name | Data Type | Nullable | Default | Comment |
|
||||
| ----------- | --------- | -------- | ------- | ------- |
|
||||
| id | BIGINT UNSIGNED | NOT NULL | AUTO_INCREMENT | |
|
||||
| provider | ENUM('wechat_mp','wechat_app') | NOT NULL | | |
|
||||
| openid | VARCHAR(64) | NOT NULL | | |
|
||||
| session_key | VARCHAR(128) | NOT NULL | | |
|
||||
| expires_at | DATETIME | NOT NULL | | |
|
||||
| created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_wechat_session_expires` (`expires_at`) - UNIQUE: `ux_wechat_session` (`provider`,`openid`)
|
||||
|
||||
### system_parameters
|
||||
| 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 | | 创建/最后修改人 |
|
||||
| key | VARCHAR(64) | NOT NULL | | 参数键 |
|
||||
| value | JSON | NOT NULL | | 参数值(JSON) |
|
||||
| created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| updated_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_sysparams_shop` (`shop_id`) - UNIQUE: `ux_sysparams_shop_key` (`shop_id`,`key`)
|
||||
**Foreign Keys**: - `fk_sysparams_shop`: `shop_id` → `shops(id)` ON UPDATE NO ACTION ON DELETE NO ACTION - `fk_sysparams_user`: `user_id` → `users(id)` ON UPDATE NO ACTION ON DELETE NO ACTION
|
||||
|
||||
### product_units
|
||||
| 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 | | |
|
||||
| name | VARCHAR(16) | NOT NULL | | |
|
||||
| created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| updated_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| deleted_at | DATETIME | YES | | |
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_units_shop` (`shop_id`) - UNIQUE: `ux_units_shop_name` (`shop_id`,`name`)
|
||||
**Foreign Keys**: - `fk_units_shop`: `shop_id` → `shops(id)` ON UPDATE NO ACTION ON DELETE NO ACTION - `fk_units_user`: `user_id` → `users(id)` ON UPDATE NO ACTION ON DELETE NO ACTION
|
||||
|
||||
### global_skus
|
||||
| Column Name | Data Type | Nullable | Default | Comment |
|
||||
| ----------- | --------- | -------- | ------- | ------- |
|
||||
| id | BIGINT UNSIGNED | NOT NULL | AUTO_INCREMENT | |
|
||||
| name | VARCHAR(120) | NOT NULL | | SKU名称 |
|
||||
| brand | VARCHAR(64) | YES | | |
|
||||
| model | VARCHAR(64) | YES | | |
|
||||
| spec | VARCHAR(128) | YES | | |
|
||||
| barcode | VARCHAR(32) | YES | | |
|
||||
| unit_id | BIGINT UNSIGNED | YES | | |
|
||||
| tags | JSON | YES | | |
|
||||
| status | ENUM('published','offline') | NOT NULL | published | |
|
||||
| created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| updated_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| deleted_at | DATETIME | YES | | |
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_global_skus_brand_model` (`brand`,`model`) - UNIQUE: `ux_global_skus_barcode` (`barcode`)
|
||||
**Foreign Keys**: - `fk_globalsku_unit`: `unit_id` → `product_units(id)` ON UPDATE NO ACTION ON DELETE NO ACTION
|
||||
|
||||
### product_categories
|
||||
| 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 | | |
|
||||
| name | VARCHAR(64) | NOT NULL | | |
|
||||
| parent_id | BIGINT UNSIGNED | YES | | |
|
||||
| sort_order | INT | NOT NULL | 0 | |
|
||||
| created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| updated_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| deleted_at | DATETIME | YES | | |
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_categories_shop` (`shop_id`) - KEY: `idx_categories_parent` (`parent_id`) - UNIQUE: `ux_categories_shop_name` (`shop_id`,`name`)
|
||||
**Foreign Keys**: - `fk_categories_shop`: `shop_id` → `shops(id)` ON UPDATE NO ACTION ON DELETE NO ACTION - `fk_categories_user`: `user_id` → `users(id)` ON UPDATE NO ACTION ON DELETE NO ACTION - `fk_categories_parent`: `parent_id` → `product_categories(id)` ON UPDATE NO ACTION ON DELETE NO ACTION
|
||||
|
||||
### products
|
||||
| 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 | | |
|
||||
| name | VARCHAR(120) | NOT NULL | | 供全文检索 |
|
||||
| category_id | BIGINT UNSIGNED | YES | | |
|
||||
| unit_id | BIGINT UNSIGNED | NOT NULL | | |
|
||||
| brand | VARCHAR(64) | YES | | |
|
||||
| model | VARCHAR(64) | YES | | |
|
||||
| spec | VARCHAR(128) | YES | | |
|
||||
| origin | VARCHAR(64) | YES | | |
|
||||
| barcode | VARCHAR(32) | YES | | |
|
||||
| alias | VARCHAR(120) | YES | | |
|
||||
| description | TEXT | YES | | |
|
||||
| global_sku_id | BIGINT UNSIGNED | YES | | |
|
||||
| safe_min | DECIMAL(18,3) | YES | | |
|
||||
| safe_max | DECIMAL(18,3) | YES | | |
|
||||
| search_text | TEXT | YES | | 供全文检索的聚合字段(名称/品牌/型号/规格/别名) |
|
||||
| created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| updated_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| deleted_at | DATETIME | YES | | |
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_products_shop` (`shop_id`) - KEY: `idx_products_category` (`category_id`) - KEY: `idx_products_unit` (`unit_id`) - FULLTEXT: `ft_products_search` (`name`,`brand`,`model`,`spec`,`search_text`) - UNIQUE: `ux_products_shop_barcode` (`shop_id`,`barcode`)
|
||||
**Foreign Keys**: - `fk_products_shop`: `shop_id` → `shops(id)` - `fk_products_user`: `user_id` → `users(id)` - `fk_products_category`: `category_id` → `product_categories(id)` - `fk_products_unit`: `unit_id` → `product_units(id)` - `fk_products_globalsku`: `global_sku_id` → `global_skus(id)`
|
||||
|
||||
### product_aliases
|
||||
| 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 | | |
|
||||
| alias | VARCHAR(120) | NOT NULL | | |
|
||||
| created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| updated_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| deleted_at | DATETIME | YES | | |
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_product_alias_product` (`product_id`) - UNIQUE: `ux_product_alias` (`product_id`,`alias`)
|
||||
**Foreign Keys**: - `fk_alias_shop`: `shop_id` → `shops(id)` - `fk_alias_user`: `user_id` → `users(id)` - `fk_alias_product`: `product_id` → `products(id)`
|
||||
|
||||
### product_images
|
||||
| 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 | | |
|
||||
| url | VARCHAR(512) | NOT NULL | | |
|
||||
| hash | VARCHAR(64) | YES | | 内容哈希(去重) |
|
||||
| sort_order | INT | NOT NULL | 0 | |
|
||||
| created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_product_images_product` (`product_id`) - UNIQUE: `ux_product_image_hash` (`product_id`,`hash`)
|
||||
**Foreign Keys**: - `fk_pimg_shop`: `shop_id` → `shops(id)` - `fk_pimg_user`: `user_id` → `users(id)` - `fk_pimg_product`: `product_id` → `products(id)` ON DELETE CASCADE
|
||||
|
||||
### product_prices
|
||||
| Column Name | Data Type | Nullable | Default | Comment |
|
||||
| ----------- | --------- | -------- | ------- | ------- |
|
||||
| product_id | BIGINT UNSIGNED | NOT NULL | | |
|
||||
| shop_id | BIGINT UNSIGNED | NOT NULL | | |
|
||||
| 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 | |
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `product_id` - KEY: `idx_prices_shop` (`shop_id`)
|
||||
**Foreign Keys**: - `fk_prices_product`: `product_id` → `products(id)` ON DELETE CASCADE - `fk_prices_shop`: `shop_id` → `shops(id)` - `fk_prices_user`: `user_id` → `users(id)`
|
||||
|
||||
### inventories
|
||||
| Column Name | Data Type | Nullable | Default | Comment |
|
||||
| ----------- | --------- | -------- | ------- | ------- |
|
||||
| product_id | BIGINT UNSIGNED | NOT NULL | | |
|
||||
| shop_id | BIGINT UNSIGNED | NOT NULL | | |
|
||||
| user_id | BIGINT UNSIGNED | NOT NULL | | |
|
||||
| quantity | DECIMAL(18,3) | NOT NULL | 0.000 | |
|
||||
| updated_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `product_id` - KEY: `idx_inventories_shop` (`shop_id`)
|
||||
**Foreign Keys**: - `fk_inv_product`: `product_id` → `products(id)` ON DELETE CASCADE - `fk_inv_shop`: `shop_id` → `shops(id)` - `fk_inv_user`: `user_id` → `users(id)`
|
||||
|
||||
### customers
|
||||
| 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 | | |
|
||||
| name | VARCHAR(120) | NOT NULL | | |
|
||||
| phone | VARCHAR(32) | YES | | |
|
||||
| level | VARCHAR(32) | YES | | 客户等级标签 |
|
||||
| price_level | ENUM('retail','distribution','wholesale','big_client') | NOT NULL | retail | 默认售价列 |
|
||||
| status | TINYINT UNSIGNED | NOT NULL | 1 | |
|
||||
| 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` - KEY: `idx_customers_shop` (`shop_id`) - KEY: `idx_customers_phone` (`phone`)
|
||||
**Foreign Keys**: - `fk_customers_shop`: `shop_id` → `shops(id)` - `fk_customers_user`: `user_id` → `users(id)`
|
||||
|
||||
### suppliers
|
||||
| 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 | | |
|
||||
| name | VARCHAR(120) | NOT NULL | | |
|
||||
| phone | VARCHAR(32) | YES | | |
|
||||
| status | TINYINT UNSIGNED | NOT NULL | 1 | |
|
||||
| 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` - KEY: `idx_suppliers_shop` (`shop_id`) - KEY: `idx_suppliers_phone` (`phone`)
|
||||
**Foreign Keys**: - `fk_suppliers_shop`: `shop_id` → `shops(id)` - `fk_suppliers_user`: `user_id` → `users(id)`
|
||||
|
||||
### accounts
|
||||
| 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 | | |
|
||||
| name | VARCHAR(64) | NOT NULL | | |
|
||||
| type | ENUM('cash','bank','alipay','wechat','other') | NOT NULL | cash | |
|
||||
| balance | DECIMAL(18,2) | NOT NULL | 0.00 | |
|
||||
| status | TINYINT UNSIGNED | NOT NULL | 1 | |
|
||||
| created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| updated_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| deleted_at | DATETIME | YES | | |
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_accounts_shop` (`shop_id`) - UNIQUE: `ux_accounts_shop_name` (`shop_id`,`name`)
|
||||
**Foreign Keys**: - `fk_accounts_shop`: `shop_id` → `shops(id)` - `fk_accounts_user`: `user_id` → `users(id)`
|
||||
|
||||
### sales_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('draft','approved','returned','void') | NOT NULL | draft | |
|
||||
| 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` - KEY: `idx_sales_shop_time` (`shop_id`,`order_time`) - KEY: `idx_sales_customer` (`customer_id`) - UNIQUE: `ux_sales_order_no` (`shop_id`,`order_no`)
|
||||
**Foreign Keys**: - `fk_sales_shop`: `shop_id` → `shops(id)` - `fk_sales_user`: `user_id` → `users(id)` - `fk_sales_customer`: `customer_id` → `customers(id)`
|
||||
|
||||
### sales_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 | 折扣百分比0-100 |
|
||||
| amount | DECIMAL(18,2) | NOT NULL | | |
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_soi_order` (`order_id`) - KEY: `idx_soi_product` (`product_id`)
|
||||
**Foreign Keys**: - `fk_soi_order`: `order_id` → `sales_orders(id)` ON DELETE CASCADE - `fk_soi_product`: `product_id` → `products(id)`
|
||||
|
||||
### purchase_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('draft','approved','void') | NOT NULL | draft | |
|
||||
| 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` - KEY: `idx_purchase_shop_time` (`shop_id`,`order_time`) - KEY: `idx_purchase_supplier` (`supplier_id`) - UNIQUE: `ux_purchase_order_no` (`shop_id`,`order_no`)
|
||||
**Foreign Keys**: - `fk_purchase_shop`: `shop_id` → `shops(id)` - `fk_purchase_user`: `user_id` → `users(id)` - `fk_purchase_supplier`: `supplier_id` → `suppliers(id)`
|
||||
|
||||
### purchase_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_poi_order` (`order_id`) - KEY: `idx_poi_product` (`product_id`)
|
||||
**Foreign Keys**: - `fk_poi_order`: `order_id` → `purchase_orders(id)` ON DELETE CASCADE - `fk_poi_product`: `product_id` → `products(id)`
|
||||
|
||||
### payments
|
||||
| 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 | | |
|
||||
| biz_type | ENUM('sale','purchase','other') | NOT NULL | | |
|
||||
| biz_id | BIGINT UNSIGNED | YES | | 业务表ID:sales_orders/purchase_orders/other_transactions |
|
||||
| account_id | BIGINT UNSIGNED | NOT NULL | | |
|
||||
| direction | ENUM('in','out') | NOT NULL | | 收款/付款 |
|
||||
| amount | DECIMAL(18,2) | NOT NULL | | |
|
||||
| pay_time | DATETIME | NOT NULL | | |
|
||||
| remark | VARCHAR(255) | YES | | |
|
||||
| created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
|
||||
**Indexes**: - PRIMARY KEY: `id` - KEY: `idx_payments_shop_time` (`shop_id`,`pay_time`) - KEY: `idx_payments_biz` (`biz_type`,`biz_id`)
|
||||
**Foreign Keys**: - `fk_payments_shop`: `shop_id` → `shops(id)` - `fk_payments_user`: `user_id` → `users(id)` - `fk_payments_account`: `account_id` → `accounts(id)`
|
||||
|
||||
### other_transactions
|
||||
| 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 | | |
|
||||
| type | ENUM('income','expense') | NOT NULL | | |
|
||||
| category | VARCHAR(64) | NOT NULL | | |
|
||||
| counterparty_type | VARCHAR(32) | YES | | customer/supplier/other |
|
||||
| counterparty_id | BIGINT UNSIGNED | YES | | |
|
||||
| account_id | BIGINT UNSIGNED | NOT NULL | | |
|
||||
| amount | DECIMAL(18,2) | NOT NULL | | |
|
||||
| tx_time | DATETIME | NOT NULL | | |
|
||||
| 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` - KEY: `idx_ot_shop_time` (`shop_id`,`tx_time`) - KEY: `idx_ot_account` (`account_id`)
|
||||
**Foreign Keys**: - `fk_ot_shop`: `shop_id` → `shops(id)` - `fk_ot_user`: `user_id` → `users(id)` - `fk_ot_account`: `account_id` → `accounts(id)`
|
||||
|
||||
### product_images 触发器
|
||||
- `trg_products_ai`: AFTER INSERT ON `products` → 更新 `products.search_text`
|
||||
- `trg_products_au`: BEFORE UPDATE ON `products` → 维护 `products.search_text`
|
||||
- `trg_palias_ai`: AFTER INSERT ON `product_aliases` → 重建 `products.search_text`
|
||||
- `trg_palias_au`: AFTER UPDATE ON `product_aliases` → 重建 `products.search_text`
|
||||
- `trg_palias_ad`: AFTER DELETE ON `product_aliases` → 重建 `products.search_text`
|
||||
|
||||
|
||||
### notices
|
||||
| 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 | | |
|
||||
| title | VARCHAR(120) | NOT NULL | | |
|
||||
| content | VARCHAR(500) | NOT NULL | | |
|
||||
| tag | VARCHAR(32) | YES | | |
|
||||
| is_pinned | TINYINT(1) | NOT NULL | 0 | |
|
||||
| starts_at | DATETIME | YES | | |
|
||||
| ends_at | DATETIME | YES | | |
|
||||
| status | ENUM('draft','published','offline') | NOT NULL | published | |
|
||||
| created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| updated_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | |
|
||||
| deleted_at | DATETIME | YES | | |
|
||||
|
||||
**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
|
||||
|
||||
67
doc/openapi.yaml
Normal file
67
doc/openapi.yaml
Normal file
@@ -0,0 +1,67 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: PartsInquiry API
|
||||
version: 0.1.0
|
||||
description: >-
|
||||
所有接口定义集中于此文件。每个 path 在 summary/description 中标注实现状态。
|
||||
servers:
|
||||
- url: /
|
||||
paths:
|
||||
/api/notices:
|
||||
get:
|
||||
summary: 公告列表(✅ Fully Implemented)
|
||||
description: 返回当前店铺可见的公告列表。后端与前端均已接入。
|
||||
parameters:
|
||||
- in: header
|
||||
name: X-Shop-Id
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
description: 店铺ID,缺省为 1
|
||||
responses:
|
||||
'200':
|
||||
description: 成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Notice'
|
||||
components:
|
||||
schemas:
|
||||
Notice:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
shopId:
|
||||
type: integer
|
||||
format: int64
|
||||
userId:
|
||||
type: integer
|
||||
format: int64
|
||||
title:
|
||||
type: string
|
||||
content:
|
||||
type: string
|
||||
tag:
|
||||
type: string
|
||||
pinned:
|
||||
type: boolean
|
||||
startsAt:
|
||||
type: string
|
||||
format: date-time
|
||||
endsAt:
|
||||
type: string
|
||||
format: date-time
|
||||
status:
|
||||
type: string
|
||||
enum: [DRAFT, PUBLISHED, OFFLINE]
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
updatedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
14
frontend/common/config.js
Normal file
14
frontend/common/config.js
Normal file
@@ -0,0 +1,14 @@
|
||||
// 统一配置:禁止在业务代码中硬编码
|
||||
// 优先级:环境变量(Vite/HBuilderX 构建注入) > 本地存储 > 默认值
|
||||
|
||||
const envBaseUrl = (typeof process !== 'undefined' && process.env && (process.env.VITE_APP_API_BASE_URL || process.env.API_BASE_URL)) || '';
|
||||
const storageBaseUrl = typeof uni !== 'undefined' ? (uni.getStorageSync('API_BASE_URL') || '') : '';
|
||||
const fallbackBaseUrl = 'http://localhost:8080';
|
||||
|
||||
export const API_BASE_URL = (envBaseUrl || storageBaseUrl || fallbackBaseUrl).replace(/\/$/, '');
|
||||
|
||||
const envShopId = (typeof process !== 'undefined' && process.env && (process.env.VITE_APP_SHOP_ID || process.env.SHOP_ID)) || '';
|
||||
const storageShopId = typeof uni !== 'undefined' ? (uni.getStorageSync('SHOP_ID') || '') : '';
|
||||
export const SHOP_ID = Number(envShopId || storageShopId || 1);
|
||||
|
||||
|
||||
26
frontend/common/http.js
Normal file
26
frontend/common/http.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { API_BASE_URL, SHOP_ID } from './config.js'
|
||||
|
||||
function buildUrl(path) {
|
||||
if (!path) return API_BASE_URL
|
||||
if (path.startsWith('http')) return path
|
||||
return API_BASE_URL + (path.startsWith('/') ? path : '/' + path)
|
||||
}
|
||||
|
||||
export function get(path, params = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.request({
|
||||
url: buildUrl(path),
|
||||
method: 'GET',
|
||||
data: params,
|
||||
header: { 'X-Shop-Id': SHOP_ID },
|
||||
success: (res) => {
|
||||
const { statusCode, data } = res
|
||||
if (statusCode >= 200 && statusCode < 300) return resolve(data)
|
||||
reject(new Error('HTTP ' + statusCode))
|
||||
},
|
||||
fail: (err) => reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,10 @@
|
||||
<!-- 公告栏:自动轮播,可点击查看详情 -->
|
||||
<view class="notice">
|
||||
<view class="notice-left">公告</view>
|
||||
<swiper class="notice-swiper" circular autoplay interval="4000" duration="400" vertical>
|
||||
<view v-if="loadingNotices" class="notice-swiper" style="display:flex;align-items:center;color:#6b5a2a;">加载中...</view>
|
||||
<view v-else-if="noticeError" class="notice-swiper" style="display:flex;align-items:center;color:#dd524d;">{{ noticeError }}</view>
|
||||
<view v-else-if="!notices.length" class="notice-swiper" style="display:flex;align-items:center;color:#6b5a2a;">暂无公告</view>
|
||||
<swiper v-else class="notice-swiper" circular autoplay interval="4000" duration="400" vertical>
|
||||
<swiper-item v-for="(n, idx) in notices" :key="idx">
|
||||
<view class="notice-item" @click="onNoticeTap(n)">
|
||||
<text class="notice-text">{{ n.text }}</text>
|
||||
@@ -77,6 +80,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { get } from '../../common/http.js'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
@@ -84,12 +88,9 @@
|
||||
monthProfit: '0.00',
|
||||
stockQty: '0.00',
|
||||
activeTab: 'home',
|
||||
notices: [
|
||||
{ text: '选材精工:不锈钢、合金钢,耐腐蚀更耐用', tag: '品质' },
|
||||
{ text: '表面工艺:电镀锌/镀镍/发黑处理,性能均衡', tag: '工艺' },
|
||||
{ text: '库存齐全:螺丝、螺母、垫圈、膨胀螺栓等现货', tag: '库存' },
|
||||
{ text: '企业采购支持:批量优惠,次日发货', tag: '服务' }
|
||||
],
|
||||
notices: [],
|
||||
loadingNotices: false,
|
||||
noticeError: '',
|
||||
features: [
|
||||
{ key: 'customer', title: '客户', img: '/static/icons/customer.png', emoji: '👥' },
|
||||
{ key: 'sale', title: '销售', img: '/static/icons/sale.png', emoji: '💰' },
|
||||
@@ -103,7 +104,25 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
this.fetchNotices()
|
||||
},
|
||||
methods: {
|
||||
async fetchNotices() {
|
||||
this.loadingNotices = true
|
||||
this.noticeError = ''
|
||||
try {
|
||||
const list = await get('/api/notices')
|
||||
this.notices = Array.isArray(list) ? list.map(n => ({
|
||||
text: n.content || n.title || '',
|
||||
tag: n.tag || ''
|
||||
})) : []
|
||||
} catch (e) {
|
||||
this.noticeError = (e && e.message) || '公告加载失败'
|
||||
} finally {
|
||||
this.loadingNotices = false
|
||||
}
|
||||
},
|
||||
onFeatureTap(item) {
|
||||
uni.showToast({ title: item.title + '(开发中)', icon: 'none' })
|
||||
},
|
||||
@@ -113,7 +132,7 @@
|
||||
onNoticeTap(n) {
|
||||
uni.showModal({
|
||||
title: '公告',
|
||||
content: n.text,
|
||||
content: n && (n.text || n.title || n.content) || '',
|
||||
showCancel: false
|
||||
})
|
||||
},
|
||||
|
||||
@@ -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>\n","import App from './App'\n\n// #ifndef VUE3\nimport Vue from 'vue'\nimport './uni.promisify.adaptor'\nVue.config.productionTip = false\nApp.mpType = 'app'\nconst app = new Vue({\n ...App\n})\napp.$mount()\n// #endif\n\n// #ifdef VUE3\nimport { createSSRApp } from 'vue'\nexport function createApp() {\n const app = createSSRApp(App)\n return {\n app\n }\n}\n// #endif"],"names":["uni","createSSRApp","App"],"mappings":";;;;;;AACC,MAAK,YAAU;AAAA,EACd,UAAU,WAAW;AACpBA,kBAAAA,MAAA,MAAA,OAAA,gBAAY,YAAY;AAAA,EACxB;AAAA,EACD,QAAQ,WAAW;AAClBA,kBAAAA,MAAY,MAAA,OAAA,gBAAA,UAAU;AAAA,EACtB;AAAA,EACD,QAAQ,WAAW;AAClBA,kBAAAA,MAAY,MAAA,OAAA,iBAAA,UAAU;AAAA,EACvB;AACD;ACIM,SAAS,YAAY;AAC1B,QAAM,MAAMC,cAAY,aAACC,SAAG;AAC5B,SAAO;AAAA,IACL;AAAA,EACD;AACH;;;"}
|
||||
{"version":3,"file":"app.js","sources":["App.vue","main.js"],"sourcesContent":["<script>\r\n\texport default {\r\n\t\tonLaunch: function() {\r\n\t\t\tconsole.log('App Launch')\r\n\t\t},\r\n\t\tonShow: function() {\r\n\t\t\tconsole.log('App Show')\r\n\t\t},\r\n\t\tonHide: function() {\r\n\t\t\tconsole.log('App Hide')\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style>\r\n\t/*每个页面公共css */\r\n</style>\r\n","import App from './App'\r\n\r\n// #ifndef VUE3\r\nimport Vue from 'vue'\r\nimport './uni.promisify.adaptor'\r\nVue.config.productionTip = false\r\nApp.mpType = 'app'\r\nconst app = new Vue({\r\n ...App\r\n})\r\napp.$mount()\r\n// #endif\r\n\r\n// #ifdef VUE3\r\nimport { createSSRApp } from 'vue'\r\nexport function createApp() {\r\n const app = createSSRApp(App)\r\n return {\r\n app\r\n }\r\n}\r\n// #endif"],"names":["uni","createSSRApp","App"],"mappings":";;;;;;AACC,MAAK,YAAU;AAAA,EACd,UAAU,WAAW;AACpBA,kBAAAA,MAAA,MAAA,OAAA,gBAAY,YAAY;AAAA,EACxB;AAAA,EACD,QAAQ,WAAW;AAClBA,kBAAAA,MAAY,MAAA,OAAA,gBAAA,UAAU;AAAA,EACtB;AAAA,EACD,QAAQ,WAAW;AAClBA,kBAAAA,MAAY,MAAA,OAAA,iBAAA,UAAU;AAAA,EACvB;AACD;ACIM,SAAS,YAAY;AAC1B,QAAM,MAAMC,cAAY,aAACC,SAAG;AAC5B,SAAO;AAAA,IACL;AAAA,EACD;AACH;;;"}
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"assets.js","sources":["../../../static/metal-bg.jpg"],"sourcesContent":["export default \"/static/metal-bg.jpg\""],"names":[],"mappings":";AAAA,MAAe,aAAA;;"}
|
||||
{"version":3,"file":"assets.js","sources":["../../../../../../static/metal-bg.jpg"],"sourcesContent":["export default \"/static/metal-bg.jpg\""],"names":[],"mappings":";AAAA,MAAe,aAAA;;"}
|
||||
1
frontend/unpackage/dist/dev/.sourcemap/mp-weixin/common/config.js.map
vendored
Normal file
1
frontend/unpackage/dist/dev/.sourcemap/mp-weixin/common/config.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"config.js","sources":["common/config.js"],"sourcesContent":["// 统一配置:禁止在业务代码中硬编码\r\n// 优先级:环境变量(Vite/HBuilderX 构建注入) > 本地存储 > 默认值\r\n\r\nconst envBaseUrl = (typeof process !== 'undefined' && process.env && (process.env.VITE_APP_API_BASE_URL || process.env.API_BASE_URL)) || '';\r\nconst storageBaseUrl = typeof uni !== 'undefined' ? (uni.getStorageSync('API_BASE_URL') || '') : '';\r\nconst fallbackBaseUrl = 'http://localhost:8080';\r\n\r\nexport const API_BASE_URL = (envBaseUrl || storageBaseUrl || fallbackBaseUrl).replace(/\\/$/, '');\r\n\r\nconst envShopId = (typeof process !== 'undefined' && process.env && (process.env.VITE_APP_SHOP_ID || process.env.SHOP_ID)) || '';\r\nconst storageShopId = typeof uni !== 'undefined' ? (uni.getStorageSync('SHOP_ID') || '') : '';\r\nexport const SHOP_ID = Number(envShopId || storageShopId || 1);\r\n\r\n\r\n"],"names":["uni"],"mappings":";;AAGA,MAAM,aAAc,OAAO,YAAY,eAAe,QAAQ,QAAQ,QAAQ,IAAI,yBAAyB,QAAQ,IAAI,iBAAkB;AACzI,MAAM,iBAAiB,OAAOA,cAAG,UAAK,cAAeA,cAAAA,MAAI,eAAe,cAAc,KAAK,KAAM;AACjG,MAAM,kBAAkB;AAEZ,MAAC,gBAAgB,cAAc,kBAAkB,iBAAiB,QAAQ,OAAO,EAAE;AAE/F,MAAM,YAAa,OAAO,YAAY,eAAe,QAAQ,QAAQ,QAAQ,IAAI,oBAAoB,QAAQ,IAAI,YAAa;AAC9H,MAAM,gBAAgB,OAAOA,cAAG,UAAK,cAAeA,cAAAA,MAAI,eAAe,SAAS,KAAK,KAAM;AAC/E,MAAC,UAAU,OAAO,aAAa,iBAAiB,CAAC;;;"}
|
||||
1
frontend/unpackage/dist/dev/.sourcemap/mp-weixin/common/http.js.map
vendored
Normal file
1
frontend/unpackage/dist/dev/.sourcemap/mp-weixin/common/http.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"http.js","sources":["common/http.js"],"sourcesContent":["import { API_BASE_URL, SHOP_ID } from './config.js'\r\n\r\nfunction buildUrl(path) {\r\n if (!path) return API_BASE_URL\r\n if (path.startsWith('http')) return path\r\n return API_BASE_URL + (path.startsWith('/') ? path : '/' + path)\r\n}\r\n\r\nexport function get(path, params = {}) {\r\n return new Promise((resolve, reject) => {\r\n uni.request({\r\n url: buildUrl(path),\r\n method: 'GET',\r\n data: params,\r\n header: { 'X-Shop-Id': SHOP_ID },\r\n success: (res) => {\r\n const { statusCode, data } = res\r\n if (statusCode >= 200 && statusCode < 300) return resolve(data)\r\n reject(new Error('HTTP ' + statusCode))\r\n },\r\n fail: (err) => reject(err)\r\n })\r\n })\r\n}\r\n\r\n\r\n"],"names":["API_BASE_URL","uni","SHOP_ID"],"mappings":";;;AAEA,SAAS,SAAS,MAAM;AACtB,MAAI,CAAC;AAAM,WAAOA,cAAY;AAC9B,MAAI,KAAK,WAAW,MAAM;AAAG,WAAO;AACpC,SAAOA,cAAAA,gBAAgB,KAAK,WAAW,GAAG,IAAI,OAAO,MAAM;AAC7D;AAEO,SAAS,IAAI,MAAM,SAAS,IAAI;AACrC,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtCC,kBAAAA,MAAI,QAAQ;AAAA,MACV,KAAK,SAAS,IAAI;AAAA,MAClB,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,QAAQ,EAAE,aAAaC,sBAAS;AAAA,MAChC,SAAS,CAAC,QAAQ;AAChB,cAAM,EAAE,YAAY,KAAI,IAAK;AAC7B,YAAI,cAAc,OAAO,aAAa;AAAK,iBAAO,QAAQ,IAAI;AAC9D,eAAO,IAAI,MAAM,UAAU,UAAU,CAAC;AAAA,MACvC;AAAA,MACD,MAAM,CAAC,QAAQ,OAAO,GAAG;AAAA,IAC/B,CAAK;AAAA,EACL,CAAG;AACH;;"}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
12
frontend/unpackage/dist/dev/mp-weixin/common/config.js
vendored
Normal file
12
frontend/unpackage/dist/dev/mp-weixin/common/config.js
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
"use strict";
|
||||
const common_vendor = require("./vendor.js");
|
||||
const envBaseUrl = typeof process !== "undefined" && process.env && (process.env.VITE_APP_API_BASE_URL || process.env.API_BASE_URL) || "";
|
||||
const storageBaseUrl = typeof common_vendor.index !== "undefined" ? common_vendor.index.getStorageSync("API_BASE_URL") || "" : "";
|
||||
const fallbackBaseUrl = "http://localhost:8080";
|
||||
const API_BASE_URL = (envBaseUrl || storageBaseUrl || fallbackBaseUrl).replace(/\/$/, "");
|
||||
const envShopId = typeof process !== "undefined" && process.env && (process.env.VITE_APP_SHOP_ID || process.env.SHOP_ID) || "";
|
||||
const storageShopId = typeof common_vendor.index !== "undefined" ? common_vendor.index.getStorageSync("SHOP_ID") || "" : "";
|
||||
const SHOP_ID = Number(envShopId || storageShopId || 1);
|
||||
exports.API_BASE_URL = API_BASE_URL;
|
||||
exports.SHOP_ID = SHOP_ID;
|
||||
//# sourceMappingURL=../../.sourcemap/mp-weixin/common/config.js.map
|
||||
29
frontend/unpackage/dist/dev/mp-weixin/common/http.js
vendored
Normal file
29
frontend/unpackage/dist/dev/mp-weixin/common/http.js
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
"use strict";
|
||||
const common_vendor = require("./vendor.js");
|
||||
const common_config = require("./config.js");
|
||||
function buildUrl(path) {
|
||||
if (!path)
|
||||
return common_config.API_BASE_URL;
|
||||
if (path.startsWith("http"))
|
||||
return path;
|
||||
return common_config.API_BASE_URL + (path.startsWith("/") ? path : "/" + path);
|
||||
}
|
||||
function get(path, params = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
common_vendor.index.request({
|
||||
url: buildUrl(path),
|
||||
method: "GET",
|
||||
data: params,
|
||||
header: { "X-Shop-Id": common_config.SHOP_ID },
|
||||
success: (res) => {
|
||||
const { statusCode, data } = res;
|
||||
if (statusCode >= 200 && statusCode < 300)
|
||||
return resolve(data);
|
||||
reject(new Error("HTTP " + statusCode));
|
||||
},
|
||||
fail: (err) => reject(err)
|
||||
});
|
||||
});
|
||||
}
|
||||
exports.get = get;
|
||||
//# sourceMappingURL=../../.sourcemap/mp-weixin/common/http.js.map
|
||||
@@ -6917,9 +6917,9 @@ function isConsoleWritable() {
|
||||
return isWritable;
|
||||
}
|
||||
function initRuntimeSocketService() {
|
||||
const hosts = "198.18.0.1,192.168.31.107,127.0.0.1";
|
||||
const hosts = "198.18.0.1,192.168.31.193,127.0.0.1";
|
||||
const port = "8090";
|
||||
const id = "mp-weixin_BJ7qAd";
|
||||
const id = "mp-weixin_UjOtWQ";
|
||||
const lazy = typeof swan !== "undefined";
|
||||
let restoreError = lazy ? () => {
|
||||
} : initOnError();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use strict";
|
||||
const common_vendor = require("../../common/vendor.js");
|
||||
const common_http = require("../../common/http.js");
|
||||
const common_assets = require("../../common/assets.js");
|
||||
const _sfc_main = {
|
||||
data() {
|
||||
@@ -8,12 +9,9 @@ const _sfc_main = {
|
||||
monthProfit: "0.00",
|
||||
stockQty: "0.00",
|
||||
activeTab: "home",
|
||||
notices: [
|
||||
{ text: "选材精工:不锈钢、合金钢,耐腐蚀更耐用", tag: "品质" },
|
||||
{ text: "表面工艺:电镀锌/镀镍/发黑处理,性能均衡", tag: "工艺" },
|
||||
{ text: "库存齐全:螺丝、螺母、垫圈、膨胀螺栓等现货", tag: "库存" },
|
||||
{ text: "企业采购支持:批量优惠,次日发货", tag: "服务" }
|
||||
],
|
||||
notices: [],
|
||||
loadingNotices: false,
|
||||
noticeError: "",
|
||||
features: [
|
||||
{ key: "customer", title: "客户", img: "/static/icons/customer.png", emoji: "👥" },
|
||||
{ key: "sale", title: "销售", img: "/static/icons/sale.png", emoji: "💰" },
|
||||
@@ -27,7 +25,25 @@ const _sfc_main = {
|
||||
]
|
||||
};
|
||||
},
|
||||
onLoad() {
|
||||
this.fetchNotices();
|
||||
},
|
||||
methods: {
|
||||
async fetchNotices() {
|
||||
this.loadingNotices = true;
|
||||
this.noticeError = "";
|
||||
try {
|
||||
const list = await common_http.get("/api/notices");
|
||||
this.notices = Array.isArray(list) ? list.map((n) => ({
|
||||
text: n.content || n.title || "",
|
||||
tag: n.tag || ""
|
||||
})) : [];
|
||||
} catch (e) {
|
||||
this.noticeError = e && e.message || "公告加载失败";
|
||||
} finally {
|
||||
this.loadingNotices = false;
|
||||
}
|
||||
},
|
||||
onFeatureTap(item) {
|
||||
common_vendor.index.showToast({ title: item.title + "(开发中)", icon: "none" });
|
||||
},
|
||||
@@ -37,7 +53,7 @@ const _sfc_main = {
|
||||
onNoticeTap(n) {
|
||||
common_vendor.index.showModal({
|
||||
title: "公告",
|
||||
content: n.text,
|
||||
content: n && (n.text || n.title || n.content) || "",
|
||||
showCancel: false
|
||||
});
|
||||
},
|
||||
@@ -50,12 +66,16 @@ const _sfc_main = {
|
||||
}
|
||||
};
|
||||
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
|
||||
return {
|
||||
return common_vendor.e({
|
||||
a: common_assets._imports_0,
|
||||
b: common_vendor.t($data.todayAmount),
|
||||
c: common_vendor.t($data.monthProfit),
|
||||
d: common_vendor.t($data.stockQty),
|
||||
e: common_vendor.f($data.notices, (n, idx, i0) => {
|
||||
e: $data.loadingNotices
|
||||
}, $data.loadingNotices ? {} : $data.noticeError ? {
|
||||
g: common_vendor.t($data.noticeError)
|
||||
} : !$data.notices.length ? {} : {
|
||||
i: common_vendor.f($data.notices, (n, idx, i0) => {
|
||||
return common_vendor.e({
|
||||
a: common_vendor.t(n.text),
|
||||
b: n.tag
|
||||
@@ -65,9 +85,12 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
|
||||
d: common_vendor.o(($event) => $options.onNoticeTap(n), idx),
|
||||
e: idx
|
||||
});
|
||||
}),
|
||||
f: common_vendor.o((...args) => $options.onNoticeList && $options.onNoticeList(...args)),
|
||||
g: common_vendor.f($data.features, (item, k0, i0) => {
|
||||
})
|
||||
}, {
|
||||
f: $data.noticeError,
|
||||
h: !$data.notices.length,
|
||||
j: common_vendor.o((...args) => $options.onNoticeList && $options.onNoticeList(...args)),
|
||||
k: common_vendor.f($data.features, (item, k0, i0) => {
|
||||
return common_vendor.e({
|
||||
a: item.img
|
||||
}, item.img ? {
|
||||
@@ -82,14 +105,14 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
|
||||
h: common_vendor.o(($event) => $options.onFeatureTap(item), item.key)
|
||||
});
|
||||
}),
|
||||
h: $data.activeTab === "home" ? 1 : "",
|
||||
i: common_vendor.o(($event) => $data.activeTab = "home"),
|
||||
j: common_vendor.o((...args) => $options.onCreateOrder && $options.onCreateOrder(...args)),
|
||||
k: $data.activeTab === "detail" ? 1 : "",
|
||||
l: common_vendor.o(($event) => $data.activeTab = "detail"),
|
||||
m: $data.activeTab === "me" ? 1 : "",
|
||||
n: common_vendor.o(($event) => $data.activeTab = "me")
|
||||
};
|
||||
l: $data.activeTab === "home" ? 1 : "",
|
||||
m: common_vendor.o(($event) => $data.activeTab = "home"),
|
||||
n: common_vendor.o((...args) => $options.onCreateOrder && $options.onCreateOrder(...args)),
|
||||
o: $data.activeTab === "detail" ? 1 : "",
|
||||
p: common_vendor.o(($event) => $data.activeTab = "detail"),
|
||||
q: $data.activeTab === "me" ? 1 : "",
|
||||
r: common_vendor.o(($event) => $data.activeTab = "me")
|
||||
});
|
||||
}
|
||||
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
|
||||
wx.createPage(MiniProgramPage);
|
||||
|
||||
@@ -1 +1 @@
|
||||
<view class="home"><image class="home-bg" src="{{a}}" mode="aspectFill"></image><view class="hero"><view class="hero-top"><text class="brand">五金配件管家</text><view class="cta"><text class="cta-text">咨询</text></view></view><view class="kpi"><view class="kpi-item"><text class="kpi-label">今日销售额</text><text class="kpi-value">{{b}}</text></view><view class="kpi-item"><text class="kpi-label">本月利润</text><text class="kpi-value">{{c}}</text></view><view class="kpi-item"><text class="kpi-label">库存数量</text><text class="kpi-value">{{d}}</text></view></view></view><view class="notice"><view class="notice-left">公告</view><swiper class="notice-swiper" circular autoplay interval="4000" duration="400" vertical><swiper-item wx:for="{{e}}" wx:for-item="n" wx:key="e"><view class="notice-item" bindtap="{{n.d}}"><text class="notice-text">{{n.a}}</text><text wx:if="{{n.b}}" class="notice-tag">{{n.c}}</text></view></swiper-item></swiper><view class="notice-right" bindtap="{{f}}">更多</view></view><view class="section-title"><text class="section-text">常用功能</text></view><view class="grid-wrap"><view class="grid"><view wx:for="{{g}}" wx:for-item="item" wx:key="g" class="grid-item" bindtap="{{item.h}}"><view class="icon icon-squircle"><image wx:if="{{item.a}}" src="{{item.b}}" class="icon-img" mode="aspectFit" binderror="{{item.c}}"></image><text wx:elif="{{item.d}}" class="icon-emoji">{{item.e}}</text><view wx:else class="icon-placeholder"></view></view><text class="grid-chip">{{item.f}}</text></view></view></view><view class="bottom-bar"><view class="{{['tab', h && 'active']}}" bindtap="{{i}}"><text>首页</text></view><view class="tab primary" bindtap="{{j}}"><text>开单</text></view><view class="{{['tab', k && 'active']}}" bindtap="{{l}}"><text>明细</text></view><view class="{{['tab', m && 'active']}}" bindtap="{{n}}"><text>我的</text></view></view></view>
|
||||
<view class="home"><image class="home-bg" src="{{a}}" mode="aspectFill"></image><view class="hero"><view class="hero-top"><text class="brand">五金配件管家</text><view class="cta"><text class="cta-text">咨询</text></view></view><view class="kpi"><view class="kpi-item"><text class="kpi-label">今日销售额</text><text class="kpi-value">{{b}}</text></view><view class="kpi-item"><text class="kpi-label">本月利润</text><text class="kpi-value">{{c}}</text></view><view class="kpi-item"><text class="kpi-label">库存数量</text><text class="kpi-value">{{d}}</text></view></view></view><view class="notice"><view class="notice-left">公告</view><view wx:if="{{e}}" class="notice-swiper" style="display:flex;align-items:center;color:#6b5a2a">加载中...</view><view wx:elif="{{f}}" class="notice-swiper" style="display:flex;align-items:center;color:#dd524d">{{g}}</view><view wx:elif="{{h}}" class="notice-swiper" style="display:flex;align-items:center;color:#6b5a2a">暂无公告</view><swiper wx:else class="notice-swiper" circular autoplay interval="4000" duration="400" vertical><swiper-item wx:for="{{i}}" wx:for-item="n" wx:key="e"><view class="notice-item" bindtap="{{n.d}}"><text class="notice-text">{{n.a}}</text><text wx:if="{{n.b}}" class="notice-tag">{{n.c}}</text></view></swiper-item></swiper><view class="notice-right" bindtap="{{j}}">更多</view></view><view class="section-title"><text class="section-text">常用功能</text></view><view class="grid-wrap"><view class="grid"><view wx:for="{{k}}" wx:for-item="item" wx:key="g" class="grid-item" bindtap="{{item.h}}"><view class="icon icon-squircle"><image wx:if="{{item.a}}" src="{{item.b}}" class="icon-img" mode="aspectFit" binderror="{{item.c}}"></image><text wx:elif="{{item.d}}" class="icon-emoji">{{item.e}}</text><view wx:else class="icon-placeholder"></view></view><text class="grid-chip">{{item.f}}</text></view></view></view><view class="bottom-bar"><view class="{{['tab', l && 'active']}}" bindtap="{{m}}"><text>首页</text></view><view class="tab primary" bindtap="{{n}}"><text>开单</text></view><view class="{{['tab', o && 'active']}}" bindtap="{{p}}"><text>明细</text></view><view class="{{['tab', q && 'active']}}" bindtap="{{r}}"><text>我的</text></view></view></view>
|
||||
Reference in New Issue
Block a user