# 规则版本管理功能 - 页面改动说明

## 文档信息

- 文档名称：规则版本管理功能 - 页面改动说明
- 所属模块：全知平台 / 风控模块 / 风险规则
- 编写目的：说明规则详情页和规则列表页增加版本管理功能的改动内容，为前端开发提供指引
- 优化背景：当前规则无显式版本概念，需建立版本体系为准确率追踪打基础

---

## 涉及页面

| 序号 | 页面文件 | 改动类型 | 说明 |
|------|----------|----------|------|
| 1 | `规则详情.html` | 修改 | 增加版本号展示、版本升级按钮、版本历史Tab、相关弹窗和JS逻辑；通过URL `from`参数控制入口权限 |
| 2 | `风险规则-我的规则.html` | 修改 | "已入库"Tab增加"当前版本"列 |
| 3 | `风险规则-公共规则库.html` | 修改 | 列表增加"当前版本"列 |

---

## 改动内容

### 1. `规则详情.html`

#### 1.1 页面头部 — 版本号展示

**位置**：`page-header-bar` 操作按钮组内，"版本升级"按钮左侧。

**展示形式**：
```
← 返回我的规则                    [当前版本号V5]  [版本升级]  [编辑]
```

**新增元素**：

```html
<span id="currentVersionBadge"
      style="display:inline-flex; align-items:center; padding:4px 12px;
             border-radius:6px; font-size:13px; font-weight:500;
             color:#409EFF; background:#ecf5ff; border:1px solid #b3d8ff;">
    当前版本号V5
</span>
```

**展示规则**：

| 条件 | 展示 |
|------|------|
| `from=my` + 规则已上线 + 有版本号 | 蓝色描边标签（如 "V5"） |
| `from=public` | 不显示 |
| 草稿/审核中规则 | 不显示 |

**JS 更新时机**：`loadRuleData()` 加载完成后根据 `from` 参数和 `ruleDatabase[code].currentVersion` 更新。

---

#### 1.2 页面头部 — 版本升级按钮

**位置**：`page-header-bar` 操作按钮组内，"编辑"按钮左侧。

**按钮名称**：`版本升级`

**新增元素**：

```html
<button class="btn-edit-rule" id="btnPublishVersion"
        style="display:none; background:#2b7de9; margin-right:4px;"
        title="版本升级">
    <i class="fa fa-tag"></i> 版本升级
</button>
```

**按钮样式**：与"编辑"按钮同系列同尺寸（`btn-edit-rule` 类），底色为深蓝（`#2b7de9`），比编辑按钮（`#409EFF`）更深，视觉上更突出。

**显示条件**（需同时满足）：

| 条件 | 结果 |
|------|------|
| 来源为"我的规则"（URL含 `from=my`） | 否则隐藏整个操作区 |
| 规则已上线 | 否则无版本概念 |
| 用户是管理员 或 规则提交人 | 否则隐藏 |

**禁用状态**：编辑模式下按钮 `disabled`，`title` 改为"请先保存或取消当前编辑"。

**JS 控制**：

```javascript
// loadRuleData() 中
const fromParam = new URLSearchParams(window.location.search).get('from');
const isMyRule = fromParam === 'my';
const isOnline = !!rule.basic.onlineTime;
const canPublish = isMyRule && isOnline && canEditRule(rule.basic);
document.getElementById('btnPublishVersion').style.display = canPublish ? 'inline-flex' : 'none';

// enterEditMode() 中
document.getElementById('btnPublishVersion').disabled = true;
document.getElementById('btnPublishVersion').title = '请先保存或取消当前编辑';

// exitEditMode() 中
document.getElementById('btnPublishVersion').disabled = false;
document.getElementById('btnPublishVersion').title = '版本升级';
```

---

#### 1.3 编辑模式提示条文案修改

**位置**：`editModeBanner` 区域（约 line 1014-1016）。

**原文案**：

> 当前处于编辑模式 — 您正在修改已上线规则的基本信息，保存后将生成新版本并上链。

**新文案**：

> 当前处于编辑模式 — 保存后将提交上链。如需升级版本，请使用页面右上角的"版本升级"按钮。

