Просмотр исходного кода

feat(s0): 去 domain 化 S0 制造只读列表(S0-DOMAIN-FILTER-REMOVAL-READONLY-1)

5 个 S0 制造只读列表页去掉工厂域(Domain)过滤,改纯租户隔离(ITenantIdFilter):
- 后端 5 控制器 GetPaged 删除 x.Domain 等值过滤,保留租户过滤与业务条件;物料补显加确定性 OrderBy(IsActive desc + Id)
- AdoS0ManufacturingDtos 4 个查询 DTO 放宽 [Required] 工厂域为可空
- 前端 5 页去除默认工厂域 guard 与“未解析工厂域→静默返空+warning”,首屏直接请求;保留 loadOrgs 供新增/编辑弹窗组织默认化
涉及 SopFileType / 生产要素参数 / 物料工艺要素 / 前处理要素 / 前处理要素参数

chore: bump version Web 2.4.256 / server 1.0.254
YY968XX 2 недель назад
Родитель
Сommit
ba995d7434

+ 1 - 1
Web/package.json

@@ -1,7 +1,7 @@
 {
   "name": "admin.net",
   "type": "module",
-  "version": "2.4.255",
+  "version": "2.4.256",
   "packageManager": "pnpm@10.32.1",
   "lastBuildTime": "2026.03.15",
   "description": "Admin.NET 站在巨人肩膀上的 .NET 通用权限开发框架",

+ 2 - 16
Web/src/views/aidop/s0/manufacturing/MaterialProcessElementList.vue

@@ -1,9 +1,6 @@
 <template>
 	<AidopDemoShell :title="pageTitle" subtitle="S0 / Manufacturing / 物料工序生产要素">
 		<el-form :inline="true" :model="query" class="mb12" @submit.prevent>
-			<el-form-item label="工厂域">
-				<el-input v-model="query.domain" clearable placeholder="例如:S0-F001" style="width: 140px" />
-			</el-form-item>
 			<el-form-item label="物料编码">
 				<el-input v-model="query.itemNum" clearable style="width: 140px" />
 			</el-form-item>
@@ -126,7 +123,6 @@ import { useRoute } from 'vue-router';
 import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus';
 import AidopDemoShell from '../../components/AidopDemoShell.vue';
 import { s0MfgMaterialProcessElementsApi } from '../api/s0ManufacturingApi';
-import { S0_DEFAULT_FACTORY_CODE } from '../composables/useS0MfgOrgScope';
 
 const route = useRoute();
 const pageTitle = computed(() => (route.meta?.title as string) || '物料工序生产要素');
@@ -172,16 +168,10 @@ const rules: FormRules = {
 async function loadList() {
 	loading.value = true;
 	try {
-		if (!query.domain.trim()) {
-			rows.value = [];
-			total.value = 0;
-			ElMessage.warning('请输入工厂域后再查询');
-			return;
-		}
+		// 去 domain 化:首屏直接请求,不再要求工厂域
 		const data = await s0MfgMaterialProcessElementsApi.list({
 			page: query.page,
 			pageSize: query.pageSize,
-			domain: query.domain.trim(),
 			itemNum: query.itemNum || undefined,
 			op: query.op || undefined,
 			line: query.line || undefined,
@@ -199,8 +189,6 @@ async function loadList() {
 }
 
 function resetQuery() {
-	// 重置后恢复默认工厂域,保证仍自动带 domain 发请求
-	query.domain = S0_DEFAULT_FACTORY_CODE;
 	query.itemNum = '';
 	query.op = '';
 	query.line = '';
@@ -291,9 +279,7 @@ function toggleEnabled(row: Row) {
 }
 
 onMounted(async () => {
-	// 首屏补默认工厂域(复用 S0 示范工厂 code),确保进入即发列表请求。
-	// 移除原 `await loadOrgs()` 无效调用:该函数在本页未定义/未导入,会抛错中断 onMounted、导致首屏不请求。
-	if (!query.domain.trim()) query.domain = S0_DEFAULT_FACTORY_CODE;
+	// 去 domain 化:首屏直接请求列表,不再依赖工厂域
 	await loadList();
 });
 </script>

+ 2 - 15
Web/src/views/aidop/s0/manufacturing/PreprocessElementList.vue

@@ -1,9 +1,6 @@
 <template>
 	<AidopDemoShell :title="pageTitle" subtitle="S0 / Manufacturing / 物料前处理要素">
 		<el-form :inline="true" :model="query" class="mb12" @submit.prevent>
-			<el-form-item label="工厂域">
-				<el-input v-model="query.domain" clearable placeholder="例如:S0-F001" style="width: 160px" />
-			</el-form-item>
 			<el-form-item label="物料编码">
 				<el-input v-model="query.itemNum" clearable style="width: 140px" />
 			</el-form-item>
@@ -106,7 +103,6 @@ import { useRoute } from 'vue-router';
 import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus';
 import AidopDemoShell from '../../components/AidopDemoShell.vue';
 import { s0MfgPreprocessElementsApi } from '../api/s0ManufacturingApi';
-import { S0_DEFAULT_FACTORY_CODE } from '../composables/useS0MfgOrgScope';
 
 const route = useRoute();
 const pageTitle = computed(() => (route.meta?.title as string) || '物料前处理要素');
@@ -150,17 +146,11 @@ const rules: FormRules = {
 async function loadList() {
 	loading.value = true;
 	try {
-		if (!query.domain.trim()) {
-			rows.value = [];
-			total.value = 0;
-			ElMessage.warning('请输入工厂域后再查询');
-			return;
-		}
+		// 去 domain 化:首屏直接请求,不再要求工厂域
 		const data = await s0MfgPreprocessElementsApi.list({
 			page: query.page,
 			pageSize: query.pageSize,
 			keyword: query.keyword || undefined,
-			domain: query.domain.trim(),
 			materialCode: query.itemNum || undefined,
 			op: query.op || undefined,
 			elementCode: query.itemCode || undefined,
@@ -177,8 +167,6 @@ async function loadList() {
 
 function resetQuery() {
 	query.keyword = '';
-	// 重置后恢复默认工厂域,保证仍自动带 domain 发请求
-	query.domain = S0_DEFAULT_FACTORY_CODE;
 	query.itemNum = '';
 	query.op = '';
 	query.itemCode = '';
@@ -256,8 +244,7 @@ function onDelete(row: Row) {
 }
 
 onMounted(async () => {
-	// 首屏补默认工厂域(复用 S0 示范工厂 code),确保进入即发列表请求
-	if (!query.domain.trim()) query.domain = S0_DEFAULT_FACTORY_CODE;
+	// 去 domain 化:首屏直接请求列表,不再依赖工厂域
 	await loadList();
 });
 </script>

+ 2 - 19
Web/src/views/aidop/s0/manufacturing/PreprocessElementParamList.vue

@@ -127,14 +127,6 @@ const query = reactive({
 	pageSize: 20,
 });
 
-// 列表按工厂域(domain)查询:查询区工厂隐藏,按默认组织自动确定 domain 范围
-// (与新增/编辑归属同口径,复用 getDefaultS0OrgScope)。
-function applyDefaultQueryScope() {
-	const d = getDefaultS0OrgScope(companyOptions.value, factoryOptions.value);
-	query.companyRefId = d.companyRefId;
-	query.factoryRefId = d.factoryRefId;
-}
-
 const loading = ref(false);
 const rows = ref<Row[]>([]);
 const total = ref(0);
@@ -180,16 +172,10 @@ const rules: FormRules = {
 async function loadList() {
 	loading.value = true;
 	try {
-		const domain = resolveDomain(query.factoryRefId);
-		if (!domain) {
-			rows.value = [];
-			total.value = 0;
-			return;
-		}
+		// 去 domain 化:首屏直接请求,不再按工厂域过滤
 		const data = await s0MfgPreprocessElementParamsApi.list({
 			page: query.page,
 			pageSize: query.pageSize,
-			domain,
 			keyword: query.keyword || undefined,
 		});
 		rows.value = data.list as Row[];
@@ -205,8 +191,6 @@ async function loadList() {
 function resetQuery() {
 	query.keyword = '';
 	query.page = 1;
-	// 重置后恢复默认工厂/域,避免清空导致空查询
-	applyDefaultQueryScope();
 	void loadList();
 }
 
@@ -306,9 +290,8 @@ function onDelete(row: Row) {
 }
 
 onMounted(async () => {
+	// loadOrgs 仍需:供新增/编辑弹窗的工厂默认化(getDefaultS0OrgScope)使用;列表本身已去 domain。
 	await loadOrgs();
-	// 查询区工厂隐藏,按默认组织自动确定 domain 范围
-	applyDefaultQueryScope();
 	await loadList();
 });
 </script>

+ 2 - 15
Web/src/views/aidop/s0/manufacturing/ProductionElementParamList.vue

@@ -4,9 +4,6 @@
 			<el-form-item label="关键字">
 				<el-input v-model="query.keyword" clearable style="width: 160px" />
 			</el-form-item>
-			<el-form-item label="工厂域">
-				<el-input v-model="query.domain" clearable placeholder="例如:S0-F001" style="width: 150px" />
-			</el-form-item>
 			<el-form-item>
 				<el-button type="primary" @click="loadList">查询</el-button>
 				<el-button @click="resetQuery">重置</el-button>
@@ -125,7 +122,6 @@ import { useRoute } from 'vue-router';
 import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus';
 import AidopDemoShell from '../../components/AidopDemoShell.vue';
 import { s0MfgElementParamsApi } from '../api/s0ManufacturingApi';
-import { S0_DEFAULT_FACTORY_CODE } from '../composables/useS0MfgOrgScope';
 
 const route = useRoute();
 const pageTitle = computed(() => (route.meta?.title as string) || '生产要素参数');
@@ -171,17 +167,11 @@ const rules: FormRules = {
 async function loadList() {
 	loading.value = true;
 	try {
-		if (!query.domain.trim()) {
-			rows.value = [];
-			total.value = 0;
-			ElMessage.warning('请输入工厂域后再查询');
-			return;
-		}
+		// 去 domain 化:首屏直接请求,不再要求工厂域
 		const data = await s0MfgElementParamsApi.list({
 			page: query.page,
 			pageSize: query.pageSize,
 			keyword: query.keyword || undefined,
-			domain: query.domain.trim(),
 		});
 		rows.value = data.list as Row[];
 		total.value = data.total;
@@ -195,8 +185,6 @@ async function loadList() {
 
 function resetQuery() {
 	query.keyword = '';
-	// 重置后恢复默认工厂域,保证仍自动带 domain 发请求
-	query.domain = S0_DEFAULT_FACTORY_CODE;
 	query.page = 1;
 	void loadList();
 }
@@ -277,8 +265,7 @@ function onDelete(row: Row) {
 }
 
 onMounted(async () => {
-	// 首屏补默认工厂域(复用 S0 示范工厂 code),确保进入即发列表请求
-	if (!query.domain.trim()) query.domain = S0_DEFAULT_FACTORY_CODE;
+	// 去 domain 化:首屏直接请求列表,不再依赖工厂域
 	await loadList();
 });
 </script>

+ 2 - 21
Web/src/views/aidop/s0/manufacturing/SopFileTypeList.vue

@@ -115,29 +115,13 @@ const rules: FormRules = {
 	descr: [{ required: true, message: '请填写描述', trigger: 'blur' }],
 };
 
-// 列表按工厂域(domain)查询:接口必传 Domain。查询栏不暴露工厂选择,
-// 统一由系统按 S0 示范组织默认化(与新增/编辑归属同口径,复用 getDefaultS0OrgScope)。
-function applyDefaultQueryScope() {
-	const d = getDefaultS0OrgScope(companyOptions.value, factoryOptions.value);
-	query.companyRefId = d.companyRefId;
-	query.factoryRefId = d.factoryRefId;
-}
-
 async function loadList() {
 	loading.value = true;
 	try {
-		const domain = resolveDomain(query.factoryRefId);
-		if (!domain) {
-			// 未解析出工厂域(默认组织缺失等):不静默失败,给出可见提示
-			rows.value = [];
-			total.value = 0;
-			ElMessage.warning('未解析到默认工厂域(S0 示范工厂),请检查 S0 组织配置');
-			return;
-		}
+		// 去 domain 化:首屏直接请求,不再按工厂域过滤
 		const data = await s0MfgSopFileTypesApi.list({
 			page: query.page,
 			pageSize: query.pageSize,
-			domain,
 			keyword: query.keyword || undefined,
 		});
 		rows.value = data.list as Row[];
@@ -153,8 +137,6 @@ async function loadList() {
 function resetQuery() {
 	query.keyword = '';
 	query.page = 1;
-	// 重置后恢复默认工厂/域,避免清空导致列表不发请求
-	applyDefaultQueryScope();
 	void loadList();
 }
 
@@ -222,9 +204,8 @@ function onDelete(row: Row) {
 }
 
 onMounted(async () => {
+	// loadOrgs 仍需:供新增/编辑弹窗的工厂默认化(getDefaultS0OrgScope)使用;列表本身已去 domain。
 	await loadOrgs();
-	// 组织加载完成后按 S0 示范组织默认化查询归属,确保首屏即发列表请求
-	applyDefaultQueryScope();
 	await loadList();
 });
 </script>

+ 3 - 3
server/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj

@@ -11,9 +11,9 @@
     <GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
     <Copyright>Admin.NET</Copyright>
     <Description>Admin.NET ͨ��Ȩ�޿���ƽ̨</Description>
-    <AssemblyVersion>1.0.253</AssemblyVersion>
-    <FileVersion>1.0.253</FileVersion>
-    <Version>1.0.253</Version>
+    <AssemblyVersion>1.0.254</AssemblyVersion>
+    <FileVersion>1.0.254</FileVersion>
+    <Version>1.0.254</Version>
   </PropertyGroup>
 
   <ItemGroup>

+ 2 - 2
server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Manufacturing/AdoS0MfgElementParamsController.cs

@@ -25,14 +25,14 @@ public class AdoS0MfgElementParamsController : ControllerBase
         var pageSize = q.PageSize;
         (page, pageSize) = PagingGuard.Normalize(page, pageSize);
 
-        var domain = q.Domain.Trim();
+        // 去 domain 化(S0-DOMAIN-FILTER-REMOVAL-READONLY-1):不再按 Domain 过滤,租户隔离由实体 ITenantIdFilter 自动生效;保留 CodeType 业务条件。
         var query = _rep.AsQueryable()
             .WhereIF(!string.IsNullOrWhiteSpace(q.Keyword),
                 x => x.DeviceNumber.Contains(q.Keyword!)
                     || (x.DeviceDescr != null && x.DeviceDescr.Contains(q.Keyword!))
                     || x.MessageCode.Contains(q.Keyword!)
                     || (x.CodeDescr != null && x.CodeDescr.Contains(q.Keyword!)))
-            .Where(x => x.Domain == domain && x.CodeType == "Prod");
+            .Where(x => x.CodeType == "Prod");
 
         var total = await query.CountAsync();
         var list = await query

+ 9 - 4
server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Manufacturing/AdoS0MfgMaterialProcessElementsController.cs

@@ -26,9 +26,9 @@ public class AdoS0MfgMaterialProcessElementsController : ControllerBase
         var pageSize = q.PageSize;
         (page, pageSize) = PagingGuard.Normalize(page, pageSize);
 
-        var domain = q.Domain.Trim();
+        // 去 domain 化(S0-DOMAIN-FILTER-REMOVAL-READONLY-1):不再按 Domain 过滤,租户隔离由 ITenantIdFilter 自动生效;保留 CodeType 业务条件。
         var query = _rep.AsQueryable()
-            .Where(x => x.Domain == domain && x.CodeType == "Prod")
+            .Where(x => x.CodeType == "Prod")
             .WhereIF(!string.IsNullOrWhiteSpace(q.Keyword),
                 x =>
                     x.ItemNum.Contains(q.Keyword!)
@@ -47,11 +47,16 @@ public class AdoS0MfgMaterialProcessElementsController : ControllerBase
 
         // 展示字段:ItemDescr / OpDescr / TypedDescr(当前阶段仅能稳定补 ItemDescr 与 OpDescr;TypedDescr 先留空)
         var itemNums = list.Select(x => x.ItemNum).Distinct().ToList();
+        // 去 domain 化:物料名补显不再按 Domain 等值;ItemMaster 带 ITenantIdFilter,租户内 ItemNum 唯一,确定性以 IsActive/RecID 稳定排序兜底。
         var items = await _rep.Context.Queryable<AdoS0ItemMaster>()
-            .Where(m => m.DomainCode == domain && itemNums.Contains(m.ItemNum))
+            .Where(m => itemNums.Contains(m.ItemNum))
+            .OrderByDescending(m => m.IsActive)
+            .OrderBy(m => m.Id)
             .Select(m => new { m.ItemNum, m.Descr })
             .ToListAsync();
-        var itemDescrMap = items.ToDictionary(x => x.ItemNum, x => x.Descr);
+        var itemDescrMap = items
+            .GroupBy(x => x.ItemNum)
+            .ToDictionary(g => g.Key, g => g.First().Descr);
 
         var opKeys = list
             .Where(x => !string.IsNullOrWhiteSpace(x.Op))

+ 1 - 2
server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Manufacturing/AdoS0MfgPreprocessElementParamsController.cs

@@ -27,9 +27,8 @@ public class AdoS0MfgPreprocessElementParamsController : ControllerBase
         var pageSize = q.PageSize;
         (page, pageSize) = PagingGuard.Normalize(page, pageSize);
 
-        var domain = q.Domain.Trim();
+        // 去 domain 化(S0-DOMAIN-FILTER-REMOVAL-READONLY-1):不再按 Domain 过滤,租户隔离由 ITenantIdFilter 自动生效。
         var query = _rep.AsQueryable()
-            .Where(x => x.Domain == domain)
             .WhereIF(!string.IsNullOrWhiteSpace(q.Keyword),
                 x => x.DeviceNumber.Contains(q.Keyword!)
                     || (x.DeviceDescr != null && x.DeviceDescr.Contains(q.Keyword!))

+ 10 - 5
server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Manufacturing/AdoS0MfgPreprocessElementsController.cs

@@ -33,15 +33,15 @@ public class AdoS0MfgPreprocessElementsController : ControllerBase
         var pageSize = q.PageSize;
         (page, pageSize) = PagingGuard.Normalize(page, pageSize);
 
+        // 去 domain 化(S0-DOMAIN-FILTER-REMOVAL-READONLY-1):主列表不再按 Domain 过滤、不再因缺 domain 返空,
+        // 租户隔离由 ITenantIdFilter 自动生效。domain 仅保留供下方工序名(GeneralizedCode,无租户过滤)补显——
+        // 该补显为 D 档例外,本批不去 domain(去掉会跨租户/ToDictionary 重复键),domain 空时工序名留空、不报错。
         var domain = q.Domain?.Trim() ?? string.Empty;
         var qsDomain = HttpContext.Request.Query["domain"].ToString();
         if (!string.IsNullOrWhiteSpace(qsDomain))
             domain = qsDomain.Trim();
-        if (string.IsNullOrWhiteSpace(domain))
-            return Ok(new { total = 0, page, pageSize, list = Array.Empty<object>() });
 
         var query = _rep.AsQueryable()
-            .Where(x => x.Domain == domain)
             .WhereIF(!string.IsNullOrWhiteSpace(q.MaterialCode), x => x.ItemNum.Contains(q.MaterialCode!))
             .WhereIF(!string.IsNullOrWhiteSpace(q.Op), x => x.Op != null && x.Op.Contains(q.Op!))
             .WhereIF(!string.IsNullOrWhiteSpace(q.ElementCode), x => x.ItemCode != null && x.ItemCode.Contains(q.ElementCode!))
@@ -58,12 +58,17 @@ public class AdoS0MfgPreprocessElementsController : ControllerBase
             .Take(pageSize)
             .ToListAsync();
 
+        // 去 domain 化:物料名补显不再按 Domain 等值;ItemMaster 带 ITenantIdFilter,租户内 ItemNum 唯一,以 IsActive/RecID 稳定排序兜底确定性。
         var distinctItemNums = list.Select(x => x.ItemNum).Distinct().ToList();
         var items = await _itemRep.AsQueryable()
-            .Where(m => m.DomainCode == domain && distinctItemNums.Contains(m.ItemNum))
+            .Where(m => distinctItemNums.Contains(m.ItemNum))
+            .OrderByDescending(m => m.IsActive)
+            .OrderBy(m => m.Id)
             .Select(m => new { m.ItemNum, m.Descr })
             .ToListAsync();
-        var itemDescrMap = items.ToDictionary(x => x.ItemNum, x => x.Descr);
+        var itemDescrMap = items
+            .GroupBy(x => x.ItemNum)
+            .ToDictionary(g => g.Key, g => g.First().Descr);
 
         var opVals = list.Select(x => x.Op).Where(x => !string.IsNullOrWhiteSpace(x)).Cast<string>().Distinct().ToList();
         var opCodes = await _codeRep.AsQueryable()

+ 1 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Manufacturing/AdoS0MfgSopFileTypesController.cs

@@ -24,8 +24,8 @@ public class AdoS0MfgSopFileTypesController : ControllerBase
         var pageSize = q.PageSize;
         (page, pageSize) = PagingGuard.Normalize(page, pageSize);
 
+        // 去 domain 化(S0-DOMAIN-FILTER-REMOVAL-READONLY-1):不再按 Domain 过滤,租户隔离由 ITenantIdFilter 自动生效。
         var query = _rep.AsQueryable()
-            .Where(x => x.Domain == q.Domain.Trim())
             .WhereIF(!string.IsNullOrWhiteSpace(q.Keyword),
                 x => x.ImageTypeID.Contains(q.Keyword!) || (x.Descr != null && x.Descr.Contains(q.Keyword!)));
 

+ 8 - 8
server/Plugins/Admin.NET.Plugin.AiDOP/Dto/S0/Manufacturing/AdoS0ManufacturingDtos.cs

@@ -147,9 +147,9 @@ public class AdoS0MfgLineMaterialQueryDto : AdoS0MfgPagedQueryBase;
 /// </summary>
 public class AdoS0MfgSopFileTypeQueryDto
 {
-    [Required(ErrorMessage = "工厂域不能为空")]
+    // 去 domain 化(S0-DOMAIN-FILTER-REMOVAL-READONLY-1):查询不再要求 Domain,放宽 [Required] 为可空;租户隔离由实体保证。
     [MaxLength(50)]
-    public string Domain { get; set; } = string.Empty;
+    public string? Domain { get; set; }
 
     public string? Keyword { get; set; }
 
@@ -284,9 +284,9 @@ public class AdoS0MfgElementParamUpsertDto
 /// </summary>
 public class AdoS0DevMonitorQueryDto
 {
-    [Required(ErrorMessage = "工厂域不能为空")]
+    // 去 domain 化(S0-DOMAIN-FILTER-REMOVAL-READONLY-1):查询不再要求 Domain,放宽 [Required] 为可空;租户隔离由实体保证。
     [MaxLength(50)]
-    public string Domain { get; set; } = string.Empty;
+    public string? Domain { get; set; }
 
     public string? Keyword { get; set; }
 
@@ -296,9 +296,9 @@ public class AdoS0DevMonitorQueryDto
 
 public class AdoS0MfgPreprocessElementParamQueryDto
 {
-    [Required(ErrorMessage = "工厂域不能为空")]
+    // 去 domain 化(S0-DOMAIN-FILTER-REMOVAL-READONLY-1):查询不再要求 Domain,放宽 [Required] 为可空;租户隔离由实体保证。
     [MaxLength(50)]
-    public string Domain { get; set; } = string.Empty;
+    public string? Domain { get; set; }
 
     public string? Keyword { get; set; }
 
@@ -379,9 +379,9 @@ public class AdoS0MfgPreprocessElementParamUpsertDto
 /// </summary>
 public class AdoS0ItemOpConditionQueryDto
 {
-    [Required(ErrorMessage = "工厂域不能为空")]
+    // 去 domain 化(S0-DOMAIN-FILTER-REMOVAL-READONLY-1):查询不再要求 Domain,放宽 [Required] 为可空;租户隔离由实体保证。
     [MaxLength(50)]
-    public string Domain { get; set; } = string.Empty;
+    public string? Domain { get; set; }
 
     public string? Keyword { get; set; }
     public string? ItemNum { get; set; }