Day 24 - 依 Yachts 前台页面分析拆解後,逐步建立 Overview 後台功能 - 动态新增栏位 - ASP.NET Web Forms C#

=x= 🌵 建立 Overview Manager - Content Page 後台页面。


Yachts 前台 Overview 页面分析 :

https://ithelp.ithome.com.tw/upload/images/20211008/201394874aYu0tZsPN.jpg

📌 进到 Yachts 页面可以看到上方的不同型号轮播图及左侧型号侧边栏,这个部分的资料我们已经在 Yacht Manager - Content Page 做完後台了,而操作 Yachts 页面内容後可以发现,上方的轮播页及红框的内容区块,都是依据左侧边栏的型号决定,而红框的内容区块,则会因为内部上方的小导览列操作改变内容,另外可以仔细操作会发现小导览列的 Video 并不是每个型号都有,所以内容区块才会把小导览列包含进去,今天的文章将专注在制作 Overview 页面内的资料,分别有 :

  1. 橘色区块 : 主要图文内容。
  2. 紫色区块 : 尺寸标题及数值。
  3. 蓝色区块 : 尺寸附图。
  4. 粉色区块 : PDF 下载档案连结及显示文字。
  5. 绿色区块 : Video 内的 YouTube 影片连结。


Overview Manager 後台页面介绍 :

https://ithelp.ithome.com.tw/upload/images/20211008/201394877viKIiIUzB.jpg

🧠 左上区块 - 型号下拉选单 + 尺寸附图上传 + PDF 上传。

🧠 右上区块 - 尺寸动态新增输入栏位 + 下载标题 + Video 连结。

🧠 下半区块 - 主要图文编辑器。

  • 🌵 型号下拉选单用来决定是哪个型号的资料。


Overview Manager 後台实作 :

左上区块页面设计 :

1. 型号下拉选单设定直接使用精灵设定并将值设为 id,页面设计参考如下

<h6>YachYacht Model :</h6>
<asp:DropDownList ID="DListModel" runat="server" DataSourceID="SqlDataSource1" DataTextField="yachtModel" DataValueField="id" AutoPostBack="True" Width="100%" Font-Bold="True" class="btn btn-outline-primary dropdown-toggle" OnSelectedIndexChanged="DListModel_SelectedIndexChanged" ></asp:DropDownList>
<asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:TayanaYachtConnectionString %>" SelectCommand="SELECT [yachtModel], [id] FROM [Yachts]"></asp:SqlDataSource>
<hr />
<h6>Dimensions Image :</h6>
<asp:Literal ID="LiteralDimImg" runat="server"></asp:Literal>
<div class="input-group my-3">
    <asp:FileUpload ID="DimImgUpload" runat="server" class="btn btn-outline-primary btn-block" />
    <asp:Button ID="BtnUploadDimImg" runat="server" Text="Upload" class="btn btn-primary" OnClick="BtnUploadDimImg_Click" />
</div>
<span class="badge badge-pill badge-warning text-dark">*Upload by No Choose File Could Clean File!</span>
<hr />
<h6>Downloads File :</h6>
<asp:Literal ID="PDFpreview" runat="server" ></asp:Literal>
<div class="input-group my-3">
    <asp:FileUpload ID="FileUpload" runat="server" class="btn btn-outline-primary btn-block" />
    <asp:Button ID="BtnUploadFile" runat="server" Text="Upload" class="btn btn-primary" OnClick="BtnUploadFile_Click" />
</div>
<span class="badge badge-pill badge-warning text-dark">*Upload by No Choose File Could Clean File!</span>


2. 在 <head> 内引用 PDF.js 用来在页面预览 PDF 内容

<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.9.359/pdf.min.js"></script>


右上区块页面设计 :

1. 表格加入新增栏位删除栏位按钮,页面设计参考如下

