Frontend.instructions.md
從 Google Drive .github/instructions/Frontend.instructions.md 匯入
共用指令內容
使用中---
applyTo: '**/*.{aspx,ascx,js}'
---
前端 (ASPX, JavaScript) 開發規範
⛔ 0. ASPX Code-Behind 強制規範 (CRITICAL)
每一個 .aspx 頁面都必須有對應的 .aspx.cs code-behind 檔案,且該類別必須繼承 BasePage。
沒有 code-behind 的頁面等於繞過 BasePage 的 SSO 驗證與權限機制,形成嚴重的資安漏洞。
0.1 @Page 指令必要屬性
.aspx 檔案的 <%@ Page %> 指令必須包含 CodeFile 與 Inherits:
<%@ Page Language="C#"
MasterPageFile="~/System/MasterPage/XXXMaster.master"
AutoEventWireup="true"
CodeFile="PageName.aspx.cs"
Inherits="Pages_Feature_PageName"
Title="頁面標題" %>
0.2 Code-Behind 類別命名規範
- 依照檔案路徑,以底線
_串接資料夾與檔名:
- Pages/Welfare/Index.aspx → Pages_Welfare_Index
- Pages/Welfare/Admin/Dashboard.aspx → Pages_Welfare_Admin_Dashboard
0.3 後台 Admin 頁面範本
using System;
public partial class Pages_Feature_Admin_PageName : BasePage
{
protected override void OnPreInit(EventArgs e)
{
this.CheckAuthorization = true; // ⛔ 後台頁面必須啟用
base.OnPreInit(e);
}
protected void Page_Load(Object sender, EventArgs e)
{
}
}
0.4 前台公開頁面範本
前台頁面仍須繼承 BasePage(取得 Session / CurrEmpInfo 基礎設施),但 CheckAuthorization 維持預設 false:
using System;
public partial class Pages_Feature_PageName : BasePage
{
protected void Page_Load(Object sender, EventArgs e)
{
}
}
0.5 禁止模式
- ❌ 建立
.aspx但不建立對應.aspx.cs - ❌
.aspx的@Page指令缺少CodeFile或Inherits - ❌ Code-behind 類別不繼承
BasePage - ❌ 後台 Admin 頁面未設定
CheckAuthorization = true
---
1. UI/UX 佈局
- 框架: 必須使用 Bootstrap 5.2.3 組件來建構 UI,並確保響應式設計。
- 表格: 使用 DataTables 來顯示和操作資料表格。
- DataTables 中文語系載入規範 (CRITICAL): 專案的語系檔
/App_JS/datatables-zh-TW.js是 JS 變數宣告(var dtZhTW = {...}),不是純 JSON。必須用<script src="/App_JS/datatables-zh-TW.js"></script>引入,再以language: dtZhTW使用。絕對禁止使用language: { url: '/App_JS/datatables-zh-TW.js' }方式載入,該方式會以 AJAX 抓取並解析為 JSON,導致SyntaxError: Unexpected token ':'與ReferenceError: dtZhTW is not defined。 - 圖表: 使用 Chart.js 來呈現數據圖表。
- 字體與圖示: 使用「芫荽字體」assets/fonts/iansui.css,assets/fonts/Iansui-Regular.ttf和 FontAwesome 圖示庫。
- 離線化: 專案為完全離線化,不可使用 CDN 引用任何外部函式庫。
2. JavaScript 開發
- 函式庫: 使用 jQuery 3.7.1 作為主要的 DOM 操作和 Ajax 函式庫。
- 事件綁定: 使用
$(document).ready()或$(function() { ... });來確保頁面載入完成後才執行腳本。事件應使用.on()方法進行綁定。 - Ajax 呼叫: 必須使用統一的 Ajax 呼叫模式與 WebService (.asmx) 進行通訊。
3. Ajax 呼叫範例
// 使用統一的 Ajax 呼叫 WebService 模式
$.ajax({
type: "POST",
url: "/App_Code/WS/UserWS.asmx/GetAllUsers", // WebService 的路徑
contentType: "application/json; charset=utf-8",
dataType: "json",
data: "{}", // 若無參數,傳遞空 JSON 物件
success: function (response) {
// 處理成功的回應。注意:ASP.NET ASMX 的回傳值會被包在 .d 屬性中
var userList = response.d;
console.log(userList);
// ...後續處理邏輯...
},
error: function (xhr, status, error) {
// 使用統一的錯誤處理機制,例如顯示一個 Bootstrap Alert
console.error("API 呼叫失敗", error);
},
});
---
## 4. ⚠️ ASMX ScriptService `resp.d` 拆包規範 (CRITICAL)
> **教訓來源**:2026-04 實際踩坑
> 使用 `FormData` 上傳檔案(`processData: false, contentType: false`)時,開發者容易誤以為 ASMX 不會包 `d`,直接 `JSON.parse(res)` 或 `res.success` 存取,導致取不到值,功能靜默失敗。
### 4.1 規則
**所有**呼叫 `.asmx` WebService 的 success callback,**無論是否使用 FormData 上傳**,回傳值**一律被包在 `resp.d` 內**:
// ✅ 正確:先拆包再使用
success: function (resp) {
var d = (typeof resp.d !== "undefined") ? resp.d : resp;
var obj = (typeof d === "string") ? JSON.parse(d) : d;
if (!obj.success) { showMsg("error", obj.message); return; }
// ...
}
// ❌ 錯誤:FormData 上傳後直接解析,漏掉 .d 拆包
success: function (res) {
var obj = (typeof res === "string") ? JSON.parse(res) : res;
// obj 實際上是 { d: "..." },obj.success === undefined → 靜默失敗
}
### 4.2 ASHX vs ASMX 對照
| 端點類型 | 回傳結構 | success callback 取值方式 |
|---|---|---|
| `.asmx` (ScriptService) | `{ "d": "{...}" }` | 先取 `resp.d`,再 parse |
| `.ashx` (Generic Handler) | `"{...}"` (純 JSON 字串) | 直接 `JSON.parse(resp)` |
---
## 5. ⚠️ TinyMCE editor 實例取得規範 (CRITICAL)
> **教訓來源**:2026-04 實際踩坑
> `tinymce.get('editorId')` 在 TinyMCE 6+ 某些載入情境下回傳 `undefined` 或拋出 `TypeError: tinymce.get is not a function`,導致儲存時取不到編輯器內容。
### 5.1 規則
在 `setup` 回呼的 `editor.on('init')` 中,將 editor 實例存入模組層級變數,後續**禁止**使用 `tinymce.get()`:
// ✅ 正確:以 _editors 物件儲存實例
var _editors = {};
tinymce.init({
selector: "#introContent, #recruitNotice",
setup: function (editor) {
editor.on('init', function () {
_editors[editor.id] = editor; // 儲存實例
});
}
});
// 讀取內容
var content = _editors['introContent'] ? _editors['introContent'].getContent() : '';
// ❌ 錯誤:直接使用 tinymce.get(),可能拋出 TypeError
var content = tinymce.get('introContent').getContent();
### 5.2 單一編輯器簡化寫法
頁面只有一個 TinyMCE 時,可用單一變數:
var _editor = null;
tinymce.init({
selector: "#txtContent",
setup: function (editor) {
editor.on('init', function () { _editor = editor; });
}
});
// 讀取
var content = _editor ? _editor.getContent() : '';
// 設值
if (_editor) _editor.setContent(data.Content || '');