feat(CRUD): feature/deliverable CRUD页面+appcodes下拉+统一dspy命名(修复URL不匹配bug)

This commit is contained in:
ymq 2026-08-17 11:35:03 +08:00
parent ad29dfab49
commit 8f2f7a11da
35 changed files with 416 additions and 0 deletions

2
.gitignore vendored
View File

@ -14,6 +14,8 @@ wwwroot/sd_bugs/
wwwroot/sd_deploy_envs/
wwwroot/sd_org_settings/
wwwroot/sd_project_repos/
wwwroot/sd_features/
wwwroot/pipeline_deliverables/
# venv
py3/

View File

@ -102,6 +102,17 @@
{"k": "delivered", "v": "已交付"},
{"k": "verified", "v": "已验证"},
{"k": "rejected", "v": "已驳回"}
]},
{"parentid": "deliverable_type", "parentname": "交付件类型", "items": [
{"k": "code", "v": "代码"},
{"k": "doc", "v": "文档"},
{"k": "config", "v": "配置"},
{"k": "test-report", "v": "测试报告"}
]},
{"parentid": "review_status", "parentname": "评审状态", "items": [
{"k": "pending", "v": "待评审"},
{"k": "approved", "v": "通过"},
{"k": "rejected", "v": "驳回"}
]}
]
}

View File

@ -0,0 +1,67 @@
{
"tblname": "pipeline_deliverables",
"title": "交付件管理",
"params": {
"sortby": [
"created_at desc"
],
"confidential_fields": [],
"browserfields": {
"exclouded": [
"content",
"file_path",
"target_path"
],
"alters": {
"project_id": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_project_options.dspy')}}",
"valueField": "value",
"textField": "text"
},
"deliverable_type": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_deliverable_type.dspy')}}",
"valueField": "value",
"textField": "text"
},
"review_status": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_review_status.dspy')}}",
"valueField": "value",
"textField": "text"
}
}
},
"editexclouded": [
"id",
"created_at",
"reviewed_by",
"created_by"
],
"editable": {
"new_data_url": "{{entire_url('../api/sd_deliverable_create.dspy')}}",
"update_data_url": "{{entire_url('../api/sd_deliverable_update.dspy')}}",
"delete_data_url": "{{entire_url('../api/sd_deliverable_delete.dspy')}}"
},
"data_filter": {
"AND": [
{
"field": "title",
"op": "LIKE",
"var": "title_input"
},
{
"field": "review_status",
"op": "=",
"var": "status_input"
},
{
"field": "deliverable_type",
"op": "=",
"var": "type_input"
}
]
}
}
}

View File

@ -0,0 +1,79 @@
{
"tblname": "sd_features",
"title": "功能管理",
"params": {
"sortby": [
"created_at desc"
],
"confidential_fields": [],
"browserfields": {
"exclouded": [
"acceptance_criteria",
"task_id"
],
"alters": {
"project_id": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_project_options.dspy')}}",
"valueField": "value",
"textField": "text"
},
"iteration_id": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_iteration_options.dspy')}}",
"valueField": "value",
"textField": "text"
},
"feature_type": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_feature_type.dspy')}}",
"valueField": "value",
"textField": "text"
},
"priority": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_priority.dspy')}}",
"valueField": "value",
"textField": "text"
},
"status": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_feature_status.dspy')}}",
"valueField": "value",
"textField": "text"
}
}
},
"editexclouded": [
"id",
"created_at",
"updated_at",
"task_id",
"created_by"
],
"editable": {
"new_data_url": "{{entire_url('../api/sd_feature_create.dspy')}}",
"update_data_url": "{{entire_url('../api/sd_feature_update.dspy')}}",
"delete_data_url": "{{entire_url('../api/sd_feature_delete.dspy')}}"
},
"data_filter": {
"AND": [
{
"field": "feature_name",
"op": "LIKE",
"var": "name_input"
},
{
"field": "status",
"op": "=",
"var": "status_input"
},
{
"field": "feature_type",
"op": "=",
"var": "type_input"
}
]
}
}
}

View File

@ -0,0 +1,9 @@
result = [{'value': '', 'text': '全部'}]
try:
async with get_sor_context(request._run_ns, 'pipeline') as sor:
rows = await sor.sqlExe("select k as value, v as text from appcodes_kv where parentid=${parentid}$ order by v", {'parentid': 'deliverable_type'})
result = [{'value': '', 'text': '全部'}] + list(rows)
return json.dumps(result, ensure_ascii=False)
except Exception as e:
debug(f'search error: {e}')
return json.dumps(result, ensure_ascii=False)

