1、新建NTSC_ReqQua实体类,一切数据处理的基础

Class User.NTSCReqQua Extends %Persistent [ ClassType = persistent, SqlTableName = NTSC_ReqQua, StorageStrategy = SQLStorageStorage ]
{

/// 需求序号
Property ntscno As %String [ Required, SqlColumnNumber = 2, SqlFieldName = NTSC_NO ];

/// 需求标题
Property ntsctitle As %String(MAXLEN = 1800) [ Required, SqlColumnNumber = 3, SqlFieldName = NTSC_Title ];

/// 质控类型
Property ntsctype As %String(MAXLEN = 1800) [ SqlColumnNumber = 4, SqlFieldName = NTSC_Type ];

/// 投诉类型
Property ntsctstype As %String(MAXLEN = 1800) [ SqlColumnNumber = 5, SqlFieldName = NTSC_TSType ];

/// 投诉内容
Property ntsctstext As %String(MAXLEN = 1800) [ SqlColumnNumber = 6, SqlFieldName = NTSC_TSText ];

/// 质量分级
Property ntsclevel As %String [ SqlColumnNumber = 7, SqlFieldName = NTSC_Level ];

/// 质量问题类型
Property ntsclevreq As %String(MAXLEN = 1800) [ SqlColumnNumber = 8, SqlFieldName = NTSC_LevReq ];

/// 质量问题描述
Property ntsclevreqdesc As %String(MAXLEN = 1800) [ SqlColumnNumber = 9, SqlFieldName = NTSC_LevReqDesc ];

/// 质检状态
Property ntscstratus As %String [ SqlColumnNumber = 10, SqlFieldName = NTSC_LevStratus ];

/// 案例需求
Property ntscmearn As %String(MAXLEN = 1800) [ SqlColumnNumber = 11, SqlFieldName = NTSC_Meran ];

/// 改进意见
Property ntsctrans As %String(MAXLEN = 1800) [ SqlColumnNumber = 12, SqlFieldName = NTSC_Trans ];

/// 交付中心
Property ntscdelcenter As %String [ SqlColumnNumber = 13, SqlFieldName = NTSC_DelCenter ];

/// 所属分区
Property ntscaffi As %String [ SqlColumnNumber = 14, SqlFieldName = NTSC_Affil ];

/// 发起人
Property ntscsponsor As %String [ SqlColumnNumber = 15, SqlFieldName = NTSC_Sponsor ];

/// 发起组
Property ntscsponsorgroup As %String [ SqlColumnNumber = 16, SqlFieldName = NTSC_SpoGroup ];

/// 接收人
Property ntscrecpient As %String [ SqlColumnNumber = 17, SqlFieldName = NTSC_Recipient ];

/// 接收组
Property ntscrecpgroup As %String [ SqlColumnNumber = 18, SqlFieldName = NTSC_RecGroup ];

/// 分配人
Property ntscassigner As %String [ SqlColumnNumber = 19, SqlFieldName = NTSC_Assigner ];

/// 需求当前人
Property ntsccueernt As %String [ SqlColumnNumber = 20, SqlFieldName = NTSC_Current ];

/// 公示截止日期
Property ntscpubdate As %Date [ SqlColumnNumber = 21, SqlFieldName = NTSC_PublicityDaye ];

/// 发布日期
Property ntscreledate As %Date [ SqlColumnNumber = 22, SqlFieldName = NTSC_ReleaseDate ];

/// 质检期数
Property ntscquaily As %String [ SqlColumnNumber = 23, SqlFieldName = NTSC_Quality ];

/// 当前组
Property ntsccurrgroup As %String [ SqlColumnNumber = 24, SqlFieldName = NTSC_CurrentGroup ];

/// 创建人
Property ntsccreatuser As %String [ SqlColumnNumber = 25, SqlFieldName = NTSC_CeartUser ];

/// 当前人
Property ntsccurruser As %String [ SqlColumnNumber = 26, SqlFieldName = NTSC_CurrentUser ];

/// 创建日期
Property ntsccreatdate As %Date [ SqlColumnNumber = 27, SqlFieldName = NTSC_CeartDate ];

/// 数据操作mac地址
Property ntscmac As %String [ SqlColumnNumber = 28, SqlFieldName = NTSC_Mac ];

/// 数据操作人
Property ntscopera As BSP.Lic.AccessMac [ SqlColumnNumber = 29, SqlFieldName = NTSC_opera ];

Index ntsccreatno On ntscno [ Unique ];

Storage SQLStorageStorage
{
<SqlIdExpression>$i(^NTSCReqQua(0))</SqlIdExpression>
<SQLMap name="NoNtscIndex">
<Global>^NTSCReqQuaI</Global>
<PopulationType>nonnull</PopulationType>
<RowIdSpec name="1">
<Expression>{L3}</Expression>
<Field>NTSC_RowID</Field>
</RowIdSpec>
<Subscript name="1">
<Expression>"NO"</Expression>
</Subscript>
<Subscript name="2">
<Expression>{NTSC_NO}</Expression>
</Subscript>
<Subscript name="3">
<Expression>{NTSC_RowID}</Expression>
</Subscript>
<Type>index</Type>
</SQLMap>
<SQLMap name="data">
<Data name="NTSC_Affil">
<Delimiter>"^"</Delimiter>
<Piece>13</Piece>
</Data>
<Data name="NTSC_Assigner">
<Delimiter>"^"</Delimiter>
<Piece>18</Piece>
</Data>
<Data name="NTSC_CeartDate">
<Delimiter>"^"</Delimiter>
<Piece>26</Piece>
</Data>
<Data name="NTSC_CeartUser">
<Delimiter>"^"</Delimiter>
<Piece>24</Piece>
</Data>
<Data name="NTSC_Current">
<Delimiter>"^"</Delimiter>
<Piece>19</Piece>
</Data>
<Data name="NTSC_CurrentGroup">
<Delimiter>"^"</Delimiter>
<Piece>23</Piece>
</Data>
<Data name="NTSC_CurrentUser">
<Delimiter>"^"</Delimiter>
<Piece>25</Piece>
</Data>
<Data name="NTSC_DelCenter">
<Delimiter>"^"</Delimiter>
<Piece>12</Piece>
</Data>
<Data name="NTSC_LevReq">
<Delimiter>"^"</Delimiter>
<Piece>7</Piece>
</Data>
<Data name="NTSC_LevReqDesc">
<Delimiter>"^"</Delimiter>
<Piece>8</Piece>
</Data>
<Data name="NTSC_LevStratus">
<Delimiter>"^"</Delimiter>
<Piece>9</Piece>
</Data>
<Data name="NTSC_Level">
<Delimiter>"^"</Delimiter>
<Piece>6</Piece>
</Data>
<Data name="NTSC_Mac">
<Delimiter>"^"</Delimiter>
<Piece>27</Piece>
</Data>
<Data name="NTSC_Meran">
<Delimiter>"^"</Delimiter>
<Piece>10</Piece>
</Data>
<Data name="NTSC_NO">
<Delimiter>"^"</Delimiter>
<Piece>1</Piece>
</Data>
<Data name="NTSC_PublicityDaye">
<Delimiter>"^"</Delimiter>
<Piece>20</Piece>
</Data>
<Data name="NTSC_Quality">
<Delimiter>"^"</Delimiter>
<Piece>22</Piece>
</Data>
<Data name="NTSC_RecGroup">
<Delimiter>"^"</Delimiter>
<Piece>17</Piece>
</Data>
<Data name="NTSC_Recipient">
<Delimiter>"^"</Delimiter>
<Piece>16</Piece>
</Data>
<Data name="NTSC_ReleaseDate">
<Delimiter>"^"</Delimiter>
<Piece>21</Piece>
</Data>
<Data name="NTSC_SpoGroup">
<Delimiter>"^"</Delimiter>
<Piece>15</Piece>
</Data>
<Data name="NTSC_Sponsor">
<Delimiter>"^"</Delimiter>
<Piece>14</Piece>
</Data>
<Data name="NTSC_TSText">
<Delimiter>"^"</Delimiter>
<Piece>5</Piece>
</Data>
<Data name="NTSC_TSType">
<Delimiter>"^"</Delimiter>
<Piece>4</Piece>
</Data>
<Data name="NTSC_Title">
<Delimiter>"^"</Delimiter>
<Piece>2</Piece>
</Data>
<Data name="NTSC_Trans">
<Delimiter>"^"</Delimiter>
<Piece>11</Piece>
</Data>
<Data name="NTSC_Type">
<Delimiter>"^"</Delimiter>
<Piece>3</Piece>
</Data>
<Data name="NTSC_opera">
<Delimiter>"^"</Delimiter>
<Piece>28</Piece>
</Data>
<Global>^NTSCReqQua</Global>
<PopulationType>nonnull</PopulationType>
<RowIdSpec name="1">
<Expression>{L1}</Expression>
<Field>NTSC_RowID</Field>
</RowIdSpec>
<Subscript name="1">
<Expression>{NTSC_RowID}</Expression>
<StartValue>1</StartValue>
</Subscript>
<Type>data</Type>
</SQLMap>
<SqlRowIdName>NTSC_RowID</SqlRowIdName>
<StreamLocation>^User.NTSCReqQuaS</StreamLocation>
<Type>%Storage.SQL</Type>
}
}

2、页面设置

首先需要再 demo 安全组中放开列编辑按钮

新版 9.0 版本,不在后台书写列名,在前台界面可快读编辑

3、对于需求页面样式引用的编写

<!DOCTYPE html>
<!--
    需求质控列表
    csp:  csp/NTSC/NTSCReqQuaCont.csp
    js:   scripts/NTSC/NTSCReqQuaCont.js
-->
<html>
<!-- 验证session过期 -->
<csp:method name=OnPreHTTP arguments="" returntype=%Boolean>
    d ##Class(websys.SessionEvents).SessionExpired() q $$$OK
</csp:method>

<head>
    <!-- iMedical版本标题 -->
    <title>
        <TRAK:TRANSLATE id=title>##(%session.Get("TITLE"))##</TRAK:TRANSLATE>
    </title>
    <TRAK:HEAD></TRAK:HEAD>
    <HISUI />
    <PHALIBV1 />
    <PHAPRINTCOM />
    <style>
        .pha-plan-create-hide {
            display: none;
        }

        .messager-popover .content {
            height: auto
        }
        
        #qCondition a{
            min-width:90px;
        }
    </style>
</head>