---

#### 1.4 保存确认弹窗文案修改

**位置**：`saveConfirmModal` 内（约 line 1101-1102）。

**原文案**：

> 您正在修改已上线的规则，保存后将立即生效并生成新版本上链。

**新文案**：

> 您正在修改已上线的规则，保存后将立即生效并上链（不会生成新版本）。

---

#### 1.5 保存成功提示文案修改

**位置**：`confirmSave()` 函数中的 alert（约 line 1555）。

**原文案**：`"规则已保存成功，新版本已生成并提交上链"`

**新文案**：`"规则已保存成功并提交上链"`

---

#### 1.6 新增版本历史 Tab

**位置**：`ruleDetailTabs`（约 line 1030-1051），在"区块链信息"之后插入（最后一个Tab）。

**Tab 按钮**：

```html
<li class="nav-item" role="presentation">
    <button class="nav-link" id="version-tab" data-bs-toggle="tab"
            data-bs-target="#versionPanel" type="button" role="tab">
        <i class="fa fa-history" style="margin-right: 6px;"></i>版本历史
    </button>
</li>
```

**Tab 面板**：

```html
<div class="tab-pane fade" id="versionPanel" role="tabpanel">
    <div id="versionHistoryContent">
        <!-- 由 renderVersionHistory(ruleCode) 动态渲染 -->
    </div>
</div>
```

---

#### 1.7 版本历史表格布局

`renderVersionHistory(ruleCode)` 函数渲染内容：

```html
<div style="padding:20px;">
    <table class="table table-hover" style="font-size:13px;">
        <thead>
            <tr>
                <th style="width:80px;">版本号</th>
                <th style="width:180px;">发布时间</th>
                <th style="width:100px;">发布人</th>
                <th style="width:80px;">角色</th>
                <th>升级说明</th>
            </tr>
        </thead>
        <tbody>
            <!-- 每行一个版本 -->
            <tr style="background:#ecf5ff;">
                <!-- 当前版本行：浅蓝背景 -->
                <td>
                    <span style="...">V5</span>
                    <span class="badge" style="...">当前</span>
                </td>
                <td>2026-02-10 17:00:00</td>
                <td>张三</td>
                <td>规则提交人</td>
                <td>修改监控逻辑，调整触发条件</td>
            </tr>
            <!-- 其他版本行：普通白底 -->
            <tr>
                <td><span style="...">V4</span></td>
                <td>2026-01-20 14:30:00</td>
                <td>张三</td>
                <td>管理员</td>
                <td>调整风险等级</td>
            </tr>
        </tbody>
    </table>
</div>
```

**版本号单元格样式**（当前版本）：

```html
<span style="display:inline-block; padding:2px 8px; border:1px solid #b3d8ff;
             border-radius:4px; font-size:12px; color:#409EFF; background:#ecf5ff;">V5</span>
```

**版本号单元格样式**（历史版本）：

```html
<span style="display:inline-block; padding:2px 8px; border:1px solid #dcdfe6;
             border-radius:4px; font-size:12px; color:#606266;">V4</span>
```

**空状态**：

```html
<div style="text-align:center; padding:60px 0; color:#909399;">
    <i class="fa fa-history" style="font-size:36px; margin-bottom:12px; display:block;"></i>
    <p>暂无版本记录。</p>
</div>
```

---

#### 1.8 版本升级确认弹窗

**位置**：`saveConfirmModal` 之后（约 line 1119 之后）。

