Files
SGGL_HBAZ/SGGL/FineUIPro.Web/HSSE/PDFDOC/PdfBookView.aspx.cs
T
2026-09-11 11:14:08 +08:00

432 lines
17 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Text;
using System.Text.RegularExpressions;
using BLL;
namespace FineUIPro.Web.HSSE.PDFDOC
{
/// <summary>
/// PDF 书籍维护页:左侧多级目录树 + 右侧富文本内容编辑(UMEditor,可输入文字、上传图片)。
/// 章节与内容全部手动录入维护:新增书籍 / 新增·编辑·删除章节 / 保存章节富文本内容。
/// 数据存 Sys_PdfBook / Sys_PdfChapter(内容为 HTML;上传图片在保存时转为 base64 内嵌,正文自包含)。
/// </summary>
public partial class PdfBookView : PageBase
{
#region
/// <summary>
/// 页面加载。
/// </summary>
protected void Page_Load(object sender, EventArgs e)
{
// 显式指定 UTF-8 编码,避免个别环境下中文乱码
this.Response.ContentEncoding = System.Text.Encoding.UTF8;
this.Response.Charset = "utf-8";
if (!IsPostBack)
{
BindBooks();
if (this.drpBook.Items.Count > 0)
{
this.drpBook.SelectedIndex = 0;
InitTree(this.drpBook.SelectedValue);
}
this.lblProgress.Text = "";
}
}
#endregion
#region
/// <summary>
/// 绑定书籍下拉框。
/// </summary>
private void BindBooks()
{
this.drpBook.Items.Clear();
foreach (PdfBook book in PdfBookService.GetBooks())
{
this.drpBook.Items.Add(new FineUIPro.ListItem(book.Title, book.BookId));
}
}
#endregion
#region
/// <summary>
/// 初始化目录树(按父章节/层级递归建树,支持多级目录)。
/// </summary>
private void InitTree(string bookId)
{
this.trCatalog.Nodes.Clear();
this.trCatalog.SelectedNodeID = "";
var chapters = PdfBookService.GetChapters(bookId);
// 1) 先为每个章节创建节点
var nodeMap = new Dictionary<string, FineUIPro.TreeNode>();
foreach (PdfChapter c in chapters)
{
var node = new FineUIPro.TreeNode
{
Text = c.Title,
NodeID = c.ChapterId,
CommandName = "chapter",
CommandArgument = c.ChapterId,
ToolTip = c.PageStart > 0 ? ("第" + c.PageStart + " - " + c.PageEnd + " 页") : "",
EnableClickEvent = true,
Expanded = true // 页面默认全部展开
};
nodeMap[c.ChapterId] = node;
}
// 2) 按父章节挂接,没有父级(或父级不在列表)的作为顶级
var roots = new List<FineUIPro.TreeNode>();
foreach (PdfChapter c in chapters)
{
var node = nodeMap[c.ChapterId];
if (!string.IsNullOrWhiteSpace(c.ParentChapterId) && nodeMap.ContainsKey(c.ParentChapterId))
{
nodeMap[c.ParentChapterId].Nodes.Add(node);
}
else
{
roots.Add(node);
}
}
// 3) 顶级节点加入树
foreach (var r in roots)
this.trCatalog.Nodes.Add(r);
}
#endregion
#region
/// <summary>
/// 切换书籍。
/// </summary>
protected void drpBook_SelectedIndexChanged(object sender, EventArgs e)
{
InitTree(this.drpBook.SelectedValue);
this.htmlContent.Text = "";
this.hfCurrentChapterId.Text = "";
this.lblProgress.Text = "";
}
#endregion
#region
/// <summary>
/// 点击目录节点,把章节富文本内容加载到编辑器。
/// 所有章节(含带子章节的目录章节)均可编辑自身正文,点击即加载。
/// </summary>
protected void trCatalog_NodeCommand(object sender, FineUIPro.TreeCommandEventArgs e)
{
if (e.Node == null)
return;
string chapterId = e.Node.CommandArgument;
if (string.IsNullOrWhiteSpace(chapterId))
chapterId = e.Node.NodeID;
PdfChapterDetail detail = PdfBookService.GetChapterContent(chapterId);
this.htmlContent.Text = detail == null ? "" : (detail.Content ?? "");
this.hfCurrentChapterId.Text = chapterId;
this.trCatalog.SelectedNodeID = e.Node.NodeID;
this.lblProgress.Text = "正在编辑:" + (detail == null ? e.Node.Text : detail.Title);
}
#endregion
#region
/// <summary>
/// 保存当前章节的富文本内容(HTML;本站上传图片引用自动转为 base64 内嵌存储)。
/// </summary>
protected void btnSaveContent_Click(object sender, EventArgs e)
{
string chapterId = this.hfCurrentChapterId.Text;
if (string.IsNullOrWhiteSpace(chapterId))
{
FineUIPro.Alert.Show("请先在左侧目录选择章节。");
return;
}
// 本地上传图片转 base64 后入库,正文自包含(不依赖 upload 目录文件)
string html = EmbedLocalImagesAsBase64(this.htmlContent.Text);
SQLHelper.ExecutSql("UPDATE dbo.Sys_PdfChapter SET Text=N'" + SqlEsc(html)
+ "' WHERE ChapterId='" + SqlEsc(chapterId) + "'");
this.lblProgress.Text = "内容已保存:" + DateTime.Now.ToString("HH:mm:ss");
}
/// <summary>
/// 把 HTML 中指向本站 UMeditor 上传相关路径(/res/umeditor/net/..,含 ../../../upload 形态)的图片引用替换为 base64 内嵌。
/// 已是 data: 的图片与外部 http(s) 图片原样保留;路径非图片扩展名或文件不存在时不做替换(回退原引用)。
/// </summary>
private static readonly Regex LocalUploadImgSrcRegex = new Regex(
"src\\s*=\\s*([\"'])([^\"']*?/res/umeditor/net/[^\"']*?)\\1",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
private string EmbedLocalImagesAsBase64(string html)
{
if (string.IsNullOrEmpty(html) ||
html.IndexOf("/res/umeditor/net/", StringComparison.OrdinalIgnoreCase) < 0)
return html;
return LocalUploadImgSrcRegex.Replace(html, delegate(Match m)
{
string quote = m.Groups[1].Value;
string raw = m.Groups[2].Value;
int i = raw.IndexOf("/res/umeditor/net/", StringComparison.OrdinalIgnoreCase);
if (i < 0)
return m.Value;
string virtualPath = raw.Substring(i);
int q = virtualPath.IndexOf('?');
if (q >= 0) virtualPath = virtualPath.Substring(0, q);
string extCheck = System.IO.Path.GetExtension(virtualPath).ToLowerInvariant();
if (extCheck != ".png" && extCheck != ".jpg" && extCheck != ".jpeg" && extCheck != ".gif" && extCheck != ".bmp")
return m.Value;
try
{
string physical = Server.MapPath(virtualPath);
if (!System.IO.File.Exists(physical))
return m.Value;
byte[] bytes = System.IO.File.ReadAllBytes(physical);
if (bytes.Length == 0)
return m.Value;
string ext = System.IO.Path.GetExtension(physical).ToLowerInvariant();
string mime = ext == ".png" ? "image/png"
: ext == ".gif" ? "image/gif"
: ext == ".bmp" ? "image/bmp"
: "image/jpeg";
return "src=" + quote + "data:" + mime + ";base64," + Convert.ToBase64String(bytes) + quote;
}
catch
{
return m.Value;
}
});
}
#endregion
#region
/// <summary>
/// 打开新增书籍弹窗。
/// </summary>
protected void btnAddBook_Click(object sender, EventArgs e)
{
this.txtBookTitle.Text = "";
this.WindowBook.Hidden = false;
}
/// <summary>
/// 保存新增书籍。
/// </summary>
protected void btnSaveBook_Click(object sender, EventArgs e)
{
string title = this.txtBookTitle.Text.Trim();
if (title.Length == 0)
{
FineUIPro.Alert.Show("请输入书名。");
return;
}
string bookId = NewId("PB");
SQLHelper.ExecutSql(
"INSERT INTO dbo.Sys_PdfBook (BookId, Title, FilePath, TotalPages, IndexStatus, CreateUser, CreateTime) VALUES ('"
+ SqlEsc(bookId) + "',N'" + SqlEsc(title) + "',NULL,0,2,N'test',GETDATE())");
this.WindowBook.Hidden = true;
BindBooks();
this.drpBook.SelectedValue = bookId;
InitTree(bookId);
this.htmlContent.Text = "";
this.hfCurrentChapterId.Text = "";
this.lblProgress.Text = "已新增书籍:《" + title + "》,请继续添加章节。";
}
/// <summary>
/// 关闭新增书籍弹窗。
/// </summary>
protected void btnCloseBook_Click(object sender, EventArgs e)
{
this.WindowBook.Hidden = true;
}
#endregion
#region //
/// <summary>
/// 打开新增章节弹窗(父级=目录树选中节点,未选中则为顶级章节)。
/// </summary>
protected void btnAddChapter_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(this.drpBook.SelectedValue))
{
FineUIPro.Alert.Show("请先新增/选择书籍。");
return;
}
this.hfEditChapterId.Text = "";
this.txtChapterTitle.Text = "";
string parentId = this.trCatalog.SelectedNodeID;
this.lblParentChapter.Text = string.IsNullOrEmpty(parentId) ? "顶级章节" : "选中节点的子章节";
this.WindowChapter.Title = "新增章节";
this.WindowChapter.Hidden = false;
}
/// <summary>
/// 打开编辑章节弹窗(修改选中章节名称)。
/// </summary>
protected void btnEditChapter_Click(object sender, EventArgs e)
{
string chapterId = this.trCatalog.SelectedNodeID;
if (string.IsNullOrEmpty(chapterId))
{
FineUIPro.Alert.Show("请先在目录中选择要编辑的章节。");
return;
}
DataTable dt = SQLHelper.GetDataTableRunText(
"SELECT ChapterTitle FROM dbo.Sys_PdfChapter WHERE ChapterId=@id",
new[] { new SqlParameter("@id", chapterId) });
if (dt == null || dt.Rows.Count == 0)
{
FineUIPro.Alert.Show("未找到该章节。");
return;
}
this.hfEditChapterId.Text = chapterId;
this.txtChapterTitle.Text = dt.Rows[0]["ChapterTitle"].ToString();
this.lblParentChapter.Text = "修改章节名称";
this.WindowChapter.Title = "编辑章节";
this.WindowChapter.Hidden = false;
}
/// <summary>
/// 保存章节(新增:挂在选中节点下,层级=父级+1;编辑:仅改名)。
/// 保存后保持选中刚保存的章节:叶子章节加载其内容,目录章节仅刷新选中、不动编辑器。
/// </summary>
protected void btnSaveChapter_Click(object sender, EventArgs e)
{
string title = this.txtChapterTitle.Text.Trim();
if (title.Length == 0)
{
FineUIPro.Alert.Show("请输入章节名称。");
return;
}
string bookId = this.drpBook.SelectedValue;
string affectedId;
if (this.hfEditChapterId.Text != "")
{
// 编辑:仅改名称
affectedId = this.hfEditChapterId.Text;
SQLHelper.ExecutSql("UPDATE dbo.Sys_PdfChapter SET ChapterTitle=N'" + SqlEsc(title)
+ "' WHERE ChapterId='" + SqlEsc(affectedId) + "'");
}
else
{
// 新增:父级为目录树选中节点
string parentId = this.trCatalog.SelectedNodeID;
int level = 1;
if (!string.IsNullOrEmpty(parentId))
{
int parentLevel = SQLHelper.GetIntValue(
"SELECT ISNULL([Level],1) FROM dbo.Sys_PdfChapter WHERE ChapterId='" + SqlEsc(parentId) + "'");
level = parentLevel + 1;
}
int sort = SQLHelper.GetIntValue(
"SELECT ISNULL(MAX(SortNo),0) FROM dbo.Sys_PdfChapter WHERE BookId='" + SqlEsc(bookId) + "'") + 1;
affectedId = NewId("CH");
SQLHelper.ExecutSql(
"INSERT INTO dbo.Sys_PdfChapter (ChapterId, BookId, ParentChapterId, [Level], ChapterTitle, PageStart, PageEnd, Text, SortNo) VALUES ('"
+ SqlEsc(affectedId) + "','" + SqlEsc(bookId) + "',"
+ (string.IsNullOrEmpty(parentId) ? "NULL" : "'" + SqlEsc(parentId) + "'")
+ "," + level + ",N'" + SqlEsc(title) + "',NULL,NULL,N''," + sort + ")");
}
this.WindowChapter.Hidden = true;
InitTree(bookId);
// 保持选中刚保存的章节并加载其正文(目录章节同样可编辑正文)
this.trCatalog.SelectedNodeID = affectedId;
PdfChapterDetail detail = PdfBookService.GetChapterContent(affectedId);
this.htmlContent.Text = detail == null ? "" : (detail.Content ?? "");
this.hfCurrentChapterId.Text = affectedId;
this.lblProgress.Text = "章节已保存,正在编辑:" + (detail == null ? title : detail.Title);
}
/// <summary>
/// 删除选中章节及其全部子章节。
/// </summary>
protected void btnDelChapter_Click(object sender, EventArgs e)
{
string chapterId = this.trCatalog.SelectedNodeID;
if (string.IsNullOrEmpty(chapterId))
{
FineUIPro.Alert.Show("请先在目录中选择要删除的章节。");
return;
}
string bookId = this.drpBook.SelectedValue;
var all = PdfBookService.GetChapters(bookId);
// 递归收集要删除的章节(自身+全部后代)
var ids = new List<string> { chapterId };
bool changed = true;
while (changed)
{
changed = false;
foreach (PdfChapter c in all)
{
if (!ids.Contains(c.ChapterId) && ids.Contains(c.ParentChapterId ?? ""))
{
ids.Add(c.ChapterId);
changed = true;
}
}
}
var sb = new StringBuilder();
foreach (string id in ids)
{
if (sb.Length > 0)
sb.Append(",");
sb.Append("'").Append(id.Replace("'", "''")).Append("'");
}
SQLHelper.ExecutSql("DELETE FROM dbo.Sys_PdfChapter WHERE ChapterId IN (" + sb + ")");
InitTree(bookId);
this.htmlContent.Text = "";
this.hfCurrentChapterId.Text = "";
this.lblProgress.Text = "章节已删除(含子章节 " + (ids.Count - 1) + " 个)。";
}
/// <summary>
/// 关闭章节弹窗。
/// </summary>
protected void btnCloseChapter_Click(object sender, EventArgs e)
{
this.WindowChapter.Hidden = true;
}
#endregion
#region
/// <summary>SQL 字符串转义。</summary>
private static string SqlEsc(string s)
{
return (s ?? "").Replace("'", "''");
}
/// <summary>生成业务主键(前缀+时间+随机)。</summary>
private static string NewId(string prefix)
{
return prefix + DateTime.Now.ToString("yyyyMMddHHmmss")
+ Guid.NewGuid().ToString("N").Substring(0, 4);
}
#endregion
}
}