Compare commits

...

2 Commits

@ -0,0 +1,35 @@
-- ============================================================
-- 05: 品牌字段映射增加启用/禁用开关
-- 执行目标库cnc_business
-- 幂等:是(通过 IF NOT EXISTS 检查列是否已存在)
-- ============================================================
-- 1. 增加 is_enabled 列(默认启用)
SET @dbname = 'cnc_business';
SET @tablename = 'cnc_brand_field_mapping';
SET @columnname = 'is_enabled';
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = @dbname
AND TABLE_NAME = @tablename
AND COLUMN_NAME = @columnname
) > 0,
'SELECT 1',
'ALTER TABLE cnc_brand_field_mapping ADD COLUMN is_enabled tinyint(1) NOT NULL DEFAULT 1 COMMENT ''是否启用1=启用 0=禁用'' AFTER is_required'
));
PREPARE alterIfNotExists FROM @preparedStatement;
EXECUTE alterIfNotExists;
DEALLOCATE PREPARE alterIfNotExists;
-- 2. 根据采集示例数据,禁用未出现的字段映射
-- 采集示例中只出现了_io_status, Tag5, Tag8, Tag9, Tag11, Tag22, Tag23
-- 未出现的先设为禁用,后续可通过后台开关启用
UPDATE cnc_brand_field_mapping SET is_enabled = 0
WHERE field_name IN ('Tag14','Tag17','Tag18','Tag19','Tag20','Tag21','Tag24','Tag25','Tag26')
AND is_enabled = 1;
-- 3. 验证结果
SELECT id, standard_field, field_name, is_required, is_enabled
FROM cnc_brand_field_mapping
ORDER BY id;

@ -13,12 +13,13 @@
</el-card>
<el-card shadow="hover">
<template #header><div style="display:flex;justify-content:space-between"><span>字段映射列表</span><el-button size="small" @click="addMapping">+ </el-button></div></template>
<el-table :data="form.mappings" border stripe size="small" style="width:100%">
<el-table :data="form.mappings" border stripe size="small" style="width:100%" row-class-name="mapping-row" :row-style="({row}) => row.isEnabled === 0 ? { opacity: 0.5 } : {}">
<el-table-column label="标准字段" min-width="180"><template #default="{row}"><el-select v-model="row.standardField" style="width:100%"><el-option v-for="f in standardFields" :key="f" :label="f" :value="f" /></el-select></template></el-table-column>
<el-table-column label="字段名" min-width="140"><template #default="{row}"><el-input v-model="row.fieldName" /></template></el-table-column>
<el-table-column label="匹配方式" min-width="110"><template #default="{row}"><el-select v-model="row.matchBy" style="width:100%"><el-option label="id" value="id" /><el-option label="desc" value="desc" /></el-select></template></el-table-column>
<el-table-column label="数据类型" min-width="110"><template #default="{row}"><el-select v-model="row.dataType" style="width:100%"><el-option label="string" value="string" /><el-option label="number" value="number" /></el-select></template></el-table-column>
<el-table-column label="必填" width="60" align="center"><template #default="{row}"><el-checkbox v-model="row.isRequired" :true-value="1" :false-value="0" /></template></el-table-column>
<el-table-column label="启用" width="70" align="center"><template #default="{row}"><el-switch v-model="row.isEnabled" :active-value="1" :inactive-value="0" inline-prompt active-text="" inactive-text="" /></template></el-table-column>
<el-table-column label="操作" width="80" align="center"><template #default="{ $index }"><el-button link type="danger" @click="form.mappings.splice($index, 1)">删除</el-button></template></el-table-column>
</el-table>
</el-card>
@ -36,12 +37,12 @@ import request from '@/utils/request'
const route = useRoute()
const router = useRouter()
import type { Brand, ApiResponse } from '@/types'
type BrandMappingForm = { standardField: string; fieldName: string; matchBy: string; dataType: string; isRequired: number }
type BrandMappingForm = { standardField: string; fieldName: string; matchBy: string; dataType: string; isRequired: number; isEnabled: number }
const isEdit = !!route.params.id
const submitting = ref(false)
const standardFields = ['program_name','part_count','device_status','run_status','operate_mode','spindle_speed_set','feed_speed_set','spindle_speed_actual','feed_speed_actual','spindle_load','spindle_override','power_on_time','run_time','cutting_time','cycle_time','machining_status']
const form = reactive({ brandName: '', deviceField: 'device', tagsPath: 'tags', mappings: [] as BrandMappingForm[] })
function addMapping() { form.mappings.push({ standardField: '', fieldName: '', matchBy: 'id', dataType: 'string', isRequired: 0 }) }
function addMapping() { form.mappings.push({ standardField: '', fieldName: '', matchBy: 'id', dataType: 'string', isRequired: 0, isEnabled: 1 }) }
async function loadData() {
if (!isEdit) return
const r = await request.get<Brand>(`/admin/brand/${route.params.id}`)

@ -10,5 +10,7 @@ namespace CncModels.Dto.Brand
public string MatchBy { get; set; }
public string DataType { get; set; }
public int IsRequired { get; set; }
public int IsEnabled { get; set; }
}
}

