混合式提高用户编辑与操作效率

近段时间修改一个较古老的项目,把涉及到的技术与问题分享于此。
提高用户操作效率,键盘录入数据与鼠标之间操作方面。
原来的操作,用户并没有什么说法,随之需要录入的数据量大,时间与效率自然产生。

下面把改善相关问题,集中成一个小案例,
2026-04-28_16-05-16

 

项目是WebForm的asp.net。
是显示数据是使用Gridview服务控件。
每行最右边是更新按钮,各行各自更新。
在GridView外,即下面还有一个按钮,“一次性更新”,对所有行的备注进行一次性更新。
还有每行中的备注栏的文本框,用户输入备注信息后,点击文本框外,即更新。
它还可以输入完备注后,按回车亦可更新。

用户按自己习惯来操作。

为了这个示例,先准备一些数据,数据表,添加演示临时数据,创建存储过程,自定义表函数等...

-- =============================================
-- Author:        Insus.NET
-- Create date: 2026-04-28
-- Description:    数据表ZTestData
-- =============================================
CREATE TABLE [dbo].[ZTestData]
(
    [ID] INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
    [IMT_pz] NVARCHAR(30) NOT NULL,
    [IMT_gx] NVARCHAR(30) NOT NULL,
    [BeiZhu] NVARCHAR(30) NULL
)
GO

-- =============================================
-- Author:        Insus.NET
-- Create date: 2026-04-28
-- Description:    数据表添加一些示例临时数据
-- =============================================
INSERT INTO [dbo].[ZTestData]
(
    [IMT_pz],
    [IMT_gx]
)
VALUES
(
    N'f043',
    N'P10'
)
,(
    N'l52',
    N'P10'
)
,(
    N'j71',
    N'P30'
)
,(
    N'v11',
    N'P20'
)
,(
    N'U33',
    N'P20'
)
GO

-- =============================================
-- Author:        Insus.NET
-- Create date: 2026-04-28
-- Description:    存储过程,获取所有数据
-- =============================================
CREATE PROCEDURE [dbo].[usp_ZTestData_SelectAll]
AS
BEGIN
    SELECT [ID],
    [IMT_pz],
    [IMT_gx],
    [BeiZhu]
    FROM [dbo].[ZTestData]
END
GO

-- =============================================
-- Author:        Insus.NET
-- Create date: 2026-04-28
-- Description:    更新存储过程
-- =============================================
CREATE PROCEDURE [dbo].[usp_ZTestData_UpdateBeiZhu]
@ID INT,
@BeiZhu NVARCHAR(30)
AS
BEGIN
    UPDATE [dbo].[ZTestData]
    SET
        [BeiZhu] = @BeiZhu
    WHERE 
        [ID] = @ID
END
GO

-- =============================================
-- Author:        Insus.NET
-- Create date: 2026-04-28
-- Description:    创建一个表函,批量更新,作为存储过程传入的参数
-- =============================================
CREATE TYPE [dbo].[udtt_ZTestData_BZ] AS TABLE(
    [ID] [int] NOT NULL,    
    [BeiZhu] [nvarchar](30) NULL
)
GO

-- =============================================
-- Author:        Insus.NET
-- Create date: 2026-04-28
-- Description:    批理更新存储过程
-- =============================================
CREATE PROCEDURE [dbo].[usp_ZTestData_BatchUpdate]
    @BatchBeiZhu  [dbo].[udtt_ZTestData_BZ] READONLY
AS
BEGIN
    MERGE [dbo].[ZTestData] AS TARGET
    USING @BatchBeiZhu AS SOURCE        
    ON TARGET.[ID] = SOURCE.[ID]
    WHEN MATCHED THEN
        UPDATE SET        
        TARGET.[BeiZhu] = SOURCE.[BeiZhu]
    ;
END
GO
SQL Code

 

在App_Code目录下,添加2个类,虽然不是ASP.NET MVC项目,修改的项目.NET 2.0时代,那时MVC还没有出来吧。

ZTestData.cs

namespace Insus.NET
{
    public class ZTestData
    {
        public int ID { get; set; }

        public string IMT_pz { get; set; }