<h6>Dimensions Text :</h6>
<table class="table table-hover">
    <thead>
        <tr class="table-info">
            <th>Item<asp:Button ID="AddRow" runat="server" Text="Add Row" class="btn btn-outline-primary btn-sm py-0 px-1 align-top mx-5" OnClick="AddRow_Click" /></th>
            <th>Value<asp:Button ID="DeleteRow" runat="server" Text="Delete" class="btn btn-outline-danger btn-sm py-0 px-1 align-top mx-5" OnClick="DeleteRow_Click" /></th>
        </tr>
    </thead>
    <tbody>
        <asp:Literal ID="LitDimensionsHtml" runat="server"></asp:Literal>
        <tr>
            <th>
                <p class="d-inline-block m-r-20">Dimensions Image</p>
            </th>
            <td>
                <asp:TextBox ID="TBoxDimImg" runat="server" type="text" class="form-control" ReadOnly="True"></asp:TextBox>
            </td>
        </tr>
        <tr>
            <th>
                <p class="d-inline-block m-r-20">Downloads Title</p>
            </th>
            <td class="table-info">
                <asp:TextBox ID="TBoxDLTitle" runat="server" type="text" class="form-control"></asp:TextBox>
            </td>
        </tr>
        <tr>
            <th>
                <p class="d-inline-block m-r-20">Downloads File</p>
            </th>
            <td>
                <asp:TextBox ID="TBoxDLFile" runat="server" type="text" class="form-control" ReadOnly="True"></asp:TextBox>
            </td>
        </tr>
        <tr>
            <th>
                <p class="d-inline-block m-r-20">Video URL</p>
            </th>
            <td class="table-info">
                <asp:TextBox ID="TBoxVideo" runat="server" type="text" class="form-control" TextMode="Url"></asp:TextBox>
            </td>
        </tr>
    </tbody>
    <tfoot>
        <tr>
            <th>
                <asp:Label ID="LabUpdateTitle" runat="server" Text="Click for Update"></asp:Label>
            </th>
            <td>
                <asp:Button ID="BtnUpdateDimensionsList" runat="server" Text="Update Dimensions List" class="btn btn-outline-primary btn-block" OnClick="BtnUpdateDimensionsList_Click" />
                <asp:Label ID="LabUpdateDimensionsList" runat="server" Text="*Upload Success!" ForeColor="green" class="d-flex justify-content-center" Visible="False"></asp:Label>
            </td>
        </tr>
    </tfoot>
</table>
  • 🌵 动态栏位只有尺寸标题及尺寸数值,下方为固定栏位,但 JSON 资料放在一起。


下半区块页面设计 :

1. 单纯的所见及所得图文编辑器,页面设计参考如下

<h6>Main Content :</h6>
<CKEditor:CKEditorControl ID="CKEditorControl1" runat="server" BasePath="/Scripts/ckeditor/"
    Toolbar="Bold|Italic|Underline|Strike|Subscript|Superscript|-|RemoveFormat
        NumberedList|BulletedList|-|Outdent|Indent|-|JustifyLeft|JustifyCenter|JustifyRight|JustifyBlock|-|BidiLtr|BidiRtl
        /
        Styles|Format|Font|FontSize
        TextColor|BGColor
        Link|Image"
    Height="400px">
</CKEditor:CKEditorControl>
<asp:Label ID="LabUploadMainContent" runat="server" Visible="False" ForeColor="#009933" class="d-flex justify-content-center"></asp:Label>
<asp:Button ID="BtnUploadMainContent" runat="server" Text="Upload Overview Content" class="btn btn-outline-primary btn-block mt-3" OnClick="BtnUploadMainContent_Click" />
  • 🌵 动态栏位只有尺寸标题及尺寸数值,下方为固定栏位,但 JSON 资料放在一起。


後置程序码方法内容 :

1. 在 Page_Load 加入画面资料读取及渲染等相关方法如下

//宣告 List 方便用 Add 依序添加栏位资料
private List<RowData> saveRowList = new List<RowData>();
protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack) {
        ckfinderSetPath();
        DListModel.DataBind(); //先绑定取得选取值
        loadContent(); //取得尺寸栏位区外的主要内容
        loadRowList(); //取得尺寸栏位内容
        renderRowList(); //渲染尺寸栏位内容
    }
}

private void ckfinderSetPath()
{
    FileBrowser fileBrowser = new FileBrowser();
    fileBrowser.BasePath = "/ckfinder";
    fileBrowser.SetupCKEditor(CKEditorControl1);
}