@ -28,6 +28,9 @@ namespace CncModels.Entity
/// <summary>是否必填</summary>
public int IsRequired { get; set; }
/// <summary>是否启用</summary>
public int IsEnabled { get; set; }
/// <summary>创建时间</summary>
public DateTime CreatedAt { get; set; }
}

@ -19,8 +19,8 @@ namespace CncRepository.Impl
{
using (var conn = CreateConnection())
{
var sql = @"SELECT id as Id, brand_id as BrandId, standard_field as StandardField, field_name as FieldName, match_by as MatchBy, data_type as DataType, is_required as IsRequired, created_at as CreatedAt
FROM cnc_brand_field_mapping WHERE brand_id = @BrandId ORDER BY id";
var sql = @"SELECT id as Id, brand_id as BrandId, standard_field as StandardField, field_name as FieldName, match_by as MatchBy, data_type as DataType, is_required as IsRequired, is_enabled as IsEnabled, created_at as CreatedAt
FROM cnc_brand_field_mapping WHERE brand_id = @BrandId ORDER BY id";
return conn.Query<BrandFieldMapping>(sql, new { BrandId = brandId }).ToList();
}
}
@ -29,8 +29,8 @@ namespace CncRepository.Impl
{
using (var conn = CreateConnection())
{
var sql = @"SELECT id as Id, brand_id as BrandId, standard_field as StandardField, field_name as FieldName, match_by as MatchBy, data_type as DataType, is_required as IsRequired, created_at as CreatedAt
FROM cnc_brand_field_mapping WHERE id = @Id";
var sql = @"SELECT id as Id, brand_id as BrandId, standard_field as StandardField, field_name as FieldName, match_by as MatchBy, data_type as DataType, is_required as IsRequired, is_enabled as IsEnabled, created_at as CreatedAt
FROM cnc_brand_field_mapping WHERE id = @Id";
return conn.QuerySingleOrDefault<BrandFieldMapping>(sql, new { Id = id });
}
}
@ -39,9 +39,9 @@ namespace CncRepository.Impl
{
using (var conn = CreateConnection())
{
var sql = @"INSERT INTO cnc_brand_field_mapping (brand_id, standard_field, field_name, match_by, data_type, is_required, created_at)
VALUES (@BrandId, @StandardField, @FieldName, @MatchBy, @DataType, @IsRequired, @CreatedAt);
SELECT LAST_INSERT_ID();";
var sql = @"INSERT INTO cnc_brand_field_mapping (brand_id, standard_field, field_name, match_by, data_type, is_required, is_enabled, created_at)
VALUES (@BrandId, @StandardField, @FieldName, @MatchBy, @DataType, @IsRequired, @IsEnabled, @CreatedAt);
SELECT LAST_INSERT_ID();";
return conn.QuerySingle<int>(sql, entity);
}
}
@ -50,7 +50,7 @@ namespace CncRepository.Impl
{
using (var conn = CreateConnection())
{
var sql = @"UPDATE cnc_brand_field_mapping SET brand_id = @BrandId, standard_field = @StandardField, field_name = @FieldName, match_by = @MatchBy, data_type = @DataType, is_required = @IsRequired, created_at = @CreatedAt WHERE id = @Id";
var sql = @"UPDATE cnc_brand_field_mapping SET brand_id = @BrandId, standard_field = @StandardField, field_name = @FieldName, match_by = @MatchBy, data_type = @DataType, is_required = @IsRequired, is_enabled = @IsEnabled, created_at = @CreatedAt WHERE id = @Id";
return conn.Execute(sql, entity) > 0;
}
}
@ -74,8 +74,8 @@ namespace CncRepository.Impl
try
{
int count = 0;
var sql = @"INSERT INTO cnc_brand_field_mapping (brand_id, standard_field, field_name, match_by, data_type, is_required, created_at)
VALUES (@BrandId, @StandardField, @FieldName, @MatchBy, @DataType, @IsRequired, @CreatedAt);
var sql = @"INSERT INTO cnc_brand_field_mapping (brand_id, standard_field, field_name, match_by, data_type, is_required, is_enabled, created_at)
VALUES (@BrandId, @StandardField, @FieldName, @MatchBy, @DataType, @IsRequired, @IsEnabled, @CreatedAt);
SELECT LAST_INSERT_ID();";
foreach (var m in mappings)
{
@ -94,5 +94,15 @@ namespace CncRepository.Impl
}
}
}
public List<BrandFieldMapping> GetEnabledByBrandId(int brandId)
{
using (var conn = CreateConnection())
{
var sql = @"SELECT id as Id, brand_id as BrandId, standard_field as StandardField, field_name as FieldName, match_by as MatchBy, data_type as DataType, is_required as IsRequired, is_enabled as IsEnabled, created_at as CreatedAt
FROM cnc_brand_field_mapping WHERE brand_id = @BrandId AND is_enabled = 1 ORDER BY id";
return conn.Query<BrandFieldMapping>(sql, new { BrandId = brandId }).ToList();
}
}
}
}

