1、必填:工作流的必填会受到插件的影响,这里只是取了字段方案里的必填信息。

2、权限:Condition这里没有提取,因为也是存在插件原因范围比较难确定。

import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.workflow.JiraWorkflow
import com.atlassian.jira.issue.fields.screen.FieldScreen
import com.atlassian.jira.issue.fields.screen.FieldScreenTab
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.fields.CustomField;

def fieldLayoutManager = ComponentAccessor.getFieldLayoutManager()
def workflowManager = ComponentAccessor.getWorkflowManager()
def fieldScreenManager = ComponentAccessor.getFieldScreenManager()
def fieldManager = ComponentAccessor.getFieldManager()

//指定一下Issue获取工作流
MutableIssue issue = Issues.getByKey('DEMO-32') as MutableIssue
JiraWorkflow workflow = workflowManager.getWorkflow(issue)

def out = new StringBuilder()
out << "<h2>${workflow.name}</h2>"
out << "<table class='aui' id='myTable'>"
out << "<tr><th>状态(From -> To)</th><th>动作/转换</th><th>填写字段</th><th>是否必填</th><th>字段选项</th></tr>"

workflow.getAllActions().each { action ->
    def transitionName = action.getName()
    def screenId = action.getMetaAttributes()?.get("jira.fieldscreen.id")
    //排除掉创建页面
    if("Create".equals(transitionName)) {
        return
    }

    // 拼 from → to 状态
    def statusDesc = workflow.getStepsForTransition(action).collect { step ->
        def fromStatus = step.getName()
        def toStatus = workflow.getDescriptor().getStep(action.getUnconditionalResult().getStep()).getName()
        "${fromStatus} -> ${toStatus}"
    }.join("<br>")

    action.getConditionalResults()

    if (screenId) {
        FieldScreen screen = fieldScreenManager.getFieldScreen(screenId as Long)


        // 收集字段
        def fieldRows = []
        screen?.getTabs()?.each { FieldScreenTab tab ->
            tab.getFieldScreenLayoutItems().each { item ->
                def fieldId = item.getFieldId()
                def field = fieldManager.getField(fieldId)
                def layout = fieldLayoutManager.getFieldLayout(issue)
                def required = layout?.getFieldLayoutItem(fieldId)?.isRequired() ? '必填' : ''
                def optionsText = ""
                if (field instanceof CustomField) {
                    optionsText = getOptions(field as CustomField, issue)
                }
                fieldRows << [name: field?.name, id: fieldId, required: required,
                    options: optionsText]
            }
            
        }
        // 🔽 排序:先按字段名,再按 ID
        fieldRows.sort { a, b -> 
            b.required <=> a.required
        }

        if (fieldRows) {
            def rowspan = fieldRows.size()
            // 第一行带 rowspan
            def first = fieldRows[0]
            out << "<tr><td rowspan='${rowspan}'>${statusDesc}</td>"
            out << "<td rowspan='${rowspan}'>${transitionName} (${screen?.name})</td>"
            out << "<td>${first.name} (${first.id})</td><td>${first.required}</td><td>${first.options}</td></tr>"

            // 剩余行
            fieldRows.drop(1).each { f ->
                out << "<tr><td>${f.name} (${f.id})</td><td>${f.required}</td><td>${f.options}</td></tr>"
            }
        } else {
            //界面没有字段,默认输入备注
            log.warn("============================${screen.name}")
            out << "<tr><td>${statusDesc}</td>"
            out << "<td>${transitionName} (${screen?.name})</td>"
            out << "<td>备注</td><td></td><td></td></tr>"
        }
    } else {
        out << "<tr><td>${statusDesc}</td><td>${transitionName}(无输入项)</td><td colspan='3'> </td></tr>"
    }
}

out << "</table>"

// 在表格前加按钮
out.insert(out.indexOf("<h2>"),
    """
    <button class='aui-button'
        onclick="
            var table=document.getElementById('myTable')||document.querySelector('table');
            if(!table)return;
            var html=table.outerHTML;
            var text=table.innerText;
            navigator.clipboard.write([
                new ClipboardItem({
                    'text/html': new Blob([html], {type: 'text/html'}),
                    'text/plain': new Blob([text], {type: 'text/plain'})
                })
            ]).then(function(){
                var myFlag = AJS.flag({
                    type: 'success',
                    body: '复制成功',
                    duration: 1000,
                    close: 'auto'
                });
            }).catch(function(err){
                console.error(err);
                alert('复制失败,浏览器可能不支持 ClipboardItem API');
            });
        ">
        复制表格
    </button>
    """
)

return out.toString()

// 获取某个字段的全部选项(仅限选择类字段)
def getOptions(CustomField cf, MutableIssue issue) {
def optionsManager = ComponentAccessor.getOptionsManager()
    def config = cf.getRelevantConfig(issue)
    if (!config) {
        return ""
    }
    def options = optionsManager.getOptions(config)
    if (!options) {
        return ""
    }
    return options.collect { opt ->
        (opt.disabled ? "[禁用] " : "") + opt.value
    }.join(" / ")
}