View File

@ -0,0 +1,9 @@
result = [{'value': '', 'text': '全部'}]
try:
async with get_sor_context(request._run_ns, 'pipeline') as sor:
rows = await sor.sqlExe("select k as value, v as text from appcodes_kv where parentid=${parentid}$ order by v", {'parentid': 'sd_feature_status'})
result = [{'value': '', 'text': '全部'}] + list(rows)
return json.dumps(result, ensure_ascii=False)
except Exception as e:
debug(f'search error: {e}')
return json.dumps(result, ensure_ascii=False)

View File

@ -0,0 +1,9 @@
result = [{'value': '', 'text': '全部'}]
try:
async with get_sor_context(request._run_ns, 'pipeline') as sor:
rows = await sor.sqlExe("select k as value, v as text from appcodes_kv where parentid=${parentid}$ order by v", {'parentid': 'sd_feature_type'})
result = [{'value': '', 'text': '全部'}] + list(rows)
return json.dumps(result, ensure_ascii=False)
except Exception as e:
debug(f'search error: {e}')
return json.dumps(result, ensure_ascii=False)

View File

@ -0,0 +1,9 @@
result = [{'value': '', 'text': '全部'}]
try:
async with get_sor_context(request._run_ns, 'pipeline') as sor:
rows = await sor.sqlExe("select k as value, v as text from appcodes_kv where parentid=${parentid}$ order by v", {'parentid': 'review_status'})
result = [{'value': '', 'text': '全部'}] + list(rows)
return json.dumps(result, ensure_ascii=False)
except Exception as e:
debug(f'search error: {e}')
return json.dumps(result, ensure_ascii=False)

View File

@ -0,0 +1,26 @@
user_id = await get_user()
if not user_id:
return {"widgettype":"Error","options":{"title":"操作失败","message":'未登录',"cwidth":16,"cheight":9,"timeout":3}}
data = {
'id': getID(),
'project_id': params_kw.get('project_id', ''),
'task_id': params_kw.get('task_id', ''),
'title': params_kw.get('title', ''),
'deliverable_type': params_kw.get('deliverable_type', 'code'),
'repo_name': params_kw.get('repo_name', ''),
'target_path': params_kw.get('target_path', ''),
'file_path': params_kw.get('file_path', ''),
'content': params_kw.get('content', ''),
'quality_score': params_kw.get('quality_score', 80),
'review_status': params_kw.get('review_status', 'pending'),
'created_by': user_id,
'created_at': curDateString(),
}
try:
async with get_sor_context(request._run_ns, 'pipeline') as sor:
await sor.C('pipeline_deliverables', data)
return {"widgettype":"Message","options":{"title":"创建成功","message":"ok","cwidth":16,"cheight":9,"timeout":3,"user_data": data}}
except Exception as e:
return {"widgettype":"Error","options":{"title":"操作失败","message":str(e),"cwidth":16,"cheight":9,"timeout":3}}

View File

@ -0,0 +1,14 @@
user_id = await get_user()
if not user_id:
return {"widgettype":"Error","options":{"title":"操作失败","message":'未登录',"cwidth":16,"cheight":9,"timeout":3}}
rec_id = params_kw.get('id', '')
if not rec_id:
return {"widgettype":"Error","options":{"title":"操作失败","message":'缺少id',"cwidth":16,"cheight":9,"timeout":3}}
try:
async with get_sor_context(request._run_ns, 'pipeline') as sor:
await sor.D('pipeline_deliverables', {'id': rec_id})
return {"widgettype":"Message","options":{"title":"操作成功","message":"ok","cwidth":16,"cheight":9,"timeout":3}}
except Exception as e:
return {"widgettype":"Error","options":{"title":"操作失败","message":str(e),"cwidth":16,"cheight":9,"timeout":3}}

View File

@ -0,0 +1,19 @@
user_id = await get_user()
if not user_id:
return {"widgettype":"Error","options":{"title":"操作失败","message":'未登录',"cwidth":16,"cheight":9,"timeout":3}}
rec_id = params_kw.get('id', '')
if not rec_id:
return {"widgettype":"Error","options":{"title":"操作失败","message":'缺少id',"cwidth":16,"cheight":9,"timeout":3}}
data = {'id': rec_id}
for f in ['project_id', 'task_id', 'title', 'deliverable_type', 'repo_name', 'target_path', 'file_path', 'content', 'quality_score', 'review_status', 'review_comment']:
if f in params_kw:
data[f] = params_kw[f]
try:
async with get_sor_context(request._run_ns, 'pipeline') as sor:
await sor.U('pipeline_deliverables', data)
return {"widgettype":"Message","options":{"title":"操作成功","message":"ok","cwidth":16,"cheight":9,"timeout":3}}
except Exception as e:
return {"widgettype":"Error","options":{"title":"操作失败","message":str(e),"cwidth":16,"cheight":9,"timeout":3}}

