<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>方案预设管理</title>
    <script>
        (function () {
            var token = localStorage.getItem('pb_manage_token') || ''
            var isLoggedIn = localStorage.getItem('pb_manage_logged_in') === '1'
            if (!token || !isLoggedIn) {
                window.location.replace('/pb/manage/login')
            }
        })()
    </script>
    <style>
        * { box-sizing: border-box; }
        body { margin: 0; font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; background: linear-gradient(180deg, #edf6ff 0%, #f8fafc 100%); color: #0f172a; }
        .container { max-width: 1520px; margin: 0 auto; padding: 24px 14px 42px; }
        .topbar { background: rgba(255,255,255,0.96); border: 1px solid #dbe3f0; border-radius: 20px; box-shadow: 0 18px 50px rgba(15, 23, 42, 0.08); padding: 18px; margin-bottom: 14px; }
        .panel { background: rgba(255,255,255,0.96); border: 1px solid #dbe3f0; border-radius: 20px; box-shadow: 0 18px 50px rgba(15, 23, 42, 0.08); padding: 18px; }
        .panel + .panel { margin-top: 14px; }
        .actions, .toolbar, .form-actions, .upload-row, .summary, .product-picker-actions { display: flex; flex-wrap: wrap; gap: 10px; }
        .toolbar { display: grid; grid-template-columns: 1.2fr 1fr 1fr 1fr auto; gap: 10px; }
        .toolbar.simple { grid-template-columns: 1.5fr 1fr 1fr auto; }
        .grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
        .full { grid-column: 1 / -1; }
        .btn { border: none; border-radius: 12px; padding: 10px 16px; font-weight: 600; cursor: pointer; text-decoration: none; display: inline-flex; align-items: center; justify-content: center; }
        .btn-primary { background: #2563eb; color: #fff; }
        .btn-light { background: #f8fafc; color: #334155; border: 1px solid #dbe3f0; }
        .btn-danger { background: #dc2626; color: #fff; }
        .btn-secondary { background: #e0e7ff; color: #1e3a8a; }
        .btn:disabled { cursor: not-allowed; opacity: 0.65; }
        .status { min-height: 22px; font-size: 14px; }
        .status.success { color: #15803d; }
        .status.error { color: #b91c1c; }
        .summary-card { min-width: 180px; padding: 14px 16px; border-radius: 16px; background: linear-gradient(180deg, #eff6ff 0%, #f8fafc 100%); border: 1px solid #dbeafe; }
        .summary-card strong { display: block; font-size: 26px; color: #1d4ed8; }
        .summary-card span { color: #475569; font-size: 13px; }
        h1, h2, h3 { margin: 0; }
        h1 { font-size: 30px; }
        h2 { font-size: 22px; }
        h3 { font-size: 18px; }
        label { display: block; margin-bottom: 8px; color: #334155; font-weight: 600; }
        input, textarea, select { width: 100%; border: 1px solid #cbd5e1; border-radius: 12px; padding: 10px 12px; font-size: 14px; background: #fff; color: #0f172a; }
        textarea { min-height: 110px; resize: vertical; font-family: Consolas, "Courier New", monospace; }
        .hint { margin-top: 6px; color: #64748b; font-size: 12px; line-height: 1.5; }
        .tiny { color: #64748b; font-size: 12px; }
        .badge { display: inline-flex; align-items: center; padding: 4px 10px; border-radius: 999px; background: #e0f2fe; color: #075985; font-size: 12px; font-weight: 700; }
        .editor { display: none; }
        .editor.show { display: block; }
        .table-wrap { border: 1px solid #dbe3f0; border-radius: 16px; overflow: auto; background: #fff; }
        table { width: 100%; border-collapse: collapse; }
        thead { background: #eff6ff; }
        th, td { padding: 12px; border-bottom: 1px solid #e5e7eb; text-align: left; vertical-align: top; }
        .empty { padding: 24px; text-align: center; color: #64748b; }
        .code { font-family: Consolas, "Courier New", monospace; font-size: 12px; }
        .json-preview { white-space: pre-wrap; word-break: break-word; max-width: 420px; }
        .thumb-box { display: grid; grid-template-columns: 120px 1fr; gap: 14px; align-items: start; }
        .thumb { width: 120px; height: 120px; border-radius: 16px; border: 1px solid #dbe3f0; object-fit: cover; background: #f8fafc; }
        .thumb-empty { display: flex; align-items: center; justify-content: center; color: #94a3b8; font-size: 13px; }
        .section-title { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-bottom: 12px; }
        .product-picker-box { border: 1px solid #dbe3f0; border-radius: 16px; background: linear-gradient(180deg, #f8fbff 0%, #ffffff 100%); padding: 14px; }
        .product-picker-grid { display: grid; grid-template-columns: 1fr 1.2fr auto auto; gap: 10px; align-items: end; }
        .product-picker-actions { justify-content: flex-end; }
        .product-list-summary { margin-top: 12px; border: 1px solid #dbe3f0; border-radius: 14px; background: #fff; overflow: hidden; }
        .product-list-summary:empty { display: none; }
        .product-list-empty { padding: 16px; color: #64748b; font-size: 13px; text-align: center; }
        .product-summary-row { display: grid; grid-template-columns: auto 1fr auto; gap: 12px; align-items: center; padding: 12px 14px; border-bottom: 1px solid #e5e7eb; }
        .product-summary-row:last-child { border-bottom: none; }
        .product-summary-order { width: 28px; height: 28px; border-radius: 999px; background: #eff6ff; color: #1d4ed8; display: inline-flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 700; }
        .product-summary-main { min-width: 0; }
        .product-summary-title { font-weight: 700; color: #0f172a; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
        .product-summary-meta { margin-top: 4px; color: #64748b; font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
        .product-summary-actions { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; }
        .product-summary-actions .btn { padding: 8px 12px; font-size: 13px; }
        .loading-mask { position: fixed; inset: 0; display: none; align-items: center; justify-content: center; padding: 24px; background: rgba(15, 23, 42, 0.42); z-index: 9999; }
        .loading-mask.show { display: flex; }
        .loading-card { min-width: min(92vw, 320px); padding: 24px 20px; border-radius: 20px; background: rgba(255,255,255,0.98); border: 1px solid #dbe3f0; text-align: center; box-shadow: 0 28px 70px rgba(15, 23, 42, 0.2); }
        .spinner { width: 42px; height: 42px; margin: 0 auto 14px; border-radius: 999px; border: 4px solid #dbeafe; border-top-color: #2563eb; animation: spin 0.9s linear infinite; }
        .muted-block { padding: 12px 14px; border-radius: 14px; background: #f8fafc; border: 1px solid #e2e8f0; color: #475569; font-size: 13px; line-height: 1.7; }
        @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
        @media (max-width: 1080px) {
            .grid, .toolbar, .toolbar.simple, .thumb-box, .product-picker-grid { grid-template-columns: 1fr; }
        }
        @media (max-width: 920px) {
            table, thead, tbody, th, td, tr { display: block; }
            thead { display: none; }
            tr { margin-bottom: 12px; border-bottom: 1px solid #e5e7eb; }
            td { display: flex; justify-content: space-between; gap: 10px; }
            td::before { content: attr(data-label); font-weight: 700; color: #475569; }
            .product-summary-row { grid-template-columns: 1fr; }
            .product-summary-actions { justify-content: flex-start; }
        }
    </style>
    {{ template "theme_head" . }}
</head>
<body>
    <div class="container">
        <section class="topbar">
            <h1>方案预设管理</h1>
            <div class="tiny" style="margin-top:8px;">页面负责管理 `tbl_scheme_template` 与 `tbl_scheme`。模板图标上传会写入附件表，再把附件 ID 回填到模板记录。</div>
            <div class="actions" style="margin-top:12px;">
                <a class="btn btn-light" href="/pb/manage">返回主页</a>
                <button class="btn btn-light" id="refreshAllBtn" type="button">刷新全部</button>
            </div>
            <div class="summary" style="margin-top:16px;">
                <div class="summary-card">
                    <strong id="templateCount">0</strong>
                    <span>模板数量</span>
                </div>
                <div class="summary-card">
                    <strong id="schemeCount">0</strong>
                    <span>方案数量</span>
                </div>
                <div class="summary-card">
                    <strong id="currentMode">空闲</strong>
                    <span>当前编辑状态</span>
                </div>
            </div>
            <div class="status" id="pageStatus" style="margin-top:14px;"></div>
        </section>

        <section class="panel">
            <div class="section-title">
                <div>
                    <h2>方案模板</h2>
                    <div class="tiny" style="margin-top:6px;">录入和管理 `tbl_scheme_template`。产品清单支持按分类挑选产品，并自动同步为 JSON 数组。</div>
                </div>
                <div class="actions">
                    <button class="btn btn-secondary" id="newTemplateBtn" type="button">新建模板</button>
                </div>
            </div>
            <div class="toolbar">
                <input id="templateKeyword" placeholder="按模板 ID / 名称 / 标签 / owner 搜索" />
                <input id="templateOwnerFilter" placeholder="筛选 owner(openid)" />
                <input id="templateStatusFilter" placeholder="筛选模板状态" />
                <input id="templateSolutionTypeFilter" placeholder="筛选方案类型" />
                <button class="btn btn-light" id="searchTemplateBtn" type="button">查询模板</button>
            </div>
            <div class="table-wrap" style="margin-top:14px;">
                <table>
                    <thead>
                        <tr>
                            <th>模板 ID</th>
                            <th>模板名称</th>
                            <th>Owner</th>
                            <th>标签 / 状态</th>
                            <th>方案类型 / 特色</th>
                            <th>产品清单</th>
                            <th>操作</th>
                        </tr>
                    </thead>
                    <tbody id="templateTableBody">
                        <tr><td colspan="7" class="empty">正在加载模板数据...</td></tr>
                    </tbody>
                </table>
            </div>
        </section>

        <section class="panel editor" id="templateEditorPanel">
            <div class="section-title">
                <div>
                    <h2 id="templateEditorTitle">新建方案模板</h2>
                    <div class="tiny" id="templateEditorHint" style="margin-top:6px;">未填写 `scheme_template_id` 时，将由后端自动生成。</div>
                </div>
                <span class="badge" id="templateEditorBadge">模板编辑</span>
            </div>
            <div class="grid">
                <div>
                    <label for="templateId">模板 ID</label>
                    <input id="templateId" placeholder="留空时自动生成" />
                </div>
                <div>
                    <label for="templateName">模板名称</label>
                    <input id="templateName" placeholder="必填" />
                </div>
                <div>
                    <label for="templateOwner">模板 Owner</label>
                    <input id="templateOwner" placeholder="必填，openid" />
                </div>
                <div>
                    <label for="templateStatus">模板状态</label>
                    <input id="templateStatus" placeholder="例如：active / draft" />
                </div>
                <div>
                    <label for="templateLabel">模板标签</label>
                    <input id="templateLabel" placeholder="可选" />
                </div>
                <div>
                    <label for="templateSolutionType">方案类型</label>
                    <input id="templateSolutionType" placeholder="例如：酒店 / 民宿" />
                </div>
                <div>
                    <label for="templateSolutionFeature">方案特色</label>
                    <input id="templateSolutionFeature" placeholder="例如：高性价比" />
                </div>
                <div>
                    <label for="templateIconId">模板图标附件 ID</label>
                    <input id="templateIconId" placeholder="可手输，也可通过下方上传获得" />
                </div>
                <div class="full">
                    <label>模板图标上传</label>
                    <div class="thumb-box">
                        <div id="templateIconPreview" class="thumb thumb-empty">暂无图标</div>
                        <div>
                            <div class="upload-row">
                                <input id="templateIconFile" type="file" accept="image/*" />
                                <button class="btn btn-light" id="uploadTemplateIconBtn" type="button">上传图标</button>
                                <button class="btn btn-light" id="clearTemplateIconBtn" type="button">清空图标</button>
                            </div>
                            <div class="hint">上传后会调用 `/pb/api/attachment/upload`，成功后自动回填 `scheme_template_icon`。</div>
                            <div class="hint" id="templateIconUrlText"></div>
                        </div>
                    </div>
                </div>
                <div class="full">
                    <label>产品清单构建器</label>
                    <div class="product-picker-box">
                        <div class="product-picker-grid">
                            <div>
                                <label for="templateProductCategoryFilter">产品分类</label>
                                <select id="templateProductCategoryFilter">
                                    <option value="">请先加载分类</option>
                                </select>
                            </div>
                            <div>
                                <label for="templateProductSelect">选择产品</label>
                                <select id="templateProductSelect">
                                    <option value="">请先选择分类</option>
                                </select>
                            </div>
                            <div class="product-picker-actions">
                                <button class="btn btn-secondary" id="addTemplateProductBtn" type="button">加入产品</button>
                            </div>
                            <div class="product-picker-actions">
                                <button class="btn btn-light" id="reloadTemplateProductsBtn" type="button">刷新产品源</button>
                            </div>
                        </div>
                        <div class="hint">选择逻辑与产品管理页一致：先选分类，再选产品。加入后会把该产品整行数据写入下方 JSON 数组。</div>
                        <div class="product-list-summary" id="templateProductSummary">
                            <div class="product-list-empty">暂无已选产品，请先选择分类和产品后加入。</div>
                        </div>
                    </div>
                </div>
                <div class="full">
                    <label for="templateProductList">产品清单 JSON</label>
                    <textarea id="templateProductList" placeholder='示例：[{"prod_list_id":"PROD-001","prod_list_name":"智能面板","prod_list_barcode":"PLB-001","prod_list_category":"客控主机"}]'></textarea>
                    <div class="hint">默认由上方构建器自动同步。你也可以直接手动编辑 JSON，失去焦点后页面会尝试重新渲染简要清单。</div>
                </div>
                <div class="full">
                    <label for="templateDescription">模板说明</label>
                    <textarea id="templateDescription" placeholder="可选"></textarea>
                </div>
                <div class="full">
                    <label for="templateRemark">备注</label>
                    <textarea id="templateRemark" placeholder="可选"></textarea>
                </div>
            </div>
            <div class="form-actions" style="margin-top:14px;">
                <button class="btn btn-primary" id="saveTemplateBtn" type="button">保存模板</button>
                <button class="btn btn-light" id="resetTemplateBtn" type="button">重置表单</button>
                <button class="btn btn-light" id="cancelTemplateBtn" type="button">收起编辑器</button>
            </div>
            <div class="status" id="templateStatusText" style="margin-top:12px;"></div>
        </section>

        <section class="panel">
            <div class="section-title">
                <div>
                    <h2>方案管理</h2>
                    <div class="tiny" style="margin-top:6px;">管理 `tbl_scheme`，高/中/低配模板下拉会联动当前加载到的模板列表。</div>
                </div>
                <div class="actions">
                    <button class="btn btn-secondary" id="newSchemeBtn" type="button">新建方案</button>
                </div>
            </div>
            <div class="toolbar simple">
                <input id="schemeKeyword" placeholder="按方案 ID / 名称 / owner 搜索" />
                <input id="schemeOwnerFilter" placeholder="筛选 owner(openid)" />
                <input id="schemeStatusFilter" placeholder="筛选方案状态" />
                <button class="btn btn-light" id="searchSchemeBtn" type="button">查询方案</button>
            </div>
            <div class="table-wrap" style="margin-top:14px;">
                <table>
                    <thead>
                        <tr>
                            <th>方案 ID</th>
                            <th>方案名称</th>
                            <th>Owner</th>
                            <th>状态 / 分享</th>
                            <th>模板组合</th>
                            <th>房型</th>
                            <th>操作</th>
                        </tr>
                    </thead>
                    <tbody id="schemeTableBody">
                        <tr><td colspan="7" class="empty">正在加载方案数据...</td></tr>
                    </tbody>
                </table>
            </div>
        </section>

        <section class="panel editor" id="schemeEditorPanel">
            <div class="section-title">
                <div>
                    <h2 id="schemeEditorTitle">新建方案</h2>
                    <div class="tiny" id="schemeEditorHint" style="margin-top:6px;">未填写 `scheme_id` 时，将由后端自动生成。</div>
                </div>
                <span class="badge" id="schemeEditorBadge">方案编辑</span>
            </div>
            <div class="grid">
                <div>
                    <label for="schemeId">方案 ID</label>
                    <input id="schemeId" placeholder="留空时自动生成" />
                </div>
                <div>
                    <label for="schemeName">方案名称</label>
                    <input id="schemeName" placeholder="必填" />
                </div>
                <div>
                    <label for="schemeOwner">方案 Owner</label>
                    <input id="schemeOwner" placeholder="必填，openid" />
                </div>
                <div>
                    <label for="schemeShareStatus">分享状态</label>
                    <input id="schemeShareStatus" placeholder="例如：private / shared" />
                </div>
                <div>
                    <label for="schemeExpiresAt">过期时间</label>
                    <input id="schemeExpiresAt" type="datetime-local" />
                </div>
                <div>
                    <label for="schemeStatus">方案状态</label>
                    <input id="schemeStatus" placeholder="例如：active / draft" />
                </div>
                <div>
                    <label for="schemeHotelType">酒店类型</label>
                    <input id="schemeHotelType" placeholder="可选" />
                </div>
                <div>
                    <label for="schemeSolutionType">方案类型</label>
                    <input id="schemeSolutionType" placeholder="可选" />
                </div>
                <div>
                    <label for="schemeSolutionFeature">方案特色</label>
                    <input id="schemeSolutionFeature" placeholder="可选" />
                </div>
                <div>
                    <label for="schemeCurtains">窗帘配置</label>
                    <input id="schemeCurtains" placeholder="可选" />
                </div>
                <div>
                    <label for="schemeVoiceDevice">语音设备</label>
                    <input id="schemeVoiceDevice" placeholder="可选" />
                </div>
                <div>
                    <label for="schemeAcType">空调类型</label>
                    <input id="schemeAcType" placeholder="可选" />
                </div>
                <div>
                    <label for="schemeTemplateHighend">高配模板</label>
                    <select id="schemeTemplateHighend"></select>
                </div>
                <div>
                    <label for="schemeTemplateMidend">中配模板</label>
                    <select id="schemeTemplateMidend"></select>
                </div>
                <div>
                    <label for="schemeTemplateLowend">低配模板</label>
                    <select id="schemeTemplateLowend"></select>
                </div>
                <div class="full">
                    <label for="schemeRoomType">房型 JSON</label>
                    <textarea id="schemeRoomType" placeholder='示例：[{"room_type":"大床房","qty":20},{"room_type":"双床房","qty":18}]'></textarea>
                    <div class="hint">必须是数组，每项至少包含 `room_type` 与正数 `qty`。</div>
                </div>
                <div class="full">
                    <label for="schemeRemark">备注</label>
                    <textarea id="schemeRemark" placeholder="可选"></textarea>
                </div>
            </div>
            <div class="muted-block" style="margin-top:14px;">
                当前页面使用 ManagePlatform hooks 权限执行管理操作，因此管理员可统一维护模板与方案；终端用户走 PB 直接访问时，仍受各表 owner 规则约束。
            </div>
            <div class="form-actions" style="margin-top:14px;">
                <button class="btn btn-primary" id="saveSchemeBtn" type="button">保存方案</button>
                <button class="btn btn-light" id="resetSchemeBtn" type="button">重置表单</button>
                <button class="btn btn-light" id="cancelSchemeBtn" type="button">收起编辑器</button>
            </div>
            <div class="status" id="schemeStatusText" style="margin-top:12px;"></div>
        </section>
    </div>

    <div class="loading-mask" id="loadingMask">
        <div class="loading-card">
            <div class="spinner"></div>
            <div id="loadingText">处理中...</div>
        </div>
    </div>

    <script>
        const tokenKey = 'pb_manage_token'
        const API_BASE = '/pb/api'
        const pageStatusEl = document.getElementById('pageStatus')
        const templateStatusEl = document.getElementById('templateStatusText')
        const schemeStatusEl = document.getElementById('schemeStatusText')
        const templateTableBodyEl = document.getElementById('templateTableBody')
        const schemeTableBodyEl = document.getElementById('schemeTableBody')
        const templateEditorPanelEl = document.getElementById('templateEditorPanel')
        const schemeEditorPanelEl = document.getElementById('schemeEditorPanel')
        const loadingMaskEl = document.getElementById('loadingMask')
        const loadingTextEl = document.getElementById('loadingText')
        const currentModeEl = document.getElementById('currentMode')
        const templateCountEl = document.getElementById('templateCount')
        const schemeCountEl = document.getElementById('schemeCount')
        const templateProductSummaryEl = document.getElementById('templateProductSummary')

        const productDictNameMap = {
            category: '产品-产品分类',
        }

        const state = {
            templateMode: 'idle',
            schemeMode: 'idle',
            editingTemplateId: '',
            editingSchemeId: '',
            templates: [],
            schemes: [],
            lastUploadedTemplateIcon: null,
            productDictionariesByName: {},
            selectableProducts: [],
            templateProductItems: [],
        }

        const templateFields = {
            id: document.getElementById('templateId'),
            name: document.getElementById('templateName'),
            owner: document.getElementById('templateOwner'),
            status: document.getElementById('templateStatus'),
            label: document.getElementById('templateLabel'),
            solutionType: document.getElementById('templateSolutionType'),
            solutionFeature: document.getElementById('templateSolutionFeature'),
            iconId: document.getElementById('templateIconId'),
            iconFile: document.getElementById('templateIconFile'),
            productCategoryFilter: document.getElementById('templateProductCategoryFilter'),
            productSelect: document.getElementById('templateProductSelect'),
            productSummary: templateProductSummaryEl,
            productList: document.getElementById('templateProductList'),
            description: document.getElementById('templateDescription'),
            remark: document.getElementById('templateRemark'),
            title: document.getElementById('templateEditorTitle'),
            hint: document.getElementById('templateEditorHint'),
            badge: document.getElementById('templateEditorBadge'),
            iconPreview: document.getElementById('templateIconPreview'),
            iconUrlText: document.getElementById('templateIconUrlText'),
        }

        const schemeFields = {
            id: document.getElementById('schemeId'),
            name: document.getElementById('schemeName'),
            owner: document.getElementById('schemeOwner'),
            shareStatus: document.getElementById('schemeShareStatus'),
            expiresAt: document.getElementById('schemeExpiresAt'),
            hotelType: document.getElementById('schemeHotelType'),
            solutionType: document.getElementById('schemeSolutionType'),
            solutionFeature: document.getElementById('schemeSolutionFeature'),
            curtains: document.getElementById('schemeCurtains'),
            voiceDevice: document.getElementById('schemeVoiceDevice'),
            acType: document.getElementById('schemeAcType'),
            templateHighend: document.getElementById('schemeTemplateHighend'),
            templateMidend: document.getElementById('schemeTemplateMidend'),
            templateLowend: document.getElementById('schemeTemplateLowend'),
            roomType: document.getElementById('schemeRoomType'),
            status: document.getElementById('schemeStatus'),
            remark: document.getElementById('schemeRemark'),
            title: document.getElementById('schemeEditorTitle'),
            hint: document.getElementById('schemeEditorHint'),
            badge: document.getElementById('schemeEditorBadge'),
        }

        function getToken() {
            return localStorage.getItem(tokenKey) || ''
        }

        function getApiUrl(path) {
            return API_BASE + path
        }

        function normalizeText(value) {
            return String(value || '').replace(/^\s+|\s+$/g, '')
        }

        function escapeHtml(value) {
            return String(value || '')
                .replace(/&/g, '&amp;')
                .replace(/</g, '&lt;')
                .replace(/>/g, '&gt;')
                .replace(/"/g, '&quot;')
                .replace(/'/g, '&#39;')
        }

        function setStatus(target, message, type) {
            if (!target) {
                return
            }
            target.textContent = message || ''
            target.className = 'status' + (type ? (' ' + type) : '')
        }

        function showLoading(text) {
            loadingTextEl.textContent = text || '处理中...'
            loadingMaskEl.classList.add('show')
        }

        function hideLoading() {
            loadingMaskEl.classList.remove('show')
        }

        function updateCurrentMode() {
            const parts = []
            if (state.templateMode !== 'idle') {
                parts.push('模板-' + (state.templateMode === 'edit' ? '编辑' : '新建'))
            }
            if (state.schemeMode !== 'idle') {
                parts.push('方案-' + (state.schemeMode === 'edit' ? '编辑' : '新建'))
            }
            currentModeEl.textContent = parts.length ? parts.join(' / ') : '空闲'
        }

        function unwrapApiResponse(data, response) {
            if (data && typeof data === 'object' && Object.prototype.hasOwnProperty.call(data, 'code')) {
                return {
                    code: Number(data.code || 0),
                    errMsg: data.errMsg || data.message || '',
                    payload: Object.prototype.hasOwnProperty.call(data, 'data') ? data.data : data,
                }
            }

            return {
                code: response && !response.ok ? response.status : 0,
                errMsg: '',
                payload: data,
            }
        }

        async function parseJsonSafe(response) {
            try {
                const json = await response.json()
                return { isJson: true, json: json }
            } catch (_error) {
                return { isJson: false, json: null }
            }
        }

        async function requestJson(path, payload) {
            const token = getToken()
            if (!token) {
                window.location.replace('/pb/manage/login')
                throw new Error('登录状态已失效，请重新登录')
            }

            const response = await fetch(getApiUrl(path), {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    Authorization: 'Bearer ' + token,
                },
                body: JSON.stringify(payload || {}),
            })

            const parsed = await parseJsonSafe(response)
            const unwrapped = unwrapApiResponse(parsed.json, response)
            if (!response.ok || unwrapped.code >= 400) {
                if (response.status === 401 || response.status === 403 || unwrapped.code === 401 || unwrapped.code === 403) {
                    localStorage.removeItem('pb_manage_token')
                    localStorage.removeItem('pb_manage_logged_in')
                    window.location.replace('/pb/manage/login')
                }
                throw new Error(unwrapped.errMsg || '请求失败')
            }

            return unwrapped.payload
        }

        async function uploadAttachment(file) {
            const token = getToken()
            const form = new FormData()
            form.append('attachments_link', file)
            form.append('attachments_filename', file.name || '')
            form.append('attachments_filetype', file.type || '')
            form.append('attachments_size', String(file.size || 0))
            form.append('attachments_status', 'active')
            form.append('attachments_remark', 'scheme-manage:template-icon')

            const response = await fetch(getApiUrl('/attachment/upload'), {
                method: 'POST',
                headers: {
                    Authorization: 'Bearer ' + token,
                },
                body: form,
            })

            const parsed = await parseJsonSafe(response)
            const unwrapped = unwrapApiResponse(parsed.json, response)
            if (!response.ok || unwrapped.code >= 400) {
                throw new Error(unwrapped.errMsg || '上传图标失败')
            }

            return unwrapped.payload
        }

        function toJsonText(value) {
            try {
                return JSON.stringify(value || [], null, 2)
            } catch (_error) {
                return '[]'
            }
        }

        function parseJsonTextarea(text, fieldName) {
            const value = normalizeText(text)
            if (!value) {
                return []
            }

            try {
                const parsed = JSON.parse(value)
                if (!Array.isArray(parsed)) {
                    throw new Error(fieldName + ' 必须是 JSON 数组')
                }
                return parsed
            } catch (error) {
                throw new Error(error && error.message ? error.message : (fieldName + ' JSON 解析失败'))
            }
        }

        function cloneJsonValue(value) {
            try {
                return JSON.parse(JSON.stringify(value))
            } catch (_error) {
                return null
            }
        }

        function findProductDictByName(name) {
            return state.productDictionariesByName[name] || null
        }

        function getProductDictItems(dictName) {
            const dict = findProductDictByName(dictName)
            if (!dict || !Array.isArray(dict.items)) {
                return []
            }
            return dict.items
        }

        function fillSelectByDict(selectEl, dictName, defaultLabel) {
            if (!selectEl) {
                return
            }

            const items = getProductDictItems(dictName)
            const currentValue = selectEl.value || ''
            let html = '<option value="">' + escapeHtml(defaultLabel || '请选择') + '</option>'

            for (let i = 0; i < items.length; i += 1) {
                const value = normalizeText(items[i].enum || '')
                const label = normalizeText(items[i].description || value)
                html += '<option value="' + escapeHtml(value) + '">' + escapeHtml(label) + '</option>'
            }

            selectEl.innerHTML = html
            if (currentValue) {
                selectEl.value = currentValue
            }
        }

        function getTemplateProductId(item) {
            return normalizeText(item && (item.prod_list_id || item.product_id))
        }

        function getProductSourceById(productId) {
            const targetId = normalizeText(productId)
            if (!targetId) {
                return null
            }

            for (let i = 0; i < state.selectableProducts.length; i += 1) {
                if (normalizeText(state.selectableProducts[i].prod_list_id) === targetId) {
                    return state.selectableProducts[i]
                }
            }

            return null
        }

        function buildTemplateProductSummary(item) {
            const current = item && typeof item === 'object' ? item : {}
            const productId = getTemplateProductId(current)
            const source = getProductSourceById(productId) || current
            const title = normalizeText(source.prod_list_name || source.product_name || productId || '未命名产品')
            const model = normalizeText(source.prod_list_modelnumber)
            const barcode = normalizeText(source.prod_list_barcode || source.product_barcode)
            const category = normalizeText(source.prod_list_category)
            const status = normalizeText(source.prod_list_status)
            const price = source && (source.prod_list_basic_price || source.prod_list_basic_price === 0)
                ? ('¥' + source.prod_list_basic_price)
                : ''
            const qty = current && (current.qty || current.qty === 0) ? ('数量 ' + current.qty) : ''
            const note = normalizeText(current && current.note)

            return {
                productId: productId,
                title: title,
                meta: [productId, model, barcode, category, status, price, qty, note].filter(Boolean).join(' / '),
            }
        }

        function syncTemplateProductListTextarea() {
            templateFields.productList.value = toJsonText(state.templateProductItems)
        }

        function renderTemplateProductSummary() {
            if (!templateFields.productSummary) {
                return
            }

            if (!state.templateProductItems.length) {
                templateFields.productSummary.innerHTML = '<div class="product-list-empty">暂无已选产品，请先选择分类和产品后加入。</div>'
                return
            }

            const rows = []
            for (let i = 0; i < state.templateProductItems.length; i += 1) {
                const summary = buildTemplateProductSummary(state.templateProductItems[i])
                rows.push(
                    '<div class="product-summary-row">'
                    + '<div class="product-summary-order">' + (i + 1) + '</div>'
                    + '<div class="product-summary-main">'
                    + '<div class="product-summary-title">' + escapeHtml(summary.title) + '</div>'
                    + '<div class="product-summary-meta">' + escapeHtml(summary.meta || '未补充摘要信息') + '</div>'
                    + '</div>'
                    + '<div class="product-summary-actions">'
                    + '<button class="btn btn-light" type="button" data-template-product-up="' + i + '"' + (i === 0 ? ' disabled' : '') + '>上移</button>'
                    + '<button class="btn btn-light" type="button" data-template-product-down="' + i + '"' + (i === state.templateProductItems.length - 1 ? ' disabled' : '') + '>下移</button>'
                    + '<button class="btn btn-danger" type="button" data-template-product-remove="' + i + '">移除</button>'
                    + '</div>'
                    + '</div>'
                )
            }

            templateFields.productSummary.innerHTML = rows.join('')
        }

        function setTemplateProductItems(items, options) {
            const safeItems = Array.isArray(items) ? items : []
            const nextItems = []

            for (let i = 0; i < safeItems.length; i += 1) {
                const cloned = cloneJsonValue(safeItems[i])
                if (cloned && typeof cloned === 'object') {
                    nextItems.push(cloned)
                }
            }

            state.templateProductItems = nextItems
            if (!options || !options.skipTextareaSync) {
                syncTemplateProductListTextarea()
            }
            renderTemplateProductSummary()
        }

        function hydrateTemplateProductItemsFromTextarea() {
            try {
                const parsed = parseJsonTextarea(templateFields.productList.value, 'scheme_template_product_list')
                setTemplateProductItems(parsed)
                setStatus(templateStatusEl, '产品清单 JSON 已同步为简要列表。', 'success')
                return parsed
            } catch (error) {
                setStatus(templateStatusEl, error.message || '产品清单 JSON 解析失败', 'error')
                throw error
            }
        }

        function renderTemplateProductCategoryOptions() {
            fillSelectByDict(templateFields.productCategoryFilter, productDictNameMap.category, '全部分类')
        }

        function renderTemplateProductOptions() {
            const category = normalizeText(templateFields.productCategoryFilter.value)
            const options = ['<option value="">' + escapeHtml(category ? '请选择产品' : '请先选择分类') + '</option>']
            const items = []

            for (let i = 0; i < state.selectableProducts.length; i += 1) {
                const item = state.selectableProducts[i]
                if (category && normalizeText(item.prod_list_category) !== category) {
                    continue
                }
                items.push(item)
            }

            items.sort(function (a, b) {
                const sortDiff = Number(a.prod_list_sort || 0) - Number(b.prod_list_sort || 0)
                if (sortDiff !== 0) {
                    return sortDiff
                }
                return String(a.prod_list_name || '').localeCompare(String(b.prod_list_name || ''))
            })

            for (let i = 0; i < items.length; i += 1) {
                const item = items[i]
                const value = normalizeText(item.prod_list_id)
                const label = [
                    normalizeText(item.prod_list_name),
                    normalizeText(item.prod_list_modelnumber),
                    normalizeText(item.prod_list_barcode),
                    normalizeText(item.prod_list_category),
                ].filter(Boolean).join(' / ')
                options.push('<option value="' + escapeHtml(value) + '">' + escapeHtml(label || value) + '</option>')
            }

            templateFields.productSelect.innerHTML = options.join('')
        }

        async function loadTemplateProductResources(skipStatus) {
            if (!skipStatus) {
                showLoading('正在加载产品分类与产品库...')
            }
            try {
                const dictionaries = await requestJson('/dictionary/list', {})
                const dictItems = Array.isArray(dictionaries && dictionaries.items) ? dictionaries.items : []
                state.productDictionariesByName = {}
                for (let i = 0; i < dictItems.length; i += 1) {
                    state.productDictionariesByName[dictItems[i].dict_name] = dictItems[i]
                }

                const products = await requestJson('/product/list', {
                    keyword: '',
                    status: '',
                    prod_list_category: '',
                })
                state.selectableProducts = Array.isArray(products && products.items) ? products.items : []
                renderTemplateProductCategoryOptions()
                renderTemplateProductOptions()
                renderTemplateTable()
                renderTemplateProductSummary()

                if (!skipStatus) {
                    setStatus(pageStatusEl, '模板可选产品数据已刷新。', 'success')
                }
            } catch (error) {
                if (!skipStatus) {
                    setStatus(pageStatusEl, error.message || '加载模板可选产品失败', 'error')
                }
                throw error
            } finally {
                if (!skipStatus) {
                    hideLoading()
                }
            }
        }

        async function addSelectedTemplateProduct() {
            const productId = normalizeText(templateFields.productSelect.value)
            if (!productId) {
                setStatus(templateStatusEl, '请先选择要加入模板的产品。', 'error')
                return
            }

            for (let i = 0; i < state.templateProductItems.length; i += 1) {
                if (getTemplateProductId(state.templateProductItems[i]) === productId) {
                    setStatus(templateStatusEl, '该产品已在清单中，可直接调整顺序。', 'error')
                    return
                }
            }

            showLoading('正在读取产品详情...')
            try {
                const detail = await requestJson('/product/detail', { prod_list_id: productId })
                const nextItems = state.templateProductItems.slice()
                nextItems.push(detail)
                setTemplateProductItems(nextItems)
                setStatus(templateStatusEl, '产品已加入模板清单。', 'success')
            } catch (error) {
                setStatus(templateStatusEl, error.message || '加入产品失败', 'error')
            } finally {
                hideLoading()
            }
        }

        function moveTemplateProductItem(index, offset) {
            const currentIndex = Number(index)
            const targetIndex = currentIndex + Number(offset)
            if (!Number.isInteger(currentIndex) || currentIndex < 0 || currentIndex >= state.templateProductItems.length) {
                return
            }
            if (!Number.isInteger(targetIndex) || targetIndex < 0 || targetIndex >= state.templateProductItems.length) {
                return
            }

            const nextItems = state.templateProductItems.slice()
            const currentItem = nextItems[currentIndex]
            nextItems[currentIndex] = nextItems[targetIndex]
            nextItems[targetIndex] = currentItem
            setTemplateProductItems(nextItems)
            setStatus(templateStatusEl, '产品顺序已调整。', 'success')
        }

        function removeTemplateProductItem(index) {
            const currentIndex = Number(index)
            if (!Number.isInteger(currentIndex) || currentIndex < 0 || currentIndex >= state.templateProductItems.length) {
                return
            }

            const nextItems = state.templateProductItems.slice()
            nextItems.splice(currentIndex, 1)
            setTemplateProductItems(nextItems)
            setStatus(templateStatusEl, '产品已从模板清单移除。', 'success')
        }

        function formatDateTimeInput(value) {
            const text = normalizeText(value)
            if (!text) {
                return ''
            }
            if (text.indexOf('T') !== -1) {
                return text.slice(0, 16)
            }
            return text.replace(' ', 'T').slice(0, 16)
        }

        function renderTemplateIcon(url) {
            const iconUrl = normalizeText(url)
            if (!iconUrl) {
                templateFields.iconPreview.className = 'thumb thumb-empty'
                templateFields.iconPreview.innerHTML = '暂无图标'
                templateFields.iconUrlText.textContent = ''
                return
            }

            templateFields.iconPreview.className = 'thumb'
            templateFields.iconPreview.innerHTML = '<img class="thumb" src="' + escapeHtml(iconUrl) + '" alt="模板图标" />'
            templateFields.iconUrlText.textContent = iconUrl
        }

        function renderTemplateOptions() {
            const options = ['<option value="">未选择</option>']
            for (let i = 0; i < state.templates.length; i += 1) {
                const item = state.templates[i]
                const value = normalizeText(item.scheme_template_id)
                const label = value + ' | ' + normalizeText(item.scheme_template_name)
                options.push('<option value="' + escapeHtml(value) + '">' + escapeHtml(label) + '</option>')
            }

            schemeFields.templateHighend.innerHTML = options.join('')
            schemeFields.templateMidend.innerHTML = options.join('')
            schemeFields.templateLowend.innerHTML = options.join('')
        }

        function renderTemplateTable() {
            templateCountEl.textContent = String(state.templates.length)
            if (!state.templates.length) {
                templateTableBodyEl.innerHTML = '<tr><td colspan="7" class="empty">暂无模板数据</td></tr>'
                return
            }

            const rows = []
            for (let i = 0; i < state.templates.length; i += 1) {
                const item = state.templates[i]
                const productList = Array.isArray(item.scheme_template_product_list) ? item.scheme_template_product_list : []
                const productSummary = productList.length
                    ? productList.map(function (productItem, index) {
                        const summary = buildTemplateProductSummary(productItem)
                        return (index + 1) + '. ' + summary.title + (summary.meta ? (' / ' + summary.meta) : '')
                    }).join('\n')
                    : '-'
                rows.push(
                    '<tr>'
                    + '<td data-label="模板 ID"><div class="code">' + escapeHtml(item.scheme_template_id) + '</div></td>'
                    + '<td data-label="模板名称">' + escapeHtml(item.scheme_template_name) + '</td>'
                    + '<td data-label="Owner"><div class="code">' + escapeHtml(item.scheme_template_owner) + '</div></td>'
                    + '<td data-label="标签 / 状态">' + escapeHtml(item.scheme_template_label || '-') + '<br /><span class="tiny">' + escapeHtml(item.scheme_template_status || '-') + '</span></td>'
                    + '<td data-label="方案类型 / 特色">' + escapeHtml(item.scheme_template_solution_type || '-') + '<br /><span class="tiny">' + escapeHtml(item.scheme_template_solution_feature || '-') + '</span></td>'
                    + '<td data-label="产品清单"><div class="json-preview tiny">' + escapeHtml(productSummary) + '</div></td>'
                    + '<td data-label="操作"><div class="actions">'
                    + '<button class="btn btn-light" type="button" data-template-edit="' + escapeHtml(item.scheme_template_id) + '">编辑</button>'
                    + '<button class="btn btn-danger" type="button" data-template-delete="' + escapeHtml(item.scheme_template_id) + '">删除</button>'
                    + '</div></td>'
                    + '</tr>'
                )
            }

            templateTableBodyEl.innerHTML = rows.join('')
        }

        function renderSchemeTable() {
            schemeCountEl.textContent = String(state.schemes.length)
            if (!state.schemes.length) {
                schemeTableBodyEl.innerHTML = '<tr><td colspan="7" class="empty">暂无方案数据</td></tr>'
                return
            }

            const rows = []
            for (let i = 0; i < state.schemes.length; i += 1) {
                const item = state.schemes[i]
                const roomType = Array.isArray(item.scheme_room_type) ? item.scheme_room_type : []
                const templateSummary = [
                    item.scheme_template_highend ? ('高配: ' + item.scheme_template_highend_name + ' (' + item.scheme_template_highend + ')') : '',
                    item.scheme_template_midend ? ('中配: ' + item.scheme_template_midend_name + ' (' + item.scheme_template_midend + ')') : '',
                    item.scheme_template_lowend ? ('低配: ' + item.scheme_template_lowend_name + ' (' + item.scheme_template_lowend + ')') : '',
                ].filter(Boolean).join('\n')

                rows.push(
                    '<tr>'
                    + '<td data-label="方案 ID"><div class="code">' + escapeHtml(item.scheme_id) + '</div></td>'
                    + '<td data-label="方案名称">' + escapeHtml(item.scheme_name) + '</td>'
                    + '<td data-label="Owner"><div class="code">' + escapeHtml(item.scheme_owner) + '</div></td>'
                    + '<td data-label="状态 / 分享">' + escapeHtml(item.scheme_status || '-') + '<br /><span class="tiny">' + escapeHtml(item.scheme_share_status || '-') + '</span></td>'
                    + '<td data-label="模板组合"><div class="json-preview tiny">' + escapeHtml(templateSummary || '-') + '</div></td>'
                    + '<td data-label="房型"><div class="json-preview tiny">' + escapeHtml(toJsonText(roomType)) + '</div></td>'
                    + '<td data-label="操作"><div class="actions">'
                    + '<button class="btn btn-light" type="button" data-scheme-edit="' + escapeHtml(item.scheme_id) + '">编辑</button>'
                    + '<button class="btn btn-danger" type="button" data-scheme-delete="' + escapeHtml(item.scheme_id) + '">删除</button>'
                    + '</div></td>'
                    + '</tr>'
                )
            }

            schemeTableBodyEl.innerHTML = rows.join('')
        }

        function resetTemplateForm() {
            templateFields.id.value = ''
            templateFields.name.value = ''
            templateFields.owner.value = ''
            templateFields.status.value = ''
            templateFields.label.value = ''
            templateFields.solutionType.value = ''
            templateFields.solutionFeature.value = ''
            templateFields.iconId.value = ''
            templateFields.iconFile.value = ''
            templateFields.productCategoryFilter.value = ''
            templateFields.productSelect.innerHTML = '<option value="">请先选择分类</option>'
            templateFields.description.value = ''
            templateFields.remark.value = ''
            state.lastUploadedTemplateIcon = null
            setTemplateProductItems([])
            renderTemplateIcon('')
            renderTemplateProductOptions()
        }

        function resetSchemeForm() {
            schemeFields.id.value = ''
            schemeFields.name.value = ''
            schemeFields.owner.value = ''
            schemeFields.shareStatus.value = ''
            schemeFields.expiresAt.value = ''
            schemeFields.hotelType.value = ''
            schemeFields.solutionType.value = ''
            schemeFields.solutionFeature.value = ''
            schemeFields.curtains.value = ''
            schemeFields.voiceDevice.value = ''
            schemeFields.acType.value = ''
            schemeFields.templateHighend.value = ''
            schemeFields.templateMidend.value = ''
            schemeFields.templateLowend.value = ''
            schemeFields.roomType.value = '[]'
            schemeFields.status.value = ''
            schemeFields.remark.value = ''
        }

        function enterTemplateCreateMode() {
            state.templateMode = 'create'
            state.editingTemplateId = ''
            templateEditorPanelEl.classList.add('show')
            templateFields.title.textContent = '新建方案模板'
            templateFields.hint.textContent = '未填写 `scheme_template_id` 时，将由后端自动生成。'
            templateFields.badge.textContent = '模板新建'
            resetTemplateForm()
            updateCurrentMode()
            setStatus(templateStatusEl, '', '')
            window.scrollTo({ top: templateEditorPanelEl.offsetTop - 12, behavior: 'smooth' })
        }

        function enterSchemeCreateMode() {
            state.schemeMode = 'create'
            state.editingSchemeId = ''
            schemeEditorPanelEl.classList.add('show')
            schemeFields.title.textContent = '新建方案'
            schemeFields.hint.textContent = '未填写 `scheme_id` 时，将由后端自动生成。'
            schemeFields.badge.textContent = '方案新建'
            resetSchemeForm()
            updateCurrentMode()
            setStatus(schemeStatusEl, '', '')
            window.scrollTo({ top: schemeEditorPanelEl.offsetTop - 12, behavior: 'smooth' })
        }

        function leaveTemplateEditor() {
            state.templateMode = 'idle'
            state.editingTemplateId = ''
            templateEditorPanelEl.classList.remove('show')
            setStatus(templateStatusEl, '', '')
            updateCurrentMode()
        }

        function leaveSchemeEditor() {
            state.schemeMode = 'idle'
            state.editingSchemeId = ''
            schemeEditorPanelEl.classList.remove('show')
            setStatus(schemeStatusEl, '', '')
            updateCurrentMode()
        }

        async function loadTemplates() {
            const payload = {
                keyword: normalizeText(document.getElementById('templateKeyword').value),
                scheme_template_owner: normalizeText(document.getElementById('templateOwnerFilter').value),
                scheme_template_status: normalizeText(document.getElementById('templateStatusFilter').value),
                scheme_template_solution_type: normalizeText(document.getElementById('templateSolutionTypeFilter').value),
            }

            const data = await requestJson('/scheme-template/list', payload)
            state.templates = Array.isArray(data && data.items) ? data.items : []
            renderTemplateTable()
            renderTemplateOptions()
        }

        async function loadSchemes() {
            const payload = {
                keyword: normalizeText(document.getElementById('schemeKeyword').value),
                scheme_owner: normalizeText(document.getElementById('schemeOwnerFilter').value),
                scheme_status: normalizeText(document.getElementById('schemeStatusFilter').value),
            }

            const data = await requestJson('/scheme/list', payload)
            state.schemes = Array.isArray(data && data.items) ? data.items : []
            renderSchemeTable()
        }

        async function refreshAll() {
            showLoading('正在刷新模板与方案数据...')
            try {
                await Promise.all([
                    loadTemplates(),
                    loadSchemes(),
                    loadTemplateProductResources(true),
                ])
                setStatus(pageStatusEl, '数据刷新完成。', 'success')
            } catch (error) {
                setStatus(pageStatusEl, error.message || '刷新失败', 'error')
            } finally {
                hideLoading()
            }
        }

        async function editTemplate(templateId) {
            showLoading('正在加载模板详情...')
            try {
                const detail = await requestJson('/scheme-template/detail', { scheme_template_id: templateId })
                state.templateMode = 'edit'
                state.editingTemplateId = detail.scheme_template_id || ''
                templateEditorPanelEl.classList.add('show')
                templateFields.title.textContent = '编辑方案模板'
                templateFields.hint.textContent = '编辑模式下必须保留 `scheme_template_id`。'
                templateFields.badge.textContent = '模板编辑'
                templateFields.id.value = detail.scheme_template_id || ''
                templateFields.name.value = detail.scheme_template_name || ''
                templateFields.owner.value = detail.scheme_template_owner || ''
                templateFields.status.value = detail.scheme_template_status || ''
                templateFields.label.value = detail.scheme_template_label || ''
                templateFields.solutionType.value = detail.scheme_template_solution_type || ''
                templateFields.solutionFeature.value = detail.scheme_template_solution_feature || ''
                templateFields.iconId.value = detail.scheme_template_icon || ''
                setTemplateProductItems(detail.scheme_template_product_list || [])
                templateFields.description.value = detail.scheme_template_description || ''
                templateFields.remark.value = detail.scheme_template_remark || ''
                state.lastUploadedTemplateIcon = detail.scheme_template_icon_attachment || null
                renderTemplateIcon(detail.scheme_template_icon_url || '')
                updateCurrentMode()
                setStatus(templateStatusEl, '已进入模板编辑模式。', 'success')
                window.scrollTo({ top: templateEditorPanelEl.offsetTop - 12, behavior: 'smooth' })
            } catch (error) {
                setStatus(pageStatusEl, error.message || '加载模板详情失败', 'error')
            } finally {
                hideLoading()
            }
        }

        async function editScheme(schemeId) {
            showLoading('正在加载方案详情...')
            try {
                const detail = await requestJson('/scheme/detail', { scheme_id: schemeId })
                state.schemeMode = 'edit'
                state.editingSchemeId = detail.scheme_id || ''
                schemeEditorPanelEl.classList.add('show')
                schemeFields.title.textContent = '编辑方案'
                schemeFields.hint.textContent = '编辑模式下必须保留 `scheme_id`。'
                schemeFields.badge.textContent = '方案编辑'
                schemeFields.id.value = detail.scheme_id || ''
                schemeFields.name.value = detail.scheme_name || ''
                schemeFields.owner.value = detail.scheme_owner || ''
                schemeFields.shareStatus.value = detail.scheme_share_status || ''
                schemeFields.expiresAt.value = formatDateTimeInput(detail.scheme_expires_at || '')
                schemeFields.hotelType.value = detail.scheme_hotel_type || ''
                schemeFields.solutionType.value = detail.scheme_solution_type || ''
                schemeFields.solutionFeature.value = detail.scheme_solution_feature || ''
                schemeFields.curtains.value = detail.scheme_curtains || ''
                schemeFields.voiceDevice.value = detail.scheme_voice_device || ''
                schemeFields.acType.value = detail.scheme_ac_type || ''
                schemeFields.templateHighend.value = detail.scheme_template_highend || ''
                schemeFields.templateMidend.value = detail.scheme_template_midend || ''
                schemeFields.templateLowend.value = detail.scheme_template_lowend || ''
                schemeFields.roomType.value = toJsonText(detail.scheme_room_type || [])
                schemeFields.status.value = detail.scheme_status || ''
                schemeFields.remark.value = detail.scheme_remark || ''
                updateCurrentMode()
                setStatus(schemeStatusEl, '已进入方案编辑模式。', 'success')
                window.scrollTo({ top: schemeEditorPanelEl.offsetTop - 12, behavior: 'smooth' })
            } catch (error) {
                setStatus(pageStatusEl, error.message || '加载方案详情失败', 'error')
            } finally {
                hideLoading()
            }
        }

        function buildTemplatePayload() {
            const productList = parseJsonTextarea(templateFields.productList.value, 'scheme_template_product_list')
            setTemplateProductItems(productList)
            return {
                scheme_template_id: normalizeText(templateFields.id.value),
                scheme_template_name: normalizeText(templateFields.name.value),
                scheme_template_owner: normalizeText(templateFields.owner.value),
                scheme_template_icon: normalizeText(templateFields.iconId.value),
                scheme_template_label: normalizeText(templateFields.label.value),
                scheme_template_status: normalizeText(templateFields.status.value),
                scheme_template_solution_type: normalizeText(templateFields.solutionType.value),
                scheme_template_solution_feature: normalizeText(templateFields.solutionFeature.value),
                scheme_template_product_list: productList,
                scheme_template_description: templateFields.description.value || '',
                scheme_template_remark: templateFields.remark.value || '',
            }
        }

        function buildSchemePayload() {
            return {
                scheme_id: normalizeText(schemeFields.id.value),
                scheme_name: normalizeText(schemeFields.name.value),
                scheme_owner: normalizeText(schemeFields.owner.value),
                scheme_share_status: normalizeText(schemeFields.shareStatus.value),
                scheme_expires_at: normalizeText(schemeFields.expiresAt.value),
                scheme_hotel_type: normalizeText(schemeFields.hotelType.value),
                scheme_solution_type: normalizeText(schemeFields.solutionType.value),
                scheme_solution_feature: normalizeText(schemeFields.solutionFeature.value),
                scheme_room_type: parseJsonTextarea(schemeFields.roomType.value, 'scheme_room_type'),
                scheme_curtains: normalizeText(schemeFields.curtains.value),
                scheme_voice_device: normalizeText(schemeFields.voiceDevice.value),
                scheme_ac_type: normalizeText(schemeFields.acType.value),
                scheme_template_highend: normalizeText(schemeFields.templateHighend.value),
                scheme_template_midend: normalizeText(schemeFields.templateMidend.value),
                scheme_template_lowend: normalizeText(schemeFields.templateLowend.value),
                scheme_status: normalizeText(schemeFields.status.value),
                scheme_remark: schemeFields.remark.value || '',
            }
        }

        async function saveTemplate() {
            let payload = null
            const isEdit = state.templateMode === 'edit'
            try {
                payload = buildTemplatePayload()
            } catch (error) {
                setStatus(templateStatusEl, error.message || '模板数据格式有误', 'error')
                return
            }

            showLoading(isEdit ? '正在保存模板修改...' : '正在创建模板...')
            try {
                const path = isEdit ? '/scheme-template/update' : '/scheme-template/create'
                const saved = await requestJson(path, payload)
                await loadTemplates()
                if (saved && saved.scheme_template_id) {
                    await editTemplate(saved.scheme_template_id)
                }
                setStatus(templateStatusEl, isEdit ? '模板修改成功。' : '模板创建成功。', 'success')
                setStatus(pageStatusEl, '模板数据已同步刷新。', 'success')
            } catch (error) {
                setStatus(templateStatusEl, error.message || '保存模板失败', 'error')
            } finally {
                hideLoading()
            }
        }

        async function saveScheme() {
            let payload = null
            const isEdit = state.schemeMode === 'edit'
            try {
                payload = buildSchemePayload()
            } catch (error) {
                setStatus(schemeStatusEl, error.message || '方案数据格式有误', 'error')
                return
            }

            showLoading(isEdit ? '正在保存方案修改...' : '正在创建方案...')
            try {
                const path = isEdit ? '/scheme/update' : '/scheme/create'
                const saved = await requestJson(path, payload)
                await loadSchemes()
                if (saved && saved.scheme_id) {
                    await editScheme(saved.scheme_id)
                }
                setStatus(schemeStatusEl, isEdit ? '方案修改成功。' : '方案创建成功。', 'success')
                setStatus(pageStatusEl, '方案数据已同步刷新。', 'success')
            } catch (error) {
                setStatus(schemeStatusEl, error.message || '保存方案失败', 'error')
            } finally {
                hideLoading()
            }
        }

        async function deleteTemplate(templateId) {
            if (!window.confirm('确认删除模板「' + templateId + '」吗？')) {
                return
            }

            showLoading('正在删除模板...')
            try {
                await requestJson('/scheme-template/delete', { scheme_template_id: templateId })
                if (state.editingTemplateId === templateId) {
                    leaveTemplateEditor()
                }
                await loadTemplates()
                await loadSchemes()
                setStatus(pageStatusEl, '模板删除成功。', 'success')
            } catch (error) {
                setStatus(pageStatusEl, error.message || '删除模板失败', 'error')
            } finally {
                hideLoading()
            }
        }

        async function deleteScheme(schemeId) {
            if (!window.confirm('确认删除方案「' + schemeId + '」吗？')) {
                return
            }

            showLoading('正在删除方案...')
            try {
                await requestJson('/scheme/delete', { scheme_id: schemeId })
                if (state.editingSchemeId === schemeId) {
                    leaveSchemeEditor()
                }
                await loadSchemes()
                setStatus(pageStatusEl, '方案删除成功。', 'success')
            } catch (error) {
                setStatus(pageStatusEl, error.message || '删除方案失败', 'error')
            } finally {
                hideLoading()
            }
        }

        async function handleTemplateIconUpload() {
            const file = templateFields.iconFile.files && templateFields.iconFile.files[0]
            if (!file) {
                setStatus(templateStatusEl, '请先选择图标文件。', 'error')
                return
            }

            showLoading('正在上传模板图标...')
            try {
                const uploaded = await uploadAttachment(file)
                templateFields.iconId.value = uploaded.attachments_id || ''
                state.lastUploadedTemplateIcon = uploaded
                renderTemplateIcon(uploaded.attachments_url || '')
                setStatus(templateStatusEl, '图标上传成功，已回填附件 ID。', 'success')
            } catch (error) {
                setStatus(templateStatusEl, error.message || '上传图标失败', 'error')
            } finally {
                hideLoading()
            }
        }

        document.getElementById('refreshAllBtn').addEventListener('click', function () {
            refreshAll()
        })
        document.getElementById('searchTemplateBtn').addEventListener('click', function () {
            refreshAll()
        })
        document.getElementById('searchSchemeBtn').addEventListener('click', function () {
            refreshAll()
        })
        document.getElementById('newTemplateBtn').addEventListener('click', function () {
            enterTemplateCreateMode()
        })
        document.getElementById('newSchemeBtn').addEventListener('click', function () {
            enterSchemeCreateMode()
        })
        document.getElementById('cancelTemplateBtn').addEventListener('click', function () {
            leaveTemplateEditor()
        })
        document.getElementById('cancelSchemeBtn').addEventListener('click', function () {
            leaveSchemeEditor()
        })
        document.getElementById('resetTemplateBtn').addEventListener('click', function () {
            resetTemplateForm()
        })
        document.getElementById('resetSchemeBtn').addEventListener('click', function () {
            resetSchemeForm()
        })
        document.getElementById('saveTemplateBtn').addEventListener('click', function () {
            saveTemplate()
        })
        document.getElementById('saveSchemeBtn').addEventListener('click', function () {
            saveScheme()
        })
        document.getElementById('uploadTemplateIconBtn').addEventListener('click', function () {
            handleTemplateIconUpload()
        })
        document.getElementById('addTemplateProductBtn').addEventListener('click', function () {
            addSelectedTemplateProduct()
        })
        document.getElementById('reloadTemplateProductsBtn').addEventListener('click', function () {
            loadTemplateProductResources()
        })
        document.getElementById('clearTemplateIconBtn').addEventListener('click', function () {
            templateFields.iconId.value = ''
            templateFields.iconFile.value = ''
            state.lastUploadedTemplateIcon = null
            renderTemplateIcon('')
        })
        templateFields.productCategoryFilter.addEventListener('change', function () {
            renderTemplateProductOptions()
        })
        templateFields.productList.addEventListener('blur', function () {
            if (!normalizeText(templateFields.productList.value)) {
                setTemplateProductItems([])
                return
            }

            try {
                hydrateTemplateProductItemsFromTextarea()
            } catch (_error) {}
        })

        templateFields.iconId.addEventListener('input', function () {
            if (!normalizeText(templateFields.iconId.value)) {
                renderTemplateIcon('')
                return
            }
            if (state.lastUploadedTemplateIcon && normalizeText(state.lastUploadedTemplateIcon.attachments_id) === normalizeText(templateFields.iconId.value)) {
                renderTemplateIcon(state.lastUploadedTemplateIcon.attachments_url || '')
            }
        })

        document.addEventListener('click', function (event) {
            const target = event.target
            if (!target) {
                return
            }

            const templateEditId = target.getAttribute('data-template-edit')
            if (templateEditId) {
                editTemplate(templateEditId)
                return
            }

            const templateDeleteId = target.getAttribute('data-template-delete')
            if (templateDeleteId) {
                deleteTemplate(templateDeleteId)
                return
            }

            const templateProductUpIndex = target.getAttribute('data-template-product-up')
            if (templateProductUpIndex !== null) {
                moveTemplateProductItem(templateProductUpIndex, -1)
                return
            }

            const templateProductDownIndex = target.getAttribute('data-template-product-down')
            if (templateProductDownIndex !== null) {
                moveTemplateProductItem(templateProductDownIndex, 1)
                return
            }

            const templateProductRemoveIndex = target.getAttribute('data-template-product-remove')
            if (templateProductRemoveIndex !== null) {
                removeTemplateProductItem(templateProductRemoveIndex)
                return
            }

            const schemeEditId = target.getAttribute('data-scheme-edit')
            if (schemeEditId) {
                editScheme(schemeEditId)
                return
            }

            const schemeDeleteId = target.getAttribute('data-scheme-delete')
            if (schemeDeleteId) {
                deleteScheme(schemeDeleteId)
            }
        })

        ;(async function initPage() {
            resetTemplateForm()
            resetSchemeForm()
            leaveTemplateEditor()
            leaveSchemeEditor()
            await refreshAll()
        })()
    </script>
    {{ template "theme_body" . }}
</body>
</html>