```html
<div class="modal fade" id="publishVersionModal" tabindex="-1">
    <div class="modal-dialog modal-dialog-centered">
        <div class="modal-content">
            <div class="modal-header" style="background:#ecf5ff; border-bottom:1px solid #b3d8ff;">
                <h5 class="modal-title" style="color:#409EFF; font-size:16px;">
                    <i class="fa fa-tag" style="margin-right:8px;"></i>版本升级
                </h5>
                <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
            </div>
            <div class="modal-body" style="padding:20px;">
                <!-- 版本号对比 -->
                <div style="margin-bottom:16px;">
                    <span style="font-size:13px; color:#606266;">当前版本：</span>
                    <span id="publishCurrentVersion"
                          style="font-weight:600; color:#303133;">V5</span>
                    <i class="fa fa-arrow-right" style="margin:0 8px; color:#909399;"></i>
                    <span style="font-size:13px; color:#606266;">新版本：</span>
                    <span id="publishNewVersion"
                          style="font-weight:600; color:#409EFF;">V6</span>
                </div>
                <!-- 升级说明 -->
                <div style="margin-bottom:16px;">
                    <label style="font-size:13px; font-weight:500; color:#606266;
                                  margin-bottom:6px; display:block;">
                        升级说明（必填）
                    </label>
                    <textarea id="versionChangeSummary" class="form-control" rows="3"
                              placeholder="请描述本次版本变更内容，例如：调整监控逻辑阈值，优化触发条件">
                    </textarea>
                </div>
                <!-- 警告提示 -->
                <div style="font-size:12px; color:#E6A23C; background:#fdf6ec;
                            padding:8px 12px; border-radius:4px;">
                    <i class="fa fa-info-circle" style="margin-right:4px;"></i>
                    发布后将生成新版本快照并上链记录，版本号不可回退。
                </div>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-outline-secondary"
                        data-bs-dismiss="modal">取消</button>
                <button type="button" class="btn btn-primary" id="btnConfirmPublish">
                    <i class="fa fa-check"></i> 确认发布
                </button>
            </div>
        </div>
    </div>
</div>
```

---

#### 1.9 JavaScript — 数据模型新增

**`ruleDatabase` 增加 `currentVersion` 字段**：

```javascript
const ruleDatabase = {
    'GZ177019905698527': {
        currentVersion: 'V1',  // 新增
        basic: { ... },
        tech: { ... }
    },
    'GZ1716607905340192': {
        currentVersion: 'V5',  // 新增，已有V1~V5链事件
        basic: { ... },
        tech: { ... }
    }
};
```

**新增 `versionHistoryMap`**：

```javascript
const versionHistoryMap = {
    'GZ177019905698527': [
        {
            version: 'V1',
            publishTime: '2026-02-10 09:00:00',
            publisher: '张三',
            publisherRole: '管理员',
            changeSummary: '规则初始上线',
            snapshot: { /* deep clone of basic + tech at publish time */ }
        }
    ],
    'GZ1716607905340192': [
        { version: 'V1', publishTime: '2025-12-29 12:00:00', publisher: '孙二',
          publisherRole: '业务用户', changeSummary: '规则初始上线', snapshot: {...} },
        { version: 'V2', publishTime: '2026-01-06 14:00:00', publisher: '孙二',
          publisherRole: '业务用户', changeSummary: '修改监控逻辑，调整触发条件', snapshot: {...} },
        { version: 'V3', publishTime: '2026-01-15 10:00:00', publisher: '孙二',
          publisherRole: '业务用户', changeSummary: '调整风险等级为重要', snapshot: {...} },
        { version: 'V4', publishTime: '2026-01-20 14:30:00', publisher: '孙二',
          publisherRole: '业务用户', changeSummary: '优化监控目标描述', snapshot: {...} },
        { version: 'V5', publishTime: '2026-02-10 17:00:00', publisher: '张三',
          publisherRole: '管理员', changeSummary: '调整处置类型，更新制度关联', snapshot: {...} }
    ]
};
```

---

#### 1.10 JavaScript — `renderVersionHistory(ruleCode)`

