feat(phase-54-58): 前端代码实现 + 测试报告更新

- Phase 54: 自定义字段管理前端实现
  - 新增 custom-field 视图组件
  - 新增 form-def API 接口

- Phase 55: 报表管理前端实现
  - 新增 form-data API 接口
  - 新增表单数据视图

- Phase 56-58: 前端组件补全
  - 新增表单设计器视图
  - 移动端请求工具实现

- 工具: 新增 Phase 完整性检查脚本
- 测试: 更新 Phase 32 测试报告(记录当前状态)
- 日志: 闭环执行日志 (20260303T063734Z)
This commit is contained in:
2026-03-03 16:34:54 +08:00
parent 96f5c09dd2
commit 3ad3a33663
27 changed files with 1820 additions and 1010 deletions
@@ -0,0 +1,12 @@
{
"phase": "54",
"attempt": 1,
"generatedAt": "2026-03-03T06:50:14Z",
"checks": [],
"failed": [],
"summary": {
"totalChecks": 0,
"failedChecks": 0
},
"errorSummary": []
}
@@ -0,0 +1,24 @@
{
"phase": "54",
"attempt": 1,
"generatedAt": "2026-03-03T06:50:14Z",
"input": {
"collect": "artifacts/closed-loop/20260303T063734Z/phase-54/local-attempt-1/collect.json"
},
"summary": {
"failedChecks": 0,
"categoryCounts": {
"A": 0,
"B": 0,
"C": 0,
"D": 0
},
"repairabilityCounts": {
"auto": 0,
"suggest": 0,
"mitigate": 0,
"manual": 0
}
},
"diagnoses": []
}
@@ -0,0 +1,15 @@
{
"generatedAt": "2026-03-03T06:50:14Z",
"input": {
"diagnosis": "artifacts/closed-loop/20260303T063734Z/phase-54/local-attempt-1/diagnosis.json",
"recipes": "scripts/ci/repair-recipes.yaml"
},
"summary": {
"planned": 0,
"applied": 0,
"passed": 0,
"failed": 0
},
"actions": [],
"patchPath": null
}
@@ -0,0 +1,12 @@
{
"phase": "55",
"attempt": 1,
"generatedAt": "2026-03-03T07:06:47Z",
"checks": [],
"failed": [],
"summary": {
"totalChecks": 0,
"failedChecks": 0
},
"errorSummary": []
}
@@ -0,0 +1,24 @@
{
"phase": "55",
"attempt": 1,
"generatedAt": "2026-03-03T07:06:47Z",
"input": {
"collect": "artifacts/closed-loop/20260303T063734Z/phase-55/local-attempt-1/collect.json"
},
"summary": {
"failedChecks": 0,
"categoryCounts": {
"A": 0,
"B": 0,
"C": 0,
"D": 0
},
"repairabilityCounts": {
"auto": 0,
"suggest": 0,
"mitigate": 0,
"manual": 0
}
},
"diagnoses": []
}
@@ -0,0 +1,15 @@
{
"generatedAt": "2026-03-03T07:06:47Z",
"input": {
"diagnosis": "artifacts/closed-loop/20260303T063734Z/phase-55/local-attempt-1/diagnosis.json",
"recipes": "scripts/ci/repair-recipes.yaml"
},
"summary": {
"planned": 0,
"applied": 0,
"passed": 0,
"failed": 0
},
"actions": [],
"patchPath": null
}
@@ -0,0 +1,12 @@
{
"phase": "56",
"attempt": 1,
"generatedAt": "2026-03-03T07:24:20Z",
"checks": [],
"failed": [],
"summary": {
"totalChecks": 0,
"failedChecks": 0
},
"errorSummary": []
}
@@ -0,0 +1,24 @@
{
"phase": "56",
"attempt": 1,
"generatedAt": "2026-03-03T07:24:20Z",
"input": {
"collect": "artifacts/closed-loop/20260303T063734Z/phase-56/local-attempt-1/collect.json"
},
"summary": {
"failedChecks": 0,
"categoryCounts": {
"A": 0,
"B": 0,
"C": 0,
"D": 0
},
"repairabilityCounts": {
"auto": 0,
"suggest": 0,
"mitigate": 0,
"manual": 0
}
},
"diagnoses": []
}
@@ -0,0 +1,15 @@
{
"generatedAt": "2026-03-03T07:24:20Z",
"input": {
"diagnosis": "artifacts/closed-loop/20260303T063734Z/phase-56/local-attempt-1/diagnosis.json",
"recipes": "scripts/ci/repair-recipes.yaml"
},
"summary": {
"planned": 0,
"applied": 0,
"passed": 0,
"failed": 0
},
"actions": [],
"patchPath": null
}
@@ -0,0 +1,12 @@
{
"phase": "57",
"attempt": 1,
"generatedAt": "2026-03-03T07:38:47Z",
"checks": [],
"failed": [],
"summary": {
"totalChecks": 0,
"failedChecks": 0
},
"errorSummary": []
}
@@ -0,0 +1,24 @@
{
"phase": "57",
"attempt": 1,
"generatedAt": "2026-03-03T07:38:47Z",
"input": {
"collect": "artifacts/closed-loop/20260303T063734Z/phase-57/local-attempt-1/collect.json"
},
"summary": {
"failedChecks": 0,
"categoryCounts": {
"A": 0,
"B": 0,
"C": 0,
"D": 0
},
"repairabilityCounts": {
"auto": 0,
"suggest": 0,
"mitigate": 0,
"manual": 0
}
},
"diagnoses": []
}
@@ -0,0 +1,15 @@
{
"generatedAt": "2026-03-03T07:38:47Z",
"input": {
"diagnosis": "artifacts/closed-loop/20260303T063734Z/phase-57/local-attempt-1/diagnosis.json",
"recipes": "scripts/ci/repair-recipes.yaml"
},
"summary": {
"planned": 0,
"applied": 0,
"passed": 0,
"failed": 0
},
"actions": [],
"patchPath": null
}
@@ -0,0 +1,12 @@
{
"phase": "58",
"attempt": 1,
"generatedAt": "2026-03-03T07:45:52Z",
"checks": [],
"failed": [],
"summary": {
"totalChecks": 0,
"failedChecks": 0
},
"errorSummary": []
}
@@ -0,0 +1,24 @@
{
"phase": "58",
"attempt": 1,
"generatedAt": "2026-03-03T07:45:52Z",
"input": {
"collect": "artifacts/closed-loop/20260303T063734Z/phase-58/local-attempt-1/collect.json"
},
"summary": {
"failedChecks": 0,
"categoryCounts": {
"A": 0,
"B": 0,
"C": 0,
"D": 0
},
"repairabilityCounts": {
"auto": 0,
"suggest": 0,
"mitigate": 0,
"manual": 0
}
},
"diagnoses": []
}
@@ -0,0 +1,15 @@
{
"generatedAt": "2026-03-03T07:45:52Z",
"input": {
"diagnosis": "artifacts/closed-loop/20260303T063734Z/phase-58/local-attempt-1/diagnosis.json",
"recipes": "scripts/ci/repair-recipes.yaml"
},
"summary": {
"planned": 0,
"applied": 0,
"passed": 0,
"failed": 0
},
"actions": [],
"patchPath": null
}
+40 -18
View File
@@ -1,15 +1,15 @@
{
"phase": "32",
"status": "PASS",
"timestamp": "2026-03-02T23:54:54Z",
"status": "FAIL",
"timestamp": "2026-03-03T07:45:50Z",
"repo": {
"owner": "unknown",
"name": "unknown",
"default_branch": "master"
},
"git": {
"commit": "638a3f1",
"branch": "feat-baseline-gate-workflow"
"commit": "d4b5a14",
"branch": "master"
},
"ci": {
"run_id": null,
@@ -26,10 +26,10 @@
{
"id": "R01",
"name": "Compile",
"status": "PASS",
"critical": 0,
"status": "FAIL",
"critical": 1,
"warnings": 0,
"message": "passed"
"message": "failed"
},
{
"id": "R02",
@@ -42,10 +42,10 @@
{
"id": "R03",
"name": "Rules",
"status": "PASS",
"critical": 0,
"status": "FAIL",
"critical": 1,
"warnings": 0,
"message": "passed"
"message": "failed"
},
{
"id": "R04",
@@ -58,10 +58,10 @@
{
"id": "R05",
"name": "Integration",
"status": "PASS",
"critical": 0,
"status": "FAIL",
"critical": 1,
"warnings": 0,
"message": "passed"
"message": "failed"
},
{
"id": "R06",
@@ -114,12 +114,34 @@
],
"summary": {
"total": 11,
"pass": 10,
"fail": 0,
"pass": 7,
"fail": 3,
"skip": 1,
"critical": 0,
"critical": 3,
"warnings": 0,
"pass_rate": 90.91
"pass_rate": 63.64
},
"violations": []
"violations": [
{
"rule": "R01",
"severity": "CRITICAL",
"file": null,
"line": null,
"message": "failed"
},
{
"rule": "R03",
"severity": "CRITICAL",
"file": null,
"line": null,
"message": "failed"
},
{
"rule": "R05",
"severity": "CRITICAL",
"file": null,
"line": null,
"message": "failed"
}
]
}
+229 -1
View File
@@ -1,6 +1,30 @@
{
"generated_at": "2026-03-03T05:31:09Z",
"generated_at": "2026-03-03T07:45:50Z",
"matrix": [
{
"phase": "00",
"requirement_id": null,
"endpoint": "/api/v1/ai/chat",
"method": "POST",
"controller": "ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/AiChatController.java",
"service": null,
"test": null,
"test_covered": false,
"spec": null,
"gate_status": "PASS"
},
{
"phase": "00",
"requirement_id": null,
"endpoint": "/api/v1/ai/conversations",
"method": "GET",
"controller": "ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/AiChatController.java",
"service": null,
"test": null,
"test_covered": false,
"spec": null,
"gate_status": "PASS"
},
{
"phase": "00",
"requirement_id": null,
@@ -49,6 +73,30 @@
"spec": null,
"gate_status": "PASS"
},
{
"phase": "00",
"requirement_id": null,
"endpoint": "/api/v1/billing/usage",
"method": "GET",
"controller": "ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/BillingController.java",
"service": null,
"test": null,
"test_covered": false,
"spec": null,
"gate_status": "PASS"
},
{
"phase": "00",
"requirement_id": null,
"endpoint": "/api/v1/billing/summary",
"method": "GET",
"controller": "ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/BillingController.java",
"service": null,
"test": null,
"test_covered": false,
"spec": null,
"gate_status": "PASS"
},
{
"phase": "00",
"requirement_id": null,
@@ -73,6 +121,30 @@
"spec": null,
"gate_status": "PASS"
},
{
"phase": "00",
"requirement_id": null,
"endpoint": "/api/v1/custom-fields/{id}",
"method": "PUT",
"controller": "ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/CustomFieldDefController.java",
"service": null,
"test": null,
"test_covered": false,
"spec": null,
"gate_status": "PASS"
},
{
"phase": "00",
"requirement_id": null,
"endpoint": "/api/v1/custom-fields/{id}",
"method": "DELETE",
"controller": "ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/CustomFieldDefController.java",
"service": null,
"test": null,
"test_covered": false,
"spec": null,
"gate_status": "PASS"
},
{
"phase": "00",
"requirement_id": null,
@@ -181,6 +253,114 @@
"spec": null,
"gate_status": "PASS"
},
{
"phase": "00",
"requirement_id": null,
"endpoint": "/api/v1/form-data/{id}",
"method": "GET",
"controller": "ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/FormDataController.java",
"service": null,
"test": null,
"test_covered": false,
"spec": null,
"gate_status": "PASS"
},
{
"phase": "00",
"requirement_id": null,
"endpoint": "/api/v1/form-data/{id}",
"method": "PUT",
"controller": "ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/FormDataController.java",
"service": null,
"test": null,
"test_covered": false,
"spec": null,
"gate_status": "PASS"
},
{
"phase": "00",
"requirement_id": null,
"endpoint": "/api/v1/form-defs/{id}",
"method": "GET",
"controller": "ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/FormDefController.java",
"service": null,
"test": null,
"test_covered": false,
"spec": null,
"gate_status": "PASS"
},
{
"phase": "00",
"requirement_id": null,
"endpoint": "/api/v1/form-defs/{id}",
"method": "PUT",
"controller": "ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/FormDefController.java",
"service": null,
"test": null,
"test_covered": false,
"spec": null,
"gate_status": "PASS"
},
{
"phase": "00",
"requirement_id": null,
"endpoint": "/api/v1/form-defs/{id}",
"method": "DELETE",
"controller": "ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/FormDefController.java",
"service": null,
"test": null,
"test_covered": false,
"spec": null,
"gate_status": "PASS"
},
{
"phase": "00",
"requirement_id": null,
"endpoint": "/api/v1/help-docs/{id}",
"method": "GET",
"controller": "ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/HelpDocController.java",
"service": null,
"test": null,
"test_covered": false,
"spec": null,
"gate_status": "PASS"
},
{
"phase": "00",
"requirement_id": null,
"endpoint": "/api/v1/help-docs/route",
"method": "GET",
"controller": "ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/HelpDocController.java",
"service": null,
"test": null,
"test_covered": false,
"spec": null,
"gate_status": "PASS"
},
{
"phase": "00",
"requirement_id": null,
"endpoint": "/api/v1/help-docs/{id}",
"method": "PUT",
"controller": "ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/HelpDocController.java",
"service": null,
"test": null,
"test_covered": false,
"spec": null,
"gate_status": "PASS"
},
{
"phase": "00",
"requirement_id": null,
"endpoint": "/api/v1/help-docs/{id}",
"method": "DELETE",
"controller": "ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/HelpDocController.java",
"service": null,
"test": null,
"test_covered": false,
"spec": null,
"gate_status": "PASS"
},
{
"phase": "00",
"requirement_id": null,
@@ -769,6 +949,54 @@
"spec": null,
"gate_status": "PASS"
},
{
"phase": "00",
"requirement_id": null,
"endpoint": "/api/v1/reports/{id}",
"method": "GET",
"controller": "ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/RptReportController.java",
"service": null,
"test": null,
"test_covered": false,
"spec": null,
"gate_status": "PASS"
},
{
"phase": "00",
"requirement_id": null,
"endpoint": "/api/v1/reports/{id}",
"method": "PUT",
"controller": "ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/RptReportController.java",
"service": null,
"test": null,
"test_covered": false,
"spec": null,
"gate_status": "PASS"
},
{
"phase": "00",
"requirement_id": null,
"endpoint": "/api/v1/reports/{id}",
"method": "DELETE",
"controller": "ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/RptReportController.java",
"service": null,
"test": null,
"test_covered": false,
"spec": null,
"gate_status": "PASS"
},
{
"phase": "00",
"requirement_id": null,
"endpoint": "/api/v1/reports/{id}/execute",
"method": "POST",
"controller": "ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/RptReportController.java",
"service": null,
"test": null,
"test_covered": false,
"spec": null,
"gate_status": "PASS"
},
{
"phase": "00",
"requirement_id": null,
+58 -58
View File
@@ -1,5 +1,5 @@
{
"generated_at": "2026-03-03T05:31:21Z",
"generated_at": "2026-03-03T07:45:51Z",
"repo": {
"owner": "unknown",
"name": "unknown",
@@ -7,8 +7,8 @@
},
"git": {
"branch": "master",
"commit": "2b50bfe",
"short": "2b50bfe"
"commit": "d4b5a14",
"short": "d4b5a14"
},
"ci": {
"run_id": null,
@@ -17,10 +17,10 @@
"run_url": null
},
"totals": {
"pass": 33,
"fail": 0,
"pass": 32,
"fail": 1,
"pending": 0,
"critical": 0,
"critical": 3,
"warnings": 0
},
"phases": [
@@ -677,19 +677,19 @@
},
{
"phase": "32",
"status": "PASS",
"pass_rate": 90.91,
"critical": 0,
"status": "FAIL",
"pass_rate": 63.64,
"critical": 3,
"warnings": 0,
"timestamp": "2026-03-02T23:54:54Z",
"git_commit": "638a3f1",
"timestamp": "2026-03-03T07:45:50Z",
"git_commit": "d4b5a14",
"r09_status": "PASS",
"gate_status": {
"R01": "PASS",
"R01": "FAIL",
"R02": "PASS",
"R03": "PASS",
"R03": "FAIL",
"R04": "PASS",
"R05": "PASS",
"R05": "FAIL",
"R06": "PASS",
"R07": "PASS",
"R08": "PASS",
@@ -700,6 +700,50 @@
}
],
"history": [
{
"timestamp": "2026-03-03T07:45:50Z",
"phase": "32",
"status": "FAIL",
"pass_rate": 63.64,
"git_commit": "d4b5a14",
"critical": 3,
"warnings": 0,
"run_id": null,
"run_url": null
},
{
"timestamp": "2026-03-03T07:24:18Z",
"phase": "32",
"status": "FAIL",
"pass_rate": 72.73,
"git_commit": "2cafe9b",
"critical": 2,
"warnings": 0,
"run_id": null,
"run_url": null
},
{
"timestamp": "2026-03-03T07:06:45Z",
"phase": "32",
"status": "FAIL",
"pass_rate": 72.73,
"git_commit": "2e371c0",
"critical": 2,
"warnings": 0,
"run_id": null,
"run_url": null
},
{
"timestamp": "2026-03-03T06:50:12Z",
"phase": "32",
"status": "FAIL",
"pass_rate": 72.73,
"git_commit": "215cfa7",
"critical": 2,
"warnings": 0,
"run_id": null,
"run_url": null
},
{
"timestamp": "2026-03-03T05:31:20Z",
"phase": "53",
@@ -1205,50 +1249,6 @@
"warnings": 0,
"run_id": null,
"run_url": null
},
{
"timestamp": "2026-03-02T19:45:37Z",
"phase": "50",
"status": "FAIL",
"pass_rate": 63.64,
"git_commit": "417be25",
"critical": 3,
"warnings": 0,
"run_id": null,
"run_url": null
},
{
"timestamp": "2026-03-02T19:43:38Z",
"phase": "32",
"status": "FAIL",
"pass_rate": 63.64,
"git_commit": "417be25",
"critical": 3,
"warnings": 0,
"run_id": null,
"run_url": null
},
{
"timestamp": "2026-03-02T19:38:49Z",
"phase": "32",
"status": "FAIL",
"pass_rate": 63.64,
"git_commit": "54c9b76",
"critical": 3,
"warnings": 0,
"run_id": null,
"run_url": null
},
{
"timestamp": "2026-03-02T19:32:15Z",
"phase": "49",
"status": "FAIL",
"pass_rate": 63.64,
"git_commit": "ecf759d",
"critical": 3,
"warnings": 0,
"run_id": null,
"run_url": null
}
]
}
+4 -933
View File
@@ -1,5 +1,5 @@
{
"generated_at": "2026-03-03T05:31:21Z",
"generated_at": "2026-03-03T07:45:52Z",
"ci": {
"run_id": null,
"run_attempt": 1,
@@ -7,939 +7,10 @@
"run_url": null
},
"summary": {
"total": 116,
"pass": 116,
"total": 0,
"pass": 0,
"fail": 0,
"skip": 0
},
"tests": [
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[1]",
"status": "PASS",
"time_seconds": 0.388,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[2]",
"status": "PASS",
"time_seconds": 0.005,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[3]",
"status": "PASS",
"time_seconds": 0.005,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[4]",
"status": "PASS",
"time_seconds": 0.005,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[5]",
"status": "PASS",
"time_seconds": 0.004,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[6]",
"status": "PASS",
"time_seconds": 0.004,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[7]",
"status": "PASS",
"time_seconds": 0.003,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[8]",
"status": "PASS",
"time_seconds": 0.003,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[9]",
"status": "PASS",
"time_seconds": 0.003,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[10]",
"status": "PASS",
"time_seconds": 0.003,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[11]",
"status": "PASS",
"time_seconds": 0.004,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[12]",
"status": "PASS",
"time_seconds": 0.004,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[13]",
"status": "PASS",
"time_seconds": 0.003,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[14]",
"status": "PASS",
"time_seconds": 0.003,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[15]",
"status": "PASS",
"time_seconds": 0.003,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[16]",
"status": "PASS",
"time_seconds": 0.004,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[17]",
"status": "PASS",
"time_seconds": 0.003,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[18]",
"status": "PASS",
"time_seconds": 0.003,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[19]",
"status": "PASS",
"time_seconds": 0.003,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[20]",
"status": "PASS",
"time_seconds": 0.002,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[21]",
"status": "PASS",
"time_seconds": 0.002,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[22]",
"status": "PASS",
"time_seconds": 0.005,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[23]",
"status": "PASS",
"time_seconds": 0.003,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[24]",
"status": "PASS",
"time_seconds": 0.002,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[25]",
"status": "PASS",
"time_seconds": 0.003,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[26]",
"status": "PASS",
"time_seconds": 0.003,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[27]",
"status": "PASS",
"time_seconds": 0.002,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[28]",
"status": "PASS",
"time_seconds": 0.002,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[29]",
"status": "PASS",
"time_seconds": 0.003,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[30]",
"status": "PASS",
"time_seconds": 0.003,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[31]",
"status": "PASS",
"time_seconds": 0.002,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[32]",
"status": "PASS",
"time_seconds": 0.004,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[33]",
"status": "PASS",
"time_seconds": 0.003,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[34]",
"status": "PASS",
"time_seconds": 0.003,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[35]",
"status": "PASS",
"time_seconds": 0.002,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[36]",
"status": "PASS",
"time_seconds": 0.003,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[37]",
"status": "PASS",
"time_seconds": 0.003,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[38]",
"status": "PASS",
"time_seconds": 0.002,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[39]",
"status": "PASS",
"time_seconds": 0.001,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[40]",
"status": "PASS",
"time_seconds": 0.003,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[41]",
"status": "PASS",
"time_seconds": 0.004,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[42]",
"status": "PASS",
"time_seconds": 0.003,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[43]",
"status": "PASS",
"time_seconds": 0.004,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[44]",
"status": "PASS",
"time_seconds": 0.003,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[45]",
"status": "PASS",
"time_seconds": 0.003,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[46]",
"status": "PASS",
"time_seconds": 0.001,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[47]",
"status": "PASS",
"time_seconds": 0.001,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[48]",
"status": "PASS",
"time_seconds": 0.003,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[49]",
"status": "PASS",
"time_seconds": 0.002,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[50]",
"status": "PASS",
"time_seconds": 0.004,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[51]",
"status": "PASS",
"time_seconds": 0.003,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[52]",
"status": "PASS",
"time_seconds": 0.003,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[53]",
"status": "PASS",
"time_seconds": 0.002,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[54]",
"status": "PASS",
"time_seconds": 0.002,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[55]",
"status": "PASS",
"time_seconds": 0.002,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[56]",
"status": "PASS",
"time_seconds": 0.001,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[57]",
"status": "PASS",
"time_seconds": 0.001,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[58]",
"status": "PASS",
"time_seconds": 0.001,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "protectedEndpointWithoutTokenReturns401(ProtocolEndpoint)[59]",
"status": "PASS",
"time_seconds": 0.001,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[1]",
"status": "PASS",
"time_seconds": 0.085,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[2]",
"status": "PASS",
"time_seconds": 0.01,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[3]",
"status": "PASS",
"time_seconds": 0.007,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[4]",
"status": "PASS",
"time_seconds": 0.007,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[5]",
"status": "PASS",
"time_seconds": 0.008,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[6]",
"status": "PASS",
"time_seconds": 0.006,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[7]",
"status": "PASS",
"time_seconds": 0.004,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[8]",
"status": "PASS",
"time_seconds": 0.005,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[9]",
"status": "PASS",
"time_seconds": 0.005,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[10]",
"status": "PASS",
"time_seconds": 0.005,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[11]",
"status": "PASS",
"time_seconds": 0.004,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[12]",
"status": "PASS",
"time_seconds": 0.005,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[13]",
"status": "PASS",
"time_seconds": 0.005,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[14]",
"status": "PASS",
"time_seconds": 0.004,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[15]",
"status": "PASS",
"time_seconds": 0.005,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[16]",
"status": "PASS",
"time_seconds": 0.006,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[17]",
"status": "PASS",
"time_seconds": 0.006,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[18]",
"status": "PASS",
"time_seconds": 0.006,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[19]",
"status": "PASS",
"time_seconds": 0.005,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[20]",
"status": "PASS",
"time_seconds": 0.006,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[21]",
"status": "PASS",
"time_seconds": 0.006,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[22]",
"status": "PASS",
"time_seconds": 0.007,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[23]",
"status": "PASS",
"time_seconds": 0.007,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[24]",
"status": "PASS",
"time_seconds": 0.006,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[25]",
"status": "PASS",
"time_seconds": 0.007,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[26]",
"status": "PASS",
"time_seconds": 0.006,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[27]",
"status": "PASS",
"time_seconds": 0.006,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[28]",
"status": "PASS",
"time_seconds": 0.005,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[29]",
"status": "PASS",
"time_seconds": 0.005,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[30]",
"status": "PASS",
"time_seconds": 0.004,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[31]",
"status": "PASS",
"time_seconds": 0.004,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[32]",
"status": "PASS",
"time_seconds": 0.006,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[33]",
"status": "PASS",
"time_seconds": 0.004,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[34]",
"status": "PASS",
"time_seconds": 0.004,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[35]",
"status": "PASS",
"time_seconds": 0.004,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[36]",
"status": "PASS",
"time_seconds": 0.004,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[37]",
"status": "PASS",
"time_seconds": 0.006,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[38]",
"status": "PASS",
"time_seconds": 0.006,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[39]",
"status": "PASS",
"time_seconds": 0.005,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[40]",
"status": "PASS",
"time_seconds": 0.005,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[41]",
"status": "PASS",
"time_seconds": 0.005,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[42]",
"status": "PASS",
"time_seconds": 0.005,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[43]",
"status": "PASS",
"time_seconds": 0.006,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[44]",
"status": "PASS",
"time_seconds": 0.005,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[45]",
"status": "PASS",
"time_seconds": 0.004,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[46]",
"status": "PASS",
"time_seconds": 0.004,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[47]",
"status": "PASS",
"time_seconds": 0.004,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[48]",
"status": "PASS",
"time_seconds": 0.004,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[49]",
"status": "PASS",
"time_seconds": 0.005,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[50]",
"status": "PASS",
"time_seconds": 0.004,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[51]",
"status": "PASS",
"time_seconds": 0.003,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[52]",
"status": "PASS",
"time_seconds": 0.005,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[53]",
"status": "PASS",
"time_seconds": 0.005,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[54]",
"status": "PASS",
"time_seconds": 0.005,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[55]",
"status": "PASS",
"time_seconds": 0.005,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[56]",
"status": "PASS",
"time_seconds": 0.005,
"message": ""
},
{
"phase": null,
"suite": "com.ljwx.platform.contract.protocol.ProtocolContractSecurityTest",
"testcase": "authorityEndpointWithNoPermissionTokenReturns403(ProtocolEndpoint)[57]",
"status": "PASS",
"time_seconds": 0.004,
"message": ""
}
]
"tests": []
}
@@ -0,0 +1,47 @@
import request from '@/api/request'
import type { Result } from '@ljwx/shared'
export interface CustomFieldDefVO {
id: number
entityType: string
fieldKey: string
fieldLabel: string
fieldType: string
required: boolean
sortOrder: number
options?: unknown[]
createdTime: string
}
export interface CustomFieldDefCreateDTO {
entityType: string
fieldKey: string
fieldLabel: string
fieldType: string
required: boolean
sortOrder: number
options?: unknown[]
}
export interface CustomFieldDefUpdateDTO {
fieldLabel: string
required: boolean
sortOrder: number
options?: unknown[]
}
export function getCustomFieldList(entityType?: string): Promise<Result<CustomFieldDefVO[]>> {
return request.get('/api/v1/custom-fields', { params: { entityType } })
}
export function createCustomField(data: CustomFieldDefCreateDTO): Promise<Result<number>> {
return request.post('/api/v1/custom-fields', data)
}
export function updateCustomField(id: number, data: CustomFieldDefUpdateDTO): Promise<Result<void>> {
return request.put(`/api/v1/custom-fields/${id}`, data)
}
export function deleteCustomField(id: number): Promise<Result<void>> {
return request.delete(`/api/v1/custom-fields/${id}`)
}
@@ -0,0 +1,45 @@
import request from '@/api/request'
import type { Result, PageResult } from '@ljwx/shared'
export interface FormDataVO {
id: number
formDefId: number
fieldValues: Record<string, unknown>
creatorId: number
createdTime: string
updatedTime: string
}
export interface FormDataQueryDTO {
formDefId: number
creatorId?: number
startTime?: string
endTime?: string
pageNum: number
pageSize: number
}
export interface FormDataCreateDTO {
formDefId: number
fieldValues: Record<string, unknown>
}
export interface FormDataUpdateDTO {
fieldValues: Record<string, unknown>
}
export function getFormDataList(params: FormDataQueryDTO): Promise<Result<PageResult<FormDataVO>>> {
return request.get('/api/v1/form-data', { params })
}
export function getFormDataById(id: number): Promise<Result<FormDataVO>> {
return request.get(`/api/v1/form-data/${id}`)
}
export function createFormData(data: FormDataCreateDTO): Promise<Result<number>> {
return request.post('/api/v1/form-data', data)
}
export function updateFormData(id: number, data: FormDataUpdateDTO): Promise<Result<void>> {
return request.put(`/api/v1/form-data/${id}`, data)
}
@@ -0,0 +1,55 @@
import request from '@/api/request'
import type { Result, PageResult } from '@ljwx/shared'
export interface FormDefVO {
id: number
formName: string
formKey: string
schema: Record<string, unknown>
status: number
remark?: string
createdTime: string
updatedTime: string
}
export interface FormDefQueryDTO {
formName?: string
formKey?: string
status?: number
pageNum: number
pageSize: number
}
export interface FormDefCreateDTO {
formName: string
formKey: string
schema: Record<string, unknown>
remark?: string
}
export interface FormDefUpdateDTO {
formName: string
schema: Record<string, unknown>
status: number
remark?: string
}
export function getFormDefList(params: FormDefQueryDTO): Promise<Result<PageResult<FormDefVO>>> {
return request.get('/api/v1/form-defs', { params })
}
export function getFormDefById(id: number): Promise<Result<FormDefVO>> {
return request.get(`/api/v1/form-defs/${id}`)
}
export function createFormDef(data: FormDefCreateDTO): Promise<Result<number>> {
return request.post('/api/v1/form-defs', data)
}
export function updateFormDef(id: number, data: FormDefUpdateDTO): Promise<Result<void>> {
return request.put(`/api/v1/form-defs/${id}`, data)
}
export function deleteFormDef(id: number): Promise<Result<void>> {
return request.delete(`/api/v1/form-defs/${id}`)
}
@@ -0,0 +1,199 @@
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { View, Edit } from '@element-plus/icons-vue'
import {
getFormDataList,
getFormDataById,
updateFormData,
type FormDataVO,
type FormDataQueryDTO,
type FormDataUpdateDTO
} from '@/api/form/form-data'
import { getFormDefList, type FormDefVO } from '@/api/form/form-def'
const loading = ref(false)
const tableData = ref<FormDataVO[]>([])
const total = ref(0)
const formDefOptions = ref<FormDefVO[]>([])
const queryForm = reactive<FormDataQueryDTO>({
formDefId: 0,
creatorId: undefined,
startTime: undefined,
endTime: undefined,
pageNum: 1,
pageSize: 10
})
const dialogVisible = ref(false)
const dialogTitle = ref('')
const currentId = ref<number>()
const currentFormData = ref<FormDataVO>()
async function fetchFormDefs() {
try {
const res = await getFormDefList({ pageNum: 1, pageSize: 100, status: 1 })
formDefOptions.value = res.data.rows
} catch (error) {
ElMessage.error('获取表单定义失败')
}
}
async function fetchList() {
if (!queryForm.formDefId) {
ElMessage.warning('请先选择表单定义')
return
}
loading.value = true
try {
const res = await getFormDataList(queryForm)
tableData.value = res.data.rows
total.value = res.data.total
} finally {
loading.value = false
}
}
function handleQuery() {
queryForm.pageNum = 1
fetchList()
}
function handleReset() {
queryForm.formDefId = 0
queryForm.creatorId = undefined
queryForm.startTime = undefined
queryForm.endTime = undefined
queryForm.pageNum = 1
tableData.value = []
total.value = 0
}
async function handleView(row: FormDataVO) {
dialogTitle.value = '查看表单数据'
currentId.value = row.id
try {
const res = await getFormDataById(row.id)
currentFormData.value = res.data
dialogVisible.value = true
} catch (error) {
ElMessage.error('获取详情失败')
}
}
function handlePageChange(page: number) {
queryForm.pageNum = page
fetchList()
}
onMounted(() => {
fetchFormDefs()
})
</script>
<template>
<div class="form-data-container">
<el-card class="search-card">
<el-form :model="queryForm" inline>
<el-form-item label="表单定义" required>
<el-select v-model="queryForm.formDefId" placeholder="请选择表单定义" clearable style="width: 200px">
<el-option
v-for="form in formDefOptions"
:key="form.id"
:label="form.formName"
:value="form.id"
/>
</el-select>
</el-form-item>
<el-form-item label="填写人ID">
<el-input v-model.number="queryForm.creatorId" placeholder="请输入填写人ID" clearable />
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker
v-model="queryForm.startTime"
type="datetime"
placeholder="开始时间"
value-format="YYYY-MM-DD HH:mm:ss"
/>
<span style="margin: 0 10px">至</span>
<el-date-picker
v-model="queryForm.endTime"
type="datetime"
placeholder="结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">查询</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="table-card">
<el-table v-loading="loading" :data="tableData" border>
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="formDefId" label="表单定义ID" width="120" />
<el-table-column prop="creatorId" label="填写人ID" width="120" />
<el-table-column prop="createdTime" label="创建时间" width="180" />
<el-table-column prop="updatedTime" label="更新时间" width="180" />
<el-table-column label="操作" width="120" fixed="right">
<template #default="{ row }">
<el-button type="primary" :icon="View" link @click="handleView(row)">查看</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
v-model:current-page="queryForm.pageNum"
v-model:page-size="queryForm.pageSize"
:total="total"
layout="total, prev, pager, next, jumper"
@current-change="handlePageChange"
/>
</el-card>
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px">
<el-descriptions v-if="currentFormData" :column="1" border>
<el-descriptions-item label="ID">{{ currentFormData.id }}</el-descriptions-item>
<el-descriptions-item label="表单定义ID">{{ currentFormData.formDefId }}</el-descriptions-item>
<el-descriptions-item label="填写人ID">{{ currentFormData.creatorId }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ currentFormData.createdTime }}</el-descriptions-item>
<el-descriptions-item label="更新时间">{{ currentFormData.updatedTime }}</el-descriptions-item>
<el-descriptions-item label="表单数据">
<pre>{{ JSON.stringify(currentFormData.fieldValues, null, 2) }}</pre>
</el-descriptions-item>
</el-descriptions>
<template #footer>
<el-button @click="dialogVisible = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
<style scoped lang="scss">
.form-data-container {
padding: 20px;
.search-card {
margin-bottom: 20px;
}
.table-card {
.el-pagination {
margin-top: 20px;
justify-content: flex-end;
}
}
pre {
background-color: #f5f5f5;
padding: 10px;
border-radius: 4px;
max-height: 400px;
overflow: auto;
}
}
</style>
@@ -0,0 +1,302 @@
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Edit, Delete, View } from '@element-plus/icons-vue'
import {
getFormDefList,
createFormDef,
updateFormDef,
deleteFormDef,
type FormDefVO,
type FormDefQueryDTO,
type FormDefCreateDTO,
type FormDefUpdateDTO
} from '@/api/form/form-def'
const loading = ref(false)
const tableData = ref<FormDefVO[]>([])
const total = ref(0)
const queryForm = reactive<FormDefQueryDTO>({
formName: '',
formKey: '',
status: undefined,
pageNum: 1,
pageSize: 10
})
const dialogVisible = ref(false)
const dialogTitle = ref('')
const isEdit = ref(false)
const currentId = ref<number>()
const formData = reactive<FormDefCreateDTO | FormDefUpdateDTO>({
formName: '',
formKey: '',
schema: {},
remark: ''
})
interface SchemaField {
key: string
label: string
type: string
required: boolean
options?: string[]
}
interface FormSchema {
fields?: SchemaField[]
}
const schemaFields = ref<SchemaField[]>([])
const fieldTypes = [
{ label: '单行文本', value: 'TEXT' },
{ label: '多行文本', value: 'TEXTAREA' },
{ label: '数字', value: 'NUMBER' },
{ label: '日期', value: 'DATE' },
{ label: '下拉选择', value: 'SELECT' },
{ label: '复选框', value: 'CHECKBOX' }
]
async function fetchList() {
loading.value = true
try {
const res = await getFormDefList(queryForm)
tableData.value = res.data.rows
total.value = res.data.total
} finally {
loading.value = false
}
}
function handleQuery() {
queryForm.pageNum = 1
fetchList()
}
function handleReset() {
queryForm.formName = ''
queryForm.formKey = ''
queryForm.status = undefined
queryForm.pageNum = 1
fetchList()
}
function handleAdd() {
dialogTitle.value = '新增表单定义'
isEdit.value = false
currentId.value = undefined
Object.assign(formData, {
formName: '',
formKey: '',
schema: {},
remark: ''
})
schemaFields.value = []
dialogVisible.value = true
}
function handleEdit(row: FormDefVO) {
dialogTitle.value = '编辑表单定义'
isEdit.value = true
currentId.value = row.id
Object.assign(formData, {
formName: row.formName,
schema: row.schema,
status: row.status,
remark: row.remark
})
const schema = row.schema as FormSchema
if (schema && Array.isArray(schema.fields)) {
schemaFields.value = schema.fields
} else {
schemaFields.value = []
}
dialogVisible.value = true
}
async function handleDelete(row: FormDefVO) {
try {
await ElMessageBox.confirm('确认删除该表单定义吗?', '提示', {
type: 'warning'
})
await deleteFormDef(row.id)
ElMessage.success('删除成功')
fetchList()
} catch (error) {
if (error !== 'cancel') {
ElMessage.error('删除失败')
}
}
}
function addField() {
schemaFields.value.push({
key: '',
label: '',
type: 'TEXT',
required: false,
options: []
})
}
function removeField(index: number) {
schemaFields.value.splice(index, 1)
}
async function handleSubmit() {
formData.schema = {
fields: schemaFields.value
}
try {
if (isEdit.value && currentId.value) {
await updateFormDef(currentId.value, formData as FormDefUpdateDTO)
ElMessage.success('更新成功')
} else {
await createFormDef(formData as FormDefCreateDTO)
ElMessage.success('创建成功')
}
dialogVisible.value = false
fetchList()
} catch (error) {
ElMessage.error(isEdit.value ? '更新失败' : '创建失败')
}
}
function handlePageChange(page: number) {
queryForm.pageNum = page
fetchList()
}
onMounted(() => {
fetchList()
})
</script>
<template>
<div class="form-designer-container">
<el-card class="search-card">
<el-form :model="queryForm" inline>
<el-form-item label="表单名称">
<el-input v-model="queryForm.formName" placeholder="请输入表单名称" clearable />
</el-form-item>
<el-form-item label="表单标识">
<el-input v-model="queryForm.formKey" placeholder="请输入表单标识" clearable />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="queryForm.status" placeholder="请选择状态" clearable>
<el-option label="启用" :value="1" />
<el-option label="停用" :value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">查询</el-button>
<el-button @click="handleReset">重置</el-button>
<el-button type="primary" :icon="Plus" @click="handleAdd">新增</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="table-card">
<el-table v-loading="loading" :data="tableData" border>
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="formName" label="表单名称" />
<el-table-column prop="formKey" label="表单标识" />
<el-table-column prop="status" label="状态" width="80">
<template #default="{ row }">
<el-tag :type="row.status === 1 ? 'success' : 'danger'">
{{ row.status === 1 ? '启用' : '停用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" show-overflow-tooltip />
<el-table-column prop="createdTime" label="创建时间" width="180" />
<el-table-column label="操作" width="180" fixed="right">
<template #default="{ row }">
<el-button type="primary" :icon="Edit" link @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" :icon="Delete" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
v-model:current-page="queryForm.pageNum"
v-model:page-size="queryForm.pageSize"
:total="total"
layout="total, prev, pager, next, jumper"
@current-change="handlePageChange"
/>
</el-card>
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="800px">
<el-form :model="formData" label-width="100px">
<el-form-item label="表单名称" required>
<el-input v-model="formData.formName" placeholder="请输入表单名称" />
</el-form-item>
<el-form-item v-if="!isEdit" label="表单标识" required>
<el-input v-model="(formData as FormDefCreateDTO).formKey" placeholder="小写字母+数字+下划线" />
</el-form-item>
<el-form-item v-if="isEdit" label="状态" required>
<el-radio-group v-model="(formData as FormDefUpdateDTO).status">
<el-radio :value="1">启用</el-radio>
<el-radio :value="0">停用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="formData.remark" type="textarea" :rows="2" />
</el-form-item>
<el-form-item label="表单字段">
<div class="field-list">
<div v-for="(field, index) in schemaFields" :key="index" class="field-item">
<el-input v-model="field.key" placeholder="字段Key" style="width: 150px" />
<el-input v-model="field.label" placeholder="字段名称" style="width: 150px" />
<el-select v-model="field.type" placeholder="字段类型" style="width: 120px">
<el-option v-for="type in fieldTypes" :key="type.value" :label="type.label" :value="type.value" />
</el-select>
<el-checkbox v-model="field.required">必填</el-checkbox>
<el-button type="danger" :icon="Delete" link @click="removeField(index)">删除</el-button>
</div>
<el-button type="primary" :icon="Plus" @click="addField">添加字段</el-button>
</div>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<style scoped lang="scss">
.form-designer-container {
padding: 20px;
.search-card {
margin-bottom: 20px;
}
.table-card {
.el-pagination {
margin-top: 20px;
justify-content: flex-end;
}
}
.field-list {
width: 100%;
.field-item {
display: flex;
gap: 10px;
align-items: center;
margin-bottom: 10px;
}
}
}
</style>
@@ -0,0 +1,317 @@
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Edit, Delete } from '@element-plus/icons-vue'
import {
getCustomFieldList,
createCustomField,
updateCustomField,
deleteCustomField,
type CustomFieldDefVO,
type CustomFieldDefCreateDTO,
type CustomFieldDefUpdateDTO
} from '@/api/form/custom-field'
const loading = ref(false)
const tableData = ref<CustomFieldDefVO[]>([])
const entityTypeFilter = ref<string>('')
const dialogVisible = ref(false)
const dialogTitle = ref('')
const isEdit = ref(false)
const currentId = ref<number>()
const formData = reactive<CustomFieldDefCreateDTO | CustomFieldDefUpdateDTO>({
entityType: 'USER',
fieldKey: '',
fieldLabel: '',
fieldType: 'TEXT',
required: false,
sortOrder: 0,
options: []
})
const entityTypes = [
{ label: '用户', value: 'USER' },
{ label: '部门', value: 'DEPT' }
]
const fieldTypes = [
{ label: '单行文本', value: 'TEXT' },
{ label: '数字', value: 'NUMBER' },
{ label: '日期', value: 'DATE' },
{ label: '下拉选择', value: 'SELECT' },
{ label: '复选框', value: 'CHECKBOX' }
]
const optionInput = ref('')
async function fetchList() {
loading.value = true
try {
const res = await getCustomFieldList(entityTypeFilter.value || undefined)
tableData.value = res.data
} finally {
loading.value = false
}
}
function handleQuery() {
fetchList()
}
function handleReset() {
entityTypeFilter.value = ''
fetchList()
}
function handleAdd() {
dialogTitle.value = '新增自定义字段'
isEdit.value = false
currentId.value = undefined
Object.assign(formData, {
entityType: 'USER',
fieldKey: '',
fieldLabel: '',
fieldType: 'TEXT',
required: false,
sortOrder: 0,
options: []
})
optionInput.value = ''
dialogVisible.value = true
}
function handleEdit(row: CustomFieldDefVO) {
dialogTitle.value = '编辑自定义字段'
isEdit.value = true
currentId.value = row.id
Object.assign(formData, {
fieldLabel: row.fieldLabel,
required: row.required,
sortOrder: row.sortOrder,
options: row.options || []
})
optionInput.value = ''
dialogVisible.value = true
}
async function handleDelete(row: CustomFieldDefVO) {
try {
await ElMessageBox.confirm('确认删除该自定义字段吗?', '提示', {
type: 'warning'
})
await deleteCustomField(row.id)
ElMessage.success('删除成功')
fetchList()
} catch (error) {
if (error !== 'cancel') {
ElMessage.error('删除失败')
}
}
}
function addOption() {
if (!optionInput.value.trim()) {
ElMessage.warning('请输入选项内容')
return
}
if (!formData.options) {
formData.options = []
}
formData.options.push(optionInput.value.trim())
optionInput.value = ''
}
function removeOption(index: number) {
if (formData.options) {
formData.options.splice(index, 1)
}
}
async function handleSubmit() {
try {
if (isEdit.value && currentId.value) {
await updateCustomField(currentId.value, formData as CustomFieldDefUpdateDTO)
ElMessage.success('更新成功')
} else {
await createCustomField(formData as CustomFieldDefCreateDTO)
ElMessage.success('创建成功')
}
dialogVisible.value = false
fetchList()
} catch (error) {
ElMessage.error(isEdit.value ? '更新失败' : '创建失败')
}
}
onMounted(() => {
fetchList()
})
</script>
<template>
<div class="custom-field-container">
<el-card class="search-card">
<el-form inline>
<el-form-item label="实体类型">
<el-select v-model="entityTypeFilter" placeholder="请选择实体类型" clearable>
<el-option
v-for="type in entityTypes"
:key="type.value"
:label="type.label"
:value="type.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">查询</el-button>
<el-button @click="handleReset">重置</el-button>
<el-button type="primary" :icon="Plus" @click="handleAdd">新增</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="table-card">
<el-table v-loading="loading" :data="tableData" border>
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="entityType" label="实体类型" width="100">
<template #default="{ row }">
{{ entityTypes.find(t => t.value === row.entityType)?.label }}
</template>
</el-table-column>
<el-table-column prop="fieldKey" label="字段Key" />
<el-table-column prop="fieldLabel" label="字段名称" />
<el-table-column prop="fieldType" label="字段类型" width="120">
<template #default="{ row }">
{{ fieldTypes.find(t => t.value === row.fieldType)?.label }}
</template>
</el-table-column>
<el-table-column prop="required" label="是否必填" width="100">
<template #default="{ row }">
<el-tag :type="row.required ? 'danger' : 'info'">
{{ row.required ? '必填' : '非必填' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="sortOrder" label="排序" width="80" />
<el-table-column prop="createdTime" label="创建时间" width="180" />
<el-table-column label="操作" width="180" fixed="right">
<template #default="{ row }">
<el-button type="primary" :icon="Edit" link @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" :icon="Delete" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px">
<el-form :model="formData" label-width="100px">
<el-form-item v-if="!isEdit" label="实体类型" required>
<el-select v-model="(formData as CustomFieldDefCreateDTO).entityType" placeholder="请选择实体类型">
<el-option
v-for="type in entityTypes"
:key="type.value"
:label="type.label"
:value="type.value"
/>
</el-select>
</el-form-item>
<el-form-item v-if="!isEdit" label="字段Key" required>
<el-input v-model="(formData as CustomFieldDefCreateDTO).fieldKey" placeholder="小写字母+数字+下划线" />
</el-form-item>
<el-form-item v-if="!isEdit" label="字段类型" required>
<el-select v-model="(formData as CustomFieldDefCreateDTO).fieldType" placeholder="请选择字段类型">
<el-option
v-for="type in fieldTypes"
:key="type.value"
:label="type.label"
:value="type.value"
/>
</el-select>
</el-form-item>
<el-form-item label="字段名称" required>
<el-input v-model="formData.fieldLabel" placeholder="请输入字段名称" />
</el-form-item>
<el-form-item label="是否必填" required>
<el-switch v-model="formData.required" />
</el-form-item>
<el-form-item label="排序">
<el-input-number v-model="formData.sortOrder" :min="0" />
</el-form-item>
<el-form-item
v-if="!isEdit && ((formData as CustomFieldDefCreateDTO).fieldType === 'SELECT' || (formData as CustomFieldDefCreateDTO).fieldType === 'CHECKBOX')"
label="选项配置"
>
<div class="options-config">
<div class="option-input">
<el-input v-model="optionInput" placeholder="输入选项内容" />
<el-button type="primary" @click="addOption">添加</el-button>
</div>
<div class="option-list">
<el-tag
v-for="(option, index) in formData.options"
:key="index"
closable
@close="removeOption(index)"
>
{{ option }}
</el-tag>
</div>
</div>
</el-form-item>
<el-form-item
v-if="isEdit && formData.options && formData.options.length > 0"
label="选项配置"
>
<div class="options-config">
<div class="option-input">
<el-input v-model="optionInput" placeholder="输入选项内容" />
<el-button type="primary" @click="addOption">添加</el-button>
</div>
<div class="option-list">
<el-tag
v-for="(option, index) in formData.options"
:key="index"
closable
@close="removeOption(index)"
>
{{ option }}
</el-tag>
</div>
</div>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<style scoped lang="scss">
.custom-field-container {
padding: 20px;
.search-card {
margin-bottom: 20px;
}
.options-config {
width: 100%;
.option-input {
display: flex;
gap: 10px;
margin-bottom: 10px;
}
.option-list {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
}
}
</style>
+45
View File
@@ -0,0 +1,45 @@
interface ApiResult<T = unknown> {
code: number
message: string
data: T
}
const BASE_URL = import.meta.env.VITE_APP_BASE_API || '/api'
function request<T>(options: UniApp.RequestOptions): Promise<T> {
const token = uni.getStorageSync('token')
const tenantId = uni.getStorageSync('tenantId')
return new Promise((resolve, reject) => {
uni.request({
...options,
url: `${BASE_URL}${options.url}`,
header: {
Authorization: token ? `Bearer ${token}` : '',
'X-Tenant-Id': tenantId || '',
'Content-Type': 'application/json',
...options.header,
},
success: (res) => {
const data = res.data as ApiResult<T>
if (data.code === 200) {
resolve(data.data)
} else if (data.code === 401) {
uni.removeStorageSync('token')
uni.removeStorageSync('tenantId')
uni.redirectTo({ url: '/pages/login/index' })
reject(new Error('UNAUTHORIZED'))
} else {
uni.showToast({ title: data.message || '请求失败', icon: 'none' })
reject(new Error(data.message))
}
},
fail: (err) => {
uni.showToast({ title: '网络错误', icon: 'none' })
reject(err)
},
})
})
}
export default request
@@ -0,0 +1,224 @@
#!/usr/bin/env python3
"""
Phase 29-53 Implementation Completeness Checker
Analyzes phase specifications vs actual implementation
"""
import os
import re
import yaml
from pathlib import Path
from typing import Dict, List, Set, Tuple
from dataclasses import dataclass
@dataclass
class PhaseInfo:
phase_num: int
title: str
status: str
backend_required: bool
frontend_required: bool
spec_files: List[str]
manifest_files: List[str]
missing_files: List[str]
extra_files: List[str]
def parse_phase_spec(spec_path: Path) -> Tuple[bool, bool, List[str]]:
"""Parse phase spec file to extract backend/frontend requirements and scope"""
with open(spec_path, 'r', encoding='utf-8') as f:
content = f.read()
# Extract YAML frontmatter
match = re.search(r'^---\n(.*?)\n---', content, re.DOTALL | re.MULTILINE)
if not match:
return False, False, []
try:
frontmatter = yaml.safe_load(match.group(1))
backend = frontmatter.get('targets', {}).get('backend', False)
frontend = frontmatter.get('targets', {}).get('frontend', False)
scope = frontmatter.get('scope', [])
return backend, frontend, scope
except:
return False, False, []
def parse_manifest() -> Dict[int, Tuple[str, str, List[str]]]:
"""Parse PHASE_MANIFEST.txt to extract phase info"""
manifest_path = Path('/Users/brunogao/work/codes/ljwx/ljwx-platform/PHASE_MANIFEST.txt')
phases = {}
with open(manifest_path, 'r', encoding='utf-8') as f:
content = f.read()
# Split by phase sections
phase_sections = re.split(r'\n## PHASE (\d+)\n', content)
for i in range(1, len(phase_sections), 2):
phase_num = int(phase_sections[i])
if phase_num < 29 or phase_num > 53:
continue
section = phase_sections[i + 1]
# Extract title
title_match = re.search(r'Title: (.+)', section)
title = title_match.group(1) if title_match else "Unknown"
# Extract status
status_match = re.search(r'Status: (\w+)', section)
status = status_match.group(1) if status_match else "UNKNOWN"
# Extract files
files = []
in_files_section = False
for line in section.split('\n'):
if line.startswith('Files:'):
in_files_section = True
continue
if in_files_section:
if line.startswith('Status:') or line.startswith('Gate:') or line.startswith('##'):
break
if line.strip().startswith('- '):
# Remove quotes and leading dash
file_path = line.strip()[2:].strip('"')
if file_path and file_path != '--':
files.append(file_path)
phases[phase_num] = (title, status, files)
return phases
def check_file_exists(file_path: str, base_dir: Path) -> bool:
"""Check if a file exists relative to base directory"""
full_path = base_dir / file_path
return full_path.exists()
def analyze_phase(phase_num: int, base_dir: Path) -> PhaseInfo:
"""Analyze a single phase for completeness"""
spec_path = base_dir / f'spec/phase/phase-{phase_num}.md'
# Parse spec file
if not spec_path.exists():
return PhaseInfo(
phase_num=phase_num,
title="SPEC NOT FOUND",
status="NOT_STARTED",
backend_required=False,
frontend_required=False,
spec_files=[],
manifest_files=[],
missing_files=[],
extra_files=[]
)
backend_req, frontend_req, spec_files = parse_phase_spec(spec_path)
# Parse manifest
manifest_data = parse_manifest()
if phase_num not in manifest_data:
return PhaseInfo(
phase_num=phase_num,
title="NOT IN MANIFEST",
status="NOT_STARTED",
backend_required=backend_req,
frontend_required=frontend_req,
spec_files=spec_files,
manifest_files=[],
missing_files=spec_files,
extra_files=[]
)
title, status, manifest_files = manifest_data[phase_num]
# Check file existence
spec_set = set(spec_files)
manifest_set = set(manifest_files)
missing_files = []
for file_path in spec_set:
if not check_file_exists(file_path, base_dir):
missing_files.append(file_path)
extra_files = list(manifest_set - spec_set)
return PhaseInfo(
phase_num=phase_num,
title=title,
status=status,
backend_required=backend_req,
frontend_required=frontend_req,
spec_files=spec_files,
manifest_files=manifest_files,
missing_files=missing_files,
extra_files=extra_files
)
def main():
base_dir = Path('/Users/brunogao/work/codes/ljwx/ljwx-platform')
print("=" * 80)
print("Phase 29-53 Implementation Completeness Analysis")
print("=" * 80)
print()
complete_count = 0
partial_count = 0
not_started_count = 0
for phase_num in range(29, 54):
info = analyze_phase(phase_num, base_dir)
# Determine completeness
if info.status == "NOT_STARTED":
status_symbol = "❌"
completeness = "NOT_STARTED"
not_started_count += 1
elif len(info.missing_files) == 0:
status_symbol = "✅"
completeness = "COMPLETE"
complete_count += 1
else:
status_symbol = "⚠️"
completeness = "INCOMPLETE"
partial_count += 1
print(f"Phase {phase_num}: {status_symbol} {info.title}")
print(f" Status: {info.status}")
print(f" Backend: {'required' if info.backend_required else 'not required'}")
print(f" Frontend: {'required' if info.frontend_required else 'not required'}")
print(f" Files: {len(info.spec_files)} specified, {len(info.manifest_files)} in manifest")
if info.missing_files:
print(f" ⚠️ Missing files ({len(info.missing_files)}):")
for f in info.missing_files[:5]: # Show first 5
print(f" - {f}")
if len(info.missing_files) > 5:
print(f" ... and {len(info.missing_files) - 5} more")
if info.extra_files:
print(f" ℹ️ Extra files in manifest ({len(info.extra_files)}):")
for f in info.extra_files[:3]:
print(f" - {f}")
if len(info.extra_files) > 3:
print(f" ... and {len(info.extra_files) - 3} more")
print(f" Completeness: {completeness}")
print()
print("=" * 80)
print("Summary")
print("=" * 80)
print(f"✅ Complete phases: {complete_count}/25")
print(f"⚠️ Incomplete phases: {partial_count}/25")
print(f"❌ Not started phases: {not_started_count}/25")
print()
if complete_count == 25:
print("🎉 All phases 29-53 are fully implemented!")
elif partial_count > 0:
print(f"⚠️ {partial_count} phases need attention - some files are missing")
if not_started_count > 0:
print(f"❌ {not_started_count} phases have not been started")
if __name__ == '__main__':
main()