feat(分类): 添加分类管理相关功能

refactor(商品类型): 将商品类型从 simple 改为 single
fix(数据库配置): 更新数据库连接配置
style(分类): 修正分类中文标题
chore: 移除无用的脚本文件
This commit is contained in:
tikkhun 2025-12-02 17:01:34 +08:00
parent 0180360519
commit b8aee530e8
9 changed files with 185 additions and 66 deletions

View File

@ -1 +0,0 @@
export {};

View File

@ -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

View File

@ -1 +0,0 @@
export {};

View File

@ -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

View File

@ -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);
}
}
}

View File

@ -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,

View File

@ -24,7 +24,7 @@ export default class CategorySeeder implements Seeder {
{
name: 'pouches-can',
title: 'Pouches Can',
titleCN: ' nicotine 袋',
titleCN: '口含烟盒',
sort: 3
},
];

View File

@ -65,9 +65,9 @@ export class CreateProductDTO {
@Rule(RuleType.number())
promotionPrice?: number;
// 中文注释:商品类型(默认 simplebundle 需手动设置组成)
@ApiProperty({ description: '商品类型', enum: ['simple', 'bundle'], default: 'simple', required: false })
@Rule(RuleType.string().valid('simple', 'bundle').default('simple'))
// 中文注释:商品类型(默认 singlebundle 需手动设置组成)
@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;
}

View File

@ -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<Category[]> {
return this.categoryModel.find({
order: {
sort: 'ASC',
},
});
}
// 获取分类下的属性配置
async getCategoryAttributes(categoryId: number): Promise<any[]> {
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<Category>): Promise<Category> {
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<Category>): Promise<Category> {
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<boolean> {
const result = await this.categoryModel.delete(id);
return result.affected > 0;
}
// 创建分类属性关联
async createCategoryAttribute(payload: { categoryId: number; dictId: number }): Promise<any> {
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<boolean> {
const result = await this.categoryModel.manager.delete(CategoryAttribute, id);
return result.affected > 0;
}
// async findProductsByName(name: string): Promise<Product[]> {
@ -309,7 +399,7 @@ export class ProductService {
if (categoryItem) {
product.category = categoryItem;
}
// 条件判断(中文注释:设置商品类型,默认 simple
// 条件判断(中文注释:设置商品类型,默认 single
product.type = (createProductDTO.type as any) || 'single';
// 生成或设置 SKU中文注释基于属性字典项的 name 生成)