<!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, #eef4ff 0%, #f8fbff 100%); color: #1f2937; }
        .container { max-width: 1520px; margin: 0 auto; padding: 24px 14px 40px; }
        .panel { background: rgba(255,255,255,0.97); 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 { display: flex; flex-wrap: wrap; gap: 10px; }
        .toolbar { display: grid; grid-template-columns: minmax(0, 1fr) auto auto 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; }
        .status { margin-top: 12px; min-height: 24px; font-size: 14px; }
        .status.success { color: #15803d; }
        .status.error { color: #b91c1c; }
        input, select, textarea { width: 100%; border: 1px solid #cbd5e1; border-radius: 12px; padding: 10px 12px; font-size: 14px; background: #fff; }
        textarea { min-height: 90px; resize: vertical; }
        .layout { display: grid; grid-template-columns: minmax(340px, 420px) minmax(0, 1fr); gap: 14px; align-items: start; }
        .user-list { display: grid; gap: 10px; max-height: 72vh; overflow: auto; }
        .user-card { border: 1px solid #dbe3f0; border-radius: 16px; padding: 14px; background: #f8fbff; cursor: pointer; }
        .user-card.active { border-color: #2563eb; background: #eff6ff; box-shadow: inset 0 0 0 1px #bfdbfe; }
        .user-name { font-size: 18px; font-weight: 700; color: #0f172a; }
        .user-meta { margin-top: 6px; color: #64748b; font-size: 13px; word-break: break-all; }
        .user-stats { margin-top: 10px; display: flex; flex-wrap: wrap; gap: 8px; }
        .stat-tag { display: inline-flex; align-items: center; padding: 4px 10px; border-radius: 999px; background: #dbeafe; color: #1d4ed8; font-size: 12px; font-weight: 700; }
        .detail-header { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 12px; align-items: flex-start; margin-bottom: 14px; }
        .detail-title { margin: 0; font-size: 22px; }
        .detail-meta { color: #64748b; font-size: 13px; line-height: 1.7; }
        .summary-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; margin-bottom: 14px; }
        .summary-card { border: 1px solid #dbe3f0; border-radius: 16px; padding: 14px; background: #f8fbff; }
        .summary-label { color: #64748b; font-size: 13px; }
        .summary-value { margin-top: 8px; font-size: 22px; font-weight: 700; color: #0f172a; }
        .profile-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin-bottom: 14px; }
        .preview-form { border: 1px solid #dbe3f0; border-radius: 14px; background: #f8fbff; overflow: hidden; margin-bottom: 10px; }
        .preview-form-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 10px 12px; }
        .preview-form-summary { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; flex: 1; min-width: 0; }
        .preview-summary-item { min-width: 0; }
        .preview-summary-label { font-size: 11px; color: #64748b; margin-bottom: 2px; }
        .preview-summary-value { font-size: 13px; font-weight: 700; color: #0f172a; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
        .preview-toggle { min-width: 88px; }
        .preview-form-body { display: grid; grid-template-rows: 0fr; opacity: 0; transition: grid-template-rows 220ms ease, opacity 220ms ease; }
        .preview-form-body.open { grid-template-rows: 1fr; opacity: 1; }
        .preview-form-inner { min-height: 0; overflow: hidden; padding: 0 12px 0; }
        .preview-form-body.open .preview-form-inner { padding-bottom: 12px; }
        .preview-fields { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; padding-top: 2px; }
        .field-block { display: grid; gap: 6px; }
        .field-block.full { grid-column: 1 / -1; }
        .field-label { font-size: 12px; color: #64748b; }
        .preview-card { border: 1px solid #dbe3f0; border-radius: 12px; padding: 8px 10px; background: rgba(255,255,255,0.65); min-width: 0; display: grid; gap: 3px; }
        .preview-value { color: #0f172a; font-size: 13px; font-weight: 700; line-height: 1.35; word-break: break-word; }
        .user-profile-shell { display: grid; gap: 12px; }
        .profile-toolbar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px; }
        .edit-hint { display: inline-flex; align-items: center; gap: 6px; padding: 6px 10px; border-radius: 999px; font-size: 12px; font-weight: 700; }
        .edit-hint.clean { background: #ecfdf5; color: #047857; border: 1px solid #a7f3d0; }
        .edit-hint.dirty { background: #fff7ed; color: #c2410c; border: 1px solid #fdba74; }
        .edit-panel { border-top: 1px dashed #dbe3f0; display: grid; grid-template-rows: 0fr; opacity: 0; transform: translateY(-4px); transition: grid-template-rows 220ms ease, opacity 220ms ease, transform 220ms ease, padding-top 220ms ease, margin-top 220ms ease; padding-top: 0; margin-top: 0; overflow: hidden; }
        .edit-panel.open { grid-template-rows: 1fr; opacity: 1; transform: translateY(0); padding-top: 14px; margin-top: 2px; }
        .edit-panel-inner { min-height: 0; overflow: hidden; }
        .attachment-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; margin-bottom: 12px; }
        .attachment-grid.editing { grid-template-columns: repeat(2, minmax(0, 1fr)); }
        .attachment-panel { border: 1px solid #dbe3f0; border-radius: 14px; padding: 12px; background: #f8fbff; }
        .attachment-panel.compact { padding: 10px; min-width: 0; }
        .attachment-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
        .attachment-actions .btn { padding: 8px 12px; font-size: 13px; }
        .attachment-preview { margin-top: 8px; display: flex; align-items: center; gap: 10px; min-height: 60px; }
        .attachment-thumb { width: 60px; height: 60px; border-radius: 10px; border: 1px solid #dbe3f0; background: #fff; object-fit: cover; }
        .attachment-empty-thumb { width: 60px; height: 60px; border-radius: 10px; border: 1px solid #dbe3f0; background: #fff; color: #94a3b8; display: flex; align-items: center; justify-content: center; font-size: 11px; text-align: center; padding: 6px; }
        .attachment-meta { min-width: 0; display: grid; gap: 4px; }
        .attachment-meta.compact { gap: 2px; }
        .attachment-link { color: #2563eb; text-decoration: none; font-size: 12px; font-weight: 600; word-break: break-all; }
        .attachment-hidden-input { display: none; }
        .detail-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 14px; }
        .section + .section { margin-top: 14px; }
        .section-title { margin: 0 0 10px; font-size: 18px; color: #0f172a; }
        .table-wrap { overflow: auto; border: 1px solid #dbe3f0; border-radius: 16px; 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; }
        .muted { color: #64748b; font-size: 12px; }
        .empty { text-align: center; padding: 24px; color: #64748b; }
        @media (max-width: 1080px) {
            .layout { grid-template-columns: 1fr; }
            .summary-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
            .preview-form-summary { grid-template-columns: repeat(2, minmax(0, 1fr)); }
            .preview-fields { grid-template-columns: repeat(2, minmax(0, 1fr)); }
            .attachment-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
            .attachment-grid.editing { grid-template-columns: repeat(2, minmax(0, 1fr)); }
            .profile-grid { grid-template-columns: 1fr; }
        }
        @media (max-width: 720px) {
            .toolbar { grid-template-columns: 1fr; }
            .summary-grid { grid-template-columns: 1fr; }
            .preview-form-head { flex-direction: column; align-items: stretch; }
            .preview-form-summary { grid-template-columns: 1fr; }
            .preview-fields { grid-template-columns: 1fr; }
            .attachment-grid { grid-template-columns: 1fr; }
            .attachment-grid.editing { grid-template-columns: 1fr; }
            table, thead, tbody, th, td, tr { display: block; }
            thead { display: none; }
            tr { border-bottom: 1px solid #e5e7eb; }
            td { display: flex; justify-content: space-between; gap: 10px; }
            td::before { content: attr(data-label); font-weight: 700; color: #475569; }
        }
        html[data-theme="dark"] .preview-form,
        html[data-theme="dark"] .preview-card,
        html[data-theme="dark"] .attachment-panel {
            background: rgba(0, 0, 0, 0.88) !important;
            border-color: rgba(148, 163, 184, 0.22) !important;
            color: #e5e7eb !important;
            box-shadow: 0 18px 50px rgba(2, 6, 23, 0.18) !important;
        }
        html[data-theme="dark"] .preview-summary-value {
            color: #f8fafc !important;
        }
        html[data-theme="dark"] .preview-value,
        html[data-theme="dark"] .attachment-link {
            color: #f8fafc !important;
        }
        html[data-theme="dark"] .attachment-thumb,
        html[data-theme="dark"] .attachment-empty-thumb {
            background: rgba(15, 23, 42, 0.7) !important;
            border-color: rgba(148, 163, 184, 0.22) !important;
            color: #94a3b8 !important;
        }
        html[data-theme="dark"] .edit-panel {
            border-top-color: rgba(148, 163, 184, 0.22) !important;
        }
        html[data-theme="dark"] .edit-hint.clean {
            background: rgba(16, 185, 129, 0.14) !important;
            color: #86efac !important;
            border-color: rgba(110, 231, 183, 0.28) !important;
        }
        html[data-theme="dark"] .edit-hint.dirty {
            background: rgba(249, 115, 22, 0.14) !important;
            color: #fdba74 !important;
            border-color: rgba(251, 146, 60, 0.28) !important;
        }
    </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>
            </div>
            <div class="status" id="status"></div>
        </section>

        <section class="panel">
            <div class="toolbar">
                <input id="keywordInput" placeholder="按用户名 / openid / 手机号 / users_id 搜索" />
                <button class="btn btn-primary" id="searchBtn" type="button">搜索</button>
                <button class="btn btn-light" id="resetBtn" type="button">重置</button>
                <button class="btn btn-light" id="refreshBtn" type="button">重新加载</button>
            </div>
        </section>

        <section class="layout">
            <section class="panel">
                <h2 style="margin-top:0;">用户列表</h2>
                <div id="userList" class="user-list"></div>
            </section>

            <section class="panel">
                <div id="detailWrap"></div>
            </section>
        </section>
    </div>

    <script>
        const API_BASE = '/pb/api'
        const tokenKey = 'pb_manage_token'
        const state = {
            users: [],
            selectedOpenid: '',
            previewExpanded: false,
            isEditMode: false,
            userEditDraft: null,
            userLevelOptions: [],
            attachmentDetails: {
                userUsersPicture: null,
                userUsersIdPicA: null,
                userUsersIdPicB: null,
                userUsersTitlePicture: null,
            },
        }

        const statusEl = document.getElementById('status')
        const keywordInput = document.getElementById('keywordInput')
        const userListEl = document.getElementById('userList')
        const detailWrapEl = document.getElementById('detailWrap')

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

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

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

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

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

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

            const data = await res.json()
            if (!res.ok) {
                throw new Error((data && (data.errMsg || data.message)) || '请求失败')
            }

            return data
        }

        function getSelectedUser() {
            return state.users.find(function (item) {
                return normalizeText(item.openid) === normalizeText(state.selectedOpenid)
            }) || null
        }

        function buildUserDraft(user) {
            return {
                openid: user.openid || '',
                users_name: user.users_name || '',
                users_phone: user.users_phone || '',
                users_level: user.users_level || '',
                users_type: user.users_type || '',
                users_status: user.users_status || '',
                users_rank_level: user.users_rank_level === null || typeof user.users_rank_level === 'undefined' ? '' : user.users_rank_level,
                users_auth_type: user.users_auth_type === null || typeof user.users_auth_type === 'undefined' ? '' : user.users_auth_type,
                company_id: user.company_id || '',
                users_tag: user.users_tag || '',
                users_parent_id: user.users_parent_id || '',
                users_promo_code: user.users_promo_code || '',
                usergroups_id: user.usergroups_id || '',
                users_id_number: user.users_id_number || '',
                users_picture: user.users_picture || '',
                users_id_pic_a: user.users_id_pic_a || '',
                users_id_pic_b: user.users_id_pic_b || '',
                users_title_picture: user.users_title_picture || '',
            }
        }

        function ensureEditDraft(user) {
            if (!user) {
                state.userEditDraft = null
                return null
            }

            if (!state.userEditDraft || normalizeText(state.userEditDraft.openid) !== normalizeText(user.openid)) {
                state.userEditDraft = buildUserDraft(user)
            }

            return state.userEditDraft
        }

        function normalizeDraftSnapshot(source) {
            const current = source || {}
            return {
                openid: normalizeText(current.openid),
                users_name: normalizeText(current.users_name),
                users_phone: normalizeText(current.users_phone),
                users_level: normalizeText(current.users_level),
                users_type: normalizeText(current.users_type),
                users_status: normalizeText(current.users_status),
                users_rank_level: normalizeText(current.users_rank_level),
                users_auth_type: normalizeText(current.users_auth_type),
                company_id: normalizeText(current.company_id),
                users_tag: normalizeText(current.users_tag),
                users_parent_id: normalizeText(current.users_parent_id),
                users_promo_code: normalizeText(current.users_promo_code),
                usergroups_id: normalizeText(current.usergroups_id),
                users_id_number: normalizeText(current.users_id_number),
                users_picture: normalizeText(current.users_picture),
                users_id_pic_a: normalizeText(current.users_id_pic_a),
                users_id_pic_b: normalizeText(current.users_id_pic_b),
                users_title_picture: normalizeText(current.users_title_picture),
            }
        }

        function hasUnsavedChanges(user) {
            if (!state.isEditMode || !user) {
                return false
            }

            const original = normalizeDraftSnapshot(buildUserDraft(user))
            const current = normalizeDraftSnapshot(state.userEditDraft || buildUserDraft(user))
            return JSON.stringify(original) !== JSON.stringify(current)
        }

        function syncEditDraftFromDom() {
            if (!state.isEditMode || !state.userEditDraft) {
                return
            }

            const fieldMap = {
                userUsersName: 'users_name',
                userUsersPhone: 'users_phone',
                userUsersLevel: 'users_level',
                userUsersType: 'users_type',
                userUsersStatus: 'users_status',
                userUsersRankLevel: 'users_rank_level',
                userUsersAuthType: 'users_auth_type',
                userCompanyId: 'company_id',
                userUsersTag: 'users_tag',
                userUsersParentId: 'users_parent_id',
                userUsersPromoCode: 'users_promo_code',
                userUsergroupsId: 'usergroups_id',
                userUsersIdNumber: 'users_id_number',
                userUsersPicture: 'users_picture',
                userUsersIdPicA: 'users_id_pic_a',
                userUsersIdPicB: 'users_id_pic_b',
                userUsersTitlePicture: 'users_title_picture',
            }

            Object.keys(fieldMap).forEach(function (id) {
                const el = document.getElementById(id)
                if (el) {
                    state.userEditDraft[fieldMap[id]] = el.value
                }
            })
        }

        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, fieldKey) {
            const token = getToken()
            if (!token) {
                window.location.replace('/pb/manage/login')
                throw new Error('登录状态已失效，请重新登录')
            }

            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', 'cart-order-manage:' + fieldKey)

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

            const parsed = await parseJsonSafe(res)
            const data = parsed.json || {}
            if (!res.ok) {
                if (res.status === 413) {
                    throw new Error('上传图片失败：文件过大或服务端 bodyLimit 未生效')
                }
                if (!parsed.isJson && parsed.text) {
                    throw new Error('上传图片失败：服务端返回了非 JSON 响应')
                }
                throw new Error((data && (data.errMsg || data.message)) || '上传图片失败')
            }

            return data
        }

        async function loadAttachmentDetail(attachmentId) {
            const id = normalizeText(attachmentId)
            if (!id) {
                return null
            }

            try {
                return await requestJson('/attachment/detail', { attachments_id: id })
            } catch (_error) {
                return {
                    attachments_id: id,
                    attachments_filename: id,
                    attachments_url: '',
                }
            }
        }

        async function refreshSelectedAttachmentDetails() {
            const user = getSelectedUser()
            if (!user) {
                state.attachmentDetails = {
                    userUsersPicture: null,
                    userUsersIdPicA: null,
                    userUsersIdPicB: null,
                    userUsersTitlePicture: null,
                }
                renderDetail()
                return
            }

            const currentOpenid = normalizeText(user.openid)
            const results = await Promise.all([
                loadAttachmentDetail(user.users_picture),
                loadAttachmentDetail(user.users_id_pic_a),
                loadAttachmentDetail(user.users_id_pic_b),
                loadAttachmentDetail(user.users_title_picture),
            ])

            if (normalizeText(state.selectedOpenid) !== currentOpenid) {
                return
            }

            state.attachmentDetails = {
                userUsersPicture: results[0],
                userUsersIdPicA: results[1],
                userUsersIdPicB: results[2],
                userUsersTitlePicture: results[3],
            }
            renderDetail()
        }

        function renderAttachmentCard(fieldId, label, value, editable) {
            const detail = state.attachmentDetails[fieldId] || null
            const url = detail && detail.attachments_url ? detail.attachments_url : ''
            const filename = detail && (detail.attachments_filename || detail.attachments_id)
                ? (detail.attachments_filename || detail.attachments_id)
                : normalizeText(value)

            return '<div class="attachment-panel compact">'
                + '<label class="field-label" for="' + fieldId + '">' + escapeHtml(label) + '</label>'
                + (editable
                    ? '<input id="' + fieldId + '" value="' + escapeHtml(value || '') + '" />'
                    : '<div class="preview-card" style="margin-top:6px;"><div class="preview-value">' + escapeHtml(value || '-') + '</div></div>')
                + (editable
                    ? '<input class="attachment-hidden-input" id="' + fieldId + 'File" type="file" accept="image/*" data-upload-field="' + escapeHtml(fieldId) + '" />'
                    : '')
                + (editable
                    ? '<div class="attachment-actions">'
                        + '<button class="btn btn-light" type="button" data-upload-trigger="' + escapeHtml(fieldId) + '">上传图片</button>'
                        + '<button class="btn btn-light" type="button" data-clear-attachment="' + escapeHtml(fieldId) + '">清空ID</button>'
                        + '</div>'
                    : '')
                + '<div class="attachment-preview">'
                + (url
                    ? '<img class="attachment-thumb" src="' + escapeHtml(url) + '" alt="' + escapeHtml(label) + '" />'
                    : '<div class="attachment-empty-thumb">暂无预览</div>')
                + '<div class="attachment-meta compact">'
                + '<div class="muted">当前附件ID：' + escapeHtml(value || '-') + '</div>'
                + (url
                    ? '<a class="attachment-link" href="' + escapeHtml(url) + '" target="_blank" rel="noreferrer">查看图片：' + escapeHtml(filename || '附件') + '</a>'
                    : '<div class="muted">上传后将先保存到 tbl_attachments，再自动回填附件ID</div>')
                + '</div>'
                + '</div>'
                + '</div>'
        }

        function renderPreviewField(label, value) {
            return '<div class="preview-card">'
                + '<div class="field-label">' + escapeHtml(label) + '</div>'
                + '<div class="preview-value">' + escapeHtml(value || '-') + '</div>'
                + '</div>'
        }

        function renderPreviewForm(user) {
            return '<div class="preview-form">'
                + '<div class="preview-form-head">'
                + '<div class="preview-form-summary">'
                + '<div class="preview-summary-item"><div class="preview-summary-label">用户名称</div><div class="preview-summary-value">' + escapeHtml(user.users_name || '-') + '</div></div>'
                + '<div class="preview-summary-item"><div class="preview-summary-label">手机号</div><div class="preview-summary-value">' + escapeHtml(user.users_phone || '-') + '</div></div>'
                + '<div class="preview-summary-item"><div class="preview-summary-label">会员等级</div><div class="preview-summary-value">' + escapeHtml(user.users_level_name || user.users_level || '-') + '</div></div>'
                + '<div class="preview-summary-item"><div class="preview-summary-label">用户类型</div><div class="preview-summary-value">' + escapeHtml(user.users_type || '-') + '</div></div>'
                + '</div>'
                + '<button class="btn btn-light preview-toggle" id="togglePreviewBtn" type="button">' + (state.previewExpanded ? '收起详情' : '展开详情') + '</button>'
                + '</div>'
                + '<div class="preview-form-body' + (state.previewExpanded ? ' open' : '') + '"><div class="preview-form-inner">'
                + '<div class="preview-fields">'
                + renderPreviewField('用户状态', user.users_status)
                + renderPreviewField('用户星级', user.users_rank_level)
                + renderPreviewField('账户类型', user.users_auth_type)
                + renderPreviewField('公司ID', user.company_id)
                + renderPreviewField('用户标签', user.users_tag)
                + renderPreviewField('上级用户ID', user.users_parent_id)
                + renderPreviewField('推广码', user.users_promo_code)
                + renderPreviewField('用户组ID', user.usergroups_id)
                + renderPreviewField('证件号', user.users_id_number)
                + '</div>'
                + '<div class="attachment-grid" style="margin-top:10px; margin-bottom:0;">'
                + renderAttachmentCard('userUsersPicture', '头像附件ID', user.users_picture || '', false)
                + renderAttachmentCard('userUsersIdPicA', '证件正面附件ID', user.users_id_pic_a || '', false)
                + renderAttachmentCard('userUsersIdPicB', '证件反面附件ID', user.users_id_pic_b || '', false)
                + renderAttachmentCard('userUsersTitlePicture', '资质附件ID', user.users_title_picture || '', false)
                + '</div>'
                + '</div></div>'
                + '</div>'
        }

        function renderUserList() {
            if (!state.users.length) {
                userListEl.innerHTML = '<div class="empty">暂无匹配用户。</div>'
                return
            }

            userListEl.innerHTML = state.users.map(function (item) {
                const isActive = normalizeText(item.openid) === normalizeText(state.selectedOpenid)
                return '<div class="user-card' + (isActive ? ' active' : '') + '" data-openid="' + escapeHtml(item.openid) + '">'
                    + '<div class="user-name">' + escapeHtml(item.users_name || item.users_id || item.openid) + '</div>'
                    + '<div class="user-meta">openid：' + escapeHtml(item.openid || '-') + '</div>'
                    + '<div class="user-meta">手机号：' + escapeHtml(item.users_phone || '-') + '</div>'
                    + '<div class="user-meta">users_id：' + escapeHtml(item.users_id || '-') + '</div>'
                    + '<div class="user-meta">会员等级：' + escapeHtml(item.users_level_name || item.users_level || '-') + '</div>'
                    + '<div class="user-stats">'
                    + '<span class="stat-tag">购物车 ' + escapeHtml(item.cart_count || 0) + '</span>'
                    + '<span class="stat-tag">购物数量 ' + escapeHtml(item.cart_total_quantity || 0) + '</span>'
                    + '<span class="stat-tag">订单 ' + escapeHtml(item.order_count || 0) + '</span>'
                    + '</div>'
                    + '</div>'
            }).join('')
        }

        function renderCartTable(items) {
            if (!items.length) {
                return '<div class="empty">当前用户暂无购物车记录。</div>'
            }

            return '<div class="table-wrap"><table><thead><tr>'
                + '<th>商品名称</th><th>型号/料号</th><th>数量</th><th>单价</th><th>状态</th><th>加入时间</th><th>购物车名</th>'
                + '</tr></thead><tbody>'
                + items.map(function (item) {
                    return '<tr>'
                        + '<td data-label="商品名称"><div>' + escapeHtml(item.product_name || item.cart_product_business_id || item.cart_product_id || '-') + '</div><div class="muted">recordId：' + escapeHtml(item.cart_product_id || '-') + '</div><div class="muted">业务ID：' + escapeHtml(item.cart_product_business_id || '-') + '</div></td>'
                        + '<td data-label="型号/料号"><div>' + escapeHtml(item.product_modelnumber || '-') + '</div><div class="muted">料号：' + escapeHtml(item.product_barcode || '-') + '</div></td>'
                        + '<td data-label="数量">' + escapeHtml(item.cart_product_quantity || 0) + '</td>'
                        + '<td data-label="单价">¥' + escapeHtml(item.cart_at_price || 0) + '</td>'
                        + '<td data-label="状态">' + escapeHtml(item.cart_status || '-') + '</td>'
                        + '<td data-label="加入时间">' + escapeHtml(item.cart_create || item.created || '-') + '</td>'
                        + '<td data-label="购物车名">' + escapeHtml(item.cart_number || '-') + '</td>'
                        + '</tr>'
                }).join('')
                + '</tbody></table></div>'
        }

        function renderUserLevelOptions(selectedValue) {
            const current = normalizeText(selectedValue)
            let html = '<option value="">未设置</option>'

            for (let i = 0; i < state.userLevelOptions.length; i += 1) {
                const item = state.userLevelOptions[i]
                const value = normalizeText(item.value)
                html += '<option value="' + escapeHtml(value) + '"' + (value === current ? ' selected' : '') + '>'
                    + escapeHtml(item.label || item.value || '')
                    + '</option>'
            }

            return html
        }

        function renderUserProfileForm(user) {
            const draft = state.isEditMode ? ensureEditDraft(user) : null
            const source = draft || user
            const dirty = hasUnsavedChanges(user)

            return '<div class="section user-profile-shell"><div class="profile-toolbar"><h3 class="section-title" style="margin:0;">用户信息维护</h3>'
                + (state.isEditMode
                    ? '<span class="edit-hint ' + (dirty ? 'dirty' : 'clean') + '">' + (dirty ? '● 有未保存变更' : '✓ 暂无未保存变更') + '</span>'
                    : '')
                + '</div>'
                + '<div class="detail-actions">'
                + (state.isEditMode
                    ? '<button class="btn btn-primary" id="saveUserBtn" type="button">保存用户信息</button><button class="btn btn-light" id="cancelEditBtn" type="button">取消编辑</button>'
                    : '<button class="btn btn-primary" id="enterEditBtn" type="button">编辑用户信息</button>')
                + '</div>'
                + (state.isEditMode ? '' : renderPreviewForm(user))
                + '<div class="edit-panel' + (state.isEditMode ? ' open' : '') + '"><div class="edit-panel-inner">'
                + '<div class="attachment-grid editing">'
                + renderAttachmentCard('userUsersPicture', '头像附件ID', source.users_picture || '', true)
                + renderAttachmentCard('userUsersIdPicA', '证件正面附件ID', source.users_id_pic_a || '', true)
                + renderAttachmentCard('userUsersIdPicB', '证件反面附件ID', source.users_id_pic_b || '', true)
                + renderAttachmentCard('userUsersTitlePicture', '资质附件ID', source.users_title_picture || '', true)
                + '</div>'
                + '<div class="profile-grid">'
                + '<div class="field-block"><label class="field-label" for="userUsersName">用户名称</label><input id="userUsersName" value="' + escapeHtml(source.users_name || '') + '" /></div>'
                + '<div class="field-block"><label class="field-label" for="userUsersPhone">手机号</label><input id="userUsersPhone" value="' + escapeHtml(source.users_phone || '') + '" /></div>'
                + '<div class="field-block"><label class="field-label" for="userUsersLevel">会员等级</label><select id="userUsersLevel">' + renderUserLevelOptions(source.users_level) + '</select></div>'
                + '<div class="field-block"><label class="field-label" for="userUsersType">用户类型</label><input id="userUsersType" value="' + escapeHtml(source.users_type || '') + '" /></div>'
                + '<div class="field-block"><label class="field-label" for="userUsersStatus">用户状态</label><input id="userUsersStatus" value="' + escapeHtml(source.users_status || '') + '" /></div>'
                + '<div class="field-block"><label class="field-label" for="userUsersRankLevel">用户星级</label><input id="userUsersRankLevel" value="' + escapeHtml(source.users_rank_level === null || typeof source.users_rank_level === 'undefined' ? '' : source.users_rank_level) + '" /></div>'
                + '<div class="field-block"><label class="field-label" for="userUsersAuthType">账户类型</label><input id="userUsersAuthType" value="' + escapeHtml(source.users_auth_type === null || typeof source.users_auth_type === 'undefined' ? '' : source.users_auth_type) + '" /></div>'
                + '<div class="field-block"><label class="field-label" for="userCompanyId">公司ID</label><input id="userCompanyId" value="' + escapeHtml(source.company_id || '') + '" /></div>'
                + '<div class="field-block"><label class="field-label" for="userUsersTag">用户标签</label><input id="userUsersTag" value="' + escapeHtml(source.users_tag || '') + '" /></div>'
                + '<div class="field-block"><label class="field-label" for="userUsersParentId">上级用户ID</label><input id="userUsersParentId" value="' + escapeHtml(source.users_parent_id || '') + '" /></div>'
                + '<div class="field-block"><label class="field-label" for="userUsersPromoCode">推广码</label><input id="userUsersPromoCode" value="' + escapeHtml(source.users_promo_code || '') + '" /></div>'
                + '<div class="field-block"><label class="field-label" for="userUsergroupsId">用户组ID</label><input id="userUsergroupsId" value="' + escapeHtml(source.usergroups_id || '') + '" /></div>'
                + '<div class="field-block"><label class="field-label" for="userUsersIdNumber">证件号</label><input id="userUsersIdNumber" value="' + escapeHtml(source.users_id_number || '') + '" /></div>'
                + '</div>'
                + '</div></div>'
                + '</div>'
        }

        function renderOrderTable(items) {
            if (!items.length) {
                return '<div class="empty">当前用户暂无订单记录。</div>'
            }

            return '<div class="table-wrap"><table><thead><tr>'
                + '<th>订单编号</th><th>来源</th><th>来源ID</th><th>金额</th><th>状态</th><th>下单时间</th>'
                + '</tr></thead><tbody>'
                + items.map(function (item) {
                    return '<tr>'
                        + '<td data-label="订单编号"><div>' + escapeHtml(item.order_number || item.order_id || '-') + '</div><div class="muted">' + escapeHtml(item.order_id || '-') + '</div></td>'
                        + '<td data-label="来源">' + escapeHtml(item.order_source || '-') + '</td>'
                        + '<td data-label="来源ID">' + escapeHtml(item.order_source_id || '-') + '</td>'
                        + '<td data-label="金额">¥' + escapeHtml(item.order_amount || 0) + '</td>'
                        + '<td data-label="状态">' + escapeHtml(item.order_status || '-') + '</td>'
                        + '<td data-label="下单时间">' + escapeHtml(item.order_create || item.created || '-') + '</td>'
                        + '</tr>'
                }).join('')
                + '</tbody></table></div>'
        }

        function renderDetail() {
            const user = getSelectedUser()
            if (!user) {
                detailWrapEl.innerHTML = '<div class="empty">请选择左侧用户查看信息维护、购物车与订单详情。</div>'
                return
            }

            detailWrapEl.innerHTML = '<div class="detail-header">'
                + '<div>'
                + '<h2 class="detail-title">' + escapeHtml(user.users_name || user.users_id || user.openid) + '</h2>'
                + '<div class="detail-meta">openid：' + escapeHtml(user.openid || '-') + '</div>'
                + '<div class="detail-meta">users_id：' + escapeHtml(user.users_id || '-') + '</div>'
                + '<div class="detail-meta">users_idtype：' + escapeHtml(user.users_idtype || '-') + '</div>'
                + '<div class="detail-meta">手机号：' + escapeHtml(user.users_phone || '-') + '</div>'
                + '<div class="detail-meta">会员等级：' + escapeHtml(user.users_level_name || user.users_level || '-') + '</div>'
                + '</div>'
                + '</div>'
                + renderUserProfileForm(user)
                + '<div class="summary-grid">'
                + '<div class="summary-card"><div class="summary-label">购物车记录数</div><div class="summary-value">' + escapeHtml(user.cart_count || 0) + '</div></div>'
                + '<div class="summary-card"><div class="summary-label">购物车商品总数</div><div class="summary-value">' + escapeHtml(user.cart_total_quantity || 0) + '</div></div>'
                + '<div class="summary-card"><div class="summary-label">订单数</div><div class="summary-value">' + escapeHtml(user.order_count || 0) + '</div></div>'
                + '<div class="summary-card"><div class="summary-label">订单总金额</div><div class="summary-value">¥' + escapeHtml(user.order_total_amount || 0) + '</div></div>'
                + '</div>'
                + '<div class="section"><h3 class="section-title">当前购物车详情</h3>' + renderCartTable(Array.isArray(user.carts) ? user.carts : []) + '</div>'
                + '<div class="section"><h3 class="section-title">订单记录</h3>' + renderOrderTable(Array.isArray(user.orders) ? user.orders : []) + '</div>'
        }

        async function loadUsers() {
            setStatus('正在加载用户信息、购物车与订单数据...', '')
            try {
                const data = await requestJson('/cart-order/manage-users', {
                    keyword: normalizeText(keywordInput.value),
                })
                state.users = Array.isArray(data.items) ? data.items : []
                state.userLevelOptions = Array.isArray(data.user_level_options) ? data.user_level_options : []
                state.previewExpanded = false
                state.isEditMode = false
                state.userEditDraft = null
                if (!state.users.length) {
                    state.selectedOpenid = ''
                } else if (!getSelectedUser()) {
                    state.selectedOpenid = normalizeText(state.users[0].openid)
                }
                renderUserList()
                renderDetail()
                refreshSelectedAttachmentDetails()
                setStatus('加载完成，共 ' + state.users.length + ' 位用户。', 'success')
            } catch (err) {
                renderUserList()
                renderDetail()
                setStatus(err.message || '加载失败', 'error')
            }
        }

        async function saveSelectedUser() {
            const user = getSelectedUser()
            if (!user) {
                setStatus('请先选择用户。', 'error')
                return
            }

            syncEditDraftFromDom()
            const draft = ensureEditDraft(user)

            try {
                const data = await requestJson('/cart-order/manage-user-update', {
                    openid: user.openid,
                    users_name: draft.users_name,
                    users_phone: draft.users_phone,
                    users_level: draft.users_level,
                    users_type: draft.users_type,
                    users_status: draft.users_status,
                    users_rank_level: draft.users_rank_level,
                    users_auth_type: draft.users_auth_type,
                    company_id: draft.company_id,
                    users_tag: draft.users_tag,
                    users_parent_id: draft.users_parent_id,
                    users_promo_code: draft.users_promo_code,
                    usergroups_id: draft.usergroups_id,
                    users_id_number: draft.users_id_number,
                    users_picture: draft.users_picture,
                    users_id_pic_a: draft.users_id_pic_a,
                    users_id_pic_b: draft.users_id_pic_b,
                    users_title_picture: draft.users_title_picture,
                })

                const updatedUser = data && data.user ? data.user : null
                if (updatedUser) {
                    const index = state.users.findIndex(function (item) {
                        return normalizeText(item.openid) === normalizeText(updatedUser.openid)
                    })
                    if (index !== -1) {
                        state.users[index] = Object.assign({}, state.users[index], updatedUser)
                    }
                }
                state.isEditMode = false
                state.userEditDraft = null
                renderUserList()
                renderDetail()
                setStatus('用户信息保存成功。', 'success')
            } catch (err) {
                setStatus(err.message || '保存用户信息失败', 'error')
            }
        }

        async function handleAttachmentUpload(fieldId, file) {
            if (!file) {
                return
            }

            const input = document.getElementById(fieldId)
            if (!input) {
                return
            }

            syncEditDraftFromDom()

            const labelMap = {
                userUsersPicture: '头像',
                userUsersIdPicA: '证件正面',
                userUsersIdPicB: '证件反面',
                userUsersTitlePicture: '资质附件',
            }

            try {
                setStatus('正在上传' + (labelMap[fieldId] || '附件') + '...', '')
                const uploaded = await uploadAttachment(file, fieldId)
                input.value = uploaded.attachments_id || ''
                if (state.userEditDraft) {
                    const draftFieldMap = {
                        userUsersPicture: 'users_picture',
                        userUsersIdPicA: 'users_id_pic_a',
                        userUsersIdPicB: 'users_id_pic_b',
                        userUsersTitlePicture: 'users_title_picture',
                    }
                    state.userEditDraft[draftFieldMap[fieldId]] = uploaded.attachments_id || ''
                }
                state.attachmentDetails[fieldId] = uploaded
                renderDetail()
                setStatus((labelMap[fieldId] || '附件') + '上传成功，请点击“保存用户信息”完成写入。', 'success')
            } catch (err) {
                setStatus(err.message || '上传附件失败', 'error')
            }
        }

        userListEl.addEventListener('click', function (event) {
            const target = event.target && event.target.closest ? event.target.closest('[data-openid]') : null
            if (!target) {
                return
            }
            state.selectedOpenid = normalizeText(target.getAttribute('data-openid'))
            renderUserList()
            renderDetail()
            refreshSelectedAttachmentDetails()
        })

        detailWrapEl.addEventListener('click', function (event) {
            const target = event.target
            if (target && target.id === 'enterEditBtn') {
                const user = getSelectedUser()
                state.isEditMode = true
                state.previewExpanded = true
                state.userEditDraft = user ? buildUserDraft(user) : null
                renderDetail()
                return
            }

            if (target && target.id === 'togglePreviewBtn') {
                state.previewExpanded = !state.previewExpanded
                renderDetail()
                return
            }

            if (target && target.id === 'cancelEditBtn') {
                state.isEditMode = false
                state.userEditDraft = null
                renderDetail()
                return
            }

            if (target && target.id === 'saveUserBtn') {
                saveSelectedUser()
                return
            }

            if (target && target.getAttribute) {
                const uploadTrigger = target.getAttribute('data-upload-trigger')
                if (uploadTrigger) {
                    const fileInput = document.getElementById(uploadTrigger + 'File')
                    if (fileInput) {
                        fileInput.click()
                    }
                    return
                }

                const clearAttachment = target.getAttribute('data-clear-attachment')
                if (clearAttachment) {
                    syncEditDraftFromDom()
                    const input = document.getElementById(clearAttachment)
                    const fileInput = document.getElementById(clearAttachment + 'File')
                    if (input) {
                        input.value = ''
                    }
                    if (fileInput) {
                        fileInput.value = ''
                    }
                    if (state.userEditDraft) {
                        const draftFieldMap = {
                            userUsersPicture: 'users_picture',
                            userUsersIdPicA: 'users_id_pic_a',
                            userUsersIdPicB: 'users_id_pic_b',
                            userUsersTitlePicture: 'users_title_picture',
                        }
                        state.userEditDraft[draftFieldMap[clearAttachment]] = ''
                    }
                    state.attachmentDetails[clearAttachment] = null
                    renderDetail()
                }
            }
        })

        detailWrapEl.addEventListener('input', function () {
            syncEditDraftFromDom()
        })

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

            const fieldId = target.getAttribute('data-upload-field')
            if (!fieldId) {
                return
            }

            const file = target.files && target.files[0] ? target.files[0] : null
            handleAttachmentUpload(fieldId, file)
        })

        document.getElementById('searchBtn').addEventListener('click', loadUsers)
        document.getElementById('reloadBtn').addEventListener('click', loadUsers)
        document.getElementById('refreshBtn').addEventListener('click', loadUsers)
        document.getElementById('resetBtn').addEventListener('click', function () {
            keywordInput.value = ''
            loadUsers()
        })

        loadUsers()
    </script>
    {{ template "theme_body" . }}
</body>
</html>