```javascript
function renderVersionHistory(ruleCode) {
    const container = document.getElementById('versionHistoryContent');
    const versions = versionHistoryMap[ruleCode] || [];
    const rule = ruleDatabase[ruleCode];
    const currentVersion = rule ? rule.currentVersion : null;

    if (versions.length === 0) {
        container.innerHTML = `
            <div style="text-align:center; padding:60px 0; color:#909399;">
                <i class="fa fa-history" style="font-size:36px; margin-bottom:12px; display:block;"></i>
                <p>暂无版本记录。</p>
            </div>`;
        return;
    }

    let html = '<div style="padding:20px;"><table class="table table-hover" style="font-size:13px;">';
    html += `<thead><tr>
        <th style="width:80px;">版本号</th>
        <th style="width:180px;">发布时间</th>
        <th style="width:100px;">发布人</th>
        <th style="width:80px;">角色</th>
        <th>升级说明</th>
    </tr></thead><tbody>`;

    // 从新到旧排列
    const sorted = [...versions].reverse();
    for (const v of sorted) {
        const isCurrent = v.version === currentVersion;
        const rowStyle = isCurrent ? 'background:#ecf5ff;' : '';
        const versionBadgeStyle = isCurrent
            ? 'border:1px solid #b3d8ff; color:#409EFF; background:#ecf5ff;'
            : 'border:1px solid #dcdfe6; color:#606266;';
        const currentBadge = isCurrent
            ? ' <span class="badge" style="background:#409EFF; font-size:10px; margin-left:4px;">当前</span>'
            : '';

        html += `<tr style="${rowStyle}">
            <td><span style="display:inline-block; padding:2px 8px; border-radius:4px;
                             font-size:12px; ${versionBadgeStyle}">${v.version}</span>${currentBadge}</td>
            <td>${v.publishTime}</td>
            <td>${v.publisher}</td>
            <td>${v.publisherRole}</td>
            <td title="${v.changeSummary}">${v.changeSummary || '-'}</td>
        </tr>`;
    }

    html += '</tbody></table></div>';
    container.innerHTML = html;
}
```

---

#### 1.12 JavaScript — `publishNewVersion()`（版本升级）

```javascript
function publishNewVersion() {
    const rule = ruleDatabase[currentRuleCode];
    if (!rule) return;

    const currentVersion = rule.currentVersion || 'V1';
    const currentNum = parseInt(currentVersion.replace('V', ''));
    const newVersion = 'V' + (currentNum + 1);

    // 更新弹窗内容
    document.getElementById('publishCurrentVersion').textContent = currentVersion;
    document.getElementById('publishNewVersion').textContent = newVersion;
    document.getElementById('versionChangeSummary').value = '';

    // 打开弹窗
    const modal = new bootstrap.Modal(document.getElementById('publishVersionModal'));
    modal.show();
}

// 确认发布按钮点击事件
document.getElementById('btnConfirmPublish').addEventListener('click', function() {
    const rule = ruleDatabase[currentRuleCode];
    const currentVersion = rule.currentVersion || 'V1';
    const currentNum = parseInt(currentVersion.replace('V', ''));
    const newVersion = 'V' + (currentNum + 1);
    const changeSummary = document.getElementById('versionChangeSummary').value.trim() || '版本更新';

    // 1. 快照当前数据
    const snapshot = {
        basic: JSON.parse(JSON.stringify(rule.basic)),
        tech: JSON.parse(JSON.stringify(rule.tech))
    };

    // 2. 更新版本号
    rule.currentVersion = newVersion;

    // 3. 新增版本历史条目
    if (!versionHistoryMap[currentRuleCode]) {
        versionHistoryMap[currentRuleCode] = [];
    }
    versionHistoryMap[currentRuleCode].push({
        version: newVersion,
        publishTime: new Date().toLocaleString('zh-CN', {
            year: 'numeric', month: '2-digit', day: '2-digit',
            hour: '2-digit', minute: '2-digit', second: '2-digit'
        }).replace(/\//g, '-'),
        publisher: getCurrentUserName(),
        publisherRole: currentUserRole,
        changeSummary: changeSummary,
        snapshot: snapshot
    });

    // 4. 新增链事件
    const chainData = chainLifecycleMap[currentRuleCode];
    if (chainData) {
        // 移除旧的 current 标记
        chainData.chain.forEach(e => e.current = false);
        // 新增版本发布事件
        chainData.chain.push({
            event: `版本发布 [${newVersion}]`,
            blockHeight: Math.max(...chainData.chain.map(e => e.blockHeight || 0)) + 1,
            txHash: '0x' + Math.random().toString(16).substr(2, 8) + '...' + Math.random().toString(16).substr(2, 4),
            time: new Date().toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }).replace(/\//g, '-'),
            evidenceNo: 'CZ' + Date.now(),
            contentHash: '0x' + Math.random().toString(16).substr(2, 8) + '...',
            version: newVersion,
            isVersionPublish: true,
            current: true
        });
    }

    // 5. 更新版本徽章
    const badge = document.getElementById('currentVersionBadge');
    if (badge) badge.textContent = newVersion;

    // 6. 刷新版本历史Tab
    renderVersionHistory(currentRuleCode);

    // 7. 刷新区块链Tab
    renderChainInfo(currentRuleCode);

    // 8. 关闭弹窗并提示
    bootstrap.Modal.getInstance(document.getElementById('publishVersionModal')).hide();
    alert(`版本 ${newVersion} 已发布成功并提交上链`);
});
```