View File

@ -0,0 +1,24 @@
user_id = await get_user()
if not user_id:
return {"widgettype":"Error","options":{"title":"操作失败","message":'未登录',"cwidth":16,"cheight":9,"timeout":3}}
data = {
'id': getID(),
'project_id': params_kw.get('project_id', ''),
'iteration_id': params_kw.get('iteration_id', ''),
'feature_name': params_kw.get('feature_name', ''),
'description': params_kw.get('description', ''),
'feature_type': params_kw.get('feature_type', 'new_feature'),
'priority': params_kw.get('priority', 'P2'),
'status': params_kw.get('status', 'proposed'),
'acceptance_criteria': params_kw.get('acceptance_criteria', ''),
'created_by': user_id,
'created_at': curDateString(),
}
try:
async with get_sor_context(request._run_ns, 'pipeline') as sor:
await sor.C('sd_features', data)
return {"widgettype":"Message","options":{"title":"创建成功","message":"ok","cwidth":16,"cheight":9,"timeout":3,"user_data": data}}
except Exception as e:
return {"widgettype":"Error","options":{"title":"操作失败","message":str(e),"cwidth":16,"cheight":9,"timeout":3}}

View File

@ -0,0 +1,14 @@
user_id = await get_user()
if not user_id:
return {"widgettype":"Error","options":{"title":"操作失败","message":'未登录',"cwidth":16,"cheight":9,"timeout":3}}
rec_id = params_kw.get('id', '')
if not rec_id:
return {"widgettype":"Error","options":{"title":"操作失败","message":'缺少id',"cwidth":16,"cheight":9,"timeout":3}}
try:
async with get_sor_context(request._run_ns, 'pipeline') as sor:
await sor.D('sd_features', {'id': rec_id})
return {"widgettype":"Message","options":{"title":"操作成功","message":"ok","cwidth":16,"cheight":9,"timeout":3}}
except Exception as e:
return {"widgettype":"Error","options":{"title":"操作失败","message":str(e),"cwidth":16,"cheight":9,"timeout":3}}

View File

@ -0,0 +1,19 @@
user_id = await get_user()
if not user_id:
return {"widgettype":"Error","options":{"title":"操作失败","message":'未登录',"cwidth":16,"cheight":9,"timeout":3}}
rec_id = params_kw.get('id', '')
if not rec_id:
return {"widgettype":"Error","options":{"title":"操作失败","message":'缺少id',"cwidth":16,"cheight":9,"timeout":3}}
data = {'id': rec_id, 'updated_at': curDateString()}
for f in ['project_id', 'iteration_id', 'feature_name', 'description', 'feature_type', 'priority', 'status', 'acceptance_criteria']:
if f in params_kw:
data[f] = params_kw[f]
try:
async with get_sor_context(request._run_ns, 'pipeline') as sor:
await sor.U('sd_features', data)
return {"widgettype":"Message","options":{"title":"操作成功","message":"ok","cwidth":16,"cheight":9,"timeout":3}}
except Exception as e:
return {"widgettype":"Error","options":{"title":"操作失败","message":str(e),"cwidth":16,"cheight":9,"timeout":3}}

View File

@ -0,0 +1,18 @@
user_id = await get_user()
if not user_id:
return {"widgettype":"Error","options":{"title":"操作失败","message":'未登录',"cwidth":16,"cheight":9,"timeout":3}}
data = {
'id': getID(),
'org_id': params_kw.get('org_id', '0'),
'workspace_root': params_kw.get('workspace_root', ''),
'skills_dir': params_kw.get('skills_dir', ''),
'created_at': curDateString(),
}
try:
async with get_sor_context(request._run_ns, 'pipeline') as sor:
await sor.C('sd_org_settings', data)
return {"widgettype":"Message","options":{"title":"创建成功","message":"ok","cwidth":16,"cheight":9,"timeout":3,"user_data": data}}
except Exception as e:
return {"widgettype":"Error","options":{"title":"操作失败","message":str(e),"cwidth":16,"cheight":9,"timeout":3}}

View File