<body>
    <div class="hisui-layout" fit="true" id="lyMainView">
        <div data-options="region:'center',border:false" class="pha-body">
            <div class="hisui-layout" fit="true">
                <div data-options = "region:'center',border:false,split:true">
                    <div  class = "hisui-panel" data-options="region:'center',title:'需求质控列表'" id="layout-plan-create-panel">
                        <table id="gridItm"></table>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- 表格工具栏 -->
    <div id="gridItmBar">
        <table id="qCondition" class="pha-con-table">
            <tr>
            	<td class="r-label">
                    <label for="startDate-merge">#(..Get("开始日期"))#</label>
                </td>
                <td class="r-label">
                    <input type="text" id="startDate-merge"  data-pha="class:'hisui-datebox',clear:true,save:true,query:true,required:true">
                </td>
                
                <td class="r-label">
                    <label for="Level">#(..Get("质检分级"))#</label>
                </td>
                <td class="r-label">
                    <input type="text" id="ntsclevel" data-pha="class:'hisui-combobox',clear:true,save:true,query:true,required:true">
                </td>

                <td class="r-label">
                    <a data-pha-btn class="hisui-linkbutton" id="btnFind" data-options="iconCls:'icon-w-find'">查询</a>
                </td>
                <td style="padding-left:10px"><a id="btnImportWin" data-options="iconCls:'icon-w-import'" class="hisui-linkbutton button-width" >导入Excel</a></td>
            </tr>
            <tr>
                <td class="r-label" >
                    <label for="endDate-merge">#(..Get("结束日期"))#</label>
                </td>
                <td class="r-label">
                    <input type="text" id="endDate-merge" data-pha="class:'hisui-datebox',clear:true,save:true,query:true,required:true">
                </td>
               
                <td class="r-label">
                    <label for="remarks">#(..Get("需求序号"))#</label>
                </td>
                <td class="r-label">
                    <input type="text" id="remarNo" data-pha="class:'hisui-validatebox',clear:true,save:true,query:true">
                </td>
                <td class="r-label">
                    <a data-pha-btn class="hisui-linkbutton" id="btnClean" data-options="iconCls:'icon-w-clean'">清屏</a>
                </td>
              <td class="r-label">
                    <a data-pha-btn class="hisui-linkbutton" id="btnSave" data-options="iconCls:'icon-w-save'">保存</a>
                </td>
            </tr>
        </table>
		<!-- 页面展示的横线-->	
        <div class="pha-line"></div>	
    </div>

    
    <div id="winMergePlan" class="js-pha-com-window-sign">
        <div class="hisui-layout" fit="true">
            <div data-options="region:'center',border:false" class="pha-body pha-body-white">
                <div class="hisui-layout" fit="true" border="false">
                    <div data-options="headerCls:'panel-header-gray',iconCls:'icon-paper',region:'north',height:350,split:true,title:'需求质控列表',collapsible:true,showCollapsedTitle:true">
                        <table id="gridMain-merge"></table>
                    </div>
                    <div data-options="headerCls:'panel-header-gray',iconCls:'icon-paper',region:'center',title:'明细'">
                        <table id="gridItm-merge"></table>
                    </div>
                </div>
            </div>
        </div>
    </div>   
    <!--导出弹框-->
	<div id="ImportWin" class="hisui-dialog" data-options="closed:'true',modal:true" title=#(..Get("导入准备的需求列表"))# style="width:340px;height:145px;">
		<table class="pha-con-table">
		    <tr>
		    	<td class="r-label"><label for="conFileBox">#(..Get("文件选择"))#</label></td>
                <td><input id='conFileBox' data-pha /></td>
		    </tr>
		</table>
		<div data-options="region:'south'" border="false" style="text-align:center;padding-bottom:10px">
			<a id='btnImportFile' class="hisui-linkbutton" >导入</a>
		    <a id='btnExportFile' class="hisui-linkbutton" style="margin-left:10px;">导入模版下载</a>
		</div>
	</div>
    
    <!-- 加载本页面js -->
	<script type="text/javascript" src="../scripts/NTSC/ReqQuaCont/loading.js"></script>
    <script type="text/javascript" src="../scripts/NTSC/ReqQuaCont/com.js"></script>
    <script type="text/javascript" src="../scripts/NTSC/ReqQuaCont/compent.js"></script>
    <script type="text/javascript" src="../scripts/NTSC/NTSCReqQuaCont.js"></script>
    <!-- 导入excel文件读取插件 -->
    <script type="text/javascript" src="../scripts/pha/plugins/xlsx/xlsx.full.min.js"></script>
</body>
</html>

4、对于页面操作的主js