//栏位表 JSON 资料
public class RowData
{
    public string SaveItem { get; set; }
    public string SaveValue { get; set; }
}
  • 🌵 List<T> 为栏位 JSON 资料用。


2. 建立取得尺寸栏位外的主要内容方法 loadContent(); 方法逻辑内容如下

private void loadContent()
{
    //清空画面资料
    TBoxVideo.Text = "";
    TBoxDLTitle.Text = "";
    TBoxDimImg.Text = "";
    TBoxDLFile.Text = "";
    PDFpreview.Text = "";
    LiteralDimImg.Text = "";

    //依下拉选单选取型号取出资料
    string selectModel_id = DListModel.SelectedValue;
    SqlConnection connection = new SqlConnection(WebConfigurationManager.ConnectionStrings["TayanaYachtConnectionString"].ConnectionString);
    string sql = "SELECT * FROM Yachts WHERE id = @selectModel_id";
    SqlCommand command = new SqlCommand(sql, connection);
    command.Parameters.AddWithValue("@selectModel_id", selectModel_id);
    connection.Open();
    SqlDataReader reader = command.ExecuteReader();
    while (reader.Read()) {
        //渲染画面
        CKEditorControl1.Text = HttpUtility.HtmlDecode(reader["overviewContentHtml"].ToString());
        string imgPathStr = reader["overviewDimensionsImgPath"].ToString();
        string filePathStr = reader["overviewDownloadsFilePath"].ToString();
        TBoxDimImg.Text = imgPathStr;
        LiteralDimImg.Text = $"<img alt='Dimensions Image' class='img-thumbnail rounded mx-auto d-block' src='/Tayanahtml/upload/Images/{imgPathStr}' Width='250px'/>";
        TBoxDLFile.Text = filePathStr;
        PDFpreview.Text = $"<object type='application/pdf' data='/Tayanahtml/upload/files/{filePathStr}' width='250px' height='385px' class='rounded mx-auto d-block' ></object>";
    }
    connection.Close();
}
  • 🌵 PDF 预览用 <object> 标签放入画面。


3. 建立取得尺寸栏位内容方法 loadRowList(); 方法逻辑内容如下

private void loadRowList()
{
    //依选取型号取得栏位资料更新 List<T>
    string selectModel_id = DListModel.SelectedValue;
    SqlConnection connection = new SqlConnection(WebConfigurationManager.ConnectionStrings["TayanaYachtConnectionString"].ConnectionString);
    string sql = "SELECT * FROM Yachts WHERE id = @selectModel_id";
    SqlCommand command = new SqlCommand(sql, connection);
    command.Parameters.AddWithValue("@selectModel_id", selectModel_id);
    connection.Open();
    SqlDataReader reader = command.ExecuteReader();
    if (reader.Read()) {
        string loadJson = HttpUtility.HtmlDecode(reader["overviewDimensionsJSON"].ToString());
        saveRowList = JsonConvert.DeserializeObject<List<RowData>>(loadJson);
    }
    connection.Close();
}
  • 🌵 读取资料与渲染画面拆开,方便动态增删栏位功能制作。


4. 建立渲染尺寸栏位画面方法 renderRowList(); 方法逻辑内容如下

private void renderRowList()
{
    if (saveRowList?.Count > 0) {
        string addRowHtmlStr = "";
        int index = 0;
        //从 List<T> 载入资料
        foreach (var item in saveRowList) {
            if (index == 0) {
                TBoxVideo.Text = item.SaveValue;
            }
            if (index == 1) {
                TBoxDLTitle.Text = item.SaveValue;
            }
            if (index > 1) {
                addRowHtmlStr += $"<tr class='table-info'><th><input id='item{index}' name='item{index}' type='text' class='form-control font-weight-bold' value='{item.SaveItem}' /></th><td><input id='value{index}' name='value{index}' type='text' class='form-control' value='{item.SaveValue}' /></td></tr>";
            }
            index++;
        }
        //渲染画面
        LitDimensionsHtml.Text = addRowHtmlStr;
    }
}
  • 🌵 JSON 资料前两笔是 Video 连结及 PDF 显示文字。

  • 🌵 尺寸动态增删栏位用 HTML 的 <input> 标签并赋予 id。



分别建立按钮事件功能 :

1. 游艇型号下拉选单选项改变时触发内容