@ -14,5 +14,6 @@ namespace CncRepository.Interface
bool Update(BrandFieldMapping entity);
bool DeleteByBrandId(int brandId);
int BatchCreate(int brandId, List<BrandFieldMapping> mappings);
List<BrandFieldMapping> GetEnabledByBrandId(int brandId);
}
}

@ -56,15 +56,16 @@ namespace CncService.Impl
TagsPath = brand.TagsPath,
IsEnabled = brand.IsEnabled == 1,
FieldCount = mappings?.Count ?? 0,
Mappings = mappings?.Select(m => new BrandFieldMappingDto
{
StandardField = m.StandardField,
FieldName = m.FieldName,
MatchBy = m.MatchBy,
DataType = m.DataType,
IsRequired = m.IsRequired
}).ToList() ?? new List<BrandFieldMappingDto>()
};
Mappings = mappings?.Select(m => new BrandFieldMappingDto
{
StandardField = m.StandardField,
FieldName = m.FieldName,
MatchBy = m.MatchBy,
DataType = m.DataType,
IsRequired = m.IsRequired,
IsEnabled = m.IsEnabled
}).ToList() ?? new List<BrandFieldMappingDto>()
};
return detail;
}
@ -139,16 +140,17 @@ namespace CncService.Impl
var mappings = _mappingRepository.GetByBrandId(id);
if (mappings != null && mappings.Count > 0)
{
var newMappings = mappings.Select(m => new BrandFieldMapping
{
BrandId = newBrandId,
StandardField = m.StandardField,
FieldName = m.FieldName,
MatchBy = m.MatchBy,
DataType = m.DataType,
IsRequired = m.IsRequired,
CreatedAt = DateTime.Now
}).ToList();
var newMappings = mappings.Select(m => new BrandFieldMapping
{
BrandId = newBrandId,
StandardField = m.StandardField,
FieldName = m.FieldName,
MatchBy = m.MatchBy,
DataType = m.DataType,
IsRequired = m.IsRequired,
IsEnabled = m.IsEnabled,
CreatedAt = DateTime.Now
}).ToList();
_mappingRepository.BatchCreate(newBrandId, newMappings);
}
return newBrandId;