//需求质控列表
//csp:  csp/NTSC/NTSCReqQuaCont.csp
//js:   scripts/NTSC/NTSCReqQuaCont.js
//为了不用写重复代码,将数据验证列表和数据库查询等方法集成在一起,纯手搓的,只用做需求列表质量验证
var tipFlag = "";
$(function () {
    var biz = {
        data: {
            ntscID: '',
            status: 'SAVE',
            handleStatus: '',
            keepInputFields: ['Level', 'Remarks'],
            keepInputFlag: true,
            defaultData: [
                {
                    'Level': '',
                    'startDate-merge': 't - 30',
                    'endDate-merge': 't',
                    'Remarks': ''
                }
            ]
        },
        getData: function (key) {
            switch (key) {
                case 'mouldFlag':
                    return $('#mouldFlag').checkbox('getValue');
                default:
                    break;
            }
            return this.data[key];
        },
        setData: function (key, data) {
            this.data[key] = data;
            switch (key) {
                case 'ntscID':
                    break;
            }
        }
    };
    /**
     * 初始化
     */
    var components = PLAN_COMPONENTS();
    var com = PLAN_COM;
    var settings = com.GetSettings();
    components('Level', 'ntsclevel');
    components('Remarks', 'remarNo');
      components('Date', 'startDate-merge');
      components('Date', 'endDate-merge');
    components('MainGrid', 'gridMain-merge', {
        toolbar: '#gridMain-mergeBar',
        singleSelect: false
    });
    var rebuildColumns = PHA_COM.RebuildColumns(components('ItmGridColmuns', 'gridItm-merge'), {
        hiddenFields: ['gCheck']
    });
    components('ItmGrid', 'gridItm-merge', {
        toolbar: '#gridItm-mergeBar',
        columns: rebuildColumns.columns,
        frozenColumns: rebuildColumns.frozenColumns
    });
    
    PHA_EVENT.Bind('#btnFind', 	'click', function (){QueryMain();});
    
    // 为了不用写重复代码,将数据验证列表和数据库查询方法集成在一起,纯手搓的
	function QueryMain(){
		    let ntscnoStr = ''; 
			var rows = [];
			com.GetChangedRows('gridItm').forEach(function (it) {
            	var rowData = $.extend({}, it);
            	rows.push(rowData);
       			 });
       		const ntsctstypeList = Array.isArray(rows)
  					? rows.map(item => {
      				return (item && item.ntsctype !== undefined) 
        			? item.ntsctype 
        			: '';
    					})
  						: [];
  		    
  			const ntscnoList = rows
  			.filter(item => item.ntscno !== '' && item.ntscno !== null && item.ntscno !== undefined)
  			.map(item => item.ntscno);
			if (ntscnoList.length > 0) {
				  console.log("执行 join 前的 ntscnoList:", ntscnoList);
				  ntscnoStr = ntscnoList.join(',');
				  console.log("拼接后的 ntscnoStr:", ntscnoStr);
				  // 后续逻辑...
			    // 这里可以使用ntscnoStr进行后续操作,比如发送请求等
			}
		    var $grid = $("#gridItm");
		    var stDate =  $("#startDate-merge").datebox("getValue") || ""; 
		    var endDate =  $("#endDate-merge").datebox("getValue") || "";
		    var Level =  $("#ntsclevel").combobox("getValue") || ""; 
		     var remarNo = ntsctstypeList.some(type => type === "") 
        		? ntscnoStr // 此时能拿到拼接后的值(或空字符串)
        		: ($("#remarNo").val() || "");
		    var instFlag  =  "";
		    var pJson = {};
		    pJson.stDate = stDate;  
		    pJson.endDate = endDate;
		    pJson.Level = Level;
		    pJson.remarNo = remarNo;
		    $grid.datagrid('options').url = PHA.$URL;
		    $grid.datagrid('query',{
		        pClassName:'NTSC.Pharmacy.ReqQuaCont.NTSCReqQuaCont' ,
		        pMethodName:'GetMainDataRows',
		        pPlug:'datagrid',
		        pJson: JSON.stringify(pJson)
		    }); 
		}

    components('ItmGrid', 'gridItm', {
        toolbar: '#gridItmBar',
        pagination: false,
        showFooter: true,
        isAutoShowPanel: false,
        rownumbers: true,
        data: {
            rows: [],
            footer: [],
            total: 0
        },
        singleSelect: true,
        shiftCheck: true,
        singleSelect: true,
        checkOnSelect: false
    });
    $('#conFileBox').filebox({
		prompt: $g('请选择文件...'),
		buttonText: $g('选择'),
		width: 250,
		accept: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel'
	})
    PHA_EVENT.Bind('#btnClean', 'click', function () {
	    $('#gridItm').datagrid('clear');
        biz.setData('Level', '');
        biz.setData('Remarks', '');
    });
    PHA_EVENT.Bind('#btnSave', 'click', function () {
        biz.setData('handleStatus', 'Save');
        Save();
    });
    PHA_EVENT.Bind('#btnImportWin', 'click', function(){ImportWin();});
	PHA_EVENT.Bind('#btnImportFile', 'click', function(){ImportFile();});
    PHA_EVENT.Bind('#btnExportFile', 'click', function () { ExportFile(); });

function Save() {
    var rows = [];
    com.GetChangedRows('gridItm').forEach(function (it) {
        var rowData = $.extend({}, it);
        rows.push(rowData);
    });
    
    const ntsctstypeList = Array.isArray(rows)
        ? rows.map(item => (item && item.ntsctype !== undefined) ? item.ntsctype : '')
        : [];
    
    if (ntsctstypeList.every(type => type === "")) { 
        showProgressMessage("验证数据不要点保存, Are you OK!!!");
        return;
    }
    
    var apiMethod = com.Fmt2ApiMethod(biz.getData('handleStatus'));
    if (apiMethod === '') return;
    
    tipFlag = "1";
    PHA_GridEditor.DeleteNullRow('#gridItm', ['ntscno']);
    
    if (ValidateEditGrid() === false || ValidateSave() === false) return;
    
    createProgressBar();
    
    const batchSize = 3;
    const totalCount = rows.length;
    const totalBatches = Math.ceil(totalCount / batchSize);
    let currentBatch = 0;

    // 立即启动第一批保存
    processNextBatch();

    function processNextBatch() {
        if (currentBatch >= totalBatches) {
            completeSave();
            return;
        }

        const startIndex = currentBatch * batchSize;
        const endIndex = Math.min(startIndex + batchSize, totalCount);
        const currentBatchData = rows.slice(startIndex, endIndex);
        
        updateProgressBar(currentBatch + 1, totalBatches);
        showProgressMessage(`正在处理第 ${currentBatch + 1} 批数据(共 ${totalBatches} 批)`);

        var data4save = { rows: currentBatchData };
        
        PHA.Loading('Show');
        com.Invoke(apiMethod, data4save, 
            function (retData) {
                PHA.Loading('Hide');
                let msg = '';
                if (typeof retData === 'string') {
                    const shortMsg = retData.length > 100 ? retData.substring(0, 100) + '...' : retData;
                    msg = `第${currentBatch + 1}批保存失败\n原因: ${shortMsg}\n(继续处理下一批)`;
                } else {
                    biz.setData('ntscID', retData.data);
                    msg = `第${currentBatch + 1}批保存成功`;
                }
                showProgressMessage(msg);

                // 处理完当前批,立即处理下一批
                currentBatch++;
                processNextBatch();
            },
            function(error) {
                PHA.Loading('Hide');
                let errorMsg = `第${currentBatch + 1}批请求错误\n`;
                if (error.message) errorMsg += `错误类型: ${error.message}\n`;
                if (error.responseText) {
                    const shortResponse = error.responseText.length > 100 
                        ? error.responseText.substring(0, 100) + '...' 
                        : error.responseText;
                    errorMsg += `响应内容: ${shortResponse}`;
                }
                errorMsg += `\n(继续处理下一批)`;
                showProgressMessage(errorMsg);

                // 即使出错,也处理下一批
                currentBatch++;
                processNextBatch();
            }
        );
    }
    
    function completeSave() {
        showProgressMessage(`全部保存完成!共处理 ${totalCount} 条数据,分为 ${totalBatches} 批`);
        setTimeout(hideProgressBar, 3000);
    }
    
    function createProgressBar() {
        let progressContainer = document.getElementById('saveProgressContainer');
        if (!progressContainer) {
            progressContainer = document.createElement('div');
            progressContainer.id = 'saveProgressContainer';
            progressContainer.style.cssText = `
                position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
                width: 300px; background-color: white; border: 1px solid #ccc; 
                border-radius: 5px; padding: 15px; z-index: 9999;
            `;
            
            const title = document.createElement('div');
            title.style.cssText = 'margin-bottom: 10px; font-weight: bold;';
            title.textContent = '数据保存进度';
            progressContainer.appendChild(title);
            
            const messageArea = document.createElement('div');
            messageArea.id = 'progressMessage';
            messageArea.style.cssText = 'margin-bottom: 10px; height: auto; white-space: pre-wrap; word-break: break-all;';
            progressContainer.appendChild(messageArea);
            
            const progressBarContainer = document.createElement('div');
            progressBarContainer.style.cssText = 'height: 10px; background-color: #eee; border-radius: 5px; overflow: hidden;';
            
            const progressBar = document.createElement('div');
            progressBar.id = 'progressBar';
            progressBar.style.cssText = 'height: 100%; width: 0%; background-color: #4CAF50; transition: width 0.3s ease;';
            progressBarContainer.appendChild(progressBar);
            progressContainer.appendChild(progressBarContainer);
            
            const progressText = document.createElement('div');
            progressText.id = 'progressText';
            progressText.style.cssText = 'text-align: center; font-size: 12px; margin-top: 5px;';
            progressText.textContent = '0%';
            progressContainer.appendChild(progressText);
            
            document.body.appendChild(progressContainer);
        }
    }
    
    function updateProgressBar(current, total) {
        const progress = (current / total) * 100;
        document.getElementById('progressBar').style.width = `${progress}%`;
        document.getElementById('progressText').textContent = `${Math.round(progress)}% (${current}/${total} 批)`;
    }
    
    function showProgressMessage(message) {
        document.getElementById('progressMessage').textContent = message;
    }
    
    function hideProgressBar() {
        const container = document.getElementById('saveProgressContainer');
        if (container) container.style.display = 'none';
    }
}
    function ValidateEditGrid() {
        var val = true;
        var msg = '';
        try {
            // 验证必填
            if (!PHA_GridEditor.EndCheck('gridItm')) {
                throw '';
            }
            if ($('#gridItm').datagrid('getRows').length == 0) {
                throw '没有需要操作的数据';
            }
        } catch (error) {
            val = false;
            msg = error;
        } finally {
            if (msg !== '' && typeof msg === 'string') {
                components('Pop', msg);
            }
            return val;
        }
    }
    /// 验证单行不做提示, 故与保存的alert|pop不存在冲突
    function ValidateSave(rowData, rowIndex) {
        var mainObj = com.Condition('#qCondition', 'get', { dotype: 'save' });
        if (mainObj === undefined) {
            return;
        }
        mainObj.planID = biz.getData('planID');
        mainObj.mainRowID = biz.getData('planID');

        var rows = [];
        if (rowData === undefined) {
            var forRows = $('#gridItm').datagrid('getRows');
            forRows.forEach(function (it) {
                var forRow = $.extend({}, it);
                rows.push(forRow);
            });
        } else {
            rowData.rowIndex = rowIndex;
            if ((rowData.inciCode || '') === '') {
                return true;
            }
            rows.push(rowData);
        }
        // 异步只是稍微好那么一点, 但是感受不出来
        var valRet = PLAN_COM.InvokeSyn('ValidateSave', {
           // main: mainObj,
            rows: rows
        });
        if (valRet.type !== '') {
            
            if (rowIndex === undefined) {
                // 点击保存的提醒
                PHA.ShowWarn({ warnInfo: valRet });
                if (valRet.type === 'terminate') {
                    return false;
                }
            }else{
                PHA.Warn2Grid('#gridItm', { warnInfo: valRet });
                return false;
            }
        }
        return true;
    }


    function ControlOperation() {
        com.ControlOperation({
            '#btnExport': {
                disabled: biz.getData('planID') === ''
            },
            '#btnSave': {
                disabled: biz.getData('status') !== 'SAVE'
            }
        });
    }
    function SetDefaults() {
        PHA.SetVals(biz.getData('defaultData'));
        $('#Level').combobox('reload');
        $('#Remarks').combobox('reload');

    }

    setTimeout(function () {
        if (settings.Com.StkCatSet === 'Y') {
        }
        $.extend(biz.getData('defaultData')[0], settings.DefaultData);
        SetDefaults();
        ControlOperation();
        $('body').on('click', function () {
            $('.tooltip').hide();
        });
        // 引用其他已经写好页面的css样式,不用在自己写一遍了
        com.SetPage('pha.in.v3.plan.create.csp');
        var topPlanID = com.Top.Get('ntscID', true);
        if (topPlanID !== '') {
            // 此处不延迟夹加载会导致采购科室不能正确加载
            setTimeout(function () { biz.setData('ntscID', topPlanID); }, 300);
        }
        else{
        }
    }, 0);
    
    function ImportWin(){
		PHA.DomData('#ExportWin', {doType: 'clear'});
		$('#ImportWin')
	    .dialog({
	        iconCls: 'icon-w-import',
	        modal: true,
	        onBeforeClose: function () {},
	    }).dialog('open');
        $('#conFileBox').filebox('clear');
        
	}
	function ImportHandler() {
		$('#conFileBox').filebox({
			prompt: $g('请选择文件...'),
			buttonText: $g('选择'),
			width: 250,
			accept: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel'
		})
	}
    // 分片处理数据(避免UI阻塞,每批处理10条导入的数据,并将加载的动画集成在导入列表,这个动画太难搞了,头发都薅秃了)
	function processDataInChunks(xlsData, loading, totalLength) {
	  let processed = 0;
	  const chunkSize = 20;
	  function processChunk() {
	    const end = Math.min(processed + chunkSize, totalLength);
	    for (let i = processed; i < end; i++) {
	      try {
	        var row = xlsData[i];
	        if (row['#公示截止日期#']) {
	          var pubdate = excelDateToJsDate(row['#公示截止日期#']);
	          if (pubdate instanceof Date && !isNaN(pubdate.getTime())) {
	            row['#公示截止日期#'] = formatDate(pubdate);
	          }
	        }
	        if (row['#创建日期#']) {
	          var creatdate = excelDateToJsDate(row['#创建日期#']);
	          if (creatdate instanceof Date && !isNaN(creatdate.getTime())) {
	            row['#创建日期#'] = formatDate(creatdate);
	          }
	        }
	        row = ReplaceTitle(row);
	        var fullDataObj = PLAN_COM.InvokeSyn('GetDataImport', row);
	        
	        if (!fullDataObj) {
	          components('Pop', '数据处理错误', 'alert');
	          loading.destroy();
	          return;
	        }
	        if (fullDataObj.msgData) {
	          components('Pop', fullDataObj.msgData, 'alert');
	          loading.destroy();
	          return;
	        }
	        PHA_GridEditor.Add({
	          gridID: 'gridItm',
	          field: 'ntscno',
	          rowData: fullDataObj.rowData,
	          checkRow: false,
	          firstRow: false
	        });
	        loading.update(i + 1);
	      } catch (error) {
	        components('Pop', '单条数据处理失败', 'alert');
	        loading.destroy();
	        return;
	      }
	    }

	    processed = end;
	    if (processed < totalLength) {
	      setTimeout(processChunk, 0); // 让出UI线程,保证动画更新
	    } else {
	      loading.destroy(); // 全部处理完,销毁动画
	      $('#ImportWin').dialog('close');
	      var rows = [];
			com.GetChangedRows('gridItm').forEach(function (it) {
            	var rowData = $.extend({}, it);
            	rows.push(rowData);
       			 });
	      const ntsctstypeList = Array.isArray(rows)
			  ? rows.map(item => {
			      return (item && item.ntsctype !== undefined) 
			        ? item.ntsctype 
			        : '';
			    })
			  : [];
		  if (ntsctstypeList.every(type => type === "")) { 
			  QueryMain();
			}
	    }
	  }
	  processChunk(); 
	}

	// 全局可访问的Excel导入函数(确保事件绑定能找到)
	window.ImportFile = function() {
	  try {
	    PHA_GridEditor.End('gridItm');
	    var rows = $('#gridItm').datagrid('getRows');
	    if (rows && rows.length > 0) {
	      PHA.Msg('info', '导入时请保持明细列表为空!');
	      return;
	    }
	    var filelist = $('#conFileBox').filebox('files');
	    if (!filelist || filelist.length === 0) {
	      PHA.Msg('alert', '请先选择文件!');
	      return;
	    }
	    const loading = createLoadingAnimation();
	    loading.update(0); 
	    var file = filelist[0];
	    PHA_COM.ReadExcel(file, function(xlsData) {
	      try {
	        if (!xlsData || xlsData.length === 0) {
	          PHA.Msg('alert', 'excel中无有效明细!');
	          loading.destroy();
	          return;
	        }
	        const length = xlsData.length;
	        loading.setTotal(length); 
	        processDataInChunks(xlsData, loading, length);
	      } catch (error) {
	        loading.destroy();
	        components('Pop', '数据处理异常,请重试', 'alert');
	      }
	    });
	  } catch (error) {
	    console.error('导入初始化异常:', error);
	    components('Pop', '导入失败,请重试', 'alert');
	  }
	};
	
	function ReplaceTitle(obj){
		var objStr = JSON.stringify(obj);
		var objStr = objStr.replace(/#需求序号#/g, 'ntscno');
		var objStr = objStr.replace(/#需求标题#/g, 'ntsctitle');
		var objStr = objStr.replace(/#质控类型#/g, 'ntsctype');
		var objStr = objStr.replace(/#投诉类型#/g, 'ntsctstype');
		var objStr = objStr.replace(/#投诉内容#/g, 'ntsctstext');
		var objStr = objStr.replace(/#质量分级#/g, 'ntsclevel');
		var objStr = objStr.replace(/#质量问题类型#/g, 'ntsclevreq');
		var objStr = objStr.replace(/#质量问题描述#/g, 'ntsclevreqdesc');
		var objStr = objStr.replace(/#质检状态#/g, 'ntscstratus');
		var objStr = objStr.replace(/#案例需求#/g, 'ntscmearn');
		var objStr = objStr.replace(/#改进意见#/g, 'ntsctrans');
		var objStr = objStr.replace(/#交付中心#/g, 'ntscdelcenter');
		var objStr = objStr.replace(/#所属分区#/g, 'ntscaffi');
		var objStr = objStr.replace(/#发起人#/g, 'ntscsponsor');
		var objStr = objStr.replace(/#发起组#/g, 'ntscsponsorgroup');
		var objStr = objStr.replace(/#接收人#/g, 'ntscrecpient');
		var objStr = objStr.replace(/#接收组#/g, 'ntscrecpgroup');
		var objStr = objStr.replace(/#分配人#/g, 'ntscassigner');
		var objStr = objStr.replace(/#需求当前人#/g, 'ntsccueernt');
		var objStr = objStr.replace(/#公示截止日期#/g, 'ntscpubdate');
		var objStr = objStr.replace(/#发布日期#/g, 'ntscreledate');
		var objStr = objStr.replace(/#质检期数#/g, 'ntscquaily');
		var objStr = objStr.replace(/#当前组#/g, 'ntsccurrgroup');
		var objStr = objStr.replace(/#创建人#/g, 'ntsccreatuser');
		var objStr = objStr.replace(/#当前人#/g, 'ntsccurruser');
		var objStr = objStr.replace(/#创建日期#/g, 'ntsccreatdate');
		return JSON.parse(objStr);
	}

	function ExportFile(){
		var title={
			ntscno : '#需求序号#',
			ntsctitle : '#需求标题#',
		}
		var data = [
			{
				ntscno : '6138414',
				ntsctitle : '门诊病历打印提示检查Web-Print打印插件,请稍后,需核查',
			}, 
			{
				ntscno : '6139799',
				ntsctitle : '医嘱录入过滤无库存医嘱',
			}
		]
		var fileName='需求验证列表导入模版.xlsx'
		PHA_COM.ExportFile(title, data, fileName);
	}

 
});

6、针对页面产生的后台类方法处理

/// 需求质控列表后台类
Class NTSC.Pharmacy.ReqQuaCont.NTSCReqQuaCont Extends (PHA.IN.COM.Base, PHA.IN.PLAN.Filter, PHA.COM.SQL.Json) [ Abstract ]
{

/// Debug: w ##class(NTSC.Pharmacy.ReqQuaCont.NTSCReqQuaCont).GetDataImport({"ntscno": 6138414,"ntsctitle": "门诊病历打印提示“检查Web-Print打印插件,请稍后”,需核查","ntsctype": "质量检查-质检小组","ntsclevel": "中","ntsclevreq": "需求内容不清晰","ntsclevreqdesc": "使用环境应该描述具体菜单、安全组","ntscstratus": "公示中","ntscdelcenter": "甘青交付中心","ntscaffi": "青海组","ntscsponsor": "韩文清","ntscsponsorgroup": "青海德令哈市中医","ntscrecpient": "周玉玲","ntscrecpgroup": "电子病历技术支持","ntscassigner": "","ntsccueernt": "韩文清","ntscpubdate": 45930,"ntsccurrgroup": "青海德令哈市中医","ntsccreatuser": "王乾瞩", "ntsccurruser": "董富强","ntsccreatdate": 45908.493368055555}).%ToJSON()
ClassMethod GetDataImport(pJson As %DynamicObject) As %DynamicObject
{
	;s ^sdf("dassad")=$LB(pJson)
    #dim retObj as %DynamicObject = {
        "rowData": {},
        "msgData": ""
    }
    ;s inci = ##class(Data).GetInciByCode(pJson.inciCode, pJson.inciDesc, pJson.loc)
    s ntscno = pJson.ntscno
    ;s ntscpubdate = $zd(pJson.ntscpubdate,3)
    ;s ntsccreatdate =$zd($p(pJson.ntsccreatdate,".",1),3)
    b ;1
	i (+ntscno = 0) {
        s retObj.msgData = ntscno 
        s retObj.rowData = []
        q retObj
    }
	s row = {
		"ntscno" : (ntscno),
		"ntsctitle" : (pJson.ntsctitle),
		"ntsctype" : (pJson.ntsctype),
		"ntsctstype" : (pJson.ntsctstype),
		"ntsctstext" : (pJson.ntsctstext),
		"ntsclevel" : (pJson.ntsclevel),
		"ntsclevreq" : (pJson.ntsclevreq),
		"ntsclevreqdesc" : (pJson.ntsclevreqdesc),
		"ntscstratus" : (pJson.ntscstratus),
		"ntscmearn" : (pJson.ntscmearn),
		"ntsctrans" : (pJson.ntsctrans),
		"ntscdelcenter" : (pJson.ntscdelcenter),
		"ntscaffi" : (pJson.ntscaffi),
		"ntscsponsor" : (pJson.ntscsponsor),
		"ntscsponsorgroup" : (pJson.ntscsponsorgroup),
		"ntscrecpient" : (pJson.ntscrecpient),
		"ntscrecpgroup" : (pJson.ntscrecpgroup),
		"ntscassigner" : (pJson.ntscassigner),
		"ntsccueernt" : (pJson.ntsccueernt),
		"ntscpubdate" : (pJson.ntscpubdate),
		"ntscreledate" : (pJson.ntscreledate),
		"ntscquaily" : (pJson.ntscquaily),
		"ntsccurrgroup" : (pJson.ntsccurrgroup),
		"ntsccreatuser" : (pJson.ntsccreatuser),
		"ntsccurruser" : (pJson.ntsccurruser),
		"ntsccreatdate" : (pJson.ntsccreatdate)
	}
	b ;23
    ;s rowData = ..GetDataInput(row)
    s retObj.rowData = row
    #; 一些提醒信息等
    q retObj
}

/// Description: 校验前台传过来的数据
/// Debug: w ##class(NTSC.Pharmacy.ReqQuaCont.NTSCReqQuaCont).ValidateSave({"rows":[{"ntscno": 6138414,"ntsctitle": "门诊病历打印提示“检查Web-Print打印插件,请稍后”,需核查","ntsctype": "质量检查-质检小组","ntsclevel": "中","ntsclevreq": "需求内容不清晰","ntsclevreqdesc": "使用环境应该描述具体菜单、安全组","ntscstratus": "公示中","ntscdelcenter": "甘青交付中心","ntscaffi": "青海组","ntscsponsor": "韩文清","ntscsponsorgroup": "青海德令哈市中医","ntscrecpient": "周玉玲","ntscrecpgroup": "电子病历技术支持","ntscassigner": "","ntsccueernt": "韩文清","ntscpubdate": 45930,"ntsccurrgroup": "青海德令哈市中医","ntsccreatuser": "王乾瞩", "ntsccurruser": "董富强","ntsccreatdate": 45908.493368055555}]})
ClassMethod ValidateSave(pJson As %DynamicObject)
{
	;s ^wsh("dsad")=$lb(pJson)
	;b ;22
	s typeList = $lb("pop", "alert", "confirm", "terminate") 
	#dim currentType as %String = ""
    #dim ret As %DynamicObject = {
          "type": "",
          "rows": []
      }
    s msgRowsArr = []
    #dim tmpData
    s rows = pJson.rows
    s len = rows.%Size() - 1
    for i = 0 : 1 : len {
        s msgRows = []
        s rowData = rows.%Get(i)
        s rowIndex = rowData.rowIndex
        i (rowIndex = ""){
            s rowIndex = i
        }
        s ntscno = rowData.ntscno
        i (ntscno = ""){
            d PushMsg("需求序号","为空", "ntscno", "terminate")
            d msgRowsArr.%Push({"rowIndex": (rowIndex + 1),"rowMsgs": (msgRows)})
            continue
        }
        #; 唯一性
        s distinctData = $lb(ntscno)
        i ($d(tmpData("distntsc", distinctData), tmpI)){
            d PushMsg("", "重复数据: 需求序号 不能完全相同", "ntscno", "terminate")                        
        }
        s tmpData("distntsc", distinctData) = rowIndex
        i (msgRows.%Size() > 0){
            d msgRowsArr.%Push({"rowIndex": (rowIndex + 1),"rowMsgs": (msgRows)})
        }
    }
	    s ret.type = currentType
	    s ret.rows = msgRowsArr
	    q ret
PushMsg(fieldDesc, info, field, type = "alert")
    d msgRows.%Push({
        "fieldDesc": (fieldDesc),
        "type": (type),
        "info":(info), 
        "index": (rowIndex), 
        "field": (field)
    })
    i (currentType = ""){
        s currentType = type
    }else{
        i ($lf(typeList, type) > $lf(typeList, currentType)){
            s currentType = type
        }
    }
    q $$$OK
}

/// 保存方法,处理保存前的数据格式
/// w ##class(NTSC.Pharmacy.ReqQuaCont.NTSCReqQuaCont).HandleSave({"rows":[{"ntscno":355376,"ntsctitle":"住院工作月报台帐(1)","ntsctype":"质量检查-质检小组","ntsctstype":"","ntsctstext":"","ntsclevel":"","ntsclevreq":"","ntsclevreqdesc":"","ntscstratus":"创建","ntscmearn":"","ntsctrans":"","ntscdelcenter":"湖北交付中心","ntscaffi":"湖北西北组","ntscsponsor":"时培超","ntscsponsorgroup":"湖北东风集团医院","ntscrecpient":"陈乙","ntscrecpgroup":"综合查询","ntscassigner":"","ntsccueernt":"时培超","ntscpubdate":"","ntscreledate":"","ntscquaily":"","ntsccurrgroup":"湖北东风集团医院","ntsccreatuser":"苑思琦","ntsccurruser":"","ntsccreatdate":"2023-06-11"},{"ntscno":2427384,"ntsctitle":"部分退费后,本次医嘱的数量不对,已撤销的数量也不对","ntsctype":"质量检查-质检小组","ntsctstype":"","ntsctstext":"","ntsclevel":"低","ntsclevreq":"需求内容不清晰","ntsclevreqdesc":"需求各项描述都一样","ntscstratus":"创建","ntscmearn":"","ntsctrans":"","ntscdelcenter":"四川交付中心","ntscaffi":"粤北组","ntscsponsor":"朱亮","ntscsponsorgroup":"广东韶关曲江妇幼","ntscrecpient":"刘英勇","ntscrecpgroup":"医生站","ntscassigner":"","ntsccueernt":"朱亮","ntscpubdate":"","ntscreledate":"","ntscquaily":"","ntsccurrgroup":"广东韶关曲江妇幼","ntsccreatuser":"王乾瞩","ntsccurruser":"","ntsccreatdate":"2022-11-23"},{"ntscno":2922218,"ntsctitle":"检查、病理接口:报告完成检查状态未改变,需要调用产品组方法,产品组方法已经提供,需要的方案已经和王工沟通过","ntsctype":"质量检查-质检小组","ntsctstype":"","ntsctstext":"","ntsclevel":"中","ntsclevreq":"需求内容不清晰,需求格式不规范","ntsclevreqdesc":"“王工”是哪位?“按照标准版无需调用产品组方法,都是存入平台表内。”这个信息项目交付同学是否知晓或有咨询途径。","ntscstratus":"创建","ntscmearn":"","ntsctrans":"","ntscdelcenter":"安徽交付中心","ntscaffi":"安徽蚌淮组","ntscsponsor":"施方正","ntscsponsorgroup":"安徽蚌埠第二","ntscrecpient":"韩宏利","ntscrecpgroup":"数据中心技术支持","ntscassigner":"","ntsccueernt":"施方正","ntscpubdate":"","ntscreledate":"","ntscquaily":"","ntsccurrgroup":"安徽蚌埠第二","ntsccreatuser":"丁静","ntsccurruser":"","ntsccreatdate":"2023-06-21"},{"ntscno":3066467,"ntsctitle":"新增一个统计报表,报表内容如图","ntsctype":"质量检查-质检小组","ntsctstype":"","ntsctstext":"","ntsclevel":"低","ntsclevreq":"统计口径-描述不清晰,需求格式不规范","ntsclevreqdesc":"1、各项内容均一样,使用背景和环境没有说明\r\r\n2、缺少统计口径,及入参说明","ntscstratus":"创建","ntscmearn":"","ntsctrans":"","ntscdelcenter":"江西交付中心","ntscaffi":"江西赣南组","ntscsponsor":"滕辉","ntscsponsorgroup":"江西宜春丰城人民","ntscrecpient":"陈乙","ntscrecpgroup":"综合查询","ntscassigner":"","ntsccueernt":"滕辉","ntscpubdate":"","ntscreledate":"","ntscquaily":"","ntsccurrgroup":"江西宜春丰城人民","ntsccreatuser":"王乾瞩","ntsccurruser":"","ntsccreatdate":"2022-12-10"},{"ntscno":3066586,"ntsctitle":"国家抗肿瘤报表中,病理检查记录病案号部分数据取值错误,并且将门诊类型的住院号、住院次数置空;住院类型的门诊号、就诊次数置空,不要特殊字符填充","ntsctype":"质量检查-质检小组","ntsctstype":"","ntsctstext":"","ntsclevel":"低","ntsclevreq":"无附件/附件不匹配,需求格式不规范","ntsclevreqdesc":"","ntscstratus":"创建","ntscmearn":"","ntsctrans":"","ntscdelcenter":"安徽交付中心","ntscaffi":"安徽皖北组","ntscsponsor":"方浩宇","ntscsponsorgroup":"安徽颍上县人民","ntscrecpient":"潘磊1205","ntscrecpgroup":"综合查询","ntscassigner":"","ntsccueernt":"方浩宇","ntscpubdate":"","ntscreledate":"","ntscquaily":"","ntsccurrgroup":"安徽颍上县人民","ntsccreatuser":"林坤辉","ntsccurruser":"","ntsccreatdate":"2022-12-08"}],"logonUser":"20667","logonLoc":"181","logonHosp":"2","logonGroup":"33"})
ClassMethod HandleSave(pJson As %DynamicObject) As %DynamicObject
{
    #dim retsult as %String = ""
    ts
    try {
        s dataArr = pJson.rows
        s dataLen = dataArr.%Size()-1
        for i = 0 : 1 : dataLen {
            s rowData = dataArr.%Get(i)
            s result = ..SaveMainData(rowData)
            q:($$$phaIsError(result))
            s mainItmRowID = result
            q:($$$phaIsError(result))
            b ;11
        }       
    }
    catch e {
       s result = ##class(PHA.COM.Err).LogErrors(e)
    }
    i ($$$phaIsError(result)){
        tro
        q ..Msg(-1, result)
    }
    tc:($tl > 0)
    b ;22
    q ..Msg(0, result, mainItmRowID)
}

/// Description: 保存
/// w ##class(NTSC.Pharmacy.ReqQuaCont.NTSCReqQuaCont).SaveMainData({"ntscno":6138415,"ntsctitle":"门诊病历打印提示“检查Web-Print打印插件,请稍后”,需核查","ntsctype":"质量检查-质检小组","ntsclevel":"中","ntsclevreq":"需求内容不清晰","ntsclevreqdesc":"使用环境应该描述具体菜单、安全组","ntscstratus":"公示中","ntscdelcenter":"甘青交付中心","ntscaffi":"青海组","ntscsponsor":"韩文清","ntscsponsorgroup":"青海德令哈市中医","ntscrecpient":"周玉玲","ntscrecpgroup":"电子病历技术支持","ntscassigner":"","ntsccueernt":"韩文清","ntscpubdate":45930,"ntsccurrgroup":"青海德令哈市中医","ntsccreatuser":"王乾瞩","ntsccurruser":"董富强","ntsccreatdate":45908.493368055555})
ClassMethod SaveMainData(dataObj As %DynamicObject) As %String
{
    If dataObj = "" Quit "错误:参数为空"
    If '$IsObject(dataObj) Quit "错误:参数不是有效对象"
    Set ntscnoVal = dataObj.ntscno
    If ntscnoVal = "" Quit "错误:ntscno不能为空"
    Set obj = ##class(User.NTSCReqQua).%New()
    s (publiy,ceart)=""
    s pubdate = dataObj.ntscpubdate
    s cpubdate = dataObj.ntsccreatdate
    s:pubdate'="" publiy = $zdh(pubdate,3)
    s:cpubdate'="" ceart = $zdh(cpubdate,3)
    
    s obj.ntscno = dataObj.ntscno
	s obj.ntsctitle = dataObj.ntsctitle
	s obj.ntsctype = dataObj.ntsctype
	s obj.ntsctstype = dataObj.ntsctstype
	s obj.ntsctstext = dataObj.ntsctstext
	s obj.ntsclevel = dataObj.ntsclevel
	s obj.ntsclevreq = dataObj.ntsclevreq
	s obj.ntsclevreqdesc = dataObj.ntsclevreqdesc
	s obj.ntscstratus = dataObj.ntscstratus
	s obj.ntscmearn = dataObj.ntscmearn
	s obj.ntsctrans = dataObj.ntsctrans
	s obj.ntscdelcenter = dataObj.ntscdelcenter
	s obj.ntscaffi = dataObj.ntscaffi
	s obj.ntscsponsor = dataObj.ntscsponsor
	s obj.ntscsponsorgroup = dataObj.ntscsponsorgroup
	s obj.ntscrecpient = dataObj.ntscrecpient
	s obj.ntscrecpgroup = dataObj.ntscrecpgroup
	s obj.ntscassigner = dataObj.ntscassigner
	s obj.ntsccueernt = dataObj.ntsccueernt
	s obj.ntscpubdate = publiy
	s obj.ntscreledate = dataObj.ntscreledate
	s obj.ntscquaily = dataObj.ntscquaily
	s obj.ntsccurrgroup = dataObj.ntsccurrgroup
	s obj.ntsccreatuser = dataObj.ntsccreatuser
	s obj.ntsccurruser = dataObj.ntsccurruser
	s obj.ntsccreatdate = ceart
	b ;555
    Set sc = obj.%Save()
    If $$$ISERR(sc) {
        Do $System.Status.DisplayError(sc)
        Quit "错误:保存失败"
    }
    b ;666
    Quit obj.%Id()
}

/// 辅助方法:回滚事务并返回错误信息
ClassMethod RollbackAndReturn(msg As %String) As %String
{
    tro
    Quit msg
}

/// Description: 基本数据转换成数据库可存储的类型
ClassMethod Data2Logical4Itm(dataObj)
{
    q $$$OK
}

ClassMethod ValidateInputMainData(inputRow)
{
    #dim result as %String = ""
    try {
        i (inputRow.ntscno = "") {
            s result = "需求序号不能为空"
        }    
    }
    catch e {
        s result = $$$phaZE  
    }
    q:(result '= "") $$$phaError(result)
    q $$$OK
}

/// Description: 基本数据转换成数据库可存储的类型
ClassMethod Data2Logical(dataObj)
{
    d ..sDefinedKeyVal(dataObj, "createDate", $$$zdh(dataObj.createDate))
    d ..sDefinedKeyVal(dataObj, "createTime", $$$zth(dataObj.createTime))
    d ..sDefinedKeyVal(dataObj, "finishDate", $$$zdh(dataObj.finishDate))
    d ..sDefinedKeyVal(dataObj, "finishTime", $$$zth(dataObj.finishTime))
    d ..sDefinedKeyVal(dataObj, "auditDate", $$$zdh(dataObj.auditDate))
    d ..sDefinedKeyVal(dataObj, "auditTime", $$$zth(dataObj.auditTime))
    d ..sDefinedKeyVal(dataObj, "needDate", $$$zdh(dataObj.needDate))
    d ..sDefinedKeyVal(dataObj, "remarks", $lfs(dataObj.remarks, ","))
    d ..sDefinedKeyVal(dataObj, "mouldFlag", $$$zboolean(dataObj.mouldFlag, "Y|N"))
    q $$$OK
}

/// w ##class(NTSC.Pharmacy.ReqQuaCont.NTSCReqQuaCont).GetMainDataRows({"stDate":"2025-09-11","endDate":"2025-10-11","Level":"","remarNo":"6138414,6139799,6216078,6215756,6216047,6215807,6215780,6215850,6216074,6215819,6215853,6215874,6215931,6216054,6115700,6122162,6139609,6147662,6148216"})
/// 为了不写重复代码,将验证列表和数据库查询集成在一个方法里
ClassMethod GetMainDataRows(pJson As %DynamicObject = "") As %DynamicArray
{
	#dim retArr as %DynamicArray  = []
	s (pStartDate,pEndDate,pLevel,premarNo,preleth)=""
    i pJson'="" s pStartDate = $$$zdh(pJson.stDate)
    i pJson'="" s pEndDate = $$$zdh(pJson.endDate)
    i pJson'="" s pLevel = pJson.Level
    i pJson'="" s premar = pJson.remarNo     
    s seenNtscno = ""            
	s:premar'="" preleth = $l(premar,",")
	i $L(premar) > 1 {
		s pre = ..getJoin(premar)
		s sqlCode($i(sqlCode)) = " SELECT %ID ntscID"
    	s sqlCode($i(sqlCode)) = " FROM NTSC_ReqQua"
    	s sqlCode($i(sqlCode)) = " WHERE NTSC_NO in " _ pre
		}else{
					
        /// 动态sql与global结合依然最靠谱, sql用于选择索引   SELECT * FROM NTSC_ReqQua
	    s sqlCode($i(sqlCode)) = " SELECT %ID ntscID"
	    s sqlCode($i(sqlCode)) = " FROM NTSC_ReqQua"
	    if (pStartDate '= ""){
		    s sqlCode($i(sqlCode)) = " WHERE (NTSC_CeartDate BETWEEN " _ pStartDate _ " AND " _ pEndDate _")"
	    }
	    if (pStartDate="")&&(pLevel '= ""){
		    s sqlCode($i(sqlCode)) = " WHERE NTSC_Level = " _ $$$quotes(pLevel)
	    }
	    if (pStartDate'="")&&(pLevel '= ""){
		    s sqlCode($i(sqlCode)) = " AND NTSC_Level = " _ $$$quotes(pLevel)
		    }
	    if (pStartDate="")&&(pLevel '= "")&&(premarNo '= "")&&($d(^NTSCReqQuaI("NO", $SYSTEM.SQL.Functions.ALPHAUP(premarNo)))) {
	        s sqlCode($i(sqlCode)) = " AND NTSC_NO = " _ $$$quotes(premarNo)
	    }
	     if (pStartDate="")&&(pLevel = "")&&(premarNo '= "")&&($d(^NTSCReqQuaI("NO", $SYSTEM.SQL.Functions.ALPHAUP(premarNo)))) {
	        s sqlCode($i(sqlCode)) = " WHERE NTSC_NO = " _ $$$quotes(premarNo)
	    }
	    if (pStartDate'="")&&(pLevel = "")&&(premarNo '= "")&&($d(^NTSCReqQuaI("NO", $SYSTEM.SQL.Functions.ALPHAUP(premarNo)))) {
	        s sqlCode($i(sqlCode)) = " AND NTSC_NO = " _ $$$quotes(premarNo)
	    }
	    if (pStartDate'="")&&(pLevel '= "")&&(premarNo '= "")&&($d(^NTSCReqQuaI("NO", $SYSTEM.SQL.Functions.ALPHAUP(premarNo)))) {
	        s sqlCode($i(sqlCode)) = " AND NTSC_NO = " _ $$$quotes(premarNo)
	    }
		}		
	    s sqlStatement = ##class(%SQL.Statement).%New()
	    s sqlStatus = sqlStatement.%Prepare(.sqlCode)
	    s sqlResult = sqlStatement.%Execute() 
	    for {
	        q:('sqlResult.%Next())
	        s ntscID = sqlResult.%Get("ntscID")
	        s ntscData = $g(^NTSCReqQua(ntscID))
	        s rowData = ..GetMainData(ntscID)
	        q:(rowData.ntscno="")
	        s currentNtscno = rowData.ntscno
        	if $d(seenNtscno(currentNtscno))=0 {  // 若未记录过该ntscno
            	d retArr.%Push(rowData)
            	s seenNtscno(currentNtscno) = 1  // 标记为已出现
        		}
	    }
	    q retArr
}

///  w ##class(NTSC.Pharmacy.ReqQuaCont.NTSCReqQuaCont).GetMainData("1").%ToJSON()
///  NTSCReqQuaCont实体类map集合映射处理成可识别json
ClassMethod GetMainData(ntscID) As %DynamicObject [ SqlProc ]
{
    s ret = ##class(NTSC.Pharmacy.ReqQuaCont.NTSCReqQuaContObj).reqQuaObj(ntscID)
    #; 转换数据
    s ret.ntscno = ret.ntscno
	s ret.ntsctitle = ret.ntsctitle
	s ret.ntsctype = ret.ntsctype
	s ret.ntsctstype = ret.ntsctstype
	s ret.ntsctstext = ret.ntsctstext
	s ret.ntsclevel = ret.ntsclevel
	s ret.ntsclevreq = ret.ntsclevreq
	s ret.ntsclevreqdesc = ret.ntsclevreqdesc
	s ret.ntscstratus = ret.ntscstratus
	s ret.ntscmearn = ret.ntscmearn
	s ret.ntsctrans = ret.ntsctrans
	s ret.ntscdelcenter = ret.ntscdelcenter
	s ret.ntscaffi = ret.ntscaffi
	s ret.ntscsponsor = ret.ntscsponsor
	s ret.ntscsponsorgroup = ret.ntscsponsorgroup
	s ret.ntscrecpient = ret.ntscrecpient
	s ret.ntscrecpgroup = ret.ntscrecpgroup
	s ret.ntscassigner = ret.ntscassigner
	s ret.ntsccueernt = ret.ntsccueernt
	s ret.ntscpubdate = $zd(ret.ntscpubdate,3)
	s ret.ntscreledate = ret.ntscreledate
	s ret.ntscquaily = ret.ntscquaily
	s ret.ntsccurrgroup = ret.ntsccurrgroup
	s ret.ntsccreatuser = ret.ntsccreatuser
	s ret.ntsccurruser = ret.ntsccurruser
	s ret.ntsccreatdate = $zd(ret.ntsccreatdate,3)
    q ret
}

/// w ##class(NTSC.Pharmacy.ReqQuaCont.NTSCReqQuaCont).getJoin("6138414,6139799,6139044,6129854,6216078,6215756,6216047,6215807,6215780,6215850,6216074,6215819,6215853,6215874,6215931,6216054,6216057")
/// 为了不写重复代码,将验证列表id拼接成可查询字符串
ClassMethod getJoin(tempStr)
{
	;s tempStr = "6138414,6139799,6139044,6129854,6216078,6215756,6216047,6215807,6215780,6215850,6216074,6215819,6215853,6215874,6215931,6216054,6216057"
	s inClause = "("
	while $L(tempStr) > 0 {
	    s currentVal = $P(tempStr, ",", 1)
	    s inClause = inClause _ $CHAR(34) _ currentVal _ $CHAR(34)
	    s tempStr = $P(tempStr, ",", 2, $L(tempStr))
	    i $L(tempStr) > 0 {
	        s inClause = inClause _ ","
	    }
	}
	s inClause = inClause _ ")"
	q inClause
}

}

7、保存方法的map映射类

Class NTSC.Pharmacy.ReqQuaCont.NTSCReqQuaContObj Extends %RegisteredObject
{

/// w ##class(NTSC.Pharmacy.ReqQuaCont.NTSCReqQuaContObj).reqQuaObj("1").%ToJSON()
ClassMethod reqQuaObj(rowID = "") [ CodeMode = objectgenerator ]
{
	 s dataMap = {
        "ntscno":             "NTSC_NO",
        "ntsctitle":         "NTSC_Title",
        "ntsctype":            "NTSC_Type",
        "ntsctstype":        "NTSC_TSType",
        "ntsctstext":      "NTSC_TSText",
        "ntsclevel":     "NTSC_Level",
        "ntsclevreq":     "NTSC_LevReq",
        "ntsclevreqdesc":     "NTSC_LevReqDesc",
        "ntscstratus":    "NTSC_LevStratus",
        "ntscmearn":        "NTSC_Meran",
        "ntsctrans":       "NTSC_Trans",
        "ntscdelcenter":      "NTSC_DelCenter",
        "ntscaffi":      "NTSC_Affil",
        "ntscsponsor":      "NTSC_Sponsor",
        "ntscsponsorgroup":      "NTSC_SpoGroup",
        "ntscrecpient":      "NTSC_Recipient",
        "ntscrecpgroup":     "NTSC_RecGroup",
        "ntscassigner":     "NTSC_Assigner",
        "ntsccueernt":     "NTSC_Current",
        "ntscpubdate":     "NTSC_PublicityDaye",
        "ntscreledate":     "NTSC_ReleaseDate",
        "ntscquaily":     "NTSC_Quality",
        "ntsccurrgroup":     "NTSC_CurrentGroup",
        "ntsccreatuser":     "NTSC_CeartUser",
        "ntsccurruser":     "NTSC_CurrentUser",
        "ntsccreatdate":     "NTSC_CeartDate"
    } 
    d %code.WriteLine(" q:(rowID = """") " _ dataMap.%ToJSON())    

    s selectInto = ##class(PHA.COM.SQL.Json).MapJson2SelectInto(dataMap)
    s sqlStr = "SELECT " _ $lts(selectInto, " INTO ") _ " FROM SQLUSER.NTSC_ReqQua where %ID = :rowID"
    d %code.WriteLine(##class(PHA.COM.SQL.Generator).Select2JsonData(sqlStr))
    q $$$OK
}
}

8、csp页面引用的其他js方法,不能放在同一个文件中,太过庞大,excel导入插件使用公共的即可

8.1、scripts/NTSC/ReqQuaCont/loading.js 这个js主要是用于导入数据和验证的时候弹出过度动画和excel导入的日期处理。

// Excel日期转JavaScript Date(处理1900年闰年bug)
// wangsheng
// 2025-09-30
// 可以在其他页面调用的公共组件并初始化动态加载数据框
function excelDateToJsDate(excelDate) {
  if (typeof excelDate !== 'number') {
    const date = new Date(excelDate);
    if (!isNaN(date.getTime())) return date;
    return excelDate;
  }
  let jsDate;
  if (excelDate < 60) {
    jsDate = new Date(1900, 0, excelDate);
  } else {
    jsDate = new Date(1900, 0, excelDate - 1);
  }
  jsDate.setMinutes(jsDate.getMinutes() - jsDate.getTimezoneOffset());
  return jsDate;
}

// 日期格式化为 “YYYY-MM-DD”
function formatDate(date) {
  return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
}

// 创建简洁炫酷的加载动画(纯CSS实现,性能更优)
function createLoadingAnimation() {
  // 清理已有加载层,避免重复
  const existing = document.getElementById('loadingOverlay');
  if (existing) document.body.removeChild(existing);

  // 1. 遮罩层(半透明+背景模糊)
  const overlay = document.createElement('div');
  overlay.id = 'loadingOverlay';
  overlay.style.cssText = `
    position: fixed; top: 0; left: 0;
    width: 100%; height: 100%;
    background-color: rgba(0, 0, 0, 0.6);
    display: flex; justify-content: center; align-items: center;
    z-index: 9999;
    backdrop-filter: blur(2px); /* 背景模糊增强质感 */
  `;

  // 2. 加载容器(卡片式设计+阴影)
  const container = document.createElement('div');
  container.style.cssText = `
    background: white; padding: 24px 32px;
    border-radius: 10px; text-align: center;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
    min-width: 300px;
  `;

  // 3. 旋转动画(CSS关键帧实现)
  const loader = document.createElement('div');
  loader.style.cssText = `
    width: 48px; height: 48px;
    margin: 0 auto 16px;
    border: 4px solid #f3f3f3;
    border-top: 4px solid #409eff;
    border-radius: 50%;
    animation: spin 1s linear infinite;
  `;

  // 4. 进度文本(实时更新百分比)
  const text = document.createElement('p');
  text.id = 'loadingText';
  text.textContent = '正在加载中... 0%';
  text.style.cssText = `
    margin: 8px 0; font-size: 16px;
    color: #333; font-weight: 500;
  `;

  // 5. 进度条(渐变填充+平滑过渡)
  const progressBar = document.createElement('div');
  progressBar.style.cssText = `
    height: 6px; width: 100%;
    background: #eaeaea; border-radius: 3px;
    overflow: hidden; margin-top: 12px;
  `;

  const barFill = document.createElement('div');
  barFill.id = 'loadingBarFill';
  barFill.style.cssText = `
    height: 100%; width: 0%;
    background: linear-gradient(90deg, #409eff, #67c23a);
    transition: width 0.3s ease;
  `;
  progressBar.appendChild(barFill);

  // 6. 动画样式(旋转关键帧)
  const style = document.createElement('style');
  style.textContent = `
    @keyframes spin {
      from { transform: rotate(0deg); }
      to { transform: rotate(360deg); }
    }
  `;

  // 组合元素并插入页面
  container.appendChild(loader);
  container.appendChild(text);
  container.appendChild(progressBar);
  overlay.appendChild(container);
  document.head.appendChild(style);
  document.body.appendChild(overlay);

  // 返回控制方法(更新、销毁)
  return {
    setTotal: function(total) {
      this.total = total;
    },
    update: function(current) {
      if (!this.total) return;
      const percent = Math.min(Math.round((current / this.total) * 100), 100);
      document.getElementById('loadingText').textContent = `正在加载中... ${percent}%`;
      document.getElementById('loadingBarFill').style.width = `${percent}%`;
    },
    destroy: function() {
      overlay.style.opacity = '0';
      setTimeout(() => {
        if (document.body.contains(overlay)) {
          document.body.removeChild(overlay);
        }
      }, 300);
    }
  };
}

8.2、scripts/NTSC/ReqQuaCont/com.js 这个js是公共组件封装,部分代码用其他页面已经写好的,不然重新写太费时间。

/**
 * 需求质控列表 - 通用
 * csp: csp/NTSC/NTSCReqQuaCont.csp
 * js: scripts/NTSC/ReqQuaCont/com.js
 */
//
var PLAN_COM = {
    ApiClass: 'NTSC.Pharmacy.ReqQuaCont.NTSCReqQuaCont',
    AppCode: 'DHCSTPURPLANAUDIT',
    AppComCode: 'DHCSTCOMMON',
    GetChangedRows: function (domID) {
        return PHA_GridEditor.GetChangedRows(domID);
    },
    Invoke: function (apiMethod, pJson, callback) {
        // 仅仅用于处理保存等操作
        this.AppendLogonData(pJson);
        PHA.CM(
            {
                pClassName: PLAN_COM.ApiClass,
                pMethodName: apiMethod,
                pJson: JSON.stringify(pJson)
            },
            function (retData) {
                callback(PLAN_COM.FmtApiReturn(retData));
            },
            function () {
                callback('访问错误');
            }
        );
    },
    InvokeSyn: function (apiMethod, pJson) {
        this.AppendLogonData(pJson);
        var retData = PHA.CM(
            {
                pClassName: PLAN_COM.ApiClass,
                pMethodName: apiMethod,
                pJson: JSON.stringify(pJson)
            },
            false
        );
        var apiRet = PLAN_COM.FmtApiReturn(retData);
        if (typeof apiRet === 'string') {
            PHA.Alert('提示', apiRet, 'error');
        }
        return apiRet;
    },
    ApiMethods: {
        Save: 'HandleSave',
        SaveAsMould: 'HandleSaveAsMould',
        Delete: 'HandleDeleteMain',
        Finish: 'HandleFinish',
        FinishCancel: 'HandleFinishCancel',
        Audit: 'HandleAudit',
        AuditCancel: 'HandleAuditCancel',
        AuditRefuse: 'HandleAuditRefuse',
        DeleteItm: 'HandleDeleteItm',
        DeleteItms: 'HandleDeleteItms',
        Merge: 'HandleMerge' 
    },
    Fmt2ApiMethod: function (pHandleCode) {
        var apiMethod = this.ApiMethods[pHandleCode];
        if (apiMethod === '') {
            PHA.Alert('致命错误', pHandleCode + '没有可供调用的程序');
        }
        return apiMethod;
    },
    GetMainData: function (planID) {
        return this.InvokeSyn('GetMainData', { planID: planID });
    },
    GetItmData: function (planItmID) {
        return this.InvokeSyn('GetMainData', { planItmID: planItmID });
    },
    QueryMainGrid: function (domID, pJson) {
        var $grid = $('#' + domID);
        //if(!pJson || pJson == undefined) return;
        if(pJson !=  undefined)
         {var pJsonStr = JSON.stringify(pJson).replace(/(-q)|(-merge)/g, '');
        }
        $grid.datagrid('options').url = PHA.$URL;
        $grid.datagrid('query', {
            pClassName: PLAN_COM.ApiClass,
            pMethodName: 'GetMainDataRows',
            pJson: pJsonStr,
            pPlug: 'datagrid'
        });
    },
    QueryItmGrid: function (domID, pJson) {
        var $grid = $('#' + domID);
        var pJsonStr = JSON.stringify(pJson).replace(/-q/g, '');
        $grid.datagrid('options').url = PHA.$URL;
        $grid.datagrid('query', {
            pClassName: PLAN_COM.ApiClass,
            pMethodName: 'GetItmDataRows',
            pJson: pJsonStr,
            pPlug: 'datagrid'
        });
    },
    QueryGrid: function (domID, pMethodName, pJson) {
        var $grid = $('#' + domID);
        var pJsonStr = JSON.stringify(pJson).replace(/-q/g, '');
        $grid.datagrid('options').url = PHA.$URL;
        $grid.datagrid('query', {
            pClassName: PLAN_COM.ApiClass,
            pMethodName: pMethodName,
            pJson: pJsonStr,
            pPlug: 'datagrid'
        });
    },
    LoadData: function (domID, qJson) {
        qJson.pClassName = PLAN_COM.ApiClass;
        PHA_COM.LoadData(domID, qJson);
    },
    /**
     * 模块参数
     */
    GetSettings: function () {
        // 单例模式就需要全局变量控
        var planSettings = PHA_COM.ParamProp('DHCSTPURPLANAUDIT');
        var comSettings = PHA_COM.ParamProp('DHCSTCOMMON');
        return {
            App: planSettings,
            Com: comSettings,
            DefaultData: {
                startDate: PHA_UTIL.GetDate('t' + planSettings.DefaStartDate),
                endDate: PHA_UTIL.GetDate('t' + planSettings.DefaEndDate),
                loc: session['LOGON.CTLOCID']
            }
        };
    },
    TrimDelim: function (str) {
        return str.replace(/-\d*\^/g, '');
    },
    FmtApiReturn: function (retData) {
        if (retData.success === 0 || retData.code < 0) {
            return PLAN_COM.TrimDelim(retData.msg);
        }
        return retData;
    },
    FmtApiInput: function (retData) {},
    ValidateGridData: function (data) {
        if (data.success === 0) {
            PHA.Alert('提示', data.msg, 'error');
            return false;
        }
        return true;
    },
    /**
     * 校验返回值, 如果错误则提醒, 此类为异常报错, 需要为alert形式
     * @param {*} data 后台的原始返回值
     * @returns
     */
    ValidateApiReturn: function (data) {
        var ret = this.FmtApiReturn(data);
        if (typeof ret === 'string') {
            PHA.Alert('提示', ret, 'error');
            return false;
        }
        return true;
    },
    /**
     * 给传到后台的程序追加session信息, 建议取不到session时, 以此为准
     * @param {} jsonObj
     */
    AppendLogonData: function (jsonObj) {
        return PHA_COM.AppendLogonData(jsonObj);
    },
    Top: {
        Const: ['PHA_IN_PLAN_ID'],
        Set: function (key, value) {
            if (!top.PHA_IN_PLAN) {
                top.PHA_IN_PLAN = {};
            }
            top.PHA_IN_PLAN[key] = value;
        },
        Get: function (key, clearFlag) {
            if (top.PHA_IN_PLAN) {
                var ret = top.PHA_IN_PLAN[key] || ''; // 对象需要深拷贝
                if (clearFlag === true) {
                    delete top.PHA_IN_PLAN[key];
                }
                return ret;
            }
            return '';
        }
    },
    GetSelectedRow: function (target, field) {
        return PHA_COM.GetSelectedRow(target, field);
    },
    GetSelectedRowIndex: function (target) {
        return PHA_COM.GetSelectedRowIndex(target);
    },
    Condition: function (target, type, options) {
        return PHA_COM.Condition(target, type, options);
    },
    UpdateRow: function (target, rowIndex, pJson) {
        var rowData = this.InvokeSyn('GetMainData', pJson);
        $(target).datagrid('updateRow', {
            index: rowIndex,
            row: rowData
        });
    },
    /**
     * 集成处理某个模块的页面相关控制, 有些改动不需要每个界面都调试, 如面板高度等
     * @param {} cspName 页面csp全称
     */
    SetPage: function (cspName) {
        cspName = cspName || App_MenuCsp;
        switch (cspName) {
            case 'pha.in.v3.plan.audit.csp':
                PHA_COM.ResizePanel({
                    layoutId: 'layout-plan-audit',
                    region: 'north',
                    height: 0.5
                });
                break;
            case 'pha.in.v3.plan.query.csp':
                PHA_COM.ResizePanel({
                    layoutId: 'layout-plan-query',
                    region: 'north',
                    height: 0.5
                });

                break;
            case 'pha.in.v3.plan.create.csp':
                PHA_COM.SetPanel('#layout-plan-create-panel', $('#layout-plan-create-panel').panel('options').title);
                // $('#btnPrint-q').parent().hide()
                break;
            case 'pha.in.v3.plan.createbystock.csp':
                PHA_COM.SetPanel('#layout-plan-createbystock-panel', $('#layout-plan-createbystock-panel').panel('options').title);
                break;
            case 'pha.in.v3.plan.createbyconsume.csp':
                PHA_COM.SetPanel('#layout-plan-createbyconsume-panel', $('#layout-plan-createbyconsume-panel').panel('options').title);
                break;
            case 'pha.in.v3.plan.createbyreq.csp':
                PHA_COM.ResizePanel({
                    layoutId: 'layout-plan-createbyreq',
                    region: 'north',
                    height: 0.5
                });
                break;

            default:
                break;
        }
        /**
         * 通用快捷键, 模块内的至少应该一致
         * @fix 药品下拉弹窗清除了原注册的快捷键
         * @fix 如何按按dom区域绑定, 如弹出框与原界面有类似操作
         */
        PHA_EVENT.Key([
            // ['btnClean', 'alt+c'], // clean
            // [$('#btnSelect').length > 0 ? 'btnSelect' : 'btnFind', 'alt+f'], // find
            ['btnAddItm', 'alt+a'], // add
            ['btnDeleteItm', 'alt+d'], // delete
            ['btnAddItm', 'alt+='], // +
            ['btnDeleteItm', 'alt+-'], // -
            ['btnDelete', 'alt+shift+d'], // 上档delete, 加强版
            ['btnSave', 'ctrl+s'],
            ['btnClean', 'alt+c'],
            ['btnFind', 'alt+f']
        ]);
        $('[id^=qCondition] a').width('100%');
        PHA.SetRequired($('[id^=qCondition] [data-pha]'));
    },
    ControlOperation: function (handleObj) {
        PHA_COM.ControlOperation(handleObj);
    },
    ValidatePrice: function (val) {
        var msg = '请输入大于或等于0的数字';
        if (_.isLikeNumber(val) === false) {
            return msg;
        }
        if (parseFloat(val) < 0) {
            return msg;
        }
        return true;
    },
    ValidateQty: function (val) {
        var msg = '请输入大于0的数字';
        if (_.isLikeNumber(val) === false) {
            return msg;
        }
        if (parseFloat(val) <= 0) {
            return msg;
        }
        return true;
    },
    Calc: function () {},
    SumGridData: function (target, fieldArr) {
        return PHA_COM.SumGridData(target, fieldArr);
    },
    SumGridFooter: function (target, fieldArr) {
        PHA_COM.SumGridFooter(target, fieldArr);
    },

    HandleCheckStyle: function (target, method, rowIndex) {
        PHA_COM.HandleCheckStyle(target, method, rowIndex);
    },
    GridFinalDone: function (target) {
        PHA_GridEditor.GridFinalDone(target, 'inciCode');
    },
    Print: function (planID) {
        planID = planID || '';
        if (planID === '') {
            PHA.Popover({
                msg: '请先选择需要打印的单据',
                type: 'info'
            });
            return;
        }
        PLAN_PRINT.Print(planID);
        return;
        // 如下是xml模板形式, 但调整格式还不如直接那代码来的快
        var printStyle = PHA_COM.PrintStyle;
        PRINTCOM.XML({
            printBy: 'lodop',
            XMLTemplate: 'PHAINPLAN',
            dataOptions: {
                ClassName: 'PHA.IN.PLAN.Api',
                MethodName: 'GetPrintData',
                pJsonStr: JSON.stringify({
                    planID: planID,
                    printUserName: session['LOGON.USERNAME']
                })
            },
            // 分页位置
            page: $.extend(printStyle.page, {}),
            // 边框样式
            listBorder: $.extend(printStyle.listBorder, {}),
            // 金额居右
            listColAlign: $.extend(printStyle.listColAlign, {}),
            // 自适应底部签名
            aptListFields: printStyle.FixListFields(['printInfo', 'createUserName', 'auditUserName']) //,
            // 结束页签名
            // endPageFields: printStyle.FixListFields(['printInfo', 'createUserName', 'auditUserName'])
        });
    },
    Copy: function (pJson, callback) {
        PLAN_COM.Invoke('HandleCopy', pJson, function (retData) {
            callback(retData);
        });
    },
    GetWindowId4Event: function () {
        return $(window.event.target).closest('.js-pha-com-window-sign').attr('id') || '';
    },
    DeleteJsonKeys: function (dataObj, delFields) {
        for (var i = 0, length = delFields.length; i < length; i++) {
            delete dataObj[delFields[i]];
        }
    },
    SetKeyValue2Null: function (dataObj, delFields) {
        for (var i = 0, length = delFields.length; i < length; i++) {
            dataObj[delFields[i]] = '';
        }
    }
};

// 集成
$.extend($.fn.validatebox.defaults.rules, {
    planQty: {
        message: '请输入大于0的数字',
        validator: function (value) {
            if (_.isLikeNumber(value) === false) {
                return false;
            }
            if (parseFloat(value) < 0) {
                return false;
            }
            return true;
        }
    },
    planPrice: {
        message: '请输入大于等于0的数字',
        validator: function (value) {
            if (_.isLikeNumber(value) === false) {
                return false;
            }
            if (parseFloat(value) < 0) {
                return false;
            }
            return true;
        }
    }
});

8.3、scripts/NTSC/ReqQuaCont/compent.js 这个js是页面表头查询条件的封装

/**
 * 需求质控列表通用组件
 * scripts/NTSC/ReqQuaCont/compent.js
 */
 
var PLAN_COMPONENTS = function () {
    var components = {
        Level: function (domID, options) {
            options = options || {};
            this.LevelStatus(domID, options);
        },
        LevelStatus : function(domId, des){
			PHA.ComboBox(domId, {
		        width: 155,
		        panelHeight: 'auto',
		        data: [
	                { RowId: '高', 		Description: $g('高') },
	                { RowId: '中', 	Description: $g('中') },
	                { RowId: '低', 	Description: $g('低') },
	            ]
		    });
		},
        Date: function (domID) {
            PHA.DateBox(domID, {});
            $('#' + domID).datebox('setValue', 't');
        },

 		Remarks: function (domID) {
            PHA.ValidateBox(domID, {});
            $('#' + domID).attr('data-pha', ['class: "hisui-validatebox"', 'clear: true', 'query: true'].join(','));
        },

        ItmGrid: function (domID, opts) {
            var columnsObj = this.ItmGridColmuns(domID);

            var dataGridOption = {
                url: '',
                exportXls: false,
                columns: opts.columns ? opts.columns : columnsObj.columns,
                frozenColumns: opts.frozenColumns ? opts.frozenColumns : columnsObj.frozenColumns,
                toolbar: [],
                pageNumber: 1,
                pageSize: 100,
                rownumbers: true,
                pagination: false,
                autoSizeColumn: true,
                shiftCheck: true,
                singleSelect: true,
                checkOnSelect: false, // 互不干扰, 应保持输入与勾选分开, 但是勾选还需要能分出信息
                selectOnCheck: false,
                showFooter: true,
                showComCol: true,
                footerSumFields: ['rpAmt', 'spAmt'],
                editFieldSort: ['inci', 'qty', 'vendor', 'carrier', 'reqLoc', 'rp'],

                onLoadSuccess: function (data) {
                    PHA_GridEditor.End(this.id);
                    PLAN_COM.SumGridFooter('#' + this.id, ['rpAmt', 'spAmt']);
                }
            };
            PHA.Grid(domID, $.extend(dataGridOption, opts));
            var eventClassArr = ['pha-grid-a js-grid-inciCode','pha-grid-a js-grid-hospQty'];
            PHA.GridEvent(domID, 'click', eventClassArr, function (rowIndex, rowData, className) {
                if (className.indexOf('js-grid-inciCode') >= 0) {
                    //PHA_UX.DrugDetail({}, { inci: rowData.inci });
                    PHA_UX.InciRecList({}, { inci: rowData.inci });
                    return;
                }
                else if(className.indexOf('js-grid-hospQty') >= 0) {
                    //PHA_UX.DrugDetail({}, { inci: rowData.inci });
	                PHA_UX.HospInciStock({},{
	                    inci: rowData.inci,
	                    inclb: rowData.inclb,
	                    inciDesc: rowData.inciDesc
	                });
                    return;
                }
            },
            function(){PHA_UX.HospInciStock({},{},"close")}
            );
        },
        ItmGridColmuns: function (gridID) {
            gridID = gridID || 'gridItm';
            // 解决editor内this不统一无法正常获取表格属性的问题
            function GridOptions() {
                return {
                    gridID: gridID,
                    $grid: $('#' + gridID)
                };
            }
            var frozenColumns = [[ ] ];
            var columns = [[ ]];
            return {
                frozenColumns: frozenColumns,
                columns: columns
            };
        },
        MainGrid: function (domID, opts) {
            var singleSelect = ('singleSelect' in opts) ? opts.singleSelect : true;
            var columnsObj = this.MainGridColmuns(singleSelect);
            var dataGridOption = {
                url: '',
                exportXls: false,
                columns: columnsObj.columns,
                frozenColumns: columnsObj.frozenColumns,
                toolbar: [],
                pageNumber: 1,
                pageSize: 100,
                autoSizeColumn: true,
                isAutoShowPanel: false,
                rownumbers: true,
                loadFilter: function (data) {
                    if (data.success === 0) {
                        PHA.Alert('提示', data.msg, 'warning');
                    } else {
                        if (data.rows.length > 0) {
                            if (typeof data.rows[0] === 'string') {
                                PHA.Alert('提示', data.rows[0], 'warning');
                            }
                        }
                    }
                    return data;
                }
            };
            PHA.Grid(domID, $.extend(dataGridOption, opts));
            // 时间轴

            PHA.GridEvent(domID, 'click', ['pha-grid-a js-grid-planNo'], function (rowIndex, rowData, className) {
                if (className === 'pha-grid-a js-grid-planNo') {
                    PHA_UX.BusiTimeLine(
                        {},
                        {
                            busiCode: 'PLAN',
                            pointer: rowData.planID
                        }
                    );
                }
            },
            function(){
	            PHA_UX.BusiTimeLine({},{},"close")
            });
        },
        MainGridColmuns: function (singleSelect) {
            var frozenColumns = [[ ]];
            if (!singleSelect) {  // 不是单行选择,则为多选
                frozenColumns[0].unshift({ 
                    field: 'tSelect', 		
                    checkbox: true 
                })
            }
            var columns = [[ ]];
            return {
                frozenColumns: frozenColumns,
                columns: columns
            };
        },
        PlanInfo: function () {},
        Pop: function (msg, type) {
            type = type || 'info';
            PHA.Popover({
                msg: msg,
                type: type
            });
        }
    };
    return function (type) {
        var pParams = [].slice.call(arguments, 1, arguments.length);
        // 注意apply 第一个参数this的变化, 此处默认this指window, 因此做改动
        return components[type].apply(components, pParams);
    };
};