From b8aee530e801db49aa837a76842a332b1e3dafa7 Mon Sep 17 00:00:00 2001 From: tikkhun Date: Tue, 2 Dec 2025 17:01:34 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=88=86=E7=B1=BB):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=88=86=E7=B1=BB=E7=AE=A1=E7=90=86=E7=9B=B8=E5=85=B3=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor(商品类型): 将商品类型从 simple 改为 single fix(数据库配置): 更新数据库连接配置 style(分类): 修正分类中文标题 chore: 移除无用的脚本文件 --- scripts/create_db.d.ts | 1 - scripts/create_db.js | 21 ------- scripts/diagnose_db.d.ts | 1 - scripts/diagnose_db.js | 31 ---------- src/controller/product.controller.ts | 84 +++++++++++++++++++++++++ src/db/datasource.ts | 7 +-- src/db/seeds/category.seeder.ts | 2 +- src/dto/product.dto.ts | 12 ++-- src/service/product.service.ts | 92 +++++++++++++++++++++++++++- 9 files changed, 185 insertions(+), 66 deletions(-) delete mode 100644 scripts/create_db.d.ts delete mode 100644 scripts/create_db.js delete mode 100644 scripts/diagnose_db.d.ts delete mode 100644 scripts/diagnose_db.js diff --git a/scripts/create_db.d.ts b/scripts/create_db.d.ts deleted file mode 100644 index cb0ff5c..0000000 --- a/scripts/create_db.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/scripts/create_db.js b/scripts/create_db.js deleted file mode 100644 index 6b99291..0000000 --- a/scripts/create_db.js +++ /dev/null @@ -1,21 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const mysql = require("mysql2/promise"); -async function run() { - try { - const connection = await mysql.createConnection({ - socketPath: '/Users/zksu/Library/Application Support/Local/run/oLbUT7qMU/mysql/mysqld.sock', - user: 'root', - password: 'root', - }); - console.log('Connected to database server.'); - await connection.query('CREATE DATABASE IF NOT EXISTS inventory'); - console.log('Database "inventory" created or already exists.'); - await connection.end(); - } - catch (e) { - console.error('Error:', e); - } -} -run(); -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlX2RiLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiY3JlYXRlX2RiLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQ0Esd0NBQXdDO0FBRXhDLEtBQUssVUFBVSxHQUFHO0lBQ2hCLElBQUksQ0FBQztRQUNILE1BQU0sVUFBVSxHQUFHLE1BQU0sS0FBSyxDQUFDLGdCQUFnQixDQUFDO1lBQzlDLFVBQVUsRUFBRSwrRUFBK0U7WUFDM0YsSUFBSSxFQUFFLE1BQU07WUFDWixRQUFRLEVBQUUsTUFBTTtTQUNqQixDQUFDLENBQUM7UUFFSCxPQUFPLENBQUMsR0FBRyxDQUFDLCtCQUErQixDQUFDLENBQUM7UUFFN0MsTUFBTSxVQUFVLENBQUMsS0FBSyxDQUFDLHlDQUF5QyxDQUFDLENBQUM7UUFDbEUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxpREFBaUQsQ0FBQyxDQUFDO1FBRS9ELE1BQU0sVUFBVSxDQUFDLEdBQUcsRUFBRSxDQUFDO0lBQ3pCLENBQUM7SUFBQyxPQUFPLENBQUMsRUFBRSxDQUFDO1FBQ1gsT0FBTyxDQUFDLEtBQUssQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDLENBQUM7SUFDN0IsQ0FBQztBQUNILENBQUM7QUFFRCxHQUFHLEVBQUUsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIlxuaW1wb3J0ICogYXMgbXlzcWwgZnJvbSAnbXlzcWwyL3Byb21pc2UnO1xuXG5hc3luYyBmdW5jdGlvbiBydW4oKSB7XG4gIHRyeSB7XG4gICAgY29uc3QgY29ubmVjdGlvbiA9IGF3YWl0IG15c3FsLmNyZWF0ZUNvbm5lY3Rpb24oe1xuICAgICAgc29ja2V0UGF0aDogJy9Vc2Vycy96a3N1L0xpYnJhcnkvQXBwbGljYXRpb24gU3VwcG9ydC9Mb2NhbC9ydW4vb0xiVVQ3cU1VL215c3FsL215c3FsZC5zb2NrJyxcbiAgICAgIHVzZXI6ICdyb290JyxcbiAgICAgIHBhc3N3b3JkOiAncm9vdCcsXG4gICAgfSk7XG5cbiAgICBjb25zb2xlLmxvZygnQ29ubmVjdGVkIHRvIGRhdGFiYXNlIHNlcnZlci4nKTtcblxuICAgIGF3YWl0IGNvbm5lY3Rpb24ucXVlcnkoJ0NSRUFURSBEQVRBQkFTRSBJRiBOT1QgRVhJU1RTIGludmVudG9yeScpO1xuICAgIGNvbnNvbGUubG9nKCdEYXRhYmFzZSBcImludmVudG9yeVwiIGNyZWF0ZWQgb3IgYWxyZWFkeSBleGlzdHMuJyk7XG4gICAgXG4gICAgYXdhaXQgY29ubmVjdGlvbi5lbmQoKTtcbiAgfSBjYXRjaCAoZSkge1xuICAgIGNvbnNvbGUuZXJyb3IoJ0Vycm9yOicsIGUpO1xuICB9XG59XG5cbnJ1bigpO1xuIl19 \ No newline at end of file diff --git a/scripts/diagnose_db.d.ts b/scripts/diagnose_db.d.ts deleted file mode 100644 index cb0ff5c..0000000 --- a/scripts/diagnose_db.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/scripts/diagnose_db.js b/scripts/diagnose_db.js deleted file mode 100644 index e6ec559..0000000 --- a/scripts/diagnose_db.js +++ /dev/null @@ -1,31 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const mysql = require("mysql2/promise"); -async function run() { - try { - const connection = await mysql.createConnection({ - host: '127.0.0.1', - port: 10014, - user: 'root', - password: 'root', - database: 'inventory' - }); - console.log('Connected to database.'); - const tables = ['product', 'category_attribute', 'category']; - for (const table of tables) { - console.log(`\nConstraints for table: ${table}`); - const [rows] = await connection.execute(` - SELECT CONSTRAINT_NAME, CONSTRAINT_TYPE - FROM information_schema.TABLE_CONSTRAINTS - WHERE TABLE_SCHEMA = 'inventory' AND TABLE_NAME = '${table}' - `); - console.table(rows); - } - await connection.end(); - } - catch (e) { - console.error('Error:', e); - } -} -run(); -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGlhZ25vc2VfZGIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJkaWFnbm9zZV9kYi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUNBLHdDQUF3QztBQUV4QyxLQUFLLFVBQVUsR0FBRztJQUNoQixJQUFJLENBQUM7UUFDSCxNQUFNLFVBQVUsR0FBRyxNQUFNLEtBQUssQ0FBQyxnQkFBZ0IsQ0FBQztZQUM5QyxJQUFJLEVBQUUsV0FBVztZQUNqQixJQUFJLEVBQUUsS0FBSztZQUNYLElBQUksRUFBRSxNQUFNO1lBQ1osUUFBUSxFQUFFLE1BQU07WUFDaEIsUUFBUSxFQUFFLFdBQVc7U0FDdEIsQ0FBQyxDQUFDO1FBRUgsT0FBTyxDQUFDLEdBQUcsQ0FBQyx3QkFBd0IsQ0FBQyxDQUFDO1FBRXRDLE1BQU0sTUFBTSxHQUFHLENBQUMsU0FBUyxFQUFFLG9CQUFvQixFQUFFLFVBQVUsQ0FBQyxDQUFDO1FBRTdELEtBQUssTUFBTSxLQUFLLElBQUksTUFBTSxFQUFFLENBQUM7WUFDM0IsT0FBTyxDQUFDLEdBQUcsQ0FBQyw0QkFBNEIsS0FBSyxFQUFFLENBQUMsQ0FBQztZQUNqRCxNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsTUFBTSxVQUFVLENBQUMsT0FBTyxDQUFDOzs7NkRBR2UsS0FBSztPQUMzRCxDQUFDLENBQUM7WUFDSCxPQUFPLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ3RCLENBQUM7UUFFRCxNQUFNLFVBQVUsQ0FBQyxHQUFHLEVBQUUsQ0FBQztJQUN6QixDQUFDO0lBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQztRQUNYLE9BQU8sQ0FBQyxLQUFLLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQzdCLENBQUM7QUFDSCxDQUFDO0FBRUQsR0FBRyxFQUFFLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJcbmltcG9ydCAqIGFzIG15c3FsIGZyb20gJ215c3FsMi9wcm9taXNlJztcblxuYXN5bmMgZnVuY3Rpb24gcnVuKCkge1xuICB0cnkge1xuICAgIGNvbnN0IGNvbm5lY3Rpb24gPSBhd2FpdCBteXNxbC5jcmVhdGVDb25uZWN0aW9uKHtcbiAgICAgIGhvc3Q6ICcxMjcuMC4wLjEnLFxuICAgICAgcG9ydDogMTAwMTQsXG4gICAgICB1c2VyOiAncm9vdCcsXG4gICAgICBwYXNzd29yZDogJ3Jvb3QnLFxuICAgICAgZGF0YWJhc2U6ICdpbnZlbnRvcnknXG4gICAgfSk7XG5cbiAgICBjb25zb2xlLmxvZygnQ29ubmVjdGVkIHRvIGRhdGFiYXNlLicpO1xuXG4gICAgY29uc3QgdGFibGVzID0gWydwcm9kdWN0JywgJ2NhdGVnb3J5X2F0dHJpYnV0ZScsICdjYXRlZ29yeSddO1xuXG4gICAgZm9yIChjb25zdCB0YWJsZSBvZiB0YWJsZXMpIHtcbiAgICAgIGNvbnNvbGUubG9nKGBcXG5Db25zdHJhaW50cyBmb3IgdGFibGU6ICR7dGFibGV9YCk7XG4gICAgICBjb25zdCBbcm93c10gPSBhd2FpdCBjb25uZWN0aW9uLmV4ZWN1dGUoYFxuICAgICAgICBTRUxFQ1QgQ09OU1RSQUlOVF9OQU1FLCBDT05TVFJBSU5UX1RZUEUgXG4gICAgICAgIEZST00gaW5mb3JtYXRpb25fc2NoZW1hLlRBQkxFX0NPTlNUUkFJTlRTIFxuICAgICAgICBXSEVSRSBUQUJMRV9TQ0hFTUEgPSAnaW52ZW50b3J5JyBBTkQgVEFCTEVfTkFNRSA9ICcke3RhYmxlfSdcbiAgICAgIGApO1xuICAgICAgY29uc29sZS50YWJsZShyb3dzKTtcbiAgICB9XG4gICAgXG4gICAgYXdhaXQgY29ubmVjdGlvbi5lbmQoKTtcbiAgfSBjYXRjaCAoZSkge1xuICAgIGNvbnNvbGUuZXJyb3IoJ0Vycm9yOicsIGUpO1xuICB9XG59XG5cbnJ1bigpO1xuIl19 \ No newline at end of file diff --git a/src/controller/product.controller.ts b/src/controller/product.controller.ts index ad333da..b3e3280 100644 --- a/src/controller/product.controller.ts +++ b/src/controller/product.controller.ts @@ -536,4 +536,88 @@ export class ProductController { return errorResponse(error?.message || error); } } + + // 获取所有分类 + @ApiOkResponse({ description: '获取所有分类' }) + @Get('/categories/all') + async getCategoriesAll() { + try { + const data = await this.productService.getCategoriesAll(); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || error); + } + } + + // 获取分类下的属性配置 + @ApiOkResponse({ description: '获取分类下的属性配置' }) + @Get('/category/:id/attributes') + async getCategoryAttributes(@Param('id') id: number) { + try { + const data = await this.productService.getCategoryAttributes(id); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || error); + } + } + + // 创建分类 + @ApiOkResponse({ description: '创建分类' }) + @Post('/category') + async createCategory(@Body() body: any) { + try { + const data = await this.productService.createCategory(body); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || error); + } + } + + // 更新分类 + @ApiOkResponse({ description: '更新分类' }) + @Put('/category/:id') + async updateCategory(@Param('id') id: number, @Body() body: any) { + try { + const data = await this.productService.updateCategory(id, body); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || error); + } + } + + // 删除分类 + @ApiOkResponse({ description: '删除分类' }) + @Del('/category/:id') + async deleteCategory(@Param('id') id: number) { + try { + await this.productService.deleteCategory(id); + return successResponse(true); + } catch (error) { + return errorResponse(error?.message || error); + } + } + + // 创建分类属性 + @ApiOkResponse({ description: '创建分类属性' }) + @Post('/category/attribute') + async createCategoryAttribute(@Body() body: { categoryId: number; dictId: number }) { + try { + const data = await this.productService.createCategoryAttribute(body); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || error); + } + } + + // 删除分类属性 + @ApiOkResponse({ description: '删除分类属性' }) + @Del('/category/attribute/:id') + async deleteCategoryAttribute(@Param('id') id: number) { + try { + await this.productService.deleteCategoryAttribute(id); + return successResponse(true); + } catch (error) { + return errorResponse(error?.message || error); + } + } } diff --git a/src/db/datasource.ts b/src/db/datasource.ts index ef186cc..3596f3f 100644 --- a/src/db/datasource.ts +++ b/src/db/datasource.ts @@ -4,11 +4,10 @@ import { SeederOptions } from 'typeorm-extension'; const options: DataSourceOptions & SeederOptions = { type: 'mysql', - // host: 'localhost', - // port: 10014, - socketPath: '/Users/zksu/Library/Application Support/Local/run/oLbUT7qMU/mysql/mysqld.sock', + host: 'localhost', + port: 23306, username: 'root', - password: 'root', + password: '12345678', database: 'inventory', synchronize: true, logging: true, diff --git a/src/db/seeds/category.seeder.ts b/src/db/seeds/category.seeder.ts index d7ab329..4763d60 100644 --- a/src/db/seeds/category.seeder.ts +++ b/src/db/seeds/category.seeder.ts @@ -24,7 +24,7 @@ export default class CategorySeeder implements Seeder { { name: 'pouches-can', title: 'Pouches Can', - titleCN: ' nicotine 袋', + titleCN: '口含烟盒', sort: 3 }, ]; diff --git a/src/dto/product.dto.ts b/src/dto/product.dto.ts index ad314d2..6a9b108 100644 --- a/src/dto/product.dto.ts +++ b/src/dto/product.dto.ts @@ -65,9 +65,9 @@ export class CreateProductDTO { @Rule(RuleType.number()) promotionPrice?: number; - // 中文注释:商品类型(默认 simple;bundle 需手动设置组成) - @ApiProperty({ description: '商品类型', enum: ['simple', 'bundle'], default: 'simple', required: false }) - @Rule(RuleType.string().valid('simple', 'bundle').default('simple')) + // 中文注释:商品类型(默认 single;bundle 需手动设置组成) + @ApiProperty({ description: '商品类型', enum: ['single', 'bundle'], default: 'single', required: false }) + @Rule(RuleType.string().valid('single', 'bundle').default('single')) type?: string; // 中文注释:仅当 type 为 'bundle' 时,才需要提供 components @@ -123,9 +123,9 @@ export class UpdateProductDTO { @Rule(RuleType.array()) attributes?: AttributeInputDTO[]; - // 中文注释:商品类型更新(simple 或 bundle) - @ApiProperty({ description: '商品类型', enum: ['simple', 'bundle'], required: false }) - @Rule(RuleType.string().valid('simple', 'bundle')) + // 中文注释:商品类型(single 或 bundle) + @ApiProperty({ description: '商品类型', enum: ['single', 'bundle'], required: false }) + @Rule(RuleType.string().valid('single', 'bundle')) type?: string; } diff --git a/src/service/product.service.ts b/src/service/product.service.ts index e72bd9e..c156cf3 100644 --- a/src/service/product.service.ts +++ b/src/service/product.service.ts @@ -26,6 +26,7 @@ import { Stock } from '../entity/stock.entity'; import { StockPoint } from '../entity/stock_point.entity'; import { ProductStockComponent } from '../entity/product_stock_component.entity'; import { Category } from '../entity/category.entity'; +import { CategoryAttribute } from '../entity/category_attribute.entity'; @Provide() export class ProductService { @@ -70,7 +71,96 @@ export class ProductService { async getWpProducts() { return this.wpProductModel.find(); } + // 获取所有分类 + async getCategoriesAll(): Promise { + return this.categoryModel.find({ + order: { + sort: 'ASC', + }, + }); + } + // 获取分类下的属性配置 + async getCategoryAttributes(categoryId: number): Promise { + const category = await this.categoryModel.findOne({ + where: { id: categoryId }, + relations: ['attributes', 'attributes.attributeDict', 'attributes.attributeDict.items'], + }); + + if (!category) { + return []; + } + + // 格式化返回,方便前端使用 + return category.attributes.map(attr => ({ + id: attr.id, + dictId: attr.attributeDict.id, + name: attr.attributeDict.name, + title: attr.attributeDict.title, + items: attr.attributeDict.items, // 如果需要返回具体的选项 + })); + } + + // 创建分类 + async createCategory(payload: Partial): Promise { + const exists = await this.categoryModel.findOne({ where: { name: payload.name } }); + if (exists) { + throw new Error('分类已存在'); + } + return this.categoryModel.save(payload); + } + + // 更新分类 + async updateCategory(id: number, payload: Partial): Promise { + const category = await this.categoryModel.findOne({ where: { id } }); + if (!category) { + throw new Error('分类不存在'); + } + await this.categoryModel.update(id, payload); + return this.categoryModel.findOne({ where: { id } }); + } + + // 删除分类 + async deleteCategory(id: number): Promise { + const result = await this.categoryModel.delete(id); + return result.affected > 0; + } + + // 创建分类属性关联 + async createCategoryAttribute(payload: { categoryId: number; dictId: number }): Promise { + const category = await this.categoryModel.findOne({ where: { id: payload.categoryId } }); + if (!category) { + throw new Error('分类不存在'); + } + + const dict = await this.dictModel.findOne({ where: { id: payload.dictId } }); + if (!dict) { + throw new Error('字典不存在'); + } + + const existing = await this.categoryModel.manager.findOne(CategoryAttribute, { + where: { + category: { id: payload.categoryId }, + attributeDict: { id: payload.dictId }, + }, + }); + + if (existing) { + throw new Error('该属性已关联到此分类'); + } + + const attr = this.categoryModel.manager.create(CategoryAttribute, { + category, + attributeDict: dict + }); + return this.categoryModel.manager.save(attr); + } + + // 删除分类属性关联 + async deleteCategoryAttribute(id: number): Promise { + const result = await this.categoryModel.manager.delete(CategoryAttribute, id); + return result.affected > 0; + } // async findProductsByName(name: string): Promise { @@ -309,7 +399,7 @@ export class ProductService { if (categoryItem) { product.category = categoryItem; } - // 条件判断(中文注释:设置商品类型,默认 simple) + // 条件判断(中文注释:设置商品类型,默认 single) product.type = (createProductDTO.type as any) || 'single'; // 生成或设置 SKU(中文注释:基于属性字典项的 name 生成)