        public string IMT_gx { get; set; }

        public string BeiZhu { get; set; }

        public DataTable BatchBeiZhu { get; set; }
    }
}
ZTestData Code


ZTestDataEntity.cs

namespace Insus.NET
{
    /// <summary>
    /// ZTestDataEntity 的摘要说明
    /// </summary>
    
    public class ZTestDataEntity
    {
        BusinessBase objBusinessBase = new BusinessBase();

        public DataTable Get_DataTable_GetData()
        {
            return objBusinessBase.GetDataToDataSet("usp_ZTestData_SelectAll").Tables[0];
        }

        public void UpdateBeiZhu(ZTestData ztd)
        {
            Parameter[] parameter = {
                                        new Parameter("@ID",SqlDbType.Int,4,ztd.ID),
                                        new Parameter("@BeiZhu",SqlDbType.NVarChar,-1,ztd.BeiZhu)
                                    };
            objBusinessBase.ExecuteProcedure("usp_ZTestData_UpdateBeiZhu", parameter);
        }

        public void BatchUpdateBeiZhu(ZTestData ztd)
        {
            Parameter[] parameter = {
                                        new Parameter("@BatchBeiZhu",SqlDbType.Structured,-1,ztd.BatchBeiZhu)
                                    };
            objBusinessBase.ExecuteProcedure("usp_ZTestData_BatchUpdate", parameter);
        }
    }
}
ZTestDataEntity Code

 

在App_Code目录下,创建一个新目录APIs,然后在此目录创建WebAPI
WebForm实现Web APIhttps://www.cnblogs.com/insus/p/19937505

 后端实现的功能,每行各自更新功能,和一次性更新功能。

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            Data_Binding();
        }

    }

    private void Data_Binding()
    {
        ZTestDataEntity ztde = new ZTestDataEntity();
        this.GridViewZTestData.DataSource = ztde.Get_DataTable_GetData();
        this.GridViewZTestData.DataBind();
    }

    protected void GridViewZTestData_RowCreated(object sender, GridViewRowEventArgs e)
    {
        if (e.Row.RowType != DataControlRowType.DataRow) return;       

        Button btnUpdate = (Button)e.Row.FindControl("ButtonUpdate");
        if (btnUpdate != null)
        {
            btnUpdate.Click += BtnUpdate_Click;
        }
    }

    private void BtnUpdate_Click(object sender, EventArgs e)
    {
        Button button = (Button)sender;
        GridViewRow gvr = (GridViewRow)button.Parent.Parent;

        var textboxBeiZhu = (TextBox)gvr.FindControl("TextBoxBeiZhu");

        try
        {
            string keyVal = GridViewZTestData.DataKeys[gvr.RowIndex].Value.ToString();

            ZTestData ztd = new ZTestData();
            ztd.ID = Convert.ToInt32(keyVal);
            ztd.BeiZhu = textboxBeiZhu.Text.Trim();

            ZTestDataEntity ztde = new ZTestDataEntity();
            ztde.UpdateBeiZhu(ztd);
            //JsAlert("保存成功。");

        }
        catch (Exception ex)
        {
            //JsAlert(ex.Message);
            return;
        }
        finally
        {
            Data_Binding();
        }
    }

    protected void ButtonOneTimeUpdate_Click(object sender, EventArgs e)
    {
        DataTable _dt_bz = new DataTable();
        _dt_bz.Columns.Add("ID", typeof(int));
        _dt_bz.Columns.Add("BeiZhu", typeof(string));

        foreach (GridViewRow gvr in this.GridViewZTestData.Rows)
        {
            int keyID = Convert.ToInt32(this.GridViewZTestData.DataKeys[gvr.RowIndex].Value);
            string bz = ((TextBox)gvr.FindControl("TextBoxBeiZhu")).Text.Trim();
            _dt_bz.Rows.Add(keyID, bz);
        }

        try
        {
            ZTestData ztd = new ZTestData();
            ztd.BatchBeiZhu = _dt_bz;
            ZTestDataEntity ztde = new ZTestDataEntity();
            ztde.BatchUpdateBeiZhu(ztd);
            //JsAlert("数据已经保存。");
        }
        catch (Exception ex)
        {
            //JsAlert(ex.Message);
        }

    }
}
View Code

 

