<!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, #eef6ff 0%, #f8fafc 100%); color: #1f2937; }
        .container { max-width: 1440px; margin: 0 auto; padding: 24px 14px 40px; }
        .panel { background: rgba(255,255,255,0.96); border: 1px solid #e5e7eb; box-shadow: 0 18px 50px rgba(15, 23, 42, 0.08); border-radius: 20px; padding: 18px; }
        .panel + .panel { margin-top: 14px; }
        .actions, .form-actions, .table-actions, .param-actions { display: flex; flex-wrap: wrap; gap: 10px; }
        .toolbar { display: grid; grid-template-columns: 1.4fr 1fr 1fr auto; gap: 10px; }
        .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-warning { background: #f59e0b; color: #fff; }
        .btn-secondary { background: #e0e7ff; color: #1e3a8a; }
        .status { margin-top: 12px; min-height: 24px; font-size: 14px; }
        .status.success { color: #15803d; }
        .status.error { color: #b91c1c; }
        .editor-panel { display: none; }
        .editor-panel.show { display: block; }
        .grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
        .full { grid-column: 1 / -1; }
        .quarter-row { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; }
        .quarter-col { grid-column: span 1; }
        .quarter-col-2 { grid-column: span 2; }
        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; }
        textarea { min-height: 88px; resize: vertical; }
        .hint { color: #64748b; font-size: 12px; margin-top: 6px; }
        .option-box { border: 1px solid #dbe3f0; border-radius: 14px; background: #f8fbff; padding: 12px; }
        .option-list { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; margin-top: 10px; }
        .option-item { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border-radius: 12px; background: #fff; border: 1px solid #e5e7eb; min-height: 44px; }
        .option-item input[type="checkbox"] { width: auto; margin: 0; }
        .selection-tags { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
        .selection-tag { display: inline-flex; align-items: center; padding: 4px 10px; border-radius: 999px; background: #eff6ff; color: #1d4ed8; font-size: 12px; font-weight: 700; }
        .tag-custom { display: grid; grid-template-columns: 1fr auto; gap: 8px; margin-top: 10px; }
        .param-table-wrap { border: 1px solid #dbe3f0; border-radius: 14px; 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 { text-align: center; padding: 24px; color: #64748b; }
        .thumb { width: 84px; height: 84px; border-radius: 10px; border: 1px solid #dbe3f0; object-fit: cover; background: #fff; }
        .thumb-wrap { display: flex; gap: 20px; align-items: flex-start; flex-wrap: wrap; }
        .thumb-upload-panel { flex: 1; min-width: 320px; }
        .thumb-preview-panel { flex: 1.2; min-width: 320px; }
        .thumb-grid { display: flex; flex-wrap: wrap; gap: 12px; }
        .thumb-card { width: 104px; }
        .thumb-caption { margin-top: 6px; font-size: 12px; color: #64748b; text-align: center; }
        .thumb-remove { width: 100%; margin-top: 6px; }
        .param-op { display: flex; align-items: center; gap: 10px; }
        .param-op input { width: 120px; flex: 0 0 120px; }
        .param-op .btn { flex: 0 0 auto; white-space: nowrap; }
        .muted { color: #64748b; font-size: 12px; }
        .modal-mask { position: fixed; inset: 0; display: none; align-items: center; justify-content: center; padding: 16px; background: rgba(15, 23, 42, 0.42); z-index: 9999; }
        .modal-mask.show { display: flex; }
        .modal-card { width: min(560px, 100%); background: #fff; border: 1px solid #e5e7eb; border-radius: 16px; box-shadow: 0 24px 70px rgba(15, 23, 42, 0.2); padding: 18px; }
        .modal-title { margin: 0 0 14px; font-size: 22px; color: #0f172a; }
        .modal-grid { display: grid; grid-template-columns: 1fr; gap: 10px; }
        .modal-actions { margin-top: 14px; display: flex; justify-content: flex-end; gap: 10px; }
        .loading-mask { position: fixed; inset: 0; display: none; align-items: center; justify-content: center; padding: 24px; background: rgba(15, 23, 42, 0.42); backdrop-filter: blur(4px); z-index: 9998; }
        .loading-mask.show { display: flex; }
        .loading-card { min-width: min(92vw, 360px); padding: 24px 22px; border-radius: 20px; background: rgba(255,255,255,0.98); box-shadow: 0 28px 70px rgba(15, 23, 42, 0.22); border: 1px solid #dbe3f0; text-align: center; }
        .loading-spinner { width: 44px; height: 44px; margin: 0 auto 14px; border-radius: 999px; border: 4px solid #dbeafe; border-top-color: #2563eb; animation: spin 0.9s linear infinite; }
        .loading-text { color: #0f172a; font-size: 15px; font-weight: 700; }
        .pagination { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; margin-top: 16px; }
        .pagination-info { color: #475569; font-size: 14px; }
        .pagination-actions { display: flex; flex-wrap: wrap; gap: 8px; }
        .pagination-size { display: inline-flex; align-items: center; gap: 8px; color: #475569; font-size: 14px; }
        .pagination-btn { min-width: 42px; padding: 8px 12px; border-radius: 10px; border: 1px solid #dbe3f0; background: #fff; color: #334155; cursor: pointer; font-weight: 600; }
        .pagination-select { min-width: 88px; width: auto; padding: 8px 12px; border-radius: 10px; border: 1px solid #dbe3f0; background: #fff; color: #334155; font-size: 14px; }
        .pagination-btn.active { background: #2563eb; border-color: #2563eb; color: #fff; }
        .pagination-btn:disabled { cursor: not-allowed; color: #94a3b8; background: #f8fafc; }
        .pagination-btn:not(:disabled):hover { border-color: #94a3b8; background: #f8fafc; }
        .pagination-btn.active:hover { background: #2563eb; border-color: #2563eb; }
        html[data-theme="dark"] .pagination-info,
        html[data-theme="dark"] .pagination-size { color: #cbd5e1; }
        html[data-theme="dark"] .pagination-btn { background: rgba(15, 23, 42, 0.92); border-color: rgba(148, 163, 184, 0.24); color: #e2e8f0; box-shadow: inset 0 1px 0 rgba(255,255,255,0.04); }
        html[data-theme="dark"] .pagination-btn:not(:disabled):hover { background: rgba(30, 41, 59, 0.96); border-color: rgba(96, 165, 250, 0.5); color: #f8fafc; }
        html[data-theme="dark"] .pagination-btn.active,
        html[data-theme="dark"] .pagination-btn.active:hover { background: linear-gradient(180deg, #3b82f6 0%, #2563eb 100%); border-color: #3b82f6; color: #eff6ff; box-shadow: 0 10px 24px rgba(37, 99, 235, 0.28); }
        html[data-theme="dark"] .pagination-btn:disabled { background: rgba(15, 23, 42, 0.58); border-color: rgba(71, 85, 105, 0.45); color: #64748b; box-shadow: none; }
        html[data-theme="dark"] .pagination-select { background: rgba(15, 23, 42, 0.92); border-color: rgba(148, 163, 184, 0.24); color: #f8fafc; box-shadow: inset 0 1px 0 rgba(255,255,255,0.04); }
        html[data-theme="dark"] .pagination-select:focus { outline: none; border-color: #60a5fa; box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.18); }
        @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
        @media (max-width: 960px) {
            .grid, .toolbar, .option-list { grid-template-columns: 1fr; }
            .quarter-row { grid-template-columns: 1fr; }
            table, thead, tbody, th, td, tr { display: block; }
            thead { display: none; }
            tr { margin-bottom: 12px; border: 1px solid #e5e7eb; border-radius: 12px; overflow: hidden; }
            td { display: flex; justify-content: space-between; gap: 10px; }
            td::before { content: attr(data-label); font-weight: 700; color: #475569; }
        }
    </style>
    {{ template "theme_head" . }}
</head>
<body>
    <div class="container">
        <section class="panel">
            <h1 style="margin-top:0;">产品管理</h1>
            <div class="actions">
                <a class="btn btn-light" href="/pb/manage">返回主页</a>
                <button class="btn btn-light" id="reloadBtn" type="button">刷新列表</button>
                <button class="btn btn-light" id="createModeBtn" type="button">新建模式</button>
            </div>
            <div class="status" id="status"></div>
        </section>

        <section class="panel">
            <div class="toolbar">
                <input id="keywordInput" placeholder="按名称/型号/料号/标签搜索" />
                <select id="statusFilter"><option value="">全部状态</option></select>
                <select id="categoryFilter"><option value="">全部分类</option></select>
                <button class="btn btn-secondary" id="searchBtn" type="button">查询</button>
            </div>
        </section>

        <section class="panel editor-panel" id="editorPanel">
            <div style="display:flex;justify-content:space-between;align-items:center;gap:10px;">
                <h2 id="formTitle" style="margin:0;">新增产品</h2>
                <div class="muted" id="editorMode">当前模式：新建</div>
            </div>
            <div class="grid" style="margin-top:14px;">
                <div class="full quarter-row">
                    <div class="quarter-col-2">
                    <label for="prodListName">产品名称</label>
                    <input id="prodListName" placeholder="必填" />
                    </div>
                    <div class="quarter-col">
                    <label for="prodListBarcode">产品料号</label>
                    <input id="prodListBarcode" placeholder="可选" />
                    </div>
                    <div class="quarter-col">
                    <label for="prodListModel">产品型号</label>
                    <input id="prodListModel" placeholder="可选" />
                    </div>
                </div>
                <div>
                    <label>产品分类（必填，单选）</label>
                    <div class="option-box">
                        <div class="option-list" id="categoryOptions"></div>
                        <div class="selection-tags" id="categorySelected"></div>
                    </div>
                </div>
                <div>
                    <label>产品状态（单选）</label>
                    <div class="option-box">
                        <div class="option-list" id="prodStatusOptions"></div>
                        <div class="selection-tags" id="prodStatusSelected"></div>
                    </div>
                </div>
                <div class="full quarter-row">
                    <div class="quarter-col-2">
                        <label for="prodListFeature">产品特色</label>
                        <input id="prodListFeature" placeholder="可选" />
                    </div>
                    <div class="quarter-col">
                        <label for="prodListBasicPrice">基础价格</label>
                        <input id="prodListBasicPrice" type="number" step="0.01" placeholder="可选" />
                    </div>
                    <div class="quarter-col">
                        <label for="prodListSort">排序值</label>
                        <input id="prodListSort" type="number" step="1" placeholder="默认 0，数字越小越靠前" />
                        <div class="hint" id="prodListSortHint">请先选择产品分类后查看当前分类内排序位次。</div>
                    </div>
                </div>
                <div class="full">
                    <label>会员价</label>
                    <div class="param-table-wrap">
                        <table>
                            <thead>
                                <tr>
                                    <th style="width:35%;">会员等级</th>
                                    <th style="width:65%;">会员价格</th>
                                </tr>
                            </thead>
                            <tbody id="vipPriceBody"></tbody>
                        </table>
                    </div>
                    <div class="hint">按“数据-会员等级”字典自动生成行，一行对应一个会员等级，只需要填写价格。</div>
                </div>
                <div>
                    <label>产品方案（可多选）</label>
                    <div class="option-box">
                        <div class="option-list" id="plantypeOptions"></div>
                        <div class="selection-tags" id="plantypeSelected"></div>
                    </div>
                </div>
                <div>
                    <label>通讯类型（可多选）</label>
                    <div class="option-box">
                        <div class="option-list" id="commTypeOptions"></div>
                        <div class="selection-tags" id="commTypeSelected"></div>
                    </div>
                </div>
                <div>
                    <label>产品系列（可多选）</label>
                    <div class="option-box">
                        <div class="option-list" id="seriesOptions"></div>
                        <div class="selection-tags" id="seriesSelected"></div>
                    </div>
                </div>
                <div>
                    <label>供电方式（可多选）</label>
                    <div class="option-box">
                        <div class="option-list" id="powerSupplyOptions"></div>
                        <div class="selection-tags" id="powerSupplySelected"></div>
                    </div>
                </div>
                <div class="full">
                    <label for="prodListDescription">产品说明</label>
                    <textarea id="prodListDescription" placeholder="支持 editor 内容"></textarea>
                </div>
                <div class="full">
                    <label>产品标签（描述）</label>
                    <div class="option-box">
                        <div class="option-list" id="tagsOptions"></div>
                        <div class="tag-custom">
                            <input id="customTagInput" placeholder="输入自定义标签 description" />
                            <button class="btn btn-light" id="addCustomTagBtn" type="button">添加自定义标签</button>
                        </div>
                        <div class="selection-tags" id="tagsSelected"></div>
                    </div>
                </div>
                <div class="full">
                    <label>产品参数</label>
                    <div class="param-table-wrap">
                        <table>
                            <thead>
                                <tr>
                                    <th style="width:40%;">属性名</th>
                                    <th style="width:45%;">属性值</th>
                                    <th style="width:22%;">操作 / 排序</th>
                                </tr>
                            </thead>
                            <tbody id="paramsBody"></tbody>
                        </table>
                    </div>
                    <div style="margin-top:10px;">
                        <label for="paramsJsonInput">批量导入/导出 JSON</label>
                        <textarea id="paramsJsonInput" placeholder='示例：[{"sort":1,"name":"功率","value":"120W"},{"sort":2,"name":"电压","value":"220V"}]'></textarea>
                        <div class="hint">推荐使用数组格式：每项包含 sort、name、value。也兼容旧对象格式。导入时按属性名增量合并：同名覆盖、不同名新增，不会清空当前参数表。</div>
                    </div>
                    <div class="param-actions" style="margin-top:10px;">
                        <button class="btn btn-light" id="addParamBtn" type="button">新增参数行</button>
                        <button class="btn btn-secondary" id="importParamsBtn" type="button">批量导入</button>
                        <button class="btn btn-light" id="exportParamsBtn" type="button">批量导出</button>
                    </div>
                </div>
                <div class="full">
                    <label>产品功能</label>
                    <div class="param-table-wrap">
                        <table>
                            <thead>
                                <tr>
                                    <th style="width:40%;">功能名</th>
                                    <th style="width:45%;">功能说明</th>
                                    <th style="width:22%;">操作 / 排序</th>
                                </tr>
                            </thead>
                            <tbody id="functionsBody"></tbody>
                        </table>
                    </div>
                    <div style="margin-top:10px;">
                        <label for="functionsJsonInput">批量导入/导出 JSON</label>
                        <textarea id="functionsJsonInput" placeholder='示例：[{"sort":1,"name":"智能场景","value":"支持联动控制"},{"sort":2,"name":"告警推送","value":"支持短信与应用推送"}]'></textarea>
                        <div class="hint">推荐使用数组格式：每项包含 sort、name、value。也兼容旧对象格式。导入时按功能名增量合并：同名覆盖、不同名新增，不会清空当前功能表。</div>
                    </div>
                    <div class="param-actions" style="margin-top:10px;">
                        <button class="btn btn-light" id="addFunctionBtn" type="button">新增功能行</button>
                        <button class="btn btn-secondary" id="importFunctionsBtn" type="button">批量导入</button>
                        <button class="btn btn-light" id="exportFunctionsBtn" type="button">批量导出</button>
                    </div>
                </div>
                <div class="full">
                    <label>产品图标（保存到 tbl_attachments）</label>
                    <div class="thumb-wrap">
                        <div class="thumb-upload-panel">
                            <input id="prodListIconFile" type="file" accept="image/*" multiple />
                            <div class="hint">支持多图上传。保存时会上传到附件表，并按上传后附件 ID 用 | 写入 prod_list_icon。</div>
                            <div class="actions" style="margin-top:8px;">
                                <button class="btn btn-light" id="clearIconBtn" type="button">清空全部图标</button>
                            </div>
                        </div>
                        <div class="thumb-preview-panel">
                            <div id="iconPreviewList" class="thumb-grid"></div>
                            <div id="iconEmpty" class="muted">暂无图标</div>
                        </div>
                    </div>
                </div>
                <div class="full">
                    <label for="prodListRemark">备注</label>
                    <textarea id="prodListRemark" placeholder="可选"></textarea>
                </div>
            </div>
            <div class="form-actions" style="margin-top:14px;">
                <button class="btn btn-primary" id="submitBtn" type="button">创建产品</button>
                <button class="btn btn-warning" id="cancelEditBtn" type="button">取消编辑</button>
                <button class="btn btn-light" id="resetBtn" type="button">重置表单</button>
            </div>
            <div class="status" id="editorStatus"></div>
        </section>

        <section class="panel">
            <h2 style="margin-top:0;">产品列表</h2>
            <div style="overflow:auto;">
                <table>
                    <thead>
                        <tr>
                            <th>名称/型号</th>
                            <th>分类信息</th>
                            <th>标签</th>
                            <th>状态/价格</th>
                            <th>更新时间</th>
                            <th>操作</th>
                        </tr>
                    </thead>
                    <tbody id="tableBody">
                        <tr><td colspan="6" class="empty">暂无数据，请先查询。</td></tr>
                    </tbody>
                </table>
            </div>
            <div class="pagination" id="listPagination"></div>
        </section>
    </div>

    <div class="modal-mask" id="copyModal">
        <div class="modal-card" role="dialog" aria-modal="true" aria-labelledby="copyModalTitle">
            <h3 class="modal-title" id="copyModalTitle">复制产品</h3>
            <div class="modal-grid">
                <div>
                    <label for="copyNameInput">新产品名称</label>
                    <input id="copyNameInput" placeholder="请输入新产品名称" />
                </div>
                <div>
                    <label for="copyModelInput">新产品型号</label>
                    <input id="copyModelInput" placeholder="请输入新产品型号" />
                </div>
                <div>
                    <label for="copyBarcodeInput">新产品料号</label>
                    <input id="copyBarcodeInput" placeholder="请输入新产品料号" />
                </div>
            </div>
            <div class="modal-actions">
                <button class="btn btn-light" id="copyCancelBtn" type="button">取消</button>
                <button class="btn btn-primary" id="copyConfirmBtn" type="button">确定复制</button>
            </div>
        </div>
    </div>

    <div class="loading-mask" id="loadingMask">
        <div class="loading-card">
            <div class="loading-spinner"></div>
            <div class="loading-text" id="loadingText">处理中，请稍候...</div>
        </div>
    </div>

    <script>
        const tokenKey = 'pb_manage_token'
        const API_BASE = '/pb/api'
        const statusEl = document.getElementById('status')
        const editorStatusEl = document.getElementById('editorStatus')
        const tableBodyEl = document.getElementById('tableBody')
        const editorPanelEl = document.getElementById('editorPanel')
        const formTitleEl = document.getElementById('formTitle')
        const editorModeEl = document.getElementById('editorMode')
        const tagsOptionsEl = document.getElementById('tagsOptions')
        const tagsSelectedEl = document.getElementById('tagsSelected')
        const plantypeOptionsEl = document.getElementById('plantypeOptions')
        const plantypeSelectedEl = document.getElementById('plantypeSelected')
        const categoryOptionsEl = document.getElementById('categoryOptions')
        const categorySelectedEl = document.getElementById('categorySelected')
        const commTypeOptionsEl = document.getElementById('commTypeOptions')
        const commTypeSelectedEl = document.getElementById('commTypeSelected')
        const seriesOptionsEl = document.getElementById('seriesOptions')
        const seriesSelectedEl = document.getElementById('seriesSelected')
        const powerSupplyOptionsEl = document.getElementById('powerSupplyOptions')
        const powerSupplySelectedEl = document.getElementById('powerSupplySelected')
        const prodStatusOptionsEl = document.getElementById('prodStatusOptions')
        const prodStatusSelectedEl = document.getElementById('prodStatusSelected')
        const paramsBodyEl = document.getElementById('paramsBody')
        const functionsBodyEl = document.getElementById('functionsBody')
        const vipPriceBodyEl = document.getElementById('vipPriceBody')
        const copyModalEl = document.getElementById('copyModal')
        const copyNameInputEl = document.getElementById('copyNameInput')
        const copyModelInputEl = document.getElementById('copyModelInput')
        const copyBarcodeInputEl = document.getElementById('copyBarcodeInput')
        const loadingMaskEl = document.getElementById('loadingMask')
        const loadingTextEl = document.getElementById('loadingText')
        const iconPreviewListEl = document.getElementById('iconPreviewList')
        const iconEmptyEl = document.getElementById('iconEmpty')
        const statusFilterEl = document.getElementById('statusFilter')
        const categoryFilterEl = document.getElementById('categoryFilter')
        const prodListSortHintEl = document.getElementById('prodListSortHint')
        const listPaginationEl = document.getElementById('listPagination')

        const fields = {
            keyword: document.getElementById('keywordInput'),
            name: document.getElementById('prodListName'),
            model: document.getElementById('prodListModel'),
            barcode: document.getElementById('prodListBarcode'),
            basicPrice: document.getElementById('prodListBasicPrice'),
            feature: document.getElementById('prodListFeature'),
            sort: document.getElementById('prodListSort'),
            description: document.getElementById('prodListDescription'),
            remark: document.getElementById('prodListRemark'),
            paramsJsonInput: document.getElementById('paramsJsonInput'),
            functionsJsonInput: document.getElementById('functionsJsonInput'),
            customTag: document.getElementById('customTagInput'),
            iconFile: document.getElementById('prodListIconFile'),
        }

        const dictNameMap = {
            plantype: '产品-产品方案',
            category: '产品-产品分类',
            commType: '产品-通讯类型',
            series: '产品-产品系列',
            powerSupply: '产品-供电方式',
            tags: '产品-产品标签',
            status: '产品-产品状态',
            userLevel: '数据-会员等级',
        }

        const state = {
            list: [],
            fullList: [],
            mode: 'idle',
            editingId: '',
            editingSource: null,
            dictionariesByName: {},
            productStatus: '有效',
            selections: {
                plantype: [],
                category: [],
                commType: [],
                series: [],
                powerSupply: [],
                tags: [],
            },
            parameterRows: [],
            functionRows: [],
            vipPriceRows: [],
            currentIconAttachments: [],
            pendingIconFiles: [],
            copySourceProductId: '',
            pagination: {
                page: 1,
                pageSize: 10,
                total: 0,
                totalPages: 1,
            },
        }

        const multiFieldConfig = {
            plantype: { dictName: dictNameMap.plantype, optionsEl: plantypeOptionsEl, selectedEl: plantypeSelectedEl },
            category: { dictName: dictNameMap.category, optionsEl: categoryOptionsEl, selectedEl: categorySelectedEl },
            commType: { dictName: dictNameMap.commType, optionsEl: commTypeOptionsEl, selectedEl: commTypeSelectedEl },
            series: { dictName: dictNameMap.series, optionsEl: seriesOptionsEl, selectedEl: seriesSelectedEl },
            powerSupply: { dictName: dictNameMap.powerSupply, optionsEl: powerSupplyOptionsEl, selectedEl: powerSupplySelectedEl },
        }

        const productStatusOptions = ['有效', '失效', '自定义']

        function getProductStatusChoice(value) {
            const normalized = normalizeText(value)
            if (normalized === '有效' || normalized === '失效') {
                return normalized
            }
            return '自定义'
        }

        function getProductStatusCustomValue(value) {
            const normalized = normalizeText(value)
            if (!normalized || normalized === '有效' || normalized === '失效' || normalized === '自定义') {
                return ''
            }
            return normalized
        }

        const loadingState = { count: 0 }

        function setStatus(message, type) {
            statusEl.textContent = message || ''
            statusEl.className = 'status' + (type ? ' ' + type : '')
            editorStatusEl.textContent = message || ''
            editorStatusEl.className = 'status' + (type ? ' ' + type : '')
        }

        function showLoading(message) {
            loadingState.count += 1
            if (loadingTextEl) {
                loadingTextEl.textContent = message || '处理中，请稍候...'
            }
            if (loadingMaskEl) {
                loadingMaskEl.classList.add('show')
            }
        }

        function hideLoading() {
            loadingState.count = Math.max(0, loadingState.count - 1)
            if (loadingState.count === 0 && loadingMaskEl) {
                loadingMaskEl.classList.remove('show')
                if (loadingTextEl) {
                    loadingTextEl.textContent = '处理中，请稍候...'
                }
            }
        }

        function decodeErrMsg(value) {
            if (!value) return ''
            try {
                return decodeURIComponent(value)
            } catch (_err) {
                return String(value)
            }
        }

        function unwrapApiResponse(data, res) {
            const safe = data && typeof data === 'object' ? data : {}
            const headerCode = Number(res && res.headers ? (res.headers.get('X-Status-Code') || 0) : 0)
            const headerErrMsg = decodeErrMsg(res && res.headers ? res.headers.get('X-Err-Msg') : '')
            const code = Number(safe.statusCode || safe.code || headerCode || 0)
            const errMsg = safe.errMsg || safe.msg || headerErrMsg || ''
            const hasLegacyEnvelope = Object.prototype.hasOwnProperty.call(safe, 'data')
                && (Object.prototype.hasOwnProperty.call(safe, 'code')
                    || Object.prototype.hasOwnProperty.call(safe, 'statusCode')
                    || Object.prototype.hasOwnProperty.call(safe, 'msg')
                    || Object.prototype.hasOwnProperty.call(safe, 'errMsg'))
            const payload = hasLegacyEnvelope ? (safe.data || {}) : safe
            return { code: code, errMsg: errMsg, payload: payload }
        }

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

        function getApiUrl(path) {
            return API_BASE + path
        }

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

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

            const data = await res.json()
            const unwrapped = unwrapApiResponse(data, res)
            if (!res.ok || unwrapped.code >= 400) {
                if (res.status === 401 || res.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
        }

        function buildCategoryRankMapForList(items) {
            const grouped = {}
            for (let i = 0; i < items.length; i += 1) {
                const category = normalizeText(items[i] && items[i].prod_list_category)
                if (!category) {
                    continue
                }

                if (!grouped[category]) {
                    grouped[category] = []
                }
                grouped[category].push(items[i])
            }

            const rankMap = {}
            const categories = Object.keys(grouped)
            for (let i = 0; i < categories.length; i += 1) {
                const category = categories[i]
                const rows = grouped[category]
                rows.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(b.updated || '').localeCompare(String(a.updated || ''))
                })

                for (let j = 0; j < rows.length; j += 1) {
                    rankMap[normalizeText(rows[j].prod_list_id)] = j + 1
                }
            }

            return rankMap
        }

        function normalizeFallbackProductRecord(item) {
            return {
                pb_id: normalizeText(item && item.id),
                prod_list_id: normalizeText(item && item.prod_list_id),
                prod_list_name: normalizeText(item && item.prod_list_name),
                prod_list_modelnumber: normalizeText(item && item.prod_list_modelnumber),
                prod_list_barcode: normalizeText(item && item.prod_list_barcode),
                prod_list_icon: normalizeText(item && item.prod_list_icon),
                prod_list_description: normalizeText(item && item.prod_list_description),
                prod_list_feature: normalizeText(item && item.prod_list_feature),
                prod_list_parameters: item && item.prod_list_parameters ? item.prod_list_parameters : [],
                prod_list_function: item && item.prod_list_function ? item.prod_list_function : [],
                prod_list_plantype: normalizeText(item && item.prod_list_plantype),
                prod_list_category: normalizeText(item && item.prod_list_category),
                prod_list_sort: Number(item && item.prod_list_sort || 0),
                prod_list_comm_type: normalizeText(item && item.prod_list_comm_type),
                prod_list_series: normalizeText(item && item.prod_list_series),
                prod_list_power_supply: normalizeText(item && item.prod_list_power_supply),
                prod_list_tags: normalizeText(item && item.prod_list_tags),
                prod_list_basic_price: item && (typeof item.prod_list_basic_price !== 'undefined') ? item.prod_list_basic_price : null,
                prod_list_vip_price: item && item.prod_list_vip_price ? item.prod_list_vip_price : [],
                prod_list_status: normalizeText(item && item.prod_list_status),
                prod_list_remark: normalizeText(item && item.prod_list_remark),
                created: normalizeText(item && item.created),
                updated: normalizeText(item && item.updated),
                prod_list_icon_ids: splitPipe(item && item.prod_list_icon),
                prod_list_icon_attachments: [],
                prod_list_icon_urls: [],
                prod_list_icon_attachment: null,
                prod_list_icon_url: '',
            }
        }

        async function fetchAllFallbackProductRecords() {
            const token = getToken()
            const perPage = 200
            const result = []
            let page = 1

            while (true) {
                const res = await fetch(getApiUrl('/collections/tbl_product_list/records?page=' + page + '&perPage=' + perPage + '&sort=-updated'), {
                    method: 'GET',
                    headers: {
                        Authorization: token ? ('Bearer ' + token) : '',
                    },
                })

                const parsed = await parseJsonSafe(res)
                if (!res.ok || !parsed.isJson || !parsed.json) {
                    throw new Error('产品列表回退查询失败')
                }

                const rawItems = Array.isArray(parsed.json.items) ? parsed.json.items : []
                for (let i = 0; i < rawItems.length; i += 1) {
                    result.push(normalizeFallbackProductRecord(rawItems[i]))
                }

                if (rawItems.length < perPage) {
                    break
                }

                page += 1
            }

            return result
        }

        async function requestProductListFallback(payload) {
            const normalized = await fetchAllFallbackProductRecords()

            const keyword = normalizeText(payload && payload.keyword).toLowerCase()
            const status = normalizeText(payload && payload.status)
            const category = normalizeText(payload && payload.prod_list_category)

            const filtered = normalized.filter(function (item) {
                const matchedKeyword = !keyword
                    || normalizeText(item.prod_list_id).toLowerCase().indexOf(keyword) !== -1
                    || normalizeText(item.prod_list_name).toLowerCase().indexOf(keyword) !== -1
                    || normalizeText(item.prod_list_modelnumber).toLowerCase().indexOf(keyword) !== -1
                    || normalizeText(item.prod_list_barcode).toLowerCase().indexOf(keyword) !== -1
                    || normalizeText(item.prod_list_tags).toLowerCase().indexOf(keyword) !== -1
                const matchedStatus = !status || normalizeText(item.prod_list_status) === status
                const matchedCategory = !category || normalizeText(item.prod_list_category) === category
                return matchedKeyword && matchedStatus && matchedCategory
            })

            const rankMap = buildCategoryRankMapForList(normalized)
            for (let i = 0; i < filtered.length; i += 1) {
                const id = normalizeText(filtered[i].prod_list_id)
                filtered[i].prod_list_category_rank = rankMap[id] || 0
            }

            filtered.sort(function (a, b) {
                return String(b.updated || '').localeCompare(String(a.updated || ''))
            })

            return filtered
        }

        function clampPage(page, totalPages) {
            const normalizedPage = Number(page) || 1
            const normalizedTotalPages = Math.max(1, Number(totalPages) || 1)
            return Math.min(Math.max(1, normalizedPage), normalizedTotalPages)
        }

        function getVisiblePageNumbers(page, totalPages) {
            const result = []
            const start = Math.max(1, page - 2)
            const end = Math.min(totalPages, start + 4)
            const adjustedStart = Math.max(1, end - 4)

            for (let current = adjustedStart; current <= end; current += 1) {
                result.push(current)
            }

            return result
        }

        function renderPageSizeSelector() {
            const options = [10, 20, 50, 100, 500]
            return '<label class="pagination-size">每页'
                + '<select class="pagination-select" onchange="window.__setProductPageSize(this.value)">'
                + options.map(function (size) {
                    return '<option value="' + size + '"' + (Number(state.pagination.pageSize) === size ? ' selected' : '') + '>' + size + '</option>'
                }).join('')
                + '</select>'
                + '条</label>'
        }

        function renderPagination() {
            if (!listPaginationEl) {
                return
            }

            const total = Number(state.pagination.total || 0)
            const page = Number(state.pagination.page || 1)
            const totalPages = Math.max(1, Number(state.pagination.totalPages || 1))
            const sizeSelector = renderPageSizeSelector()

            if (!total) {
                listPaginationEl.innerHTML = '<div class="pagination-info">共 0 条</div>'
                    + '<div class="pagination-actions">' + sizeSelector + '</div>'
                return
            }

            const visiblePageNumbers = getVisiblePageNumbers(page, totalPages)
            const startIndex = (page - 1) * state.pagination.pageSize + 1
            const endIndex = Math.min(total, page * state.pagination.pageSize)
            const buttons = []

            buttons.push('<button class="pagination-btn" type="button" onclick="window.__gotoProductPage(' + (page - 1) + ')" ' + (page <= 1 ? 'disabled' : '') + '>上一页</button>')

            if (visiblePageNumbers[0] > 1) {
                buttons.push('<button class="pagination-btn" type="button" onclick="window.__gotoProductPage(1)">1</button>')
                if (visiblePageNumbers[0] > 2) {
                    buttons.push('<button class="pagination-btn" type="button" disabled>...</button>')
                }
            }

            for (let i = 0; i < visiblePageNumbers.length; i += 1) {
                const current = visiblePageNumbers[i]
                buttons.push('<button class="pagination-btn' + (current === page ? ' active' : '') + '" type="button" onclick="window.__gotoProductPage(' + current + ')">' + current + '</button>')
            }

            if (visiblePageNumbers.length && visiblePageNumbers[visiblePageNumbers.length - 1] < totalPages) {
                if (visiblePageNumbers[visiblePageNumbers.length - 1] < totalPages - 1) {
                    buttons.push('<button class="pagination-btn" type="button" disabled>...</button>')
                }
                buttons.push('<button class="pagination-btn" type="button" onclick="window.__gotoProductPage(' + totalPages + ')">' + totalPages + '</button>')
            }

            buttons.push('<button class="pagination-btn" type="button" onclick="window.__gotoProductPage(' + (page + 1) + ')" ' + (page >= totalPages ? 'disabled' : '') + '>下一页</button>')

            listPaginationEl.innerHTML = '<div class="pagination-info">共 ' + total + ' 条，显示第 ' + startIndex + '-' + endIndex + ' 条，第 ' + page + '/' + totalPages + ' 页</div>'
                + '<div class="pagination-actions">' + sizeSelector + buttons.join('') + '</div>'
        }

        function updateProductListView() {
            const pageSize = Math.max(1, Number(state.pagination.pageSize || 10))
            const total = state.fullList.length
            const totalPages = Math.max(1, Math.ceil(total / pageSize))
            const currentPage = clampPage(state.pagination.page, totalPages)
            const startIndex = (currentPage - 1) * pageSize

            state.pagination.page = currentPage
            state.pagination.total = total
            state.pagination.totalPages = totalPages
            state.list = state.fullList.slice(startIndex, startIndex + pageSize)

            renderTable()
            renderPagination()
            renderSortRankHint()
        }

        async function parseJsonSafe(res) {
            const contentType = String(res.headers.get('content-type') || '').toLowerCase()
            const rawText = await res.text()
            const isJson = contentType.indexOf('application/json') !== -1

            if (!rawText) {
                return { json: null, text: '', isJson: false }
            }

            if (isJson) {
                try {
                    return { json: JSON.parse(rawText), text: rawText, isJson: true }
                } catch (_error) {}
            }

            return { json: null, text: rawText, isJson: false }
        }

        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', 'product-manage:icon')

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

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

            return unwrapped.payload
        }

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

        function normalizeText(value) {
            return String(value || '').trim()
        }

        function generatePocketBaseRecordId() {
            const alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789'
            let output = ''
            for (let i = 0; i < 15; i += 1) {
                const idx = Math.floor(Math.random() * alphabet.length)
                output += alphabet[idx]
            }
            return output
        }

        function splitPipe(value) {
            return String(value || '')
                .split('|')
                .map(function (item) { return String(item || '').trim() })
                .filter(function (item) { return !!item })
        }

        function joinPipe(values) {
            const result = []
            for (let i = 0; i < (values || []).length; i += 1) {
                const current = normalizeText(values[i])
                if (!current || result.indexOf(current) !== -1) {
                    continue
                }
                result.push(current)
            }
            return result.join('|')
        }

        function findDictByName(name) {
            return state.dictionariesByName[name] || null
        }

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

        function fillSelectByDict(selectEl, dictName, defaultLabel) {
            const items = getDictItems(dictName)
            const currentValue = selectEl.value || ''
            let html = '<option value="">' + escapeHtml(defaultLabel || '请选择') + '</option>'
            for (let i = 0; i < items.length; i += 1) {
                const value = String(items[i].enum || '')
                const label = String(items[i].description || value)
                html += '<option value="' + escapeHtml(value) + '">' + escapeHtml(label) + '</option>'
            }
            selectEl.innerHTML = html
            if (currentValue) {
                selectEl.value = currentValue
            }
        }

        function renderFilterOptions() {
            const statusValue = statusFilterEl.value || ''
            statusFilterEl.innerHTML = '<option value="">全部状态</option>'
                + productStatusOptions.map(function (item) {
                    return '<option value="' + escapeHtml(item) + '">' + escapeHtml(item) + '</option>'
                }).join('')
            statusFilterEl.value = statusValue
            fillSelectByDict(categoryFilterEl, dictNameMap.category, '全部分类')
        }

        function renderProductStatusSelector() {
            const current = normalizeText(state.productStatus) || '有效'
            const choice = getProductStatusChoice(current)
            const customValue = getProductStatusCustomValue(current)
            prodStatusOptionsEl.innerHTML = productStatusOptions.map(function (item) {
                const checked = choice === item
                return '<label class="option-item">'
                    + '<input type="checkbox" data-status-one value="' + escapeHtml(item) + '" ' + (checked ? 'checked' : '') + ' />'
                    + '<span>' + escapeHtml(item) + '</span>'
                    + '</label>'
            }).join('')

            if (choice === '自定义') {
                prodStatusOptionsEl.innerHTML += '<div class="full" style="grid-column:1 / -1;">'
                    + '<input id="prodStatusCustomInput" data-status-custom placeholder="请输入自定义状态" value="' + escapeHtml(customValue) + '" />'
                    + '</div>'
            }

            const displayValue = choice === '自定义' ? (customValue || '自定义') : choice
            prodStatusSelectedEl.innerHTML = '<span class="selection-tag">' + escapeHtml(displayValue) + '</span>'
        }

        function renderMultiSelectionTags(selectedEl, values, labelsMap) {
            if (!values || !values.length) {
                selectedEl.innerHTML = '<span class="muted">未选择</span>'
                return
            }

            selectedEl.innerHTML = values.map(function (value) {
                return '<span class="selection-tag">' + escapeHtml(labelsMap[value] || value) + '</span>'
            }).join('')
        }

        function renderMultiOptionList(fieldName) {
            const config = multiFieldConfig[fieldName]
            if (!config) {
                return
            }

            const items = getDictItems(config.dictName)
            const selected = state.selections[fieldName] || []

            if (!items.length) {
                config.optionsEl.innerHTML = '<div class="muted">未找到字典：' + escapeHtml(config.dictName) + '</div>'
                renderMultiSelectionTags(config.selectedEl, selected, {})
                return
            }

            const labelsMap = {}
            config.optionsEl.innerHTML = items.map(function (item) {
                const value = String(item.enum || '').trim()
                const label = String(item.description || value)
                const checked = selected.indexOf(value) !== -1
                labelsMap[value] = label
                return '<label class="option-item">'
                    + '<input type="checkbox" data-multi-field="' + fieldName + '" value="' + escapeHtml(value) + '" ' + (checked ? 'checked' : '') + ' />'
                    + '<span>' + escapeHtml(label) + '</span>'
                    + '</label>'
            }).join('')

            renderMultiSelectionTags(config.selectedEl, selected, labelsMap)
        }

        function renderAllMultiOptionLists() {
            Object.keys(multiFieldConfig).forEach(function (fieldName) {
                renderMultiOptionList(fieldName)
            })
        }

        function renderSortRankHint() {
            if (!prodListSortHintEl || !fields.sort) {
                return
            }

            const category = normalizeText((state.selections.category || [])[0] || '')
            if (!category) {
                prodListSortHintEl.textContent = '请先选择产品分类后查看当前分类内排序位次。'
                return
            }

            const rawSort = normalizeText(fields.sort.value)
            const sortValue = rawSort ? Number(rawSort) : 0
            if (!Number.isFinite(sortValue)) {
                prodListSortHintEl.textContent = '排序值需为数字。'
                return
            }

            const targetSort = Math.floor(sortValue)
            const targetId = state.mode === 'edit' ? normalizeText(state.editingId) : '__draft__'
            const sameCategoryItems = state.fullList
                .filter(function (item) {
                    return normalizeText(item.prod_list_category) === category && normalizeText(item.prod_list_id) !== targetId
                })
                .map(function (item) {
                    return {
                        prod_list_id: normalizeText(item.prod_list_id),
                        prod_list_sort: Number(item.prod_list_sort || 0),
                        updated: String(item.updated || ''),
                    }
                })

            sameCategoryItems.push({
                prod_list_id: targetId,
                prod_list_sort: targetSort,
                updated: new Date().toISOString(),
            })

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

            const rank = sameCategoryItems.findIndex(function (item) {
                return item.prod_list_id === targetId
            }) + 1

            prodListSortHintEl.textContent = '当前排序值：' + targetSort + '，在分类「' + category + '」内预计第 ' + rank + ' 位。'
        }

        function renderFormOptions() {
            if (!state.productStatus) {
                state.productStatus = '有效'
            }
            renderProductStatusSelector()
            renderAllMultiOptionLists()
            renderTagOptions()
            renderVipPriceRows()
            renderSortRankHint()
        }

        function getTagDescriptionOptions() {
            const items = getDictItems(dictNameMap.tags)
            const labels = []
            for (let i = 0; i < items.length; i += 1) {
                const label = normalizeText(items[i].description || items[i].enum)
                if (label && labels.indexOf(label) === -1) {
                    labels.push(label)
                }
            }
            return labels
        }

        function renderTagOptions() {
            const options = getTagDescriptionOptions()
            if (!options.length) {
                tagsOptionsEl.innerHTML = '<div class="muted">未找到产品-产品标签字典，请先在字典管理中维护。</div>'
                renderSelectedTags()
                return
            }

            tagsOptionsEl.innerHTML = options.map(function (label) {
                const checked = state.selections.tags.indexOf(label) !== -1
                return '<label class="option-item">'
                    + '<input type="checkbox" data-tag="' + escapeHtml(label) + '" ' + (checked ? 'checked' : '') + ' />'
                    + '<span>' + escapeHtml(label) + '</span>'
                    + '</label>'
            }).join('')

            renderSelectedTags()
        }

        function renderSelectedTags() {
            if (!state.selections.tags.length) {
                tagsSelectedEl.innerHTML = '<span class="muted">未选择</span>'
                return
            }

            tagsSelectedEl.innerHTML = state.selections.tags.map(function (item) {
                return '<span class="selection-tag">' + escapeHtml(item) + '</span>'
            }).join('')
        }

        function normalizePositiveInteger(value) {
            const text = normalizeText(value)
            if (!text) {
                return null
            }

            const num = Number(text)
            if (!Number.isFinite(num) || num <= 0 || Math.floor(num) !== num) {
                return null
            }

            return num
        }

        function buildParameterRow(name, value, sort) {
            return {
                name: normalizeText(name),
                value: value === null || typeof value === 'undefined' ? '' : String(value),
                sort: normalizePositiveInteger(sort),
            }
        }

        function findInvalidParameterSortRow(rows) {
            for (let i = 0; i < rows.length; i += 1) {
                const rawSort = normalizeText(rows[i] && rows[i].sort)
                if (!rawSort) {
                    continue
                }
                if (normalizePositiveInteger(rawSort) === null) {
                    return i + 1
                }
            }
            return 0
        }

        function clearPendingIconPreview() {
            for (let i = 0; i < state.pendingIconFiles.length; i += 1) {
                const item = state.pendingIconFiles[i]
                if (!item || !item.previewUrl) {
                    continue
                }

                try {
                    URL.revokeObjectURL(item.previewUrl)
                } catch (_error) {}
            }
            state.pendingIconFiles = []
        }

        function getCollectedIconIds() {
            return state.currentIconAttachments.map(function (item) {
                return normalizeText(item && item.attachments_id)
            }).filter(function (item) {
                return !!item
            })
        }

        function renderIconPreview() {
            const cards = []

            for (let i = 0; i < state.currentIconAttachments.length; i += 1) {
                const item = state.currentIconAttachments[i]
                cards.push(
                    '<div class="thumb-card">'
                    + '<img class="thumb" src="' + escapeHtml(item.attachments_url || '') + '" alt="icon-' + (i + 1) + '" />'
                    + '<div class="thumb-caption">已保存</div>'
                    + '<button class="btn btn-light thumb-remove" type="button" data-icon-remove-existing="' + i + '">移除</button>'
                    + '</div>'
                )
            }

            for (let i = 0; i < state.pendingIconFiles.length; i += 1) {
                const item = state.pendingIconFiles[i]
                cards.push(
                    '<div class="thumb-card">'
                    + '<img class="thumb" src="' + escapeHtml(item.previewUrl || '') + '" alt="pending-icon-' + (i + 1) + '" />'
                    + '<div class="thumb-caption">待上传</div>'
                    + '<button class="btn btn-light thumb-remove" type="button" data-icon-remove-pending="' + escapeHtml(item.key) + '">移除</button>'
                    + '</div>'
                )
            }

            iconPreviewListEl.innerHTML = cards.join('')
            iconEmptyEl.style.display = cards.length ? 'none' : 'block'
        }

        function sortParameterList(rows) {
            return rows
                .slice()
                .sort(function (a, b) {
                    const sortA = normalizePositiveInteger(a.sort)
                    const sortB = normalizePositiveInteger(b.sort)
                    const safeSortA = sortA === null ? Number.MAX_SAFE_INTEGER : sortA
                    const safeSortB = sortB === null ? Number.MAX_SAFE_INTEGER : sortB
                    if (safeSortA !== safeSortB) {
                        return safeSortA - safeSortB
                    }
                    return Number(a.__inputIndex || 0) - Number(b.__inputIndex || 0)
                })
                .map(function (item) {
                    return buildParameterRow(item.name, item.value, item.sort)
                })
        }

        function renderParameterRows() {
            if (!state.parameterRows.length) {
                paramsBodyEl.innerHTML = '<tr><td colspan="3" class="empty">暂无属性，点击“新增参数行”或使用 JSON 批量导入。</td></tr>'
                return
            }

            paramsBodyEl.innerHTML = state.parameterRows.map(function (row, index) {
                return '<tr>'
                    + '<td data-label="属性名"><input data-param-name="' + index + '" value="' + escapeHtml(row.name) + '" placeholder="属性名" /></td>'
                    + '<td data-label="属性值"><input data-param-value="' + index + '" value="' + escapeHtml(row.value) + '" placeholder="属性值" /></td>'
                    + '<td data-label="操作 / 排序"><div class="param-op">'
                    + '<input data-param-sort="' + index + '" type="number" min="1" step="1" value="' + escapeHtml(row.sort || '') + '" placeholder="正整数排序" />'
                    + '<button class="btn btn-light" type="button" data-param-remove="' + index + '">删除</button>'
                    + '</div></td>'
                    + '</tr>'
            }).join('')
        }

        function renderFunctionRows() {
            if (!state.functionRows.length) {
                functionsBodyEl.innerHTML = '<tr><td colspan="3" class="empty">暂无功能，点击“新增功能行”或使用 JSON 批量导入。</td></tr>'
                return
            }

            functionsBodyEl.innerHTML = state.functionRows.map(function (row, index) {
                return '<tr>'
                    + '<td data-label="功能名"><input data-function-name="' + index + '" value="' + escapeHtml(row.name) + '" placeholder="功能名" /></td>'
                    + '<td data-label="功能说明"><input data-function-value="' + index + '" value="' + escapeHtml(row.value) + '" placeholder="功能说明" /></td>'
                    + '<td data-label="操作 / 排序"><div class="param-op">'
                    + '<input data-function-sort="' + index + '" type="number" min="1" step="1" value="' + escapeHtml(row.sort || '') + '" placeholder="正整数排序" />'
                    + '<button class="btn btn-light" type="button" data-function-remove="' + index + '">删除</button>'
                    + '</div></td>'
                    + '</tr>'
            }).join('')
        }

        function buildVipPriceRow(viplevel, viplevelName, price) {
            return {
                viplevel: normalizeText(viplevel),
                viplevel_name: normalizeText(viplevelName),
                price: normalizeText(price),
            }
        }

        function getUserLevelDictItems() {
            return getDictItems(dictNameMap.userLevel)
        }

        function normalizeVipPriceRows(value) {
            const dictItems = getUserLevelDictItems()
            const existingMap = {}
            let source = value

            if (typeof source === 'string' && normalizeText(source)) {
                try {
                    source = JSON.parse(source)
                } catch (_error) {
                    source = []
                }
            }

            if (Array.isArray(source)) {
                for (let i = 0; i < source.length; i += 1) {
                    const item = source[i] && typeof source[i] === 'object' ? source[i] : null
                    if (!item) {
                        continue
                    }
                    const key = normalizeText(item.viplevel)
                    if (!key) {
                        continue
                    }
                    existingMap[key] = normalizeText(item.price)
                }
            }

            const rows = []
            for (let i = 0; i < dictItems.length; i += 1) {
                const item = dictItems[i]
                const valueKey = normalizeText(item.enum)
                if (!valueKey) {
                    continue
                }
                rows.push(buildVipPriceRow(
                    valueKey,
                    item.description || valueKey,
                    Object.prototype.hasOwnProperty.call(existingMap, valueKey) ? existingMap[valueKey] : ''
                ))
            }

            return rows
        }

        function renderVipPriceRows() {
            if (!state.vipPriceRows.length) {
                vipPriceBodyEl.innerHTML = '<tr><td colspan="2" class="empty">未找到“数据-会员等级”字典，请先维护该字典。</td></tr>'
                return
            }

            vipPriceBodyEl.innerHTML = state.vipPriceRows.map(function (row, index) {
                return '<tr>'
                    + '<td data-label="会员等级"><div><strong>' + escapeHtml(row.viplevel_name || row.viplevel) + '</strong></div><div class="muted">' + escapeHtml(row.viplevel || '-') + '</div></td>'
                    + '<td data-label="会员价格"><input data-vip-price="' + index + '" type="number" min="0" step="0.01" value="' + escapeHtml(row.price) + '" placeholder="留空表示不设置会员价" /></td>'
                    + '</tr>'
            }).join('')
        }

        function collectVipPriceArray() {
            const result = []
            for (let i = 0; i < state.vipPriceRows.length; i += 1) {
                const row = state.vipPriceRows[i]
                const rawPrice = normalizeText(row.price)
                if (!rawPrice) {
                    continue
                }

                const num = Number(rawPrice)
                if (!Number.isFinite(num)) {
                    throw new Error('第 ' + (i + 1) + ' 行会员价必须为数字')
                }

                result.push({
                    viplevel: row.viplevel,
                    price: num,
                })
            }

            return result
        }

        function importParametersFromJson() {
            const raw = normalizeText(fields.paramsJsonInput.value)
            if (!raw) {
                setStatus('请先输入 JSON 内容。', 'error')
                return
            }

            let parsed = null
            try {
                parsed = JSON.parse(raw)
            } catch (_error) {
                setStatus('JSON 格式错误，请检查后重试。', 'error')
                return
            }

            if (Array.isArray(parsed)) {
                const invalidRow = findInvalidParameterSortRow(parsed)
                if (invalidRow) {
                    setStatus('导入失败：第 ' + invalidRow + ' 项 sort 必须为正整数。', 'error')
                    return
                }
            }

            const incomingRows = normalizeParameterRows(parsed)
            if (!incomingRows.length) {
                setStatus('导入内容为空，请至少提供一个有效属性。', 'error')
                return
            }

            const existingIndexMap = {}
            for (let i = 0; i < state.parameterRows.length; i += 1) {
                const name = normalizeText(state.parameterRows[i].name)
                if (!name) {
                    continue
                }
                if (!Object.prototype.hasOwnProperty.call(existingIndexMap, name)) {
                    existingIndexMap[name] = i
                }
            }

            let addedCount = 0
            let updatedCount = 0
            for (let i = 0; i < incomingRows.length; i += 1) {
                const row = incomingRows[i]
                const idx = existingIndexMap[row.name]
                if (typeof idx === 'number') {
                    state.parameterRows[idx] = buildParameterRow(row.name, row.value, row.sort)
                    updatedCount += 1
                } else {
                    state.parameterRows.push(buildParameterRow(row.name, row.value, row.sort))
                    existingIndexMap[row.name] = state.parameterRows.length - 1
                    addedCount += 1
                }
            }

            renderParameterRows()
            setStatus('参数增量导入完成：新增 ' + addedCount + ' 项，更新 ' + updatedCount + ' 项，当前共 ' + state.parameterRows.length + ' 项。', 'success')
        }

        function importFunctionsFromJson() {
            const raw = normalizeText(fields.functionsJsonInput.value)
            if (!raw) {
                setStatus('请先输入功能 JSON 内容。', 'error')
                return
            }

            let parsed = null
            try {
                parsed = JSON.parse(raw)
            } catch (_error) {
                setStatus('功能 JSON 格式错误，请检查后重试。', 'error')
                return
            }

            if (Array.isArray(parsed)) {
                const invalidRow = findInvalidParameterSortRow(parsed)
                if (invalidRow) {
                    setStatus('功能导入失败：第 ' + invalidRow + ' 项 sort 必须为正整数。', 'error')
                    return
                }
            }

            const incomingRows = normalizeFunctionRows(parsed)
            if (!incomingRows.length) {
                setStatus('导入内容为空，请至少提供一个有效功能。', 'error')
                return
            }

            const existingIndexMap = {}
            for (let i = 0; i < state.functionRows.length; i += 1) {
                const name = normalizeText(state.functionRows[i].name)
                if (!name) {
                    continue
                }
                if (!Object.prototype.hasOwnProperty.call(existingIndexMap, name)) {
                    existingIndexMap[name] = i
                }
            }

            let addedCount = 0
            let updatedCount = 0
            for (let i = 0; i < incomingRows.length; i += 1) {
                const row = incomingRows[i]
                const idx = existingIndexMap[row.name]
                if (typeof idx === 'number') {
                    state.functionRows[idx] = buildParameterRow(row.name, row.value, row.sort)
                    updatedCount += 1
                } else {
                    state.functionRows.push(buildParameterRow(row.name, row.value, row.sort))
                    existingIndexMap[row.name] = state.functionRows.length - 1
                    addedCount += 1
                }
            }

            renderFunctionRows()
            setStatus('功能增量导入完成：新增 ' + addedCount + ' 项，更新 ' + updatedCount + ' 项，当前共 ' + state.functionRows.length + ' 项。', 'success')
        }

        function collectParameterArray() {
            const result = []
            const indexByName = {}
            let nextAutoSort = 1

            function getAutoSort() {
                while (result.some(function (item) { return Number(item.sort) === nextAutoSort })) {
                    nextAutoSort += 1
                }
                const current = nextAutoSort
                nextAutoSort += 1
                return current
            }

            for (let i = 0; i < state.parameterRows.length; i += 1) {
                const name = normalizeText(state.parameterRows[i].name)
                const value = normalizeText(state.parameterRows[i].value)
                if (!name) {
                    continue
                }

                const rawSort = normalizeText(state.parameterRows[i].sort)
                const explicitSort = normalizePositiveInteger(rawSort)
                if (rawSort && explicitSort === null) {
                    throw new Error('第 ' + (i + 1) + ' 行参数排序必须为正整数')
                }
                const sortValue = explicitSort === null ? getAutoSort() : explicitSort
                const existingIndex = indexByName[name]
                if (typeof existingIndex === 'number') {
                    result[existingIndex].value = value
                    result[existingIndex].sort = sortValue
                } else {
                    indexByName[name] = result.length
                    result.push({
                        name: name,
                        value: value,
                        sort: sortValue,
                        __inputIndex: i + 1,
                    })
                }
            }

            return sortParameterList(result)
        }

        function collectFunctionArray() {
            const result = []
            const indexByName = {}
            let nextAutoSort = 1

            function getAutoSort() {
                while (result.some(function (item) { return Number(item.sort) === nextAutoSort })) {
                    nextAutoSort += 1
                }
                const current = nextAutoSort
                nextAutoSort += 1
                return current
            }

            for (let i = 0; i < state.functionRows.length; i += 1) {
                const name = normalizeText(state.functionRows[i].name)
                const value = normalizeText(state.functionRows[i].value)
                if (!name) {
                    continue
                }

                const rawSort = normalizeText(state.functionRows[i].sort)
                const explicitSort = normalizePositiveInteger(rawSort)
                if (rawSort && explicitSort === null) {
                    throw new Error('第 ' + (i + 1) + ' 行功能排序必须为正整数')
                }
                const sortValue = explicitSort === null ? getAutoSort() : explicitSort
                const existingIndex = indexByName[name]
                if (typeof existingIndex === 'number') {
                    result[existingIndex].value = value
                    result[existingIndex].sort = sortValue
                } else {
                    indexByName[name] = result.length
                    result.push({
                        name: name,
                        value: value,
                        sort: sortValue,
                        __inputIndex: i + 1,
                    })
                }
            }

            return sortParameterList(result)
        }

        async function exportParametersToJson() {
            let rows = []
            try {
                rows = collectParameterArray()
            } catch (err) {
                setStatus(err.message || '参数导出失败', 'error')
                return
            }

            if (!rows.length) {
                setStatus('当前参数表为空，暂无可导出的内容。', 'error')
                return
            }

            const jsonText = JSON.stringify(rows, null, 2)
            fields.paramsJsonInput.value = jsonText

            let copied = false
            try {
                if (navigator && navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
                    await navigator.clipboard.writeText(jsonText)
                    copied = true
                }
            } catch (_error) {}

            setStatus(copied
                ? '参数已批量导出，已写入导入框并复制到剪贴板。'
                : '参数已批量导出到导入框，可直接复制后用于一键导入。', 'success')
        }

        async function exportFunctionsToJson() {
            let rows = []
            try {
                rows = collectFunctionArray()
            } catch (err) {
                setStatus(err.message || '功能导出失败', 'error')
                return
            }

            if (!rows.length) {
                setStatus('当前功能表为空，暂无可导出的内容。', 'error')
                return
            }

            const jsonText = JSON.stringify(rows, null, 2)
            fields.functionsJsonInput.value = jsonText

            let copied = false
            try {
                if (navigator && navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
                    await navigator.clipboard.writeText(jsonText)
                    copied = true
                }
            } catch (_error) {}

            setStatus(copied
                ? '功能已批量导出，已写入导入框并复制到剪贴板。'
                : '功能已批量导出到导入框，可直接复制后用于一键导入。', 'success')
        }

        function normalizeParameterRows(value) {
            if (value === null || typeof value === 'undefined' || value === '') {
                return []
            }

            let source = value
            if (typeof source === 'string') {
                try {
                    source = JSON.parse(source)
                } catch (_error) {
                    return []
                }
            }

            const rows = []
            const indexByName = {}

            function upsert(nameValue, rawValue, rawSort, inputIndex) {
                const name = normalizeText(nameValue)
                if (!name) {
                    return
                }

                const currentRow = buildParameterRow(name, rawValue, rawSort || inputIndex)
                const existingIndex = indexByName[name]
                if (typeof existingIndex === 'number') {
                    rows[existingIndex] = Object.assign({ __inputIndex: inputIndex }, currentRow)
                } else {
                    indexByName[name] = rows.length
                    rows.push(Object.assign({ __inputIndex: inputIndex }, currentRow))
                }
            }

            if (Array.isArray(source)) {
                for (let i = 0; i < source.length; i += 1) {
                    const item = source[i] && typeof source[i] === 'object' ? source[i] : null
                    if (!item) {
                        continue
                    }
                    upsert(item.name || item.key, item.value, item.sort, i + 1)
                }
                return sortParameterList(rows)
            }

            if (typeof source !== 'object') {
                return []
            }

            try {
                source = JSON.parse(JSON.stringify(source))
            } catch (_error) {}

            const keys = Object.keys(source)
            for (let i = 0; i < keys.length; i += 1) {
                upsert(keys[i], source[keys[i]], i + 1, i + 1)
            }

            return sortParameterList(rows)
        }

        function normalizeFunctionRows(value) {
            if (value === null || typeof value === 'undefined' || value === '') {
                return []
            }

            let source = value
            if (typeof source === 'string') {
                try {
                    source = JSON.parse(source)
                } catch (_error) {
                    return []
                }
            }

            const rows = []
            const indexByName = {}

            function upsert(nameValue, rawValue, rawSort, inputIndex) {
                const name = normalizeText(nameValue)
                if (!name) {
                    return
                }

                const currentRow = buildParameterRow(name, rawValue, rawSort || inputIndex)
                const existingIndex = indexByName[name]
                if (typeof existingIndex === 'number') {
                    rows[existingIndex] = Object.assign({ __inputIndex: inputIndex }, currentRow)
                } else {
                    indexByName[name] = rows.length
                    rows.push(Object.assign({ __inputIndex: inputIndex }, currentRow))
                }
            }

            if (Array.isArray(source)) {
                for (let i = 0; i < source.length; i += 1) {
                    const item = source[i] && typeof source[i] === 'object' ? source[i] : null
                    if (!item) {
                        continue
                    }
                    upsert(item.name || item.key, item.value, item.sort, i + 1)
                }
                return sortParameterList(rows)
            }

            if (typeof source !== 'object') {
                return []
            }

            try {
                source = JSON.parse(JSON.stringify(source))
            } catch (_error) {}

            const keys = Object.keys(source)
            for (let i = 0; i < keys.length; i += 1) {
                upsert(keys[i], source[keys[i]], i + 1, i + 1)
            }

            return sortParameterList(rows)
        }

        function updateEditorMode() {
            editorPanelEl.classList.toggle('show', state.mode === 'create' || state.mode === 'edit')
            const submitBtn = document.getElementById('submitBtn')
            if (state.mode === 'edit') {
                formTitleEl.textContent = '编辑产品'
                editorModeEl.textContent = '当前模式：编辑 ' + (state.editingSource ? state.editingSource.prod_list_name : state.editingId)
                submitBtn.textContent = '保存产品修改'
            } else if (state.mode === 'create') {
                formTitleEl.textContent = '新增产品'
                editorModeEl.textContent = '当前模式：新建'
                submitBtn.textContent = '创建产品'
            } else {
                formTitleEl.textContent = '新增产品'
                editorModeEl.textContent = '当前模式：未打开编辑区'
                submitBtn.textContent = '创建产品'
            }
        }

        function resetForm() {
            fields.name.value = ''
            fields.model.value = ''
            fields.barcode.value = ''
            fields.basicPrice.value = ''
            fields.feature.value = ''
            fields.sort.value = '0'
            fields.description.value = ''
            fields.remark.value = ''
            fields.paramsJsonInput.value = ''
            fields.functionsJsonInput.value = ''
            fields.customTag.value = ''
            fields.iconFile.value = ''
            state.productStatus = '有效'
            state.selections.plantype = []
            state.selections.category = []
            state.selections.commType = []
            state.selections.series = []
            state.selections.powerSupply = []
            state.selections.tags = []
            state.parameterRows = []
            state.functionRows = []
            state.vipPriceRows = normalizeVipPriceRows([])
            state.currentIconAttachments = []
            clearPendingIconPreview()
            renderIconPreview()
            renderProductStatusSelector()
            renderAllMultiOptionLists()
            renderTagOptions()
            renderParameterRows()
            renderFunctionRows()
            renderVipPriceRows()
            renderSortRankHint()
        }

        function enterCreateMode() {
            state.mode = 'create'
            state.editingId = ''
            state.editingSource = null
            resetForm()
            updateEditorMode()
        }

        function enterIdleMode() {
            state.mode = 'idle'
            state.editingId = ''
            state.editingSource = null
            resetForm()
            updateEditorMode()
        }

        function fillFormFromItem(item) {
            fields.name.value = item.prod_list_name || ''
            fields.model.value = item.prod_list_modelnumber || ''
            fields.barcode.value = item.prod_list_barcode || ''
            state.productStatus = item.prod_list_status || '有效'
            fields.basicPrice.value = item.prod_list_basic_price === null || typeof item.prod_list_basic_price === 'undefined' ? '' : String(item.prod_list_basic_price)
            fields.feature.value = item.prod_list_feature || ''
            fields.sort.value = item.prod_list_sort === null || typeof item.prod_list_sort === 'undefined' ? '0' : String(item.prod_list_sort)
            fields.description.value = item.prod_list_description || ''
            fields.remark.value = item.prod_list_remark || ''
            fields.iconFile.value = ''
            fields.customTag.value = ''

            state.selections.plantype = splitPipe(item.prod_list_plantype)
            state.selections.category = splitPipe(item.prod_list_category).slice(0, 1)
            state.selections.commType = splitPipe(item.prod_list_comm_type)
            state.selections.series = splitPipe(item.prod_list_series)
            state.selections.powerSupply = splitPipe(item.prod_list_power_supply)
            state.selections.tags = splitPipe(item.prod_list_tags)
            state.parameterRows = normalizeParameterRows(item.prod_list_parameters)
            state.functionRows = normalizeFunctionRows(item.prod_list_function)
            state.vipPriceRows = normalizeVipPriceRows(item.prod_list_vip_price)
            state.currentIconAttachments = Array.isArray(item.prod_list_icon_attachments)
                ? item.prod_list_icon_attachments.slice()
                : (item.prod_list_icon_attachment ? [item.prod_list_icon_attachment] : [])
            clearPendingIconPreview()

            renderProductStatusSelector()
            renderAllMultiOptionLists()
            renderTagOptions()
            renderParameterRows()
            renderFunctionRows()
            renderVipPriceRows()
            renderIconPreview()
            renderSortRankHint()
        }

        async function loadDictionaries() {
            showLoading('正在加载字典...')
            try {
                const data = await requestJson('/dictionary/list', {})
                const items = Array.isArray(data.items) ? data.items : []
                state.dictionariesByName = {}
                for (let i = 0; i < items.length; i += 1) {
                    state.dictionariesByName[items[i].dict_name] = items[i]
                }
                renderFilterOptions()
                state.vipPriceRows = normalizeVipPriceRows(state.vipPriceRows)
                renderFormOptions()
            } finally {
                hideLoading()
            }
        }

        function renderTable() {
            if (!state.list.length) {
                tableBodyEl.innerHTML = '<tr><td colspan="6" class="empty">暂无产品数据。</td></tr>'
                return
            }

            tableBodyEl.innerHTML = state.list.map(function (item) {
                return '<tr>'
                    + '<td data-label="名称/型号"><div><strong>' + escapeHtml(item.prod_list_name || '') + '</strong></div><div class="muted">型号：' + escapeHtml(item.prod_list_modelnumber || '-') + '</div><div class="muted">料号：' + escapeHtml(item.prod_list_barcode || '-') + '</div></td>'
                    + '<td data-label="分类信息"><div>' + escapeHtml(item.prod_list_category || '-') + '</div><div class="muted">方案:' + escapeHtml(item.prod_list_plantype || '-') + ' / 系列:' + escapeHtml(item.prod_list_series || '-') + '</div><div class="muted">分类排序: ' + escapeHtml(item.prod_list_sort === null || typeof item.prod_list_sort === 'undefined' ? '-' : item.prod_list_sort) + '（第 ' + escapeHtml(item.prod_list_category_rank || '-') + ' 位）</div></td>'
                    + '<td data-label="标签"><div>' + escapeHtml(item.prod_list_tags || '-') + '</div></td>'
                    + '<td data-label="状态/价格"><div>' + escapeHtml(item.prod_list_status || '-') + '</div><div class="muted">¥' + escapeHtml(item.prod_list_basic_price === null || typeof item.prod_list_basic_price === 'undefined' ? '-' : item.prod_list_basic_price) + '</div></td>'
                    + '<td data-label="更新时间"><span class="muted">' + escapeHtml(item.updated || '') + '</span></td>'
                    + '<td data-label="操作"><div class="table-actions">'
                    + '<button class="btn btn-light" type="button" onclick="window.__editProduct(\'' + encodeURIComponent(item.prod_list_id) + '\')">编辑</button>'
                    + '<button class="btn btn-secondary" type="button" onclick="window.__copyProduct(\'' + encodeURIComponent(item.prod_list_id) + '\')">复制</button>'
                    + '<button class="btn btn-danger" type="button" onclick="window.__deleteProduct(\'' + encodeURIComponent(item.prod_list_id) + '\')">删除</button>'
                    + '</div></td>'
                    + '</tr>'
            }).join('')
        }

        function buildCopyPayload(source, nextName, nextModel, nextBarcode) {
            return {
                id: generatePocketBaseRecordId(),
                prod_list_id: '',
                prod_list_name: normalizeText(nextName),
                prod_list_modelnumber: normalizeText(nextModel),
                prod_list_barcode: normalizeText(nextBarcode),
                prod_list_icon: source && source.prod_list_icon ? normalizeText(source.prod_list_icon) : '',
                prod_list_description: source && source.prod_list_description ? normalizeText(source.prod_list_description) : '',
                prod_list_feature: source && source.prod_list_feature ? normalizeText(source.prod_list_feature) : '',
                prod_list_parameters: normalizeParameterRows(source ? source.prod_list_parameters : []),
                prod_list_function: normalizeFunctionRows(source ? source.prod_list_function : []),
                prod_list_plantype: source && source.prod_list_plantype ? normalizeText(source.prod_list_plantype) : '',
                prod_list_category: source && source.prod_list_category ? normalizeText(source.prod_list_category) : '',
                prod_list_sort: source && (source.prod_list_sort || source.prod_list_sort === 0) ? Number(source.prod_list_sort) : 0,
                prod_list_comm_type: source && source.prod_list_comm_type ? normalizeText(source.prod_list_comm_type) : '',
                prod_list_series: source && source.prod_list_series ? normalizeText(source.prod_list_series) : '',
                prod_list_power_supply: source && source.prod_list_power_supply ? normalizeText(source.prod_list_power_supply) : '',
                prod_list_tags: source && source.prod_list_tags ? normalizeText(source.prod_list_tags) : '',
                prod_list_status: source && source.prod_list_status ? normalizeText(source.prod_list_status) : '有效',
                prod_list_basic_price: source && !(source.prod_list_basic_price === null || typeof source.prod_list_basic_price === 'undefined')
                    ? String(source.prod_list_basic_price)
                    : '',
                prod_list_vip_price: source && Array.isArray(source.prod_list_vip_price) ? source.prod_list_vip_price : [],
                prod_list_remark: source && source.prod_list_remark ? normalizeText(source.prod_list_remark) : '',
            }
        }

        function openCopyModal(source) {
            state.copySourceProductId = source ? normalizeText(source.prod_list_id) : ''
            const defaultName = normalizeText(source && source.prod_list_name)
            const defaultModel = normalizeText(source && source.prod_list_modelnumber)
            const defaultBarcode = normalizeText(source && source.prod_list_barcode)

            copyNameInputEl.value = defaultName ? (defaultName + '-副本') : ''
            copyModelInputEl.value = defaultModel
            copyBarcodeInputEl.value = defaultBarcode
            copyModalEl.classList.add('show')

            setTimeout(function () {
                copyNameInputEl.focus()
                copyNameInputEl.select()
            }, 0)
        }

        function closeCopyModal() {
            state.copySourceProductId = ''
            copyModalEl.classList.remove('show')
            copyNameInputEl.value = ''
            copyModelInputEl.value = ''
            copyBarcodeInputEl.value = ''
        }

        async function copyProduct(productId) {
            const source = state.fullList.find(function (item) {
                return normalizeText(item.prod_list_id) === normalizeText(productId)
            })

            if (!source) {
                setStatus('未找到要复制的产品。', 'error')
                return
            }

            openCopyModal(source)
        }

        async function confirmCopyProduct() {
            const sourceId = normalizeText(state.copySourceProductId)
            if (!sourceId) {
                setStatus('复制失败：未找到源产品。', 'error')
                closeCopyModal()
                return
            }

            const source = state.fullList.find(function (item) {
                return normalizeText(item.prod_list_id) === sourceId
            })
            if (!source) {
                setStatus('复制失败：源产品不存在。', 'error')
                closeCopyModal()
                return
            }

            const normalizedName = normalizeText(copyNameInputEl.value)
            if (!normalizedName) {
                setStatus('复制失败：新产品名称不能为空。', 'error')
                copyNameInputEl.focus()
                return
            }

            const payload = buildCopyPayload(source, normalizedName, copyModelInputEl.value, copyBarcodeInputEl.value)

            setStatus('正在复制产品...', '')
            showLoading('正在复制产品，请稍候...')
            try {
                const created = await requestJson('/product/create', payload)
                closeCopyModal()
                await loadProducts()
                if (created && created.prod_list_id) {
                    await enterEditMode(created.prod_list_id)
                }
                setStatus('复制产品成功。', 'success')
            } catch (err) {
                setStatus(err.message || '复制产品失败', 'error')
            } finally {
                hideLoading()
            }
        }

        async function loadProducts() {
            setStatus('正在加载产品列表...', '')
            showLoading('正在加载产品列表...')
            try {
                const payload = {
                    keyword: normalizeText(fields.keyword.value),
                    status: normalizeText(statusFilterEl.value),
                    prod_list_category: normalizeText(categoryFilterEl.value),
                }
                const data = await requestJson('/product/list', {
                    keyword: payload.keyword,
                    status: payload.status,
                    prod_list_category: payload.prod_list_category,
                })
                state.fullList = Array.isArray(data.items) ? data.items : []
                updateProductListView()
                setStatus('产品列表已刷新，共 ' + state.pagination.total + ' 条。', 'success')
            } catch (err) {
                try {
                    state.fullList = await requestProductListFallback({
                        keyword: normalizeText(fields.keyword.value),
                        status: normalizeText(statusFilterEl.value),
                        prod_list_category: normalizeText(categoryFilterEl.value),
                    })
                    updateProductListView()
                    setStatus('产品列表已通过回退链路刷新，共 ' + state.pagination.total + ' 条。', 'success')
                } catch (_fallbackErr) {
                    setStatus(err.message || '加载产品列表失败', 'error')
                }
            } finally {
                hideLoading()
            }
        }

        async function submitProductSilently() {
            if (state.mode !== 'create' && state.mode !== 'edit') {
                return true
            }

            if (!normalizeText(fields.name.value)) {
                return false
            }

            try {
                await submitProduct()
                return true
            } catch (_err) {
                return false
            }
        }

        async function saveAndHideEditorBeforeProductQuery() {
            if (state.mode === 'create' || state.mode === 'edit') {
                try {
                    await submitProductSilently()
                } catch (_error) {}
                enterIdleMode()
            }
        }

        async function queryProductsWithAutoSave(resetPage) {
            if (resetPage) {
                state.pagination.page = 1
            }
            await saveAndHideEditorBeforeProductQuery()
            await loadProducts()
        }

        function goToProductPage(page) {
            const nextPage = clampPage(page, state.pagination.totalPages)
            if (nextPage === state.pagination.page) {
                return
            }

            state.pagination.page = nextPage
            updateProductListView()
        }

        function setProductPageSize(pageSize) {
            const nextPageSize = Number(pageSize) || 10
            if (nextPageSize === state.pagination.pageSize) {
                return
            }

            state.pagination.pageSize = nextPageSize
            state.pagination.page = 1
            updateProductListView()
        }

        async function enterEditMode(productId) {
            showLoading('正在加载产品详情...')
            try {
                const detail = await requestJson('/product/detail', { prod_list_id: productId })
                state.mode = 'edit'
                state.editingId = detail.prod_list_id
                state.editingSource = detail
                fillFormFromItem(detail)
                updateEditorMode()
                setStatus('已进入编辑模式：' + (detail.prod_list_name || detail.prod_list_id), 'success')
                window.scrollTo({ top: 0, behavior: 'smooth' })
            } catch (err) {
                setStatus(err.message || '加载产品详情失败', 'error')
            } finally {
                hideLoading()
            }
        }

        function addCustomTag() {
            const value = normalizeText(fields.customTag.value)
            if (!value) {
                return
            }
            if (state.selections.tags.indexOf(value) === -1) {
                state.selections.tags.push(value)
            }
            fields.customTag.value = ''
            renderTagOptions()
        }

        async function cleanupUploadedAttachments(uploadedAttachments) {
            for (let i = 0; i < uploadedAttachments.length; i += 1) {
                try {
                    await requestJson('/attachment/delete', { attachments_id: uploadedAttachments[i].attachments_id })
                } catch (_error) {}
            }
        }

        async function submitProduct() {
            if (!normalizeText(fields.name.value)) {
                setStatus('请先填写产品名称。', 'error')
                return
            }

            setStatus(state.mode === 'edit' ? '正在保存产品修改...' : '正在创建产品...', '')
            showLoading(state.mode === 'edit' ? '正在保存产品修改...' : '正在创建产品...')

            const uploadedAttachments = []
            let finalIconIds = getCollectedIconIds()

            try {
                for (let i = 0; i < state.pendingIconFiles.length; i += 1) {
                    const pendingFile = state.pendingIconFiles[i]
                    if (!pendingFile || !pendingFile.file) {
                        continue
                    }

                    const uploaded = await uploadAttachment(pendingFile.file)
                    uploadedAttachments.push(uploaded)
                    if (uploaded && uploaded.attachments_id) {
                        finalIconIds.push(uploaded.attachments_id)
                    }
                }

                const payload = {
                    id: state.mode === 'edit' ? '' : generatePocketBaseRecordId(),
                    prod_list_id: state.mode === 'edit' ? state.editingId : '',
                    prod_list_name: normalizeText(fields.name.value),
                    prod_list_modelnumber: normalizeText(fields.model.value),
                    prod_list_barcode: normalizeText(fields.barcode.value),
                    prod_list_icon: joinPipe(finalIconIds),
                    prod_list_description: normalizeText(fields.description.value),
                    prod_list_feature: normalizeText(fields.feature.value),
                    prod_list_parameters: collectParameterArray(),
                    prod_list_function: collectFunctionArray(),
                    prod_list_plantype: joinPipe(state.selections.plantype),
                    prod_list_category: joinPipe(state.selections.category),
                    prod_list_comm_type: joinPipe(state.selections.commType),
                    prod_list_series: joinPipe(state.selections.series),
                    prod_list_power_supply: joinPipe(state.selections.powerSupply),
                    prod_list_tags: joinPipe(state.selections.tags),
                    prod_list_status: normalizeText(state.productStatus) || '有效',
                    prod_list_basic_price: normalizeText(fields.basicPrice.value),
                    prod_list_vip_price: collectVipPriceArray(),
                    prod_list_sort: normalizeText(fields.sort.value),
                    prod_list_remark: normalizeText(fields.remark.value),
                }

                if (!payload.prod_list_basic_price) {
                    payload.prod_list_basic_price = ''
                }

                let saved = null
                if (state.mode === 'edit') {
                    saved = await requestJson('/product/update', payload)
                } else {
                    saved = await requestJson('/product/create', payload)
                }

                await loadProducts()
                if (saved && saved.prod_list_id) {
                    await enterEditMode(saved.prod_list_id)
                } else {
                    enterCreateMode()
                }
                setStatus(state.mode === 'edit' ? '产品修改成功。' : '产品创建成功。', 'success')
            } catch (err) {
                if (uploadedAttachments.length) {
                    await cleanupUploadedAttachments(uploadedAttachments)
                }
                setStatus(err.message || (state.mode === 'edit' ? '修改产品失败' : '创建产品失败'), 'error')
            } finally {
                hideLoading()
            }
        }

        async function deleteProduct(productId) {
            if (!window.confirm('确认删除产品「' + productId + '」吗？')) {
                return
            }

            showLoading('正在删除产品...')
            try {
                await requestJson('/product/delete', { prod_list_id: productId })
                if (state.mode === 'edit' && state.editingId === productId) {
                    enterIdleMode()
                }
                await loadProducts()
                setStatus('产品删除成功。', 'success')
            } catch (err) {
                setStatus(err.message || '删除产品失败', 'error')
            } finally {
                hideLoading()
            }
        }

        window.__editProduct = function (encodedProductId) {
            enterEditMode(decodeURIComponent(encodedProductId))
        }
        window.__deleteProduct = function (encodedProductId) {
            deleteProduct(decodeURIComponent(encodedProductId))
        }
        window.__copyProduct = function (encodedProductId) {
            copyProduct(decodeURIComponent(encodedProductId))
        }

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

            if (target.matches('input[data-multi-field]')) {
                const fieldName = normalizeText(target.getAttribute('data-multi-field'))
                if (!Object.prototype.hasOwnProperty.call(state.selections, fieldName)) {
                    return
                }

                const value = normalizeText(target.value)
                if (fieldName === 'category') {
                    state.selections.category = target.checked && value ? [value] : []
                } else {
                    state.selections[fieldName] = (state.selections[fieldName] || []).filter(function (item) {
                        return item !== value
                    })
                    if (target.checked && value) {
                        state.selections[fieldName].push(value)
                    }
                }
                renderMultiOptionList(fieldName)
                if (fieldName === 'category') {
                    renderSortRankHint()
                }
                return
            }

            if (target.matches('input[data-status-one]')) {
                const value = normalizeText(target.value)
                if (productStatusOptions.indexOf(value) === -1) {
                    return
                }

                if (value === '自定义') {
                    const customInput = document.getElementById('prodStatusCustomInput')
                    const customValue = customInput ? normalizeText(customInput.value) : ''
                    state.productStatus = customValue || '自定义'
                } else {
                    state.productStatus = value
                }
                renderProductStatusSelector()
                return
            }

            if (target.matches('input[data-status-custom]')) {
                state.productStatus = normalizeText(target.value) || '自定义'
                renderProductStatusSelector()
                return
            }

            if (target.matches('input[data-tag]')) {
                const value = normalizeText(target.getAttribute('data-tag'))
                state.selections.tags = state.selections.tags.filter(function (item) {
                    return item !== value
                })
                if (target.checked) {
                    state.selections.tags.push(value)
                }
                renderTagOptions()
                return
            }

            if (target.matches('input[data-param-name]')) {
                const index = Number(target.getAttribute('data-param-name'))
                if (Number.isInteger(index) && state.parameterRows[index]) {
                    state.parameterRows[index].name = target.value
                }
                return
            }

            if (target.matches('input[data-param-value]')) {
                const index = Number(target.getAttribute('data-param-value'))
                if (Number.isInteger(index) && state.parameterRows[index]) {
                    state.parameterRows[index].value = target.value
                }
                return
            }

            if (target.matches('input[data-param-sort]')) {
                const index = Number(target.getAttribute('data-param-sort'))
                if (Number.isInteger(index) && state.parameterRows[index]) {
                    state.parameterRows[index].sort = target.value
                }
                return
            }

            if (target.matches('input[data-function-name]')) {
                const index = Number(target.getAttribute('data-function-name'))
                if (Number.isInteger(index) && state.functionRows[index]) {
                    state.functionRows[index].name = target.value
                }
                return
            }

            if (target.matches('input[data-function-value]')) {
                const index = Number(target.getAttribute('data-function-value'))
                if (Number.isInteger(index) && state.functionRows[index]) {
                    state.functionRows[index].value = target.value
                }
                return
            }

            if (target.matches('input[data-function-sort]')) {
                const index = Number(target.getAttribute('data-function-sort'))
                if (Number.isInteger(index) && state.functionRows[index]) {
                    state.functionRows[index].sort = target.value
                }
                return
            }

            if (target.matches('input[data-vip-price]')) {
                const index = Number(target.getAttribute('data-vip-price'))
                if (Number.isInteger(index) && state.vipPriceRows[index]) {
                    state.vipPriceRows[index].price = target.value
                }
                return
            }

            if (target === fields.iconFile) {
                const fileList = target.files ? Array.prototype.slice.call(target.files) : []
                if (!fileList.length) {
                    return
                }

                for (let i = 0; i < fileList.length; i += 1) {
                    const file = fileList[i]
                    state.pendingIconFiles.push({
                        key: 'pending-' + Date.now() + '-' + i + '-' + Math.random().toString(36).slice(2, 8),
                        file: file,
                        previewUrl: URL.createObjectURL(file),
                    })
                }
                fields.iconFile.value = ''
                renderIconPreview()
            }
        })

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

            if (target.matches('button[data-param-remove]')) {
                const index = Number(target.getAttribute('data-param-remove'))
                if (Number.isInteger(index) && index >= 0 && index < state.parameterRows.length) {
                    state.parameterRows.splice(index, 1)
                    renderParameterRows()
                }
                return
            }

            if (target.matches('button[data-function-remove]')) {
                const index = Number(target.getAttribute('data-function-remove'))
                if (Number.isInteger(index) && index >= 0 && index < state.functionRows.length) {
                    state.functionRows.splice(index, 1)
                    renderFunctionRows()
                }
                return
            }

            if (target.matches('button[data-icon-remove-existing]')) {
                const index = Number(target.getAttribute('data-icon-remove-existing'))
                if (Number.isInteger(index) && index >= 0 && index < state.currentIconAttachments.length) {
                    state.currentIconAttachments.splice(index, 1)
                    renderIconPreview()
                }
                return
            }

            if (target.matches('button[data-icon-remove-pending]')) {
                const key = normalizeText(target.getAttribute('data-icon-remove-pending'))
                const nextFiles = []
                for (let i = 0; i < state.pendingIconFiles.length; i += 1) {
                    const item = state.pendingIconFiles[i]
                    if (item.key === key) {
                        try {
                            URL.revokeObjectURL(item.previewUrl)
                        } catch (_error) {}
                        continue
                    }
                    nextFiles.push(item)
                }
                state.pendingIconFiles = nextFiles
                renderIconPreview()
            }
        })

        window.__gotoProductPage = goToProductPage
        window.__setProductPageSize = setProductPageSize

        document.getElementById('reloadBtn').addEventListener('click', function () {
            queryProductsWithAutoSave(false)
        })

        document.getElementById('searchBtn').addEventListener('click', function () {
            queryProductsWithAutoSave(true)
        })

        if (fields.keyword) {
            fields.keyword.addEventListener('keydown', function (event) {
                if (event.key === 'Enter') {
                    queryProductsWithAutoSave(true)
                }
            })
        }

        if (statusFilterEl) {
            statusFilterEl.addEventListener('change', function () {
                queryProductsWithAutoSave(true)
            })
        }

        if (categoryFilterEl) {
            categoryFilterEl.addEventListener('change', function () {
                queryProductsWithAutoSave(true)
            })
        }

        document.getElementById('createModeBtn').addEventListener('click', function () {
            enterCreateMode()
        })

        document.getElementById('addCustomTagBtn').addEventListener('click', function () {
            addCustomTag()
        })

        document.getElementById('addParamBtn').addEventListener('click', function () {
            state.parameterRows.push(buildParameterRow('', '', ''))
            renderParameterRows()
        })

        document.getElementById('importParamsBtn').addEventListener('click', function () {
            importParametersFromJson()
        })

        document.getElementById('addFunctionBtn').addEventListener('click', function () {
            state.functionRows.push(buildParameterRow('', '', ''))
            renderFunctionRows()
        })

        document.getElementById('importFunctionsBtn').addEventListener('click', function () {
            importFunctionsFromJson()
        })

        const exportParamsBtn = document.getElementById('exportParamsBtn')
        if (exportParamsBtn) {
            exportParamsBtn.addEventListener('click', function () {
                exportParametersToJson()
            })
        }

        const exportFunctionsBtn = document.getElementById('exportFunctionsBtn')
        if (exportFunctionsBtn) {
            exportFunctionsBtn.addEventListener('click', function () {
                exportFunctionsToJson()
            })
        }

        if (fields.sort) {
            fields.sort.addEventListener('input', function () {
                renderSortRankHint()
            })
        }

        document.getElementById('clearIconBtn').addEventListener('click', function () {
            state.currentIconAttachments = []
            clearPendingIconPreview()
            fields.iconFile.value = ''
            renderIconPreview()
        })

        document.getElementById('copyCancelBtn').addEventListener('click', function () {
            closeCopyModal()
        })

        document.getElementById('copyConfirmBtn').addEventListener('click', function () {
            confirmCopyProduct()
        })

        copyModalEl.addEventListener('click', function (event) {
            if (event.target === copyModalEl) {
                closeCopyModal()
            }
        })

        copyModelInputEl.addEventListener('keydown', function (event) {
            if (event.key === 'Enter') {
                confirmCopyProduct()
            }
        })

        copyBarcodeInputEl.addEventListener('keydown', function (event) {
            if (event.key === 'Enter') {
                confirmCopyProduct()
            }
        })

        document.getElementById('submitBtn').addEventListener('click', function () {
            submitProduct()
        })

        document.getElementById('cancelEditBtn').addEventListener('click', function () {
            enterIdleMode()
        })

        document.getElementById('resetBtn').addEventListener('click', function () {
            resetForm()
        })

        ;(async function initPage() {
            updateEditorMode()
            enterIdleMode()
            await loadDictionaries()
            await loadProducts()
        })()
    </script>
    {{ template "theme_body" . }}
</body>
</html>
