65b0bb04f8
把 CarLog v2.8 全套源码 + 配置导入到 i 仓库作为 baseline: - server/src/ (13 个路由 + middleware + services + config) - server/migrations/ (0001~0018 共 18 个迁移 + mysql) - server/test/ (12 文件 101 测试) - client/src/ (20 个 view + components + stores + api + composables) - client/public/ + client/scripts/ - 全部配置文件 (.editorconfig, .eslintrc.json, .prettierrc.json, vitest.config.js, lighthouserc.json, .pa11yci.json, package.json, carlog-init.sql) - .husky/pre-commit (git hooks) - docs/install/ (宝塔部署文档) 不含: - node_modules/ (本地 npm install) - .env (敏感, 走 .env.example) - *.zip / *.log / *.sqlite / .DS_Store 新增文档 docs/DEV-PLAN.md: - Phase 1: 平台基座 (019 migration + 3 个 platform 路由 + 3 个 view) - Phase 2: CarLog 子系统化 (后端 routes/ → subsystems/carlog/ + 前端 views/ → views/subsystems/carlog/ + 元数据驱动菜单) - Phase 3: 验证 (测试 + E2E + DB 完整性) - 交付清单 + commit 模板 + 给 Mavis review 的材料 后续 Trae 实施, 提交后我 code review + 跑测试。
149 lines
7.4 KiB
SQL
149 lines
7.4 KiB
SQL
-- =============================================================================
|
|
-- 洗车记录系统 - Migration 0001: 基础表 (MySQL 8.x)
|
|
-- =============================================================================
|
|
|
|
-- -----------------------------------------------------------------------------
|
|
-- 1. chemicals - 药剂字典(Grocy 缓存层)
|
|
-- -----------------------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS chemicals (
|
|
grocy_product_id VARCHAR(255) NOT NULL,
|
|
name VARCHAR(255) NOT NULL,
|
|
category VARCHAR(255) DEFAULT NULL,
|
|
unit VARCHAR(50) NOT NULL DEFAULT 'ml',
|
|
standard_dose DOUBLE DEFAULT NULL,
|
|
notes TEXT DEFAULT NULL,
|
|
is_active TINYINT(1) NOT NULL DEFAULT 1,
|
|
fetched_at DATETIME DEFAULT NULL,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (grocy_product_id)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
CREATE INDEX idx_chemicals_category ON chemicals(category);
|
|
CREATE INDEX idx_chemicals_active ON chemicals(is_active);
|
|
CREATE INDEX idx_chemicals_fetched ON chemicals(fetched_at);
|
|
|
|
-- -----------------------------------------------------------------------------
|
|
-- 2. weather_snapshots - 天气快照
|
|
-- -----------------------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS weather_snapshots (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
snapshot_date VARCHAR(10) NOT NULL,
|
|
city VARCHAR(100) NOT NULL,
|
|
provider VARCHAR(50) NOT NULL,
|
|
temp_c DOUBLE DEFAULT NULL,
|
|
humidity INT DEFAULT NULL,
|
|
weather_desc VARCHAR(255) DEFAULT NULL,
|
|
weather_code VARCHAR(20) DEFAULT NULL,
|
|
wind_kph DOUBLE DEFAULT NULL,
|
|
precip_mm DOUBLE DEFAULT NULL,
|
|
raw_json TEXT DEFAULT NULL,
|
|
fetched_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
CREATE UNIQUE INDEX uk_weather_city_date ON weather_snapshots(city, snapshot_date);
|
|
CREATE INDEX idx_weather_date ON weather_snapshots(snapshot_date);
|
|
|
|
-- -----------------------------------------------------------------------------
|
|
-- 3. wash_records - 洗车记录
|
|
-- -----------------------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS wash_records (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
wash_date VARCHAR(10) NOT NULL,
|
|
wash_type VARCHAR(20) NOT NULL,
|
|
weather_snapshot_id INT DEFAULT NULL,
|
|
location VARCHAR(255) DEFAULT NULL,
|
|
cost DOUBLE NOT NULL DEFAULT 0,
|
|
duration_min INT DEFAULT NULL,
|
|
notes TEXT DEFAULT NULL,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
CONSTRAINT chk_wash_type CHECK (wash_type IN ('quick','full','detail','other')),
|
|
CONSTRAINT fk_wash_weather FOREIGN KEY (weather_snapshot_id) REFERENCES weather_snapshots(id) ON DELETE SET NULL
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
CREATE INDEX idx_wash_records_date ON wash_records(wash_date);
|
|
CREATE INDEX idx_wash_records_type ON wash_records(wash_type);
|
|
CREATE INDEX idx_wash_records_weather ON wash_records(weather_snapshot_id);
|
|
|
|
-- -----------------------------------------------------------------------------
|
|
-- 4. chemical_usage - 药剂消耗
|
|
-- -----------------------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS chemical_usage (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
usage_date VARCHAR(10) NOT NULL,
|
|
chemical_id VARCHAR(255) NOT NULL,
|
|
amount DOUBLE NOT NULL,
|
|
wash_record_id INT DEFAULT NULL,
|
|
notes TEXT DEFAULT NULL,
|
|
sync_status VARCHAR(20) NOT NULL DEFAULT 'pending',
|
|
sync_at DATETIME DEFAULT NULL,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
CONSTRAINT chk_sync_status CHECK (sync_status IN ('pending','synced','failed')),
|
|
CONSTRAINT fk_usage_chemical FOREIGN KEY (chemical_id) REFERENCES chemicals(grocy_product_id) ON DELETE RESTRICT,
|
|
CONSTRAINT fk_usage_wash FOREIGN KEY (wash_record_id) REFERENCES wash_records(id) ON DELETE SET NULL
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
CREATE INDEX idx_usage_date ON chemical_usage(usage_date);
|
|
CREATE INDEX idx_usage_chemical ON chemical_usage(chemical_id);
|
|
CREATE INDEX idx_usage_wash ON chemical_usage(wash_record_id);
|
|
CREATE INDEX idx_usage_sync ON chemical_usage(sync_status);
|
|
|
|
-- -----------------------------------------------------------------------------
|
|
-- 5. settings - 运行时配置 KV
|
|
-- -----------------------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS settings (
|
|
`key` VARCHAR(100) NOT NULL PRIMARY KEY,
|
|
value TEXT DEFAULT NULL,
|
|
is_secret TINYINT(1) NOT NULL DEFAULT 0,
|
|
description TEXT DEFAULT NULL,
|
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
INSERT IGNORE INTO settings (`key`, value, is_secret, description) VALUES
|
|
('app_city', NULL, 0, '所在城市(用于天气查询)'),
|
|
('app_timezone', 'Asia/Shanghai', 0, '时区'),
|
|
('grocy_url', NULL, 0, 'Grocy 实例 URL'),
|
|
('grocy_api_token', NULL, 1, 'Grocy REST API token'),
|
|
('backup_keep_count', '10', 0, '本地备份保留份数'),
|
|
('backup_dir', 'storage/backups', 0, '备份输出目录');
|
|
|
|
-- -----------------------------------------------------------------------------
|
|
-- 6. views
|
|
-- -----------------------------------------------------------------------------
|
|
DROP VIEW IF EXISTS v_wash_monthly_count;
|
|
CREATE VIEW v_wash_monthly_count AS
|
|
SELECT
|
|
SUBSTRING(wash_date, 1, 7) AS month,
|
|
COUNT(*) AS wash_count,
|
|
SUM(COALESCE(cost, 0)) AS total_cost
|
|
FROM wash_records
|
|
GROUP BY SUBSTRING(wash_date, 1, 7)
|
|
ORDER BY month DESC;
|
|
|
|
DROP VIEW IF EXISTS v_chemical_monthly_usage;
|
|
CREATE VIEW v_chemical_monthly_usage AS
|
|
SELECT
|
|
SUBSTRING(cu.usage_date, 1, 7) AS month,
|
|
c.grocy_product_id AS grocy_product_id,
|
|
c.name AS chemical_name,
|
|
c.unit AS unit,
|
|
SUM(cu.amount) AS total_amount,
|
|
COUNT(*) AS usage_count
|
|
FROM chemical_usage cu
|
|
JOIN chemicals c ON c.grocy_product_id = cu.chemical_id
|
|
GROUP BY SUBSTRING(cu.usage_date, 1, 7), c.grocy_product_id
|
|
ORDER BY month DESC, total_amount DESC;
|
|
|
|
DROP VIEW IF EXISTS v_last_wash;
|
|
CREATE VIEW v_last_wash AS
|
|
SELECT
|
|
id AS wash_id,
|
|
wash_date,
|
|
wash_type,
|
|
DATEDIFF(NOW(), STR_TO_DATE(wash_date, '%Y-%m-%d')) AS days_since
|
|
FROM wash_records
|
|
ORDER BY wash_date DESC, id DESC
|
|
LIMIT 1;
|