@ -0,0 +1,125 @@
using System;
using System.Linq;
using CncModels.Entity;
using CncRepository.Impl;
using Xunit;
namespace CncRepository.Tests
{
/// <summary>
/// 品牌字段映射仓储测试
/// </summary>
[Collection("Database")]
public class BrandFieldMappingRepositoryTests : IDisposable
{
private readonly BrandFieldMappingRepository _repo;
public BrandFieldMappingRepositoryTests()
{
_repo = new BrandFieldMappingRepository(TestDb.ConnectionString);
TestDb.TruncateAll();
}
public void Dispose()
{
TestDb.TruncateAll();
}
[Fact]
public void GetByBrandId_()
{
// 插入2条启用 + 1条禁用
TestDb.Execute(@"INSERT INTO cnc_brand_field_mapping (brand_id, standard_field, field_name, match_by, data_type, is_required, is_enabled, created_at)
VALUES (1, 'program_name', 'Tag5', 'id', 'string', 1, 1, NOW()),
(1, 'part_count', 'Tag8', 'id', 'number', 1, 1, NOW()),
(1, 'spindle_load', 'Tag21', 'id', 'number', 0, 0, NOW())");
var result = _repo.GetByBrandId(1);
Assert.Equal(3, result.Count);
}
[Fact]
public void GetEnabledByBrandId_()
{
TestDb.Execute(@"INSERT IGNORE INTO cnc_brand (id, brand_name, device_field, tags_path, is_enabled, created_at, updated_at)
VALUES (1, 'FANUC', 'device', 'tags', 1, NOW(), NOW())");
TestDb.Execute(@"INSERT INTO cnc_brand_field_mapping (brand_id, standard_field, field_name, match_by, data_type, is_required, is_enabled, created_at)
VALUES (1, 'program_name', 'Tag5', 'id', 'string', 1, 1, NOW()),
(1, 'part_count', 'Tag8', 'id', 'number', 1, 1, NOW()),
(1, 'spindle_load', 'Tag21', 'id', 'number', 0, 0, NOW())");
var result = _repo.GetEnabledByBrandId(1);
Assert.Equal(2, result.Count);
Assert.All(result, m => Assert.Equal(1, m.IsEnabled));
}
[Fact]
public void Create_()
{
// 确保 brand_id=1 存在
TestDb.Execute(@"INSERT IGNORE INTO cnc_brand (id, brand_name, device_field, tags_path, is_enabled, created_at, updated_at)
VALUES (1, 'FANUC', 'device', 'tags', 1, NOW(), NOW())");
var entity = new BrandFieldMapping
{
BrandId = 1,
StandardField = "program_name",
FieldName = "Tag5",
MatchBy = "id",
DataType = "string",
IsRequired = 1,
IsEnabled = 1,
CreatedAt = DateTime.Now
};
var id = _repo.Create(entity);
Assert.True(id > 0);
var loaded = _repo.GetById(id);
Assert.Equal(1, loaded.IsEnabled);
}
[Fact]
public void Update_()
{
TestDb.Execute(@"INSERT INTO cnc_brand_field_mapping (brand_id, standard_field, field_name, match_by, data_type, is_required, is_enabled, created_at)
VALUES (1, 'program_name', 'Tag5', 'id', 'string', 1, 1, NOW())");
var id = TestDb.QuerySingle<int>("SELECT MAX(id) FROM cnc_brand_field_mapping");
var entity = _repo.GetById(id);
entity.IsEnabled = 0;
var result = _repo.Update(entity);
Assert.True(result);
Assert.Equal(0, _repo.GetById(id).IsEnabled);
}
[Fact]
public void BatchCreate_()
{
// 确保 brand_id=1 存在
TestDb.Execute(@"INSERT IGNORE INTO cnc_brand (id, brand_name, device_field, tags_path, is_enabled, created_at, updated_at)
VALUES (1, 'FANUC', 'device', 'tags', 1, NOW(), NOW())");
var mappings = new[]
{
new BrandFieldMapping { StandardField = "f1", FieldName = "Tag1", MatchBy = "id", DataType = "string", IsRequired = 0, IsEnabled = 1, CreatedAt = DateTime.Now },
new BrandFieldMapping { StandardField = "f2", FieldName = "Tag2", MatchBy = "id", DataType = "number", IsRequired = 0, IsEnabled = 1, CreatedAt = DateTime.Now },
new BrandFieldMapping { StandardField = "f3", FieldName = "Tag3", MatchBy = "id", DataType = "string", IsRequired = 0, IsEnabled = 0, CreatedAt = DateTime.Now },
}.ToList();
var count = _repo.BatchCreate(1, mappings);
Assert.Equal(3, count);
var all = _repo.GetByBrandId(1);
Assert.Equal(3, all.Count);
var enabled = _repo.GetEnabledByBrandId(1);
Assert.Equal(2, enabled.Count);
}
[Fact]
public void Update_()
{
TestDb.Execute(@"INSERT INTO cnc_brand_field_mapping (brand_id, standard_field, field_name, match_by, data_type, is_required, is_enabled, created_at)
VALUES (1, 'program_name', 'Tag5', 'id', 'string', 1, 1, NOW())");
var id = TestDb.QuerySingle<int>("SELECT MAX(id) FROM cnc_brand_field_mapping");
var entity = _repo.GetById(id);
entity.FieldName = "Tag5_New";
entity.IsEnabled = 0;
_repo.Update(entity);
var loaded = _repo.GetById(id);
Assert.Equal("Tag5_New", loaded.FieldName);
Assert.Equal(0, loaded.IsEnabled);
}
}
}

@ -248,6 +248,51 @@ namespace CncService.Tests
Assert.Equal(ErrorCode.NotFound, ex.Code);
}
// ======== FieldMapping IsEnabled ========
[Fact]
public void GetById_IsEnabled()
{
// 插入字段映射(含 is_enabled=0 的)
TestDb.Execute(@"INSERT INTO cnc_brand_field_mapping (brand_id, standard_field, field_name, match_by, data_type, is_required, is_enabled, created_at)
VALUES (1, 'program_name', 'Tag5', 'id', 'string', 1, 1, NOW()),
(1, 'spindle_load', 'Tag21', 'id', 'number', 0, 0, NOW())");
var detail = _service.GetById(1);
Assert.NotNull(detail.Mappings);
Assert.Equal(2, detail.Mappings.Count);
var disabled = detail.Mappings.FirstOrDefault(m => m.StandardField == "spindle_load");
Assert.NotNull(disabled);
Assert.Equal(0, disabled.IsEnabled);
}
[Fact]
public void Copy_()
{
// 插入字段映射1启用1禁用
TestDb.Execute(@"INSERT INTO cnc_brand_field_mapping (brand_id, standard_field, field_name, match_by, data_type, is_required, is_enabled, created_at)
VALUES (1, 'program_name', 'Tag5', 'id', 'string', 1, 1, NOW()),
(1, 'spindle_load', 'Tag21', 'id', 'number', 0, 0, NOW())");
var newId = _service.Copy(1);
var copied = _service.GetById(newId);
Assert.Equal(2, copied.Mappings.Count);
var enabledMapping = copied.Mappings.First(m => m.StandardField == "program_name");
var disabledMapping = copied.Mappings.First(m => m.StandardField == "spindle_load");
Assert.Equal(1, enabledMapping.IsEnabled);
Assert.Equal(0, disabledMapping.IsEnabled);
}
[Fact]
public void GetById_()
{
TestDb.Execute(@"INSERT INTO cnc_brand_field_mapping (brand_id, standard_field, field_name, match_by, data_type, is_required, is_enabled, created_at)
VALUES (1, 'f1', 'Tag1', 'id', 'string', 0, 0, NOW()),
(1, 'f2', 'Tag2', 'id', 'string', 0, 1, NOW()),
(1, 'f3', 'Tag3', 'id', 'string', 0, 1, NOW())");
var detail = _service.GetById(1);
Assert.Equal(3, detail.Mappings.Count);
Assert.Equal(1, detail.Mappings.Count(m => m.IsEnabled == 0));
Assert.Equal(2, detail.Mappings.Count(m => m.IsEnabled == 1));
}
// ======== GetStandardFields ========
[Fact]

@ -281,6 +281,49 @@ namespace CncWebApi.Tests
#endregion
// region FieldMapping IsEnabled - 字段映射启用开关
#region FieldMapping IsEnabled - 字段映射启用开关
/// <summary>
/// 测试获取品牌详情时映射响应包含IsEnabled
/// </summary>
[Fact]
public void GetById_IsEnabled()
{
// 插入字段映射
TestDb.Execute(@"INSERT INTO cnc_brand_field_mapping (brand_id, standard_field, field_name, match_by, data_type, is_required, is_enabled, created_at)
VALUES (1, 'program_name', 'Tag5', 'id', 'string', 1, 1, NOW()),
(1, 'spindle_load', 'Tag21', 'id', 'number', 0, 0, NOW())");
var result = _controller.GetById(1);
var response = ControllerFactory.Extract<BrandDetailResponse>(result);
ControllerFactory.AssertSuccess(response);
Assert.Equal(2, response.Data.Mappings.Count);
var disabled = response.Data.Mappings.First(m => m.StandardField == "spindle_load");
Assert.Equal(0, disabled.IsEnabled);
}
/// <summary>
/// 测试:复制品牌后映射启用状态一致
/// </summary>
[Fact]
public void Copy_()
{
TestDb.Execute(@"INSERT INTO cnc_brand_field_mapping (brand_id, standard_field, field_name, match_by, data_type, is_required, is_enabled, created_at)
VALUES (1, 'program_name', 'Tag5', 'id', 'string', 1, 1, NOW()),
(1, 'spindle_load', 'Tag21', 'id', 'number', 0, 0, NOW())");
var copyResult = _controller.Copy(1);
var copyResponse = ControllerFactory.Extract<object>(copyResult);
ControllerFactory.AssertSuccess(copyResponse);
int newId = TestDb.QuerySingle<int>("SELECT MAX(id) FROM cnc_brand WHERE id <> 1");
var detail = ControllerFactory.Extract<BrandDetailResponse>(_controller.GetById(newId));
Assert.Equal(2, detail.Data.Mappings.Count);
var enabledMapping = detail.Data.Mappings.First(m => m.StandardField == "program_name");
var disabledMapping = detail.Data.Mappings.First(m => m.StandardField == "spindle_load");
Assert.Equal(1, enabledMapping.IsEnabled);
Assert.Equal(0, disabledMapping.IsEnabled);
}
#endregion
#region GetStandardFields - 标准字段列表
/// <summary>

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save