```=csharp protected void Page_Load(object sender, EventArgs e) { var sm = ScriptManager.GetCurrent(Page); if (sm != null) sm.EnablePageMethods = true; // 不用動 MasterPage 標記 } [System.Web.Services.WebMethod] public static object Search(string q, int take) { /* 回 { ok,data } */ } ``` ```=SQL -- [Feature] Article Chunked Upload Schema -- 主表:文章主體 (儲存壓縮後 HTML) CREATE TABLE fwpdb.article ( article_id VARCHAR2(64 CHAR) NOT NULL, title VARCHAR2(200 CHAR) NOT NULL, version_no NUMBER DEFAULT 1 NOT NULL, -- 目前版本號 content_gz BLOB NOT NULL, -- 壓縮後 HTML created_by VARCHAR2(50 CHAR), created_at DATE DEFAULT SYSDATE, updated_at DATE DEFAULT SYSDATE, CONSTRAINT pk_article_id primary key (article_id) ); / --版本交易表:記錄每次上傳的完整版本資訊 CREATE TABLE fwpdb.article_version_tx ( version_id VARCHAR2(64 CHAR) NOT NULL, -- 公開唯一 ID article_id VARCHAR2(64 CHAR) NOT NULL, version_no NUMBER NOT NULL, -- 對應主表版本 total_chunks NUMBER NOT NULL, -- 分段總數 received_chunks NUMBER, -- 已上傳段數 chunk_size NUMBER, -- 每段大小 sha256_hex VARCHAR2(64), -- 壓縮內容 Hash orig_bytes NUMBER, -- 原始大小 gzip_bytes NUMBER, -- 壓縮大小 completed CHAR(1) DEFAULT 'N' CHECK (completed IN ('Y','N')), uploaded_by VARCHAR2(50 CHAR), created_at DATE DEFAULT SYSDATE NOT NULL, updated_at DATE DEFAULT SYSDATE NOT NULL, CONSTRAINT pk_version_id primary key (version_id), CONSTRAINT uq_article_version UNIQUE (article_id, version_no), CONSTRAINT uq_article_version_id UNIQUE (article_id) ) / -- 分段紀錄表:追蹤每個上傳分段 CREATE TABLE fwpdb.article_chunk_tx ( chunk_id VARCHAR2(64 CHAR) NOT NULL, -- 公開唯一 ID article_id VARCHAR2(64 CHAR) NOT NULL, version_no NUMBER NOT NULL, chunk_seq NUMBER NOT NULL, -- 第幾段 (1-based) chunk_data BLOB, -- 暫存上傳分段 chunk_size NUMBER, -- 實際段大小 uploaded_by VARCHAR2(50 CHAR), created_at DATE DEFAULT SYSDATE, CONSTRAINT uq_article_chunk UNIQUE (article_id, version_no, chunk_seq), CONSTRAINT uq_article_chunk_id UNIQUE (chunk_id) ) / COMMENT ON TABLE fwpdb.article IS 'HELEPER CENTER 主文章表'; COMMENT ON COLUMN fwpdb.article.article_id IS '文章唯一識別碼 (UUID)'; COMMENT ON COLUMN fwpdb.article.title IS '文章標題'; COMMENT ON COLUMN fwpdb.article.version_no IS '當前版本號'; COMMENT ON COLUMN fwpdb.article.content_gz IS 'Gzip 壓縮 HTML 內容'; COMMENT ON COLUMN fwpdb.article.created_by IS '建立者'; COMMENT ON COLUMN fwpdb.article.created_at IS '建立時間'; COMMENT ON COLUMN fwpdb.article.updated_at IS '更新時間'; / COMMENT ON TABLE fwpdb.article_version_tx IS '文章版本交易紀錄表'; COMMENT ON COLUMN fwpdb.article_version_tx.version_no IS '版本號'; COMMENT ON COLUMN fwpdb.article_version_tx.total_chunks IS '分段總數'; COMMENT ON COLUMN fwpdb.article_version_tx.received_chunks IS '已上傳段數'; COMMENT ON COLUMN fwpdb.article_version_tx.completed IS '是否完成 (Y/N)'; / COMMENT ON TABLE fwpdb.article_chunk_tx IS '文章分段上傳紀錄表'; COMMENT ON COLUMN fwpdb.article_chunk_tx.chunk_seq IS '分段順序 (1-based)'; COMMENT ON COLUMN fwpdb.article_chunk_tx.chunk_data IS '分段資料 (BLOB)'; COMMENT ON COLUMN fwpdb.article_chunk_tx.uploaded_by IS '上傳使用者'; / SELECT * FROM fwpdb.article / SELECT * FROM fwpdb.article_version_tx / SELECT * FROM fwpdb.article_chunk_tx ``` ------------- ```=javaScript //init const editor = CKEDITOR.replace('editor1'); const getData = async (apiUrl, payload) => { try { const res = await fetch(apiUrl, { method: "POST", headers: { "Content-Type": "application/json; charset=utf-8", }, body: JSON.stringify(payload), }) return res?.d } catch (e) { console.error(e.errorMessage); } } function htmlToTextWithoutBase64(html) { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); doc.querySelectorAll('script,style,noscript,img[src^="data:image"]').forEach(el => el.remove()); const text = (doc.body.textContent || '').replace(/\u00A0/g, ' ').trim(); return text; } // main const sumbitBtn = document.querySelector('.btn'); // 將 ArrayBuffer 的 SHA-256 轉成 hex 字串 function bytesToHex(buffer) { return [...new Uint8Array(buffer)] .map(b => b.toString(16).padStart(2, "0")) .join(""); } //加壓Gzip(Binary) 轉 base64 async function compressionStream(html) { const encoder = new TextEncoder(); const htmlBytes = encoder.encode(html); //轉 Uint8Array //壓縮 const stream = new Blob([htmlBytes]).stream().pipeThrough(new CompressionStream("gzip")); //轉 ArrayBuffer const gzipBytes = await new Response(stream).arrayBuffer(); //將 Uint8Array 轉 base64 const base64 = btoa(String.fromCharCode(...new Uint8Array(gzipBytes))); // 計算 SHA-256(對 gzip bytes) const hashBuffer = await crypto.subtle.digest("SHA-256", gzipBytes); const sha256_hex = bytesToHex(hashBuffer); return { base64, sha256_hex } ; } //切分 base64 chunks function getUploadChuncks(chunkSize = 512 * KB) { const KB = 1024; const MB = 1024 * KB; const totalChunks = Math.ceil(base64Data.length / chunkSize); const chunks = []; for (let i = 0; i < totalChunks; i++) { const chunk = base64Data.slice(i * chunkSize, (i + 1) * chunkSize); chunk.push({ article_uid: crypto.randomUUID(), chunkIndex: i + 1, totalChunks, chunkSize, chunk, }); } return { chunks, chunkSize, totalChunks } } // 解碼base64 async function ungzipBase64(base64Str) { const binary = atob(base64Str); //base64 to binary string const bytes = new Uint8Array(binary.length); //new uint8array for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); const ds = new DecompressionStream("gzip"); const decompressedStream = new Response( new Blob([bytes]).stream().pipeThrough(ds) ); return await decompressedStream.text(); } //base64 to html function getFormData() { const title = document.querySelector('.title').textContent; const html = editor.getData(); const text = htmlToTextWithoutBase64(html); return { title, html, text }; } sumbitBtn.addEventListener('click', async (e) => { try { const formData = getFormData(); //1. 加壓範例 const { base64, sha256_hex } = await compressionStream(formData.html); console.log('加壓準備傳送Base64:', base64, 'sha-256 給後端驗證文件碼要先轉譯 Gzip:', sha256_hex); //2. chuncks upload const { chunks, chunkSize, totalChunks } = getUploadChuncks(); console.log('這裡要寫分段Call API, insert Version Table, 分段上傳chunk size'); console.log(chunks, chunkSize, totalChunks); //3. call upload api chunks.forEach(async (chunk) => { //fetch('/UploadChunck') }); //4. verify complete api call //fetch('/VerifyUploadComplete') body: {articleId} //解壓 const unGzipBase64 = await ungzipBase64(base64); console.log(unGzipBase64); //chuncks forEach call api //const res = getData('Default2.aspx/GetData', { // data: JSON.stringify(formData) //}); //console.log(res); } catch (e) { console.error(e); } }); ``` ------------------------------- ![messageImage_1746622104751](https://hackmd.io/_uploads/SygTQeFllg.jpg) ![StanleyBF_MachineGuid](https://hackmd.io/_uploads/Syp-b1YCJl.jpg) MachineGuid REG_SZ edafb182-9c50-4cb3-ab8d-7a5b2b1f7bda ## WebFrom ```=HTML <asp:Content ID="BodyContent" ContentPlaceHolderID="MainContent" runat="server"> <style> .d-flex { display: flex; } .justify-content-center { justify-content: center; } .align-items-center { align-items: center; } .gap-1 { gap: 1rem; } label { padding: 8px 16px; border-radius: 2px; cursor: pointer; margin-bottom: 4px; border:1px solid #d2cece; } input[type='radio'] { display: none; } input[type='radio']:checked + label { background: #0d6efd; color: white; } </style> <div> <h1>Test Webfrom form BarCode Demo</h1> </div> <!-- Release 選擇項目 --> <asp:RadioButtonList ID="ReleaseItem" runat="server" RepeatDirection="Horizontal" > <asp:ListItem Text="LotId" Value="LotId" Selected="True" /> <asp:ListItem Text="MagazineId" Value="MagazineId" /> </asp:RadioButtonList> <!-- Release 輸入框 --> <div> <asp:TextBox CssClass="form-control" ID="ReleaseId" runat="server" placeholder="請輸入LotID" /> <asp:Button ID="ReleaseSearchBtn" Style="display: none" runat="server" Text="查詢" OnClick="ReleaseSearchBtn_Click" /> </div> <!-- 表格 --> <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" OnRowCommand="GridView1_RowCommand" EditRowStyle-HorizontalAlign="Justify"> <Columns> <asp:TemplateField HeaderText="項目"> <ItemTemplate> <asp:Label ID="lblSeqNo" runat="server" Text='<%# Container.DataItemIndex + 1 %>' /> </ItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="選擇"> <ItemTemplate> <asp:CheckBox ID="chkSelect" runat="server" /> </ItemTemplate> </asp:TemplateField> <asp:BoundField DataField="LotId" HeaderText="LotId" /> <asp:BoundField DataField="MagazineId" HeaderText="MagazineId" /> <asp:TemplateField> <ItemTemplate> <asp:Button ID="btnDetails" runat="server" Text="詳細" CommandName="Details" CommandArgument='<%# Container.DataItemIndex %>' /> </ItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="刪除"> <ItemTemplate> <asp:Button ID="btnDelete" runat="server" Text="刪除" CommandName="Delete" CommandArgument='<%# Container.DataItemIndex %>' /> </ItemTemplate> </asp:TemplateField> </Columns> </asp:GridView> <script> const releaseItem = document.querySelector("#<%= ReleaseItem.ClientID%>"); const releaseInput = document.querySelector("#<%= ReleaseId.ClientID%>"); const releaseSearchBtn = document.querySelector("#<%= ReleaseSearchBtn.ClientID%>"); releaseInput.value = ""; releaseItem.addEventListener('click', (e) => { releaseInput.value = ""; releaseInput.focus(); releaseInput.placeholder = e.target.value === 'LotId' ? '請輸入LotID' : '請輸入MagazinID'; }) releaseSearchBtn.addEventListener("keydown", (e) => { if (e.keyCode == 13) { releaseSearchBtn.click(); } }) </script> </asp:Content> ``` ```=csharp using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Xml.Linq; public partial class About : Page { public static List<ReleaseRow> DataBase = new List<ReleaseRow>() { new ReleaseRow { LotId = "lotId1", MagazineId = "Magazine1" }, new ReleaseRow { LotId = "lotId2", MagazineId = "Magazine2" } }; [Serializable] public class ReleaseRow { public string LotId { get; set; } public string MagazineId { get; set; } } protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { //UpdateReleaseGridView(); } } protected void UpdateReleaseGridView() { List<ReleaseRow> updateData = (List<ReleaseRow>)ViewState["releaseData"]; try { if (updateData == null || updateData.Count == 0) return; GridView1.DataSource = updateData; GridView1.DataBind(); } catch (Exception ex) { } } protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e) { if (e.CommandName == "Details") { //rowId int rowIndex = Convert.ToInt32(e.CommandArgument); // Get rowData GridViewRow row = GridView1.Rows[rowIndex]; Console.WriteLine(row); CheckBox chkSelect = (CheckBox)row.FindControl("chkSelect"); string c2 = row.Cells[2].Text; string c3 = row.Cells[3].Text; // 測試得到該行數據 string lotId = ((Label)row.FindControl("LotId")).Text; string magazineId = ((Label)row.FindControl("MagazineId")).Text; // 顯示詳細資訊 //Response.Write("<script>alert('LotId: " + lotId + ", MagazineId: " + magazineId + "');</script>"); } } private void ShowDetails(int id) { // 彈窗handler string message = $"彈窗{id}"; // 測試call bootstrap modal Response.Write("<script>alert('" + message + "');</script>"); } protected void btnSubmit_Click(object sender, EventArgs e) { // 拿出選中的資料 foreach (GridViewRow row in GridView1.Rows) { CheckBox chkSelect = (CheckBox)row.FindControl("chkSelect"); if (chkSelect != null && chkSelect.Checked) { // 獲取行ID int id = Convert.ToInt32(GridView1.DataKeys[row.RowIndex].Value); // 執行Release Handler HandleSelectedData(id); } } } //後端Release處理 private void HandleSelectedData(int id) { // 模拟执行处理 Response.Write("<script>alert('處理 " + id + " 的資料');</script>"); } protected void ReleaseSearchBtn_Click(object sender, EventArgs e) { string releaseItem = string.Empty; try { List<ReleaseRow> searchReleaseData = new List<ReleaseRow>(); //DateBase Call if (ReleaseItem.SelectedItem.ToString() == "LotId") { searchReleaseData = DataBase.Where(item => item.LotId == ReleaseId.Text).ToList(); } if (ReleaseItem.SelectedItem.ToString() == "MagazineId") { searchReleaseData = DataBase.Where(item => item.MagazineId == ReleaseId.Text).ToList(); } List<ReleaseRow> updateData = (List<ReleaseRow>)ViewState["releaseData"] ?? new List<ReleaseRow>(); foreach (ReleaseRow item in searchReleaseData) { updateData.Add(item); } List<ReleaseRow> distinctList = updateData .GroupBy(item => new { item.LotId, item.MagazineId }) .Select(group => group.First()) .ToList(); Console.WriteLine(distinctList); ViewState["releaseData"] = distinctList; UpdateReleaseGridView(); //var listData = DataBase.Where(item => x.LotId == ReleaseId.Text).ToList(); //ReleaseId.Text //search DataBase ReleaseId.Focus(); } catch (Exception ex) { Console.WriteLine(ex); } } } ``` ## Demo ```=css *, *::after, *::before { padding: 0; margin: 0; box-sizing: border-box; font-family: Arial, Helvetica, sans-serif; } body::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; /* background: linear-gradient(180deg, rgb(21, 19, 59), rgb(132, 130, 209)); */ background-color: #fafbffff; z-index: -1; } /* width */ ::-webkit-scrollbar { width: 0.5rem; } /* Track */ ::-webkit-scrollbar-track { box-shadow: inset 0 0 5px grey; border-radius: 0.25rem; } /* Handle */ ::-webkit-scrollbar-thumb { background: rgb(62, 59, 135); border-radius: 0.25rem; transition: all 0.3s ease-in; } /* Handle on hover */ ::-webkit-scrollbar-thumb:hover { background: rgb(51, 47, 170); } ul { list-style: none; } .logo { display: flex; align-items: center; } .logo-text { font-size: large; } .logo-img { position: relative; } .img-1 { max-height: 56px; position: relative; top: 0; object-fit: cover; } .img-2 { max-height: 56px; object-fit: cover; position: absolute; left: 50%; } .svg { pointer-events: none; } input { all: unset; position: relative; } input::placeholder { font-weight: bold; font-size: large; } button { all: unset; cursor: pointer; } a { display: block; } .ft-white { color: white; } .bg-primary { background-color: rgb(10, 8, 59); } .font-white { color: white; } .log-image { width: 48px; height: 48px; object-fit: cover; } .container { max-width: 95%; /* padding-left: 12px; */ /* padding-right: 12px; */ margin: 0 auto; } .header { position: sticky; top: 0; padding-right: 24px; padding-left: 24px; padding-top: 0.5rem; padding-bottom: 0.5rem; display: flex; align-items: center; gap: 2rem; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); } .btn { padding: 0.25rem 0.5rem; border-radius: 0.25rem; background-color: rgb(62, 59, 135); } .btn:hover { background-color: rgb(51, 47, 170); } .nav-bar_list[disabled='true'] { position: relative; pointer-events: none; } .nav-bar_list[disabled='true']::after { display: block; position: absolute; content: ''; background-color: gray; border-radius: 0.25rem 0.25rem 0 0; opacity: 0.8; top: 0; right: 0; bottom: 0; left: 0; } .nav-bar_list[disabled='true'] .list-item input { color: gray; } .nav-bar { display: flex; flex-wrap: wrap; gap: 1rem; } .ml-auto { margin-left: auto; } .nav-bar_list { border-bottom: 2px solid gray; } .nav-bar_list:hover { border-bottom: 2px solid rgb(86, 81, 225); } .nav-bar_list .list-item { background-color: transparent; border-radius: 0.25rem; padding: 0.5rem 0.5rem; transition: all 0.25s ease-in; cursor: pointer; } .nav-bar_list .list-item:hover { background-color: rgb(62, 59, 135); } .input-box { padding: 0.5rem 0.5rem; } .arrow::after { content: '▼'; /* Unicode 向下箭頭 */ font-size: 12px; margin-left: 5px; } .nav-bar_list { position: relative; transition: all 0.25s ease-in; } .nav-bar_list ul { position: absolute; color: white; top: 105%; display: flex; flex-direction: column; gap: 0.25rem; padding: 0.5rem; list-style: none; background-color: rgb(10, 8, 59); box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); border-radius: 5px; right: 0; left: 0; opacity: 0; transform: translateY(-10px); transition: all 0.3s ease; z-index: 20; visibility: hidden; max-height: 220px; overflow-y: scroll; } /* 顯示下拉內容 */ .nav-bar_list:hover ul { opacity: 1; visibility: visible; transform: translateY(0); } .user-section { display: flex; align-items: center; gap: 0.25rem; } .user-section p { color: white; } .avatar { width: 50px; height: 50px; border-radius: 50%; max-width: 100%; max-height: 100%; object-fit: contain; } /* navbar-上的按鈕 */ .search-section { position: relative; } .search-btn { padding: 0.25rem 0.5rem; border-radius: 0.25rem; background-color: transparent; transition: all 0.3 ease; } .search-btn:hover { background-color: rgb(62, 59, 135); } .search-btn:hover .search-filter { background-color: rgb(62, 59, 135); } /* btn 叫出視窗效果 */ .search-filter.showFilter { min-width: 320px; min-height: 36px; opacity: 1; visibility: visible; } /* 按下搜索 Btn 的方框 */ .search-filter { min-width: 0; min-height: 0; overflow: hidden; opacity: 0; visibility: hidden; background-color: rgb(10, 8, 59); position: absolute; border: 1px solid gray; padding: 0.5rem; right: 0; transition: all 0.75s ease; } .search-input { all: unset; display: block; border-bottom: 1px solid white; width: 100%; color: white; font-size: 1.25rem; } .search-input-icon { position: absolute; right: 0.5rem; } /* 機台列表 */ .line-area { display: flex; flex-direction: column; gap: 2rem; } .mach-card__List { display: flex; gap: 2rem; box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); border: 1px solid rgb(233, 233, 233); border-radius: 0.75rem; padding: 1rem; } .mach-card { box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); border: 1px solid rgb(233, 233, 233); padding: 0.5rem; } .line-title { display: inline-block; border-radius: 0.25rem; padding: 0.25rem; margin-bottom: 0.75rem; box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); border: 1px solid rgb(233, 233, 233); } .mt-40 { margin-top: 40px; } ``` # JS ``` ////////utils/////////// //call api async function getData(methodName, method = 'GET', data = {}) { if (!methodName) return; const domain = 'http://localhost:3000/'; //EQP_DashBoard.aspx/ const setting = { method, //POST headers: { 'Content-Type': 'application/json', }, }; if (method === 'POST') { setting.body = JSON.stringify(data); } try { const res = await fetch(`${domain}${methodName}`, setting); const data = await res.json(); if (!data?.d) throw Error('找不到data'); return data?.d; // return JSON.parse(data?.d); } catch (err) { console.error(`[${methodName} Error] ${err}`); } } class NavBarDropDownList { constructor(containerClass, name, initData) { this.containerClass = containerClass; this.name = name; this.state = {}; this.create(initData); } init(data) { this.ItemList.innerHTML = ''; this.input.value = ''; this.disable(false); if (data) this.setListItems(data); } create(initData) { //createElement this.container = document.querySelector(`.${this.containerClass}`); this.machBox = document.createElement('div'); this.inputBox = document.createElement('div'); this.input = document.createElement('input'); this.ItemList = document.createElement('ul'); this.machBox.classList.add('nav-bar_list'); this.inputBox.classList.add('input-box', 'arrow', 'font-white'); //input setting this.input.type = 'text'; this.input.placeholder = this.name; this.input.oninput = (e) => this.onInputHandler(e); this.machBox.onclick = (e) => this.onClickHandler(e); this.inputBox.append(this.input); ///init item list this.setListItems(initData); //append mach component div this.machBox.append(this.inputBox); this.machBox.append(this.ItemList); //final append dom to html this.container.append(this.machBox); } //設定下拉選單顯示data setListItems(data) { if (!data?.length) return; this.ItemList.innerHTML = ''; data?.forEach((mach) => { const li = document.createElement('li'); li.classList.add('list-item'); li.textContent = mach; this.ItemList.append(li); }); this.machBox.append(this.ItemList); } addSearchFilter(outFilterData, targetValue) { this.input.value = targetValue; this.disable(true); outFilterData[this.name] = targetValue; } //切換 input disable 樣式 disable(isDisable) { this.machBox.setAttribute('disabled', isDisable); } //click callback onClickHandler = (e) => { if (this.clickCallBack) { this.clickCallBack(e); } }; onClickHandlerCallBack = (cb) => { this.clickCallBack = cb; }; //input callback onInputHandler = (e) => { if (this.inputCallback) { this.inputCallback(e); } }; onInputHandlerCallback(cb) { this.inputCallback = cb; } } //搜索 Array 顯示邏輯 function searchItems(arr, searchWord) { const filterItems = [...arr].filter((item) => item.toUpperCase().includes(searchWord.toUpperCase()) ); if (searchWord.length === 0) { return arr; } return filterItems; } //Avatar 頭像區域 async function avatarCreate() { const empData = await getData('GetEmpData'); const empName = document.querySelector('.empName'); const avatar = document.querySelector('.avatar'); empName.textContent = empData?.empName; // avatar.setAttribute( // 'src', // `https://myvf.kh.asegroup.com/utility/get_emp_photo.asp?emp_no=${empData?.empNo}` // ); } ////////主程式/////////// async function main() { //def let filterData = {}; //[feature] 這裡之後要改成, url search ?=... const buildDropList = new NavBarDropDownList('mach-list', '棟別'); const floorDropList = new NavBarDropDownList('mach-list', '樓層'); //origin data const areaData = await getData('initDropDownData'); const buildListData = Object.keys(areaData).map((item) => item); buildDropList.setListItems(buildListData); // 搜索邏輯 buildDropList.onInputHandlerCallback((e) => { const searchWord = e.target.value; buildDropList.setListItems(searchItems(buildListData, searchWord)); }); buildDropList.onClickHandlerCallBack((e) => { if (e.target.classList.contains('list-item')) { const target = e.target.textContent; buildDropList.addSearchFilter(filterData, target); floorDropList.setListItems(areaData[target]); } }); floorDropList.onInputHandlerCallback((e) => { const searchWord = e.target.value; if (areaData[filterData[buildDropList.name]]) { floorDropList.setListItems( searchItems(areaData[filterData[buildDropList.name]], searchWord) ); } }); floorDropList.onClickHandlerCallBack((e) => { const target = e.target.textContent; if (e.target.classList.contains('list-item')) { floorDropList.addSearchFilter(filterData, target); } }); //search 搜索關鍵字Search bar //search button document.querySelector('.search-btn').addEventListener('click', (e) => { const filter = document.querySelector('.search-filter'); filter.classList.toggle('showFilter'); }); // 搜索下方 Tree Table document.querySelector('.search-input').addEventListener('input', (e) => { const keyword = e.target.value; console.log(filterData); if ( Object.keys(filterData)?.map((item) => filterData?.[item] !== '') ?.length === 2 ) { console.log('call API', filterData); } }); //clear filter document.querySelector('.filterclearBtn').addEventListener('click', (e) => { buildDropList.init(buildListData); floorDropList.init(); filterData = {}; }); } main(); ``` ``` ``` ``` ``` ## NavBar Demo ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Demo Navbar</title> <link rel="stylesheet" href="style.css" /> </head> <body> <section class="bg-primary"> <header class="header container"> <!-- Logo --> <h1 class="font-white">Logo</h1> <!-- nav list --> <nav class="nav-bar mach-list"></nav> <!-- search btn --> <div class="ml-auto search-section"> <button class="search-btn" type="button"> <!-- 放大鏡ICON --> <svg stroke="white" fill="white" stroke-width="1" version="1.1" id="search" x="0px" y="0px" viewBox="0 0 24 24" height="24px" width="24px" xmlns="http://www.w3.org/2000/svg" > <g> <path d="M20.031,20.79c0.46,0.46,1.17-0.25,0.71-0.7l-3.75-3.76c1.27-1.41,2.04-3.27,2.04-5.31 c0-4.39-3.57-7.96-7.96-7.96s-7.96,3.57-7.96,7.96c0,4.39,3.57,7.96,7.96,7.96c1.98,0,3.81-0.73,5.21-1.94L20.031,20.79z M4.11,11.02c0-3.84,3.13-6.96,6.96-6.96c3.84,0,6.96,3.12,6.96,6.96c0,3.84-3.12,6.96-6.96,6.96C7.24,17.98,4.11,14.86,4.11,11.02 z" ></path> </g> </svg> </button> <div class="search-filter"> <!-- 放大鏡ICON --> <div class="search-input-icon"> <svg stroke="white" fill="white" stroke-width="1" version="1.1" id="search" x="0px" y="0px" viewBox="0 0 24 24" height="12px" width="12px" xmlns="http://www.w3.org/2000/svg" > <g> <path d="M20.031,20.79c0.46,0.46,1.17-0.25,0.71-0.7l-3.75-3.76c1.27-1.41,2.04-3.27,2.04-5.31 c0-4.39-3.57-7.96-7.96-7.96s-7.96,3.57-7.96,7.96c0,4.39,3.57,7.96,7.96,7.96c1.98,0,3.81-0.73,5.21-1.94L20.031,20.79z M4.11,11.02c0-3.84,3.13-6.96,6.96-6.96c3.84,0,6.96,3.12,6.96,6.96c0,3.84-3.12,6.96-6.96,6.96C7.24,17.98,4.11,14.86,4.11,11.02 z" ></path> </g> </svg> </div> <input class="search-input" placeholder="搜索關鍵字" type="text" /> </div> </div> <!-- user avatar --> <section class="user-section"> <img class="avatar" src="https://myvf.kh.asegroup.com/utility/get_emp_photo.asp?emp_no=K14814" alt="user-avatar" /> <p>username</p> </section> </header> </section> <button class="getBtn">setExsample</button> <button class="clearBtn">clearExsample</button> <script src="./app.js"></script> </body> </html> ``` ``` // 各元件產生function /** * * @param {*} className * @param {*} name * @param {*} list * @returns {Array} [HTMLDOM, targetString] */ function navDropDownList(className, name, list) { const container = document.querySelector(`.${className}`); const machList = document.createElement("div"); const btn = document.createElement("button"); const ul = document.createElement("ul"); let targetItem = { [`${name}`]: "" }; const setValueHandler = (value) => { if (!value) return targetItem; targetItem = { [`${name}`]: value }; btn.textContent = value; return targetItem; }; const clearValueHandler = () => { btn.textContent = name; targetItem = { [`${name}`]: "" }; }; machList.classList.add("nav-bar_list"); btn.classList.add("arrow", "list-item", "font-white"); btn.textContent = name; list.forEach((mach) => { const li = document.createElement("li"); li.classList.add("list-item"); li.textContent = mach; li.onclick = handler; function handler(e) { let item = e?.target?.textContent; btn.textContent = item; targetItem = item; targetItem = { [`${name}`]: item, }; } ul.append(li); }); machList.append(btn); machList.append(ul); container.append(machList); return [setValueHandler, clearValueHandler]; } ////////////////////////////使用範例///////////////////////////////// // 範例一:產生下拉選單範例 const [setList1Value, clearList1Value] = navDropDownList("mach-list", "區域", [ "test1", "test2", ]); const [setList2Value, clearList2Value] = navDropDownList("mach-list", "樓層", [ "test1", "test2", ]); const [setList3Value, clearList3Value] = navDropDownList("mach-list", "群組", [ "test1", "test2", ]); // 範例二: 拿到下拉選單目前, 選中的值方法 document.querySelector(".getBtn").addEventListener("click", (e) => { let value1 = setList1Value("123"); let value2 = setList2Value("13123"); let value3 = setList3Value("2222"); console.log(value1, value2, value3); }); // 範例三: 清除下拉選單的值 document.querySelector(".clearBtn").addEventListener("click", (e) => { //清除 clearList1Value(); clearList2Value(); clearList3Value(); //單存拿到目前值 不要帶任何參數即可 let value1 = setList1Value(); let value2 = setList2Value(); let value3 = setList3Value(); console.log(value1, value2, value3); }); //search 搜索關鍵字Search bar document.querySelector(".search-btn").addEventListener("click", (e) => { const filter = document.querySelector(".search-filter"); filter.classList.toggle("showFilter"); }); ``` ``` *, *::after, *::before { padding: 0; margin: 0; box-sizing: border-box; font-family: Arial, Helvetica, sans-serif; } svg { pointer-events: none; } button { all: unset; cursor: pointer; } a { display: block; } .ft-white { color: white; } .bg-primary { background-color: #3450c1; } .font-white { color: white; } .container { max-width: 1320px; padding-left: 12px; padding-right: 12px; margin: 0 auto; /* border: 1px solid black; */ } .header { padding-top: 0.5rem; padding-bottom: 0.5rem; display: flex; align-items: center; gap: 2rem; } .nav-bar { display: flex; gap: 1rem; } .ml-auto { margin-left: auto; } .nav-bar_list .list-item { background-color: transparent; border-radius: 0.25rem; padding: 0.5rem 0.5rem; transition: all 0.25s ease-in; cursor: pointer; } .nav-bar_list .list-item:hover { background-color: rgba(140, 140, 140, 0.5); } .arrow::after { content: "▼"; /* Unicode 向下箭頭 */ font-size: 12px; margin-left: 5px; } .nav-bar_list { position: relative; transition: all 0.25s ease-in; } .nav-bar_list ul { position: absolute; top: 105%; display: flex; flex-direction: column; gap: 0.25rem; padding: 0.5rem; list-style: none; background-color: white; box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); border-radius: 5px; visibility: hidden; /* overflow: hidden; */ opacity: 0; transform: translateY(-10px); transition: all 0.3s ease; } /* 顯示下拉內容 */ .nav-bar_list:hover ul { opacity: 1; visibility: visible; transform: translateY(0); } .user-section { display: flex; align-items: center; gap: 0.25rem; } .user-section p { color: white; } .avatar { width: 48px; height: 48px; border-radius: 100%; object-fit: cover; } /* navbar-上的按鈕 */ .search-section { position: relative; } .search-btn { padding: 0.25rem 0.5rem; border-radius: 0.25rem; transition: all 0.3 ease; } .search-btn:hover .search-filter { background-color: rgba(140, 140, 140, 0.5); } /* btn 叫出視窗效果 */ .search-filter.showFilter { min-width: 320px; min-height: 36px; opacity: 1; visibility: visible; } /* 按下搜索 Btn 的方框 */ .search-filter { min-width: 0; min-height: 0; overflow: hidden; opacity: 0; visibility: hidden; background-color: #3450c1; position: absolute; border: 1px solid gray; padding: 0.5rem; right: 0; transition: all 0.75s ease; } .search-input { all: unset; display: block; border-bottom: 1px solid white; width: 100%; color: white; font-size: 1.25rem; } .search-input-icon { position: absolute; right: 0.5rem; } ``` ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Vite App</title> <link rel="stylesheet" href="style.css" /> </head> <body> <div id="app"> <div class="cardView header"></div> </div> <script type="module" src="/main.js"></script> </body> </html> ``` ``` .container { max-width: 1140px; padding-left: 16px; padding-right: 16px; margin-right: 8px; margin-left: 8px; } .cardView { display: flex; gap: 1.25rem; flex-wrap: wrap; } .card { cursor: pointer; max-width: 256px; } .list { max-height: 0; transform: scaleY(0); transform-origin: top; display: flex; flex-direction: column; gap: 0.25rem; overflow: hidden; border-left: 6px solid #662d8c; padding-left: 0.8rem; transition: all 0.15s ease-in; } .list-active { max-height: fit-content; transform: scaleY(100%); } .list li { list-style: none; font-size: 1.25rem; border-radius: 0.25rem; padding: 0.25rem 0.5rem; background-color: #ed1e79; color: white; } .btn { all: unset; color: white; font-weight: 700; font-size: 1.25rem; position: relative; background: transparent; padding: 0.5rem 1rem; border-radius: 0.15rem; box-shadow: 10px 10px 20px rgba(0, 0, 0, 0.2); border-radius: 0.25rem; background: linear-gradient(45deg, #662d8c, #ed1e79); } .btn:hover { animation: Animation 0.25s ease; } ``` ``` const data = [ { root: 'root1', machExs: ['item1', 'item2'] }, , { root: 'root2', machExs: ['item1', 'item2'] }, { root: 'root2', machExs: ['item1', 'item2'] }, { root: 'root2', machExs: ['item1', 'item2'] }, { root: 'root2', machExs: ['item1', 'item2'] }, { root: 'root2', machExs: ['item1', 'item2'] }, { root: 'root2', machExs: ['item1', 'item2'] }, { root: 'root2', machExs: ['item1', 'item2'] }, { root: 'root2', machExs: ['item1', 'item2'] }, { root: 'root2', machExs: ['item1', 'item2'] }, { root: 'root2', machExs: ['item1', 'item2'] }, { root: 'root2', machExs: ['item1', 'item2'] }, { root: 'root2', machExs: ['item1', 'item2'] }, { root: 'root2', machExs: ['item1', 'item2'] }, { root: 'root2', machExs: ['item1', 'item2'] }, { root: 'root2', machExs: ['item1', 'item2'] }, ]; function cardView(data) { const container = document?.querySelector('.cardView'); if (!container) { throw Error('element class name:cardView, not found!'); } data.forEach((cardData) => { const card = document.createElement('div'); const btn = document.createElement('button'); const list = document.createElement('ul'); card.classList.add('card'); btn.classList.add('btn'); list.classList.add('list'); btn.textContent = cardData?.root; btn.addEventListener('click', (e) => { const target = e.target.parentElement; const list = target.children[1]; list.classList.toggle('list-active'); target.classList.toggle('card-active'); }); cardData?.machExs.forEach((it) => { const listItem = document.createElement('li'); const link = document.createElement('a'); listItem.classList.add('item'); link.classList.add('link'); listItem.textContent = it; list.append(listItem); }); card.append(btn, list); container.append(card); }); } cardView(data); ``` windows 11 vb open IE https://www.perplexity.ai/ https://github.com/jgraph/drawio-desktop/releases/tag/v24.6.1 ``` const fs = require('node:fs'); const str = fs.readFileSync('./test.json', 'utf-8', (err) => console.log(err)); const data = JSON.parse(str); console.log(data?.results[0].columns); const TABLE_NAME = 'TABLE_NAME'; const columnDefine = data?.results[0].columns; const dataItems = data?.results[0].items; dataItems.forEach((it) => { //Number -> 不用單引號 //DATE -> to_DATE(); //VARCHAR2 switch (key) { case 'DATE': break; case 'VARCHAR2': break; case 'NUMBER': break; default: console.error('請檢查JS欄位定義有缺少'); break; } }); ``` IE11.vbs ```vb= CreateObject("InternetExplorer.Application").Visible=true ``` ```javascript= $(document).ready(function(){ $("#openModal").click(function(){ $("#myModal").modal({ backdrop: 'static', keyboard: false }); }); }); ``` ```csharp= ScriptManager.RegisterStartupScript(this, this.GetType(), "model", "window.addEventListener('load', function () {const modal = new bootstrap.Modal(document.getElementById('exampleModal'));modal.show();});", true); ``` ```htmlembedded= <!-- Button trigger modal --> <asp:Button type="button" class="btn btn-primary" Text="Model" runat="server" OnClick="Unnamed3_Click"/> <!-- Modal --> <div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true"> <div class="modal-dialog modal-dialog-centered"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="exampleModalLabel">Modal title</h5> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">&times;</span> </button> </div> <div class="modal-body"> <asp:Button type="button" class="btn btn-primary" Text="Q" runat="server"/> </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-primary">Save changes</button> </div> </div> </div> </div> ``` https://blog.csdn.net/zhu_zhu_xia/article/details/132354488 ![圖片](https://hackmd.io/_uploads/r1FBRogSA.png) ------------------------------------------------- JavaScirpt Call C# using PageMethods (ajax) 要確定是否啟用 FriendlyUrlSetting() RouteConfig.cs -> 看要沒有註解掉它 ```csharp= //routes.EnableFriendlyUrls(settings); ``` ------------------------------------------------- 自動化工具筆記 Electron https://electron-vite.org/guide/ https://tailwindcss.com/docs/installation/using-postcss https://ui.shadcn.com/docs/installation/vite ------------------------------------------- myOA -> Self Service -> 居家辦公SSLVPN -> 個人家用電腦 ---------------------- ## 表達式 目標範例 20240616140200>><S:XXXXXXX><C:XXXXXXXX-WXX> | XXXXXXXXXXX | MESAGE ## 一. 文字辨識 1. 辨認日期 2. Server name 3. Clinet 裝置名稱 4. Sequence(編碼) 5. 訊息 - (Eazy)辨認時間: 取得數字為固定14碼時間 ``` \d{14} ``` - (Eazy)辨認Server name ``` (?<=<S:)[^>]+ ``` - (Eazy)辨認Client name ``` (?<=<C:)[^>]+ ``` - (Hard)辨認Sch: 字串前後為非英文及數字的6 或 9 長度字串 ```javascript= /* * 這個正則表達式解釋如下: (?<![a-zA-Z0-9]) 確保字串前不是英文或數字。 (?=\w*[a-zA-Z]) 確保字串中至少包含一個字母。 (?=\w*\d) 確保字串中至少包含一個數字。 \w{6} 或 \w{9} 表示字串長度為6或9。 (?![a-zA-Z0-9]) 確保字串後不是英文或數字。 * * */ const regex = /(?<![a-zA-Z0-9])(?=\w*[a-zA-Z])(?=\w*\d)\w{6}(?![a-zA-Z0-9])|(?<![a-zA-Z0-9])(?=\w*[a-zA-Z])(?=\w*\d)\w{9}(?![a-zA-Z0-9])/g; ``` ## 二. 內部寄信 1. nodemailer ```javascript= const transporter = nodemailer.createTransport({ host: "smtp.forwardemail.net", port: 465, secure: true, auth: { // TODO: replace `user` and `pass` values from //<https://forwardemail.net> user: 'REPLACE-WITH-YOUR-ALIAS@YOURDOMAIN.COM', pass: 'REPLACE-WITH-YOUR-GENERATED-PASSWORD' } }); ``` ## 三.Server 資料夾內文件讀寫流程 1. 複製檔案至新資料夾ServerLogBak 2. 建立JSON來讀取, 紀錄上次讀取 - normal -> 依照日期時間排序 整理檔案 - 中途關閉 -> 讀取紀錄為reading 的檔案, 砍掉再來一遍 ```json= [ { fileName:'XXXXXX.log', createAt: '', status:'', // start, reading, end } ] ``` ## 程式執行時間規則 1. 每次執行主程式紀錄起始時間 - Case 1 程式執行完成後總結時間低於1小時, 等待一小時後重新執行 - Case 2 執行程式超過等於1小時直接執行 ## 環境變設置 1. 定時多長時間執行一次 2. config.env 若可以跨Server 建立一個做統整 3. 不行by server 放置15個 ----- 瘋狂版 ```javascript= const fs = require("node:fs"); const readline = require("node:readline"); const fsWrite = fs.createWriteStream("output.txt"); const fileStream = fs.createReadStream( "C:\\Users\\s3394\\OneDrive\\桌面\\wirteText\\large_log_file.txt", { highWaterMark: 16 * 1024, // 16KB } ); const rl = readline.createInterface({ input: fileStream, crlfDelay: Infinity, }); rl.on("line", (line) => { // console.log(`Received: ${line}`); fsWrite.write(`mod ${line}\n`); }); rl.on("close", () => { console.log("File reading completed."); }); ``` CPU 等等版 ```javascript= const fs = require("node:fs"); const readline = require("node:readline"); const fsWrite = fs.createWriteStream("output.txt"); const fileStream = fs.createReadStream( "C:\\Users\\s3394\\OneDrive\\桌面\\wirteText\\large_log_file.txt", { highWaterMark: 16 * 1024, // 16KB } ); const rl = readline.createInterface({ input: fileStream, crlfDelay: Infinity, }); rl.on("line", (line) => { rl.pause(); // 暫停接收新的行 setTimeout(() => { const modifiedLine = `Modified: ${line}\n`; fsWrite.write(modifiedLine); rl.resume(); // 繼續接收新的行 }, 10); // 增加延遲以減少 CPU 使用 }); rl.on("close", () => { console.log("File reading completed."); }); ``` ```javascript= const fs = require('fs'); const path = require('path'); const directoryPath = path.join(__dirname, 'your-directory-name'); fs.readdir(directoryPath, (err, files) => { if (err) { console.log('無法讀取目錄: ', err); return; } files.forEach(file => { console.log(file); // 這裡你可以獲取到檔案名,如果需要完整路徑可以使用 path.join(directoryPath, file) }); }); ``` ```javascript= const fs = require("fs"); const path = require("path"); const directoryPath = "C:\\Users\\s3394\\OneDrive\\桌面\\wirteText"; try { const files = fs.readdirSync(directoryPath); files.forEach((item) => { console.log(item); fs.stat(path.join(directoryPath, item), (err, stats) => console.log(stats)); }); } catch (err) { console.log("無法讀取目錄: ", err); } ``` 隨機產生LOG(不考慮中文做判斷), 做單元測試 ```javascript= const fs = require("node:fs"); const fsPromises = require("node:fs/promises"); const readline = require("node:readline"); //20240616140200>><S:XXXXXXX><C:XXXXXXXX-WXX> | XXXXXXXXXXX | MESAGE //隨機文字 const CH = ["中1文", "中2文", "中3文"]; const ENG_UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; const ENG_LOWERERCASE = "abcdefghijklmnopqrstuvwxyz"; //隨機數字 const getRadomInt = (len) => Math.floor(Math.random() * len); //隨機產生Sch function generateRandomSch(len) { let result = ""; for (let index of Array.from(Array(len).keys())) { result += getRadomInt(2) > 0 ? getRadomInt(10) : ENG_UPPERCASE[getRadomInt(ENG_UPPERCASE.length)]; } return result; } //Time stemp function generateTime() { const date = new Date(); return ( date.toLocaleDateString().split("/").join("") + date.toTimeString().split(" ")[0].split(":").join("") + "0" ); } function generateTestLog() { const taskCount = getRadomInt(99); let msg = ""; for (let index of Array.from(Array(taskCount).keys())) { getRadomInt(2) > 0 ? (msg += ` ${generateRandomSch()} `) : (msg += `${ENG_UPPERCASE[getRadomInt(ENG_UPPERCASE.length)]}`); } return `${generateTime()}>><S:Server${getRadomInt(14)}><C:CLient${getRadomInt( 17 )}> | |${msg}`; } console.log(generateTestLog()); console.log(generateTestLog()); ``` ```javascript= const fs = require('fs'); const path = require('path'); function generateRandomLogEntry() { const logLevels = ['INFO', 'WARN', 'ERROR', 'DEBUG']; const messages = [ 'User login successful', 'User login failed', 'Database connection established', 'Database connection failed', 'File not found', 'File uploaded successfully', 'Unauthorized access attempt', 'User password changed', ]; const timestamp = new Date().toISOString(); const logLevel = logLevels[Math.floor(Math.random() * logLevels.length)]; const message = messages[Math.floor(Math.random() * messages.length)]; return `[${timestamp}] ${logLevel}: ${message}\n`; } function generateLargeTextFile(filePath, sizeInMB) { const sizeInBytes = sizeInMB * 1024 * 1024; let totalWritten = 0; const stream = fs.createWriteStream(filePath, { flags: 'w' }); function writeChunk() { if (totalWritten < sizeInBytes) { let chunk = ''; while (chunk.length < 1024 * 1024 && totalWritten + chunk.length < sizeInBytes) { chunk += generateRandomLogEntry(); } stream.write(chunk, () => { totalWritten += chunk.length; console.log(`Written ${Math.round(totalWritten / (1024 * 1024))} MB...`); writeChunk(); }); } else { stream.end(); console.log(`File ${filePath} generated with size ${Math.round(totalWritten / (1024 * 1024))} MB`); } } writeChunk(); } const filePath = path.join(__dirname, 'large_log_file.txt'); const sizeInMB = 500; // Set the size in MB generateLargeTextFile(filePath, sizeInMB); ``` ### 要求SRE 也使用 C# 轉譯版本 ```csharp= using System; using System.IO; using System.Threading.Tasks; class Program { static async Task Main(string[] args) { string inputFilePath = @"C:\Users\s3394\OneDrive\桌面\wirteText\large_log_file.txt"; string outputFilePath = "output.txt"; using (var fsRead = new FileStream(inputFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, 16 * 1024, FileOptions.SequentialScan)) using (var fsWrite = new StreamWriter(outputFilePath)) using (var sr = new StreamReader(fsRead)) { string line; while ((line = await sr.ReadLineAsync()) != null) { await Task.Delay(10); // 增加延遲以減少 CPU 使用 string modifiedLine = $"Modified: {line}"; await fsWrite.WriteLineAsync(modifiedLine); } } Console.WriteLine("File reading completed."); } } ```