protected void DListModel_SelectedIndexChanged(object sender, EventArgs e)
{
    //隐藏上传成功提示
    LabUploadMainContent.Visible = false;
    LabUpdateDimensionsList.Visible = false;
    //渲染画面
    loadContent();
    loadRowList();
    renderRowList();
}


2. 建立尺寸附图上传 Upload 按钮 OnClick 功能如下

protected void BtnUploadDimImg_Click(object sender, EventArgs e)
{
    string savePath = Server.MapPath("~/Tayanahtml/upload/Images/");
    string fileName = DimImgUpload.FileName;
    string selectModel_id = DListModel.SelectedValue;
    SqlConnection connection = new SqlConnection(WebConfigurationManager.ConnectionStrings["TayanaYachtConnectionString"].ConnectionString);
    //有选档案才可上传,没选档案则执行清空
    if (DimImgUpload.HasFile) {
        string sqlDel = "SELECT overviewDimensionsImgPath FROM Yachts WHERE id = @selectModel_id";
        SqlCommand commandDel = new SqlCommand(sqlDel, connection);
        commandDel.Parameters.AddWithValue("@selectModel_id", selectModel_id);

        //删除旧图档
        connection.Open();
        SqlDataReader reader = commandDel.ExecuteReader();
        if (reader.Read()) {
            string delFileName = reader["overviewDimensionsImgPath"].ToString();
            if (!String.IsNullOrEmpty(delFileName)) {
                File.Delete(savePath + delFileName);
            }
        }
        connection.Close();

        //储存图片档案及图片名称
        //检查专案资料夹内有无同名档案,有同名就加流水号
        DirectoryInfo directoryInfo = new DirectoryInfo(savePath);
        string[] fileNameArr = fileName.Split('.');
        int count = 0;
        foreach (var fileItem in directoryInfo.GetFiles()) {
            if (fileItem.Name.Contains(fileNameArr[0])) {
                count++;
            }
        }
        fileName = fileNameArr[0] + $"({count + 1})." + fileNameArr[1];
        DimImgUpload.SaveAs(savePath + fileName);

        //更新资料
        string sql = "UPDATE Yachts SET overviewDimensionsImgPath = @fileName WHERE id = @selectModel_id";
        SqlCommand command = new SqlCommand(sql, connection);
        command.Parameters.AddWithValue("@fileName", fileName);
        command.Parameters.AddWithValue("@selectModel_id", selectModel_id);
        connection.Open();
        command.ExecuteNonQuery();
        connection.Close();

        //渲染画面
        loadContent();
        loadRowList();
        renderRowList();
    }
    else {
        //没选档案点上传则清空
        string sql = "UPDATE Yachts SET overviewDimensionsImgPath = @imgPath WHERE id = @selectModel_id";
        SqlCommand command = new SqlCommand(sql, connection);
        command.Parameters.AddWithValue("@imgPath", "");
        command.Parameters.AddWithValue("@selectModel_id", selectModel_id);
        connection.Open();
        command.ExecuteNonQuery();
        connection.Close();

        //渲染画面
        loadContent();
        loadRowList();
        renderRowList();
    }
}
  • 🌵 用提示告知使用者没选图片点上传会清空档案,这样可以不用写清除功能。


3. 建立 PDF 档上传 Upload 按钮 OnClick 功能如下