---

#### 1.11 JavaScript — 修改 `loadRuleData()`

在 `loadRuleData()` 函数末尾（约 line 1700 之后），增加：

```javascript
// 更新版本徽章
const versionBadge = document.getElementById('currentVersionBadge');
if (versionBadge && rule) {
    const ver = rule.currentVersion;
    versionBadge.textContent = ver || '未发布';
    if (ver) {
        versionBadge.style.background = '#409EFF';
        versionBadge.style.color = '#fff';
    } else {
        versionBadge.style.background = '#909399';
        versionBadge.style.color = '#fff';
    }
}

// 显示/隐藏发布新版本按钮
const btnPublish = document.getElementById('btnPublishVersion');
if (btnPublish) {
    const isOnline = /* 根据规则状态判断 */;
    btnPublish.style.display = (isOnline && canEditRule(basic)) ? 'inline-flex' : 'none';
}

// 渲染版本历史Tab
renderVersionHistory(code);
```

---

#### 1.12 JavaScript — 修改 `enterEditMode()` / `exitEditMode()`

**`enterEditMode()`（约 line 1307）末尾增加**：

```javascript
const btnPublish = document.getElementById('btnPublishVersion');
if (btnPublish) {
    btnPublish.disabled = true;
    btnPublish.title = '请先保存或取消当前编辑';
}
```

**`exitEditMode()`（约 line 1339）末尾增加**：

```javascript
const btnPublish = document.getElementById('btnPublishVersion');
if (btnPublish) {
    btnPublish.disabled = false;
    btnPublish.title = '版本升级';
}
```

---

#### 1.13 JavaScript — 绑定按钮事件

在页面初始化区域增加：

```javascript
// 版本升级按钮点击
document.getElementById('btnPublishVersion').addEventListener('click', function() {
    if (isEditMode) return;
    publishNewVersion();
});
```

---

#### 1.14 入口来源控制（URL `from` 参数）

规则详情页通过 URL `from` 参数区分入口来源，控制操作按钮的显示/隐藏。

**参数定义**：

| 参数值 | 来源 | 显示内容 |
|--------|------|----------|
| `from=my` | 从"我的规则"进入 | 显示版本号、版本升级按钮、编辑按钮 |
| `from=public` | 从"公共规则库"进入 | 隐藏版本号、隐藏版本升级按钮、隐藏编辑按钮 |
| 无参数 | 默认 | 按 `from=my` 处理 |

**链接示例**：

```html
<!-- 我的规则.html 中 -->
<a href="规则详情.html?code=GZ177019905698527&from=my">规则名称</a>

<!-- 公共规则库.html 中 -->
<a href="规则详情.html?code=GZ177019905698527&from=public">规则名称</a>
```

**JS 控制逻辑**（在 `loadRuleData()` 中）：

```javascript
const fromParam = new URLSearchParams(window.location.search).get('from');
const isMyRule = !fromParam || fromParam === 'my';  // 默认按 my 处理

if (!isMyRule) {
    // 公共规则库入口：隐藏所有操作按钮和版本号
    document.getElementById('btnEditRule').style.display = 'none';
    document.getElementById('btnPublishVersion').style.display = 'none';
    document.getElementById('currentVersionBadge').style.display = 'none';
} else {
    // 我的规则入口：正常显示
    // ... 正常权限判断逻辑
}
```