本随笔着重是GridView每行的备注文本框,让其实现OnBlur和用户输入完后按回车提交更新。
打开上面

protected void GridViewZTestData_RowCreated(object sender, GridViewRowEventArgs e)

 

为文本框添加2个属性,一个OnBlur和数据属性,
2026-04-29_08-23-26

 
前端js,写一个方法,传入更新参数,去呼叫Web API,
2026-04-29_10-37-58

let isUpdating = false; //更新请求
let skipBlurOnce = false; //区分是否为blur事件

function updateBeizhuByTextBox(txtboxElement) {
    return new Promise((resolve, reject) => {
        if (isUpdating) return reject("已有请求进行中");

        var id = txtboxElement.id;
        var baseId = id.substring(0, id.lastIndexOf("_") + 1);
        var hiddenKey = document.getElementById(baseId + "HiddenFieldID");

        isUpdating = true;
        let url = '/api/MyTest/UpdateBeiZhu';
        var postData = {
            ID: hiddenKey.value,
            BeiZhu: txtboxElement.value.trim()
        };

        axios.post(url, JSON.stringify(postData), {
            headers: { 'Content-Type': 'application/json; charset=utf-8' }
        })
            .then(response => {
                if (!response.data.Status) {
                    alert(response.data.Message);
                    reject(response.data.Message);
                } else {
                    alert(response.data.Message);
                    resolve(response.data);
                }
            })
            .catch(error => {
                console.error(error);
                reject(error);
            })
            .finally(() => {
                isUpdating = false;
            });
    });
}
View Code

 

 OnBlur前端事件,
2026-04-29_10-45-54

function UpdateBeiZhu(event) {
    if (skipBlurOnce) {
        skipBlurOnce = false;
        return;
    }

    updateBeizhuByTextBox(event.target)
        .catch(console.warn);   
}
View Code

 

最后是用户按回车更新前端事件,
2026-04-29_10-49-37

document.addEventListener('keydown', function (e) {
    if (e.key === 'Enter' || e.keyCode === 13) {
        var activeEl = document.activeElement;
        if (activeEl && activeEl.getAttribute('data-bz') === 'gv-txtbox-beizhu') {
            e.preventDefault();
            skipBlurOnce = true;  // 阻止随后的 blur 触发  
            activeEl.blur();      //让按完回车后文框的focus焦点去除。
            updateBeizhuByTextBox(activeEl)
                .catch(console.warn)
                .finally(() => {
                    // 延迟重置标志,确保 blur 已被忽略
                    setTimeout(() => { skipBlurOnce = false; }, 300);
                });
        } else {
            // 如果其他元素按回车不想提交表单,可以阻止默认
            // e.preventDefault();  // 根据需求决定是否全局阻止
        }
    }
}); 
View Code


最后截屏图,还可以与下这篇对比,单独文框与GridView行内的文框按回车实现的方法。
文本框输入完后直接按回车提交数据https://www.cnblogs.com/insus/p/19811442

另外,你会看到下面代码:

 var id = txtboxElement.id;
 var baseId = id.substring(0, id.lastIndexOf("_") + 1);
 var hiddenKey = document.getElementById(baseId + "HiddenFieldID");

日后你可以参考这个方法来获取GridView行中各列服务控件。
下面是示例运行时,按F12,在查看器展开代码可见,
2026-04-29_11-13-41

TextBoxBeiZhu这是服务控件的ID,生成网页时,它的id会添加前缀高亮部分。GridView的ID + _Ctl +行索引_ + 列服务控件ID
var id = txtboxElement.id; 
获取本身的ID。
var baseId = id.substring(0, id.lastIndexOf("_") + 1);
然后使用substring()进行切割得到ID前缀(高亮部分),
var hiddenKey = document.getElementById(baseId + "HiddenFieldID");
最后与服务控件ID拼接。


整篇关键功能随笔至此。看不明或者参考使用有啥问题,可以跟Insus.NET反馈或分享。

posted @ 2026-04-29 11:22  Insus.NET  阅读(13)  评论(0)    收藏  举报