protected void BtnUploadFile_Click(object sender, EventArgs e)
{
    string savePath = Server.MapPath("~/Tayanahtml/upload/files/");
    string fileName = FileUpload.FileName;
    string selectModel_id = DListModel.SelectedValue;
    SqlConnection connection = new SqlConnection(WebConfigurationManager.ConnectionStrings["TayanaYachtConnectionString"].ConnectionString);
    //有选档案才可上传
    if (FileUpload.HasFile) {
        //先删除旧档
        string sqlDel = "SELECT overviewDownloadsFilePath FROM Yachts WHERE id = @selectModel_id";
        SqlCommand commandDel = new SqlCommand(sqlDel, connection);
        commandDel.Parameters.AddWithValue("@selectModel_id", selectModel_id);
        connection.Open();
        SqlDataReader reader = commandDel.ExecuteReader();
        if (reader.Read()) {
            string delFileName = reader["overviewDownloadsFilePath"].ToString();
            if (!String.IsNullOrEmpty(delFileName)) {
                File.Delete(savePath + delFileName);
            }
        }
        connection.Close();

        //储存上传档案
        //检查专案资料夹内有无同名档案,有同名就加流水号
        DirectoryInfo directoryInfo = new DirectoryInfo(savePath);
        int count = 0;
        foreach (var fileItem in directoryInfo.GetFiles()) {
            if (fileItem.Name.Contains(fileName)) {
                count++;
            }
        }
        string[] fileNameArr = fileName.Split('.');
        fileName = fileNameArr[0] + $"({count + 1})." + fileNameArr[1];
        FileUpload.SaveAs(savePath + fileName);

        //更新资料库资料
        string sql = "UPDATE Yachts SET overviewDownloadsFilePath = @fileName WHERE id = @selectModel_id";
        SqlCommand command = new SqlCommand(sql, connection);
        command.Parameters.AddWithValue("@fileName", fileName);
        command.Parameters.AddWithValue("@selectModel_id", selectModel_id);
        connection.Open();
        command.ExecuteNonQuery();
        connection.Close();

        //渲染画面
        loadContent();
        loadRowList();
        renderRowList();
    }
    else {
        //没选档案则清空资料
        string sqlDelPath = "UPDATE Yachts SET overviewDownloadsFilePath = @fileName WHERE id = @selectModel_id";
        SqlCommand commandDelPath = new SqlCommand(sqlDelPath, connection);
        commandDelPath.Parameters.AddWithValue("@fileName", "");
        commandDelPath.Parameters.AddWithValue("@selectModel_id", selectModel_id);
        connection.Open();
        commandDelPath.ExecuteNonQuery();
        connection.Close();

        //渲染画面
        loadContent();
        loadRowList();
        renderRowList();
    }
}
  • 🌵 同上,没选图片点上传会清空档案当作清除功能。


4. 动态新增栏位按钮功能利用增加空白 List 资料,配合前面的读取及渲染方法达成

protected void AddRow_Click(object sender, EventArgs e)
{
    //将 JSON 资料载入 List<T>
    loadRowList();
    //增加新栏位
    saveRowList.Add(new RowData { SaveItem = "", SaveValue = "" });
    //更新资料库资料
    uploadRowList();
    //渲染画面
    renderRowList();
    //将画面移至出现上传按钮处
    Page.SetFocus(BtnUpdateDimensionsList);
}
  • 🌵 使用内建方法 Page.SetFocus(); 在刷新页面时跳至指定区域。


5. 新增上方出现的更新尺寸栏位资料至资料库的 uploadRowList(); 方法

private void uploadRowList()
{
    //先更新 List<T> 前两个资料 Video 及 Download File
    saveRowList[0].SaveValue = TBoxVideo.Text;
    saveRowList[1].SaveValue = TBoxDLTitle.Text;
    //更新 List<T> 资料,栏位内容用 Request.Form 
    for (int i = 2; i < saveRowList.Count; i++) {
        saveRowList[i].SaveItem = Request.Form[$"item{i}"];
        saveRowList[i].SaveValue = Request.Form[$"value{i}"];
    }

    //依选取型号更新资料库资料
    string selectModel_id = DListModel.SelectedValue;
    //将 List<T> 资料转为 JSON 格式字串
    string saveRowListJsonStr = JsonConvert.SerializeObject(saveRowList);
    SqlConnection connection = new SqlConnection(WebConfigurationManager.ConnectionStrings["TayanaYachtConnectionString"].ConnectionString);
    string sql = "UPDATE Yachts SET overviewDimensionsJSON = @saveRowListJsonStr WHERE id = @selectModel_id";
    SqlCommand command = new SqlCommand(sql, connection);
    command.Parameters.AddWithValue("@saveRowListJsonStr", saveRowListJsonStr);
    command.Parameters.AddWithValue("@selectModel_id", selectModel_id);
    connection.Open();
    command.ExecuteNonQuery();
    connection.Close();
}
  • 🌵 传统 HTML 标签的 <input> 内容。

  • 🌵 栏位区的 3 个按钮都要配合进行刷新资料库内容。


