Template message
ภาพรวม
Template message คือคลัง LINE Flex Message ที่สร้างจากแม่แบบสำเร็จรูป โดยไม่ต้องเขียน JSON เอง (แต่เขียนเองก็ได้ผ่านแม่แบบ Custom JSON) เทมเพลตที่สร้างไว้จะถูกนำไปใช้เป็นหนึ่งในรายการเนื้อหาของเมนู Rich message รวมถึงถูกเลือกใช้จากกฎทริกเกอร์ เวิร์กโฟลว์ และตัวสร้างฟอร์ม
จุดสำคัญที่ต้องเข้าใจก่อนเริ่มคือ ทุกเทมเพลตถูกเก็บเป็นสองคอลัมน์ควบคู่กัน — คอลัมน์ "รูปแบบฟอร์ม" ที่เก็บสิ่งที่ผู้ใช้กรอกไว้เพื่อเอากลับมาแสดงในฟอร์มตอนแก้ไข และคอลัมน์ "รูปแบบ LINE" ที่เก็บ Flex Message ที่คอมไพล์เสร็จแล้วสำหรับส่งจริง ทั้งสองคอลัมน์ถูกเขียนใหม่ทุกครั้งที่กดบันทึก และแม่แบบแต่ละชนิดตัดสินใจต่างกันว่า ใครเป็นคนคอมไพล์ — เซิร์ฟเวอร์หรือเบราว์เซอร์ (ดูหัวข้อ รายละเอียดสำคัญ)
บทบาทที่ใช้งาน — ทีมคอนเทนต์ที่ต้องการสร้างข้อความหน้าตาสวยงามซ้ำ ๆ ได้เร็วโดยไม่ต้องเขียนโค้ด (โมดูลนี้มี ModuleGate บังคับจริงที่ backend — ดูหัวข้อ สิทธิ์การเข้าถึง)
เส้นทางผู้ใช้งาน
1. ดูรายการเทมเพลต
เปิดเมนู ข้อความเทมเพลต หน้ารายการเป็นตารางเดียว คอลัมน์มี ชื่อ / ชนิด / วันที่แก้ไข (กำกับ "(เวลากรุงเทพฯ)") / การดำเนินการ ตัวกรองมี 2 ช่อง — ค้นหาจากชื่อ และเลือก ชนิด ของแม่แบบ
2. สร้างเทมเพลตใหม่
- กดปุ่ม สร้างข้อความเทมเพลต ระบบตั้งชนิดเริ่มต้นเป็น Card Builder ให้อัตโนมัติ
- กรอกชื่อ — ไม่เกิน 100 ตัวอักษร ต้องไม่ซ้ำกับเทมเพลตอื่นใน OA เดียวกัน ใต้ช่องชื่อมีกล่อง เงื่อนไขการกรอกข้อมูล อธิบายอักขระ/สัญลักษณ์ที่รองรับ และข้อจำกัดว่าห้ามขึ้นต้นด้วยสัญลักษณ์
- เลือกชนิดแม่แบบจาก 6 แบบ — Register, Shopping, Notification (มีการ์ดเดียว), Carousel, Custom JSON (พิมพ์ Flex JSON เอง), หรือ Card Builder (ประกอบจากบล็อกย่อยแบบ visual) การเปลี่ยนชนิดจะโหลดเนื้อหาตัวอย่างของชนิดนั้นมาให้เป็นจุดเริ่มต้น
- หากใช้แม่แบบมีโครงสร้าง (Register / Shopping / Notification / Carousel) — กรอกหัวเรื่อง คำอธิบาย รูปภาพ/ไอคอน และปุ่ม
- Carousel เริ่มด้วย 2 การ์ด และเพิ่มได้ถึง 6 การ์ด (ปุ่มแสดงเป็น
"เพิ่มการ์ด {n} / 6"พร้อมคำอธิบายว่าเพิ่มได้สูงสุด 6 การ์ด) - ปุ่มเลือกการกระทำได้ 4 แบบ — ส่งข้อความ, เปิดลิงก์, โทรออก, Postback
- ส่งข้อความ — ช่องข้อความเป็น autocomplete ที่แนะนำ keyword จากเมนู Auto-response message ที่เปิดใช้งานอยู่ เพราะข้อความที่ปุ่มส่งกลับเข้าแชทกระตุ้นกฎตอบกลับได้จริง
- เปิดลิงก์ — กรอกป้ายชื่อ (ไม่เกิน 100 ตัวอักษร) และ URL ที่ต้องขึ้นต้นด้วย http หรือ https
- โทรออก — มีช่องเดียวคือ เบอร์โทรศัพท์ (ไม่เกิน 10 หลัก ตรวจด้วย regex) ไม่มีช่องป้ายชื่อแยก
- Postback — กรอกข้อมูล postback และข้อความที่แสดง (ทั้งสองช่องบังคับ)
- ทุกช่องข้อความมีปุ่ม Merge Tag เปิดรายการ tag ให้แทรกที่ตำแหน่งเคอร์เซอร์ในรูปแบบ
{{key}}
- Carousel เริ่มด้วย 2 การ์ด และเพิ่มได้ถึง 6 การ์ด (ปุ่มแสดงเป็น
- หากใช้ Card Builder — เพิ่มบล็อกทีละชิ้น (รูปภาพหลัก, วิดีโอ, ข้อความ, ปุ่ม, เส้นคั่น) ลากจัดลำดับ กำหนดการกระทำต่อบล็อก แล้วกรอก ข้อความสำรอง (alt text) ที่จะโชว์ในรายการแชท (ไม่เกิน 400 ตัวอักษร) — เกณฑ์ที่ทำให้ปุ่มบันทึกทำงานได้คือ ต้องมีอย่างน้อยหนึ่งบล็อก, ทุกบล็อกต้องกรอกครบตามชนิดของมัน และต้องมีอย่างน้อยหนึ่งบล็อกที่แสดงเนื้อหาจริง (เส้นคั่นล้วน ๆ ไม่ผ่าน)
- รูปภาพหลักของ Card Builder อัปโหลดผ่าน endpoint แยก (
POST /template-message/upload-image) เพื่อได้ URL กลับมาก่อน แล้วตอนบันทึกจึงส่งเป็น JSON ล้วน
- รูปภาพหลักของ Card Builder อัปโหลดผ่าน endpoint แยก (
- หากใช้ Custom JSON — วาง Flex JSON เองในช่องเดียว
- ดูตัวอย่างที่คอลัมน์ขวา ซึ่ง render ด้วยตัวเรนเดอร์ Flex Message จริง กดบันทึกเมื่อครบเกณฑ์ของชนิดแม่แบบที่เลือก
3. นำไปใช้ต่อที่เมนูอื่น
- Rich message — เลือกชนิดเนื้อหาเป็น "ข้อความเทมเพลต" แล้วอ้างอิงเทมเพลตที่สร้างไว้ที่นี่ ระบบจะคัดลอกเนื้อหารูปแบบ LINE ของเทมเพลตนั้นไปเก็บไว้ใน Rich message ตอนกดบันทึก
- เมนูปลายทางเรียก
GET /template-message/find-all-objectซึ่งคืนข้อมูลได้ 3 รูปแบบตามพารามิเตอร์format— object (ค่าเริ่มต้น),dropdownและdropdownGroup
รายละเอียดสำคัญที่ตรวจสอบจากซอร์สโค้ดจริง
ยืนยันจาก internal/modules/templatemessage/{controller,service,dto,formdata,flex}.go และ internal/modules/planlimits/ ของ line-management-cms-api-go:
- แม่แบบ 2 ชนิดจาก 6 ชนิดไม่มีตัวคอมไพล์ฝั่งเซิร์ฟเวอร์เลย — ชนิด 1-4 (Register / Shopping / Notification / Carousel) ถูกคอมไพล์เป็น Flex Message ที่เซิร์ฟเวอร์ ด้วยตัวสร้างที่เขียนไว้เฉพาะแต่ละชนิด (
createType1Flex…createType4Flex) แต่ชนิด 5 (Custom JSON) และชนิด 6 (Card Builder) ให้เบราว์เซอร์คอมไพล์แล้วส่งผลลัพธ์ขึ้นมาเก็บตรง ๆ คอมเมนต์ในโค้ดระบุไว้ชัดว่า Card Builder "ไม่มีตัวคอมไพล์ flex ฝั่งเซิร์ฟเวอร์ — เป็นโมเดลความเชื่อถือเดียวกับ Custom JSON" ผลคือ ทั้งสองชนิดนี้ไม่มีการตรวจโครงสร้าง Flex ที่เซิร์ฟเวอร์ใด ๆ (ต่างจากเมนู Rich message ที่ยิง JSON ไปให้ LINE ตรวจให้จริง) เซิร์ฟเวอร์ตรวจแค่ว่าเป็น JSON ที่ parse ได้ - ปุ่ม "โทรออก" ใช้เบอร์โทรเป็นทั้งป้ายชื่อของปุ่มและปลายทางของการโทร — ตัวคอมไพล์ฝั่งเซิร์ฟเวอร์สร้างค่า uri ของปุ่มด้วย
"tel:" + labelและฟอร์มก็ผูกช่อง "เบอร์โทรศัพท์" เข้ากับฟิลด์labelโดยตรง (ไม่มีช่องป้ายชื่อแยกให้กรอก) ผลคือ ปุ่มบนบับเบิลจะแสดงเป็นตัวเลขเบอร์โทร ตั้งชื่อปุ่มเป็นข้อความอย่าง "โทรหาเรา" ไม่ได้ - ปุ่ม "Postback" ก็ยุบสองความหมายเข้าด้วยกันแบบเดียวกัน — ค่า
dataของ postback ถูกอ่านจากฟิลด์labelและฟอร์มตั้งชื่อช่องนั้นว่า "ป้ายชื่อ" แต่ใส่ placeholder ว่า "กรอกข้อมูล" และข้อความ error ว่า "กรุณากรอกข้อมูล" — ข้อมูล postback จึงเป็นข้อความที่ปรากฏบนปุ่มด้วย ส่วนช่อง "ข้อความที่แสดง" ถูกส่งเป็นฟิลด์textแล้วกลายเป็นdisplayTextของ action นอกจากนี้ หน้าเว็บส่งขึ้นมาแค่ 4 ฟิลด์ต่อปุ่มเท่านั้น (type,label,text,link) ไม่มีฟิลด์dataแยกในสายข้อมูลจริงเลย - ป้ายชื่อปุ่มที่ไม่ได้กรอกจะกลายเป็นคำว่า "Click" — ตัวคอมไพล์ใช้ลำดับสำรอง
label || text || "Click"ดังนั้นปุ่มที่ไม่มีทั้งป้ายชื่อและข้อความจะออกไปเป็นปุ่มที่เขียนว่า "Click" ไม่ใช่ปุ่มว่างหรือข้อผิดพลาด - ชนิดปุ่มที่ระบบไม่รู้จักจะคอมไพล์เป็น action ชนิด
text— ตัวคอมไพล์แมปuri/tel→uri,postback→postback,message→messageและ ทุกค่าอื่นตกไปที่ชนิดtextซึ่งไม่ใช่ชนิด action ที่ LINE รองรับ (ค่าno_actionมีอยู่ในโค้ดฝั่งหน้าเว็บแต่ไม่ได้ถูกเสนอในตัวเลือกให้ผู้ใช้เลือก จึงไม่เกิดขึ้นจากหน้าจอปกติ) - โควตาเทมเพลตบังคับตอน "สร้าง" เท่านั้น และคิดเป็นรายช่องทาง — ค่าเริ่มต้นของแพลตฟอร์มคือ 20 เทมเพลต และการทับค่าไล่ตามลำดับ ค่าเริ่มต้นแพลตฟอร์ม <
organization.plan_limits<line_oa.plan_limits(ทับกันทีละคีย์) โดยเมื่อมี LINE OA อยู่ใน context ระบบจะนับโควตา ต่อช่องทาง ไม่ใช่ต่อองค์กร ค่า-1หมายถึงไม่จำกัด การนับใช้เงื่อนไขstatus != 'delete' AND deleted_date IS NULL(เทมเพลตที่ลบแล้วไม่กินโควตา) เมื่อเต็มจะได้ 400 พร้อมข้อความบอกตัวเลขจริง — "Plan limit reached: you are using 20 of 20 templates. Please upgrade your plan." — และ การแก้ไขเทมเพลตเดิมไม่ถูกตรวจโควตาเลย - การแก้ไขเทมเพลตจะ cascade ไปแก้ Rich message ที่อ้างอิงอยู่ให้ด้วย และเป็นแบบ best-effort — หลังบันทึกสำเร็จ ระบบยิง SQL ดิบค้นหา
rich_messageที่content::text LIKE '%<templateMessageId>%'แล้วไล่แก้ค่าflexของรายการที่type = "templateMessage"และtemplateMessageIdตรงกัน ให้เป็นเนื้อหาชุดใหม่ จุดที่ต้องรู้คือ ข้อผิดพลาดของขั้นตอนนี้ถูกกลืนไว้ (เขียน log เท่านั้น) ตามพฤติกรรมของระบบต้นฉบับ การ cascade ที่ล้มเหลวจึงไม่ทำให้การแก้ไขเทมเพลตล้มเหลวและไม่แจ้งผู้ใช้ นอกจากนี้ คำสั่งค้นหาไม่ได้กรองline_oa_id(กรองแค่deleted_date IS NULL) จึงสแกนข้าม OA ทั้งฐานข้อมูล — ในทางปฏิบัติจับคู่ด้วย uuid ของเทมเพลตอยู่แล้วจึงไม่แก้ข้อมูลผิดตัว - การ cascade ทำงานเฉพาะตอนแก้ไข ไม่ทำงานตอนลบ — การลบเทมเพลตไม่ไปแตะ Rich message ที่อ้างอิงอยู่เลย Rich message เหล่านั้นจะยังส่งเนื้อหาชุดสุดท้ายที่คัดลอกไว้ต่อไปเรื่อย ๆ โดยไม่มีข้อผิดพลาด
- ช่องข้อความที่กรอกเป็นช่องว่างล้วนถูกปฏิเสธเป็นพิเศษ — คอมเมนต์ระบุเหตุผลไว้ตรง ๆ ว่าค่าที่เป็นช่องว่างล้วนจะไปลงเอยใน text node ของ flex แล้วทำให้การส่งไป LINE ล้มเหลวปลายทาง ("may be empty") ระบบจึงตรวจทุกฟิลด์ที่เป็นข้อความในโครงสร้าง flex โดย ตรวจเฉพาะคีย์ที่ส่งมาจริง เพราะคีย์ที่มีต่างกันไปตามชนิดแม่แบบ
- merge tag ระดับ system มีอยู่ที่ backend แต่หน้าจอนี้ไม่เคยแสดง — endpoint
GET /template-message/merge-tagsคืน tag สามกลุ่ม:fixed(7 รายการที่ hardcode ไว้ — display_name, firstname, lastname, email, mobile_no, language, follow),custom(จาก custom attribute ที่เปิดใช้งานของ OA เติม prefixcustom.) และsystem(เติม prefixsystem.มี cache 60 วินาที) แต่กลุ่มsystemจะถูกส่งกลับมา เฉพาะเมื่อเรียกด้วย?showSystem=trueซึ่ง ตัวเลือก merge tag ในหน้าจอนี้เรียกโดยไม่ส่งพารามิเตอร์นั้นเลย — ผู้ใช้จึงเห็นได้แค่ 2 กลุ่ม - merge tag ไม่ถูกแทนค่าที่นี่ และไม่ถูกตรวจว่ามีจริง — ค่าที่พิมพ์ในรูปแบบ
{{key}}ถูกเก็บลงฐานข้อมูลตามตัวอักษร ไม่มีการตรวจว่า key นั้นมีอยู่ในรายการ tag จริงหรือไม่ การแทนค่าเกิดที่โมดูลปลายทางตอนส่งข้อความจริง - เส้นทางรายการมีโหมด cache แบบเลือกเปิด และคีย์ถูกสร้างให้ตรงกับระบบเดิมทีละไบต์ — ส่ง
cache=trueจะเข้าเส้นทางที่ hash คีย์จากการเลียนแบบJSON.stringify(payload)ของ JavaScript รวมถึงลำดับคีย์ (page, orderBy, order, limit ก่อน แล้วค่อยพารามิเตอร์อื่นตามลำดับใน URL) และคีย์ที่มีค่าเป็น undefined จะถูกข้ามไปเหมือน JavaScript ทุกการเขียนจะล้าง cache ของโมดูลนี้ทั้งหมด - ตัวกรอง
typeมีพฤติกรรมตามกฎการแปลงค่าของ JavaScript — ส่งtype=(ค่าว่าง) จะถูกมองว่าไม่ได้ส่งมา แต่ส่งtype=0จะถูกแปลงเป็นเลข 0 ซึ่งชั้น repository มองว่า "ไม่ได้กรอง" อยู่ดี ส่วนค่าที่แปลงเป็นตัวเลขไม่ได้จะถูกปฏิเสธด้วย "type must be a number conforming to the specified constraints" - รูปภาพของ Card Builder รับ GIF ได้ ต่างจากเมนู Rich message — endpoint
upload-imageรับimage/jpeg,image/pngและimage/gifขนาดไม่เกิน 10 MB (บังคับที่เซิร์ฟเวอร์จริง พร้อมข้อความ "Invalid file type. Only JPEG, PNG, and GIF are allowed" และ "File size too large. Maximum size is 10MB") ขณะที่เมนู Rich message รับแค่ JPEG/PNG
เมนูที่เกี่ยวข้องกันในเส้นทางนี้
- Rich message — ผู้ใช้ปลายทางหลัก เลือกเทมเพลตเป็นรายการเนื้อหาชนิดหนึ่ง โดยเนื้อหาถูกคัดลอกไปเก็บตอนบันทึก และการแก้ไขเทมเพลตที่นี่จะ cascade กลับไปแก้ค่าที่คัดลอกไว้ให้ (แต่การลบไม่ cascade)
- Auto-response message — keyword ของกฎที่เปิดใช้งานอยู่จะโผล่เป็นคำแนะนำในช่องข้อความของปุ่มชนิด "ส่งข้อความ" ที่นี่
- Quick reply — ไม่ รองรับการผูกกับเทมเพลตโดยตรง (ตาราง
template_messageไม่มีคอลัมน์รองรับ) ถ้าต้องการปุ่มตอบกลับด่วนพร้อมเทมเพลต ต้องผูกที่ระดับ Rich message ที่ห่อเทมเพลตนั้นอยู่ - เทมเพลตยังถูกเลือกใช้จากกฎทริกเกอร์ เวิร์กโฟลว์ และตัวสร้างฟอร์ม ผ่าน endpoint
find-all-objectเดียวกัน
จุดที่ควรระวัง
- Custom JSON และ Card Builder ไม่ถูกตรวจโครงสร้าง Flex ที่เซิร์ฟเวอร์เลย — บันทึกผ่านไม่ได้แปลว่า LINE จะยอมรับ ควรเปิดหน้าตัวอย่างให้แน่ใจก่อนนำไปใช้จริง (ต่างจากเนื้อหาชนิด Flex ในเมนู Rich message ที่ถูกยิงไปให้ LINE ตรวจให้)
- โควตาเทมเพลตไม่มีการเตือนล่วงหน้าในหน้าเว็บ — ถึงเพดานแล้วจะรู้ตอนกดบันทึกแล้วไม่ผ่านเท่านั้น ข้อความที่ได้จะบอกตัวเลขที่ใช้ไปกับเพดานจริง และเพดานนี้อาจถูกตั้งเป็นรายช่องทาง ไม่ใช่ค่าเดียวทั้งองค์กร
- ปุ่มโทรออกจะโชว์เบอร์โทรเป็นข้อความบนปุ่ม — ถ้าอยากให้ปุ่มเขียนว่า "โทรหาเรา" ต้องใช้ชนิด "เปิดลิงก์" แล้วกรอก URL เป็น
tel:เอง หรือใช้แม่แบบ Card Builder - แก้เทมเพลตแล้วอาจไม่ถึง Rich message ทุกตัว — การ cascade เป็นแบบ best-effort และล้มเหลวเงียบ ๆ ได้ ถ้าเปลี่ยนเนื้อหาสำคัญ ควรไล่เปิด Rich message ที่ใช้เทมเพลตนั้นเพื่อยืนยันด้วยตา
- ลบเทมเพลตแล้ว Rich message ยังส่งเนื้อหาเดิมต่อไปได้ — ไม่มีการเตือนว่าเทมเพลตนี้ถูกใช้งานอยู่ที่ไหนก่อนลบ (ต่างจากเมนู Quick reply ที่มีกลไกเตือนและบล็อกการลบ) และจะเจอปัญหาก็ตอนเปิด Rich message นั้นขึ้นมากดบันทึกใหม่ ซึ่งจะได้
APP_007 - merge tag ที่พิมพ์ผิดจะไม่มีใครเตือน — ไม่มีการตรวจว่า key มีอยู่จริงทั้งที่หน้าเว็บและที่ backend ควรแทรกจากปุ่ม Merge Tag แทนการพิมพ์เอง
- ชื่อซ้ำถูกปฏิเสธเป็นรายฟิลด์ — ระบบตอบ 400 พร้อมชี้ที่ฟิลด์
titleว่า "Title is already exist" หน้าเว็บจึงขึ้นข้อผิดพลาดใต้ช่องชื่อโดยตรง - การลบเป็นการลบแบบนุ่มนวล (soft delete) — ระบบเปลี่ยนสถานะเป็น
deleteแล้วประทับเวลาลบ ข้อมูลยังอยู่ในฐานข้อมูล และโมดูลนี้ไม่มีเส้นทางลบถาวรเลย
สิทธิ์การเข้าถึง (ตรวจสอบจากซอร์สโค้ดจริง)
- ฝั่งหน้าเว็บ — ถูกครอบด้วย
PermissionGuardตรวจ actionVIEWของ subjecttemplate-message - ฝั่งเซิร์ฟเวอร์ — ทุกเส้นทางทั้ง 8 เส้นทางของโมดูลนี้ถูกครอบด้วย
ModuleGate(d, "template-message")ที่บังคับใช้จริง (เหมือนโมดูล Rich message และ Campaign และต่างจากโมดูล Quick reply ที่ไม่มี ModuleGate เลย) คอมเมนต์ในโค้ดระบุพฤติกรรมไว้ชัด: ผู้เรียกประเภทลูกค้าที่องค์กรถูกผู้ดูแลระดับแพลตฟอร์มปิดโมดูล "template-message" ไว้ จะได้ 403 ส่วนผู้เรียกภายใน (onemoby) ผ่านได้ GET /template-message/find-all-objectไม่มี policy annotation ติดอยู่เลย ต่างจากเส้นทางอื่นในโมดูลเดียวกัน (คอมเมนต์ระบุว่า "@CheckPoliciesNOT present on find-all-object in TS" — รักษาไว้ตามระบบเดิม) แต่ยังถูกModuleGateครอบอยู่เหมือนกัน จึงยังถูกกันด้วยการเปิด/ปิดโมดูลระดับองค์กร- policy annotation ระดับการกระทำของเส้นทางอื่น (
create/read/update/delete) มีประกาศไว้ครบ แต่วิ่งผ่าน middleware ที่เป็น no-op เหมือนโมดูลอื่นส่วนใหญ่ — การบังคับจริงจึงอยู่ที่ ModuleGate ระดับโมดูล - การกันข้อมูลข้ามองค์กรบังคับที่ทุกเส้นทาง — การอ่าน แก้ไข และลบ ผูก
line_oa_idของผู้เรียกไว้ทั้งหมด คอมเมนต์ระบุว่าเป็นการอุดช่องที่เคยแก้ไข (I1) และลบ (I2) เทมเพลตขององค์กรอื่นได้ การเรียก id ที่ไม่ใช่ของตนจะได้ 404 ส่วนการลบที่ไม่พบเป้าหมายจะได้ 400AUS_102 - ไม่มีเส้นทางลบถาวร (hard delete) และไม่มีเส้นทางที่ต้องเป็น Super Admin ในโมดูลนี้
ไฟล์อ้างอิงจากซอร์สโค้ด
Frontend (cms-web-2026)
src/app/template-message/page.tsx,src/app/template-message/form/page.tsx— จุดเข้าหน้ารายการและฟอร์ม พร้อมPermissionGuardsrc/components/template-message/list/template-message.container.tsx— ตาราง, ตัวกรองชื่อและชนิดsrc/components/template-message/form/template-message-form.tsx— ฟิลด์ของแต่ละชนิดแม่แบบ, ชนิดปุ่ม 4 แบบและการผูกฟิลด์, เพดาน 6 การ์ดของ Carouselsrc/components/template-message/form/template-message-form.container.tsx— การประกอบFormData, การแยกทางของชนิด 5 และ 6, การเติมค่ากลับตอนแก้ไขsrc/components/template-message/form/flex-card-builder/—flex-card-builder.tsx,compile.ts(ตัวคอมไพล์ฝั่งเบราว์เซอร์),validate.ts(เกณฑ์เปิดปุ่มบันทึก),default-blocks.tssrc/components/template-message/form/merge-tag-picker.tsx,merge-tag-input.tsx— ตัวเลือกและการแทรก merge tagsrc/components/template-message/constants/template-message.constants.ts— เนื้อหาตั้งต้นของแม่แบบทั้ง 6 ชนิดsrc/services/template-message.service.ts— endpoint ทั้งหมดที่หน้าเว็บเรียกใช้messages/th/template-message.json,messages/en/template-message.json— ข้อความ UI ทั้งหมด
Backend (line-management-cms-api-go)
internal/modules/templatemessage/controller.go— 8 เส้นทาง,ModuleGate("template-message"), กฎของupload-imageinternal/modules/templatemessage/service.go— การแยกทางของชนิดแม่แบบ, การตรวจโควตา, กลไก cascade ไปที่rich_message, endpoint merge taginternal/modules/templatemessage/flex.go— ตัวคอมไพล์ Flex ฝั่งเซิร์ฟเวอร์ของชนิด 1-4 และการแมป action ของปุ่มinternal/modules/templatemessage/formdata.go— การถอดรหัส multipart แบบ bracket notation และการตรวจฟิลด์ข้อความinternal/modules/templatemessage/dto.go— กฎตรวจสอบข้อมูลขาเข้าและการสร้างคีย์ cacheinternal/modules/planlimits/service.go,constant.go— โควตาเทมเพลต, ลำดับการทับค่า, ค่าเริ่มต้น 20
ดูรายละเอียดเชิงเทคนิคเพิ่มเติม (เวอร์ชันก่อนหน้าของทีมเอกสาร) ที่ จัดการ Template Message