@ -0,0 +1,14 @@
user_id = await get_user()
if not user_id:
return {"widgettype":"Error","options":{"title":"操作失败","message":'未登录',"cwidth":16,"cheight":9,"timeout":3}}
rec_id = params_kw.get('id', '')
if not rec_id:
return {"widgettype":"Error","options":{"title":"操作失败","message":'缺少id',"cwidth":16,"cheight":9,"timeout":3}}
try:
async with get_sor_context(request._run_ns, 'pipeline') as sor:
await sor.D('sd_org_settings', {'id': rec_id})
return {"widgettype":"Message","options":{"title":"操作成功","message":"ok","cwidth":16,"cheight":9,"timeout":3}}
except Exception as e:
return {"widgettype":"Error","options":{"title":"操作失败","message":str(e),"cwidth":16,"cheight":9,"timeout":3}}

View File

@ -0,0 +1,19 @@
user_id = await get_user()
if not user_id:
return {"widgettype":"Error","options":{"title":"操作失败","message":'未登录',"cwidth":16,"cheight":9,"timeout":3}}
rec_id = params_kw.get('id', '')
if not rec_id:
return {"widgettype":"Error","options":{"title":"操作失败","message":'缺少id',"cwidth":16,"cheight":9,"timeout":3}}
data = {'id': rec_id, 'updated_at': curDateString()}
for f in ['org_id', 'workspace_root', 'skills_dir']:
if f in params_kw:
data[f] = params_kw[f]
try:
async with get_sor_context(request._run_ns, 'pipeline') as sor:
await sor.U('sd_org_settings', data)
return {"widgettype":"Message","options":{"title":"操作成功","message":"ok","cwidth":16,"cheight":9,"timeout":3}}
except Exception as e:
return {"widgettype":"Error","options":{"title":"操作失败","message":str(e),"cwidth":16,"cheight":9,"timeout":3}}

View File

@ -0,0 +1,21 @@
user_id = await get_user()
if not user_id:
return {"widgettype":"Error","options":{"title":"操作失败","message":'未登录',"cwidth":16,"cheight":9,"timeout":3}}
data = {
'id': getID(),
'project_id': params_kw.get('project_id', ''),
'repo_name': params_kw.get('repo_name', ''),
'repo_url': params_kw.get('repo_url', ''),
'default_branch': params_kw.get('default_branch', 'main'),
'local_path': params_kw.get('local_path', ''),
'org_id': params_kw.get('org_id', '0'),
'created_at': curDateString(),
}
try:
async with get_sor_context(request._run_ns, 'pipeline') as sor:
await sor.C('sd_project_repos', data)
return {"widgettype":"Message","options":{"title":"创建成功","message":"ok","cwidth":16,"cheight":9,"timeout":3,"user_data": data}}
except Exception as e:
return {"widgettype":"Error","options":{"title":"操作失败","message":str(e),"cwidth":16,"cheight":9,"timeout":3}}

View File

@ -0,0 +1,14 @@
user_id = await get_user()
if not user_id:
return {"widgettype":"Error","options":{"title":"操作失败","message":'未登录',"cwidth":16,"cheight":9,"timeout":3}}
rec_id = params_kw.get('id', '')
if not rec_id:
return {"widgettype":"Error","options":{"title":"操作失败","message":'缺少id',"cwidth":16,"cheight":9,"timeout":3}}
try:
async with get_sor_context(request._run_ns, 'pipeline') as sor:
await sor.D('sd_project_repos', {'id': rec_id})
return {"widgettype":"Message","options":{"title":"操作成功","message":"ok","cwidth":16,"cheight":9,"timeout":3}}
except Exception as e:
return {"widgettype":"Error","options":{"title":"操作失败","message":str(e),"cwidth":16,"cheight":9,"timeout":3}}

View File

@ -0,0 +1,19 @@
user_id = await get_user()
if not user_id:
return {"widgettype":"Error","options":{"title":"操作失败","message":'未登录',"cwidth":16,"cheight":9,"timeout":3}}
rec_id = params_kw.get('id', '')
if not rec_id:
return {"widgettype":"Error","options":{"title":"操作失败","message":'缺少id',"cwidth":16,"cheight":9,"timeout":3}}
data = {'id': rec_id}
for f in ['project_id', 'repo_name', 'repo_url', 'default_branch', 'local_path']:
if f in params_kw:
data[f] = params_kw[f]
try:
async with get_sor_context(request._run_ns, 'pipeline') as sor:
await sor.U('sd_project_repos', data)
return {"widgettype":"Message","options":{"title":"操作成功","message":"ok","cwidth":16,"cheight":9,"timeout":3}}
except Exception as e:
return {"widgettype":"Error","options":{"title":"操作失败","message":str(e),"cwidth":16,"cheight":9,"timeout":3}}