---

### 2. `风险规则-我的规则.html`

#### 2.0 链接增加 `from=my` 参数

已入库 Tab 中所有指向规则详情的链接需增加 `&from=my` 参数：

```html
<!-- 修改前 -->
<a href="规则详情.html?code=GZ177019905698527">规则名称</a>

<!-- 修改后 -->
<a href="规则详情.html?code=GZ177019905698527&from=my">规则名称</a>
```

#### 2.1 已入库 Tab 增加"当前版本"列

**位置**：已入库 Tab 的 `<thead>`（约 line 1240-1256），"规则名称"列之后。**"未入库"Tab 不增加版本列。**

**表头新增**：

```html
<th>当前版本</th>
```

**完整表头**：

```html
<tr>
    <th>序号</th>
    <th>规则状态</th>
    <th>业务域</th>
    <th>规则编号</th>
    <th>规则名称</th>
    <th>当前版本</th>        <!-- 新增 -->
    <th>风险等级</th>
    <th>处置类型</th>
    <th>提出人</th>
    <th>提出单位</th>
    <th>关联场景数</th>
    <th>创建时间</th>
    <th>上链状态</th>
    <th>制度绑定状态</th>
    <th class="sticky-column">操作</th>
</tr>
```

#### 2.2 表体数据行新增版本单元格

在每行 `<tbody>` 的"规则名称" `<td>` 之后插入：

```html
<td>
    <span style="display:inline-block; padding:2px 8px; border:1px solid #b3d8ff;
                 border-radius:4px; font-size:12px; color:#409EFF; background:#ecf5ff;">
        V1
    </span>
</td>
```

无版本时显示 `<td>-</td>`。

---

### 3. `风险规则-公共规则库.html`

#### 3.0 链接增加 `from=public` 参数

列表中所有指向规则详情的链接需增加 `&from=public` 参数：

```html
<!-- 修改前 -->
<a href="规则详情.html?code=GZ177019905698527">规则名称</a>

<!-- 修改后 -->
<a href="规则详情.html?code=GZ177019905698527&from=public">规则名称</a>
```

#### 3.1 列表增加"当前版本"列

**位置**：列表 `<thead>`（约 line 590-604），"规则名称"列之后。

**表头新增**：

```html
<th>当前版本<span class="resize-handle"></span></th>
```

**完整表头**：

```html
<tr>
    <th>序号<span class="resize-handle"></span></th>
    <th>业务域<span class="resize-handle"></span></th>
    <th>规则编号<span class="resize-handle"></span></th>
    <th>规则名称<span class="resize-handle"></span></th>
    <th>当前版本<span class="resize-handle"></span></th>  <!-- 新增 -->
    <th>风险等级<span class="resize-handle"></span></th>
    <th>处置类型<span class="resize-handle"></span></th>
    <th>规则提出人<span class="resize-handle"></span></th>
    <th>提出单位<span class="resize-handle"></span></th>
    <th>上线时间<span class="resize-handle"></span></th>
    <th>上链状态<span class="resize-handle"></span></th>
</tr>
```

#### 3.2 表体数据行新增版本单元格

在每行 `<tbody>` 的"规则名称" `<td>` 之后插入：

```html
<td>
    <span style="display:inline-block; padding:2px 8px; border:1px solid #b3d8ff;
                 border-radius:4px; font-size:12px; color:#409EFF; background:#ecf5ff;">
        V1
    </span>
</td>
```

无版本时显示 `<td>-</td>`。

---

## 数据结构变更汇总

### ruleDatabase 新增字段

| 字段 | 类型 | 说明 |
|------|------|------|
| `currentVersion` | String | 当前版本号（如 'V1', 'V2'） |

### 新增全局数据结构

| 变量名 | 类型 | 说明 |
|--------|------|------|
| `versionHistoryMap` | Object | 按规则编码索引的版本历史数组 |

### chainLifecycleMap 链事件新增字段

| 字段 | 类型 | 说明 |
|------|------|------|
| `isVersionPublish` | Boolean | 标识该事件为版本发布事件 |