6. 动态删除栏位按钮功能利用移除最末笔 List 资料,配合前面的读取及渲染方法达成

protected void DeleteRow_Click(object sender, EventArgs e)
{
    //将 JSON 资料载入 List<T>
    loadRowList();
    //更新资料库资料
    uploadRowList();
    //删除尺寸栏位最末栏
    saveRowList.RemoveAt(saveRowList.Count - 1);
    //更新资料库资料
    uploadRowList();
    //渲染表格画面
    renderRowList();
    //将画面移至出现上传按钮处
    Page.SetFocus(BtnUpdateDimensionsList);
}


7. 建立更新尺寸栏位 Update Dimensions List 按钮的 OnClick 功能

protected void BtnUpdateDimensionsList_Click(object sender, EventArgs e)
{
    //将 JSON 资料载入 List<T>
    loadRowList();
    //更新资料库资料
    uploadRowList();
    //渲染表格画面
    renderRowList();
    //渲染上传成功提示
    DateTime nowtime = DateTime.Now;
    LabUpdateDimensionsList.Visible = true;
    LabUpdateDimensionsList.Text = "*Upload Success! - " + nowtime.ToString("G");
}


8. 建立图文编辑区上传 Upload Overview Content 按钮 OnClick 功能

protected void BtnUploadMainContent_Click(object sender, EventArgs e)
{
    //将文字编辑器 HTML 内容转为 HTML 字元编码
    string mainContentHtmlStr = HttpUtility.HtmlEncode(CKEditorControl1.Text);
    //依下拉选单选取型号存入型号介绍主要图文内容
    string selectModel_id = DListModel.SelectedValue;
    SqlConnection connection = new SqlConnection(WebConfigurationManager.ConnectionStrings["TayanaYachtConnectionString"].ConnectionString);
    string sql = "UPDATE Yachts SET overviewContentHtml = @mainContentHtmlStr WHERE id = @selectModel_id";
    SqlCommand command = new SqlCommand(sql, connection);
    command.Parameters.AddWithValue("@mainContentHtmlStr", mainContentHtmlStr);
    command.Parameters.AddWithValue("@selectModel_id", selectModel_id);
    connection.Open();
    command.ExecuteNonQuery();
    connection.Close();

    //渲染上传成功提示
    DateTime nowtime = DateTime.Now;
    LabUploadMainContent.Visible = true;
    LabUploadMainContent.Text = "*Upload Success! - " + nowtime.ToString("G");
}

9. 模拟页面後测试功能,Overview Manager 後台完成 ~



本日总结 :

📢 本日的重点在重复使用的方法抽出来使用的好处,及动态增删栏位的作法,由於这个後台并非多人使用,因此使用连接资料库刷新的作法,也有看到用 ViewState 或其它方式实作的方法,可以自行研究;後续文章因为实作内容较多,并且接近收尾,都会如本文尽量将完整程序码放上来,而分层架构处理会在最後一天文章说明实作可以精修的部分。

👀 大神系列文章 ViewState 介绍参考 : 91之ASP.NET由浅入深 不负责讲座

  • 明日将介绍制作 Yachts - Master Page 主版页面的相关细节。

<<:  23. 闲聊 x VFS 办理英签申请

>>:  【Day23】Git 版本控制 - 修改 commit 纪录:reset

[Day 33] 再访碰撞侦测与解析(五) - Debug Ray vs Rect (二)

今日目标 继续Debug Ray vs Rect的部分 MISSION FAILED! QAQ 今天...

Day9-D3绘图:绘制形状的Helper Functions

本篇大纲:Generator、Component、Layout 截至目前,我们已经学会 D3 如何...

简报版-第五章-从手机安全更新认识安全更新年限、回收资料安全与定位追踪

其实原本最初规画想要做Index方式的纪录,然後多增加一些没写到的面向 不过,总是计画赶不上变化 ...

第18车厢-动ㄘ动ㄘ!tab页签切换+轮播应用篇

本篇介绍透过bootstrap4直接使用tab切换功能,并且实作tab切换自动循环播放 我们在昨篇...

Day-4: SSR 与 SPA

SSR — Server Side Rendering 服务器渲染 服务器端渲染 — 在服务器(Se...