第277集:Vue 3低代码平台版本管理与回滚
一、版本管理概述
在低代码平台中,版本管理是一项至关重要的功能,它允许用户保存、管理和回滚应用的不同版本。通过版本管理,用户可以:
- 跟踪应用的演进历史
- 备份重要的应用状态
- 在出现问题时快速回滚到之前的稳定版本
- 支持团队协作开发
- 实现A/B测试和灰度发布
1.1 核心功能
- 版本创建:保存应用的当前状态为一个版本
- 版本列表:查看所有保存的版本
- 版本详情:查看版本的详细信息和变更内容
- 版本回滚:将应用恢复到指定版本
- 版本比较:比较不同版本之间的差异
- 版本删除:删除不需要的版本
- 版本发布:将版本发布到不同环境
1.2 架构设计
版本管理系统通常采用以下架构设计:
- 表现层:提供可视化的版本管理界面
- 核心服务层:处理版本的创建、查询、回滚等核心逻辑
- 存储层:保存版本数据和应用状态
- 集成层:与其他系统(如Git、CI/CD)集成
二、核心实现
2.1 数据模型设计
首先,我们需要设计版本管理的数据模型:
// 版本状态枚举
export enum VersionStatus {
DRAFT = 'draft', // 草稿
PUBLISHED = 'published', // 已发布
ARCHIVED = 'archived' // 已归档
}
// 版本信息接口
export interface Version {
id: string;
appId: string; // 应用ID
name: string; // 版本名称
description?: string; // 版本描述
versionNumber: string; // 版本号(如:1.0.0)
status: VersionStatus; // 版本状态
createdAt: Date; // 创建时间
createdBy: string; // 创建人
updatedAt: Date; // 更新时间
snapshot: AppSnapshot; // 应用快照
changeLog?: string; // 变更日志
tags?: string[]; // 标签
}
// 应用快照接口
export interface AppSnapshot {
pages: PageSnapshot[]; // 页面快照列表
components: ComponentSnapshot[]; // 组件快照列表
dataSources: DataSourceSnapshot[]; // 数据源快照列表
config: AppConfig; // 应用配置
dependencies?: Record<string, string>; // 依赖信息
}
// 页面快照接口
export interface PageSnapshot {
id: string;
name: string;
path: string;
structure: any; // 页面结构
config: any; // 页面配置
}
// 组件快照接口
export interface ComponentSnapshot {
id: string;
name: string;
type: string;
config: any; // 组件配置
}
// 数据源快照接口
export interface DataSourceSnapshot {
id: string;
name: string;
type: string;
config: any; // 数据源配置
}
// 应用配置接口
export interface AppConfig {
theme: string;
layout: string;
router: any;
settings: any;
}
// 版本比较结果接口
export interface VersionDiff {
added: any[];
modified: any[];
deleted: any[];
}2.2 版本管理服务
实现版本管理的核心服务,包括版本创建、查询、回滚等功能:
import type { Version, VersionStatus, AppSnapshot, VersionDiff } from './types';
// 版本管理服务
export class VersionManager {
private versions: Map<string, Version[]> = new Map();
private storageKey = 'lowcode_versions';
constructor() {
this.loadFromStorage();
}
// 创建版本
createVersion(appId: string, name: string, description: string, snapshot: AppSnapshot): Version {
const appVersions = this.versions.get(appId) || [];
const versionNumber = this.generateVersionNumber(appVersions);
const newVersion: Version = {
id: `version_${Date.now()}_${Math.floor(Math.random() * 1000)}`,
appId,
name,
description,
versionNumber,
status: VersionStatus.DRAFT,
createdAt: new Date(),
createdBy: 'admin',
updatedAt: new Date(),
snapshot
};
appVersions.push(newVersion);
this.versions.set(appId, appVersions);
this.saveToStorage();
return newVersion;
}
// 获取应用的所有版本
getVersions(appId: string): Version[] {
return this.versions.get(appId) || [];
}
// 获取指定版本
getVersion(versionId: string): Version | undefined {
for (const appVersions of this.versions.values()) {
const version = appVersions.find(v => v.id === versionId);
if (version) return version;
}
return undefined;
}
// 删除版本
deleteVersion(versionId: string): boolean {
for (const [appId, appVersions] of this.versions.entries()) {
const index = appVersions.findIndex(v => v.id === versionId);
if (index >= 0) {
appVersions.splice(index, 1);
this.versions.set(appId, appVersions);
this.saveToStorage();
return true;
}
}
return false;
}
// 更新版本状态
updateVersionStatus(versionId: string, status: VersionStatus): boolean {
const version = this.getVersion(versionId);
if (version) {
version.status = status;
version.updatedAt = new Date();
this.saveToStorage();
return true;
}
return false;
}
// 比较两个版本
compareVersions(version1: Version, version2: Version): VersionDiff {
// 实现版本比较逻辑
return {
added: [],
modified: [],
deleted: []
};
}
// 生成版本号
private generateVersionNumber(existingVersions: Version[]): string {
if (existingVersions.length === 0) {
return '1.0.0';
}
// 按版本号排序,取最大的版本号
const sortedVersions = [...existingVersions].sort((a, b) => {
const aParts = a.versionNumber.split('.').map(Number);
const bParts = b.versionNumber.split('.').map(Number);
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
const aVal = aParts[i] || 0;
const bVal = bParts[i] || 0;
if (aVal !== bVal) {
return bVal - aVal;
}
}
return 0;
});
// 递增版本号(简单实现:只递增修订号)
const latestParts = sortedVersions[0].versionNumber.split('.').map(Number);
latestParts[2] = (latestParts[2] || 0) + 1;
return latestParts.join('.');
}
// 从本地存储加载版本数据
private loadFromStorage(): void {
const savedData = localStorage.getItem(this.storageKey);
if (savedData) {
try {
const data = JSON.parse(savedData);
// 转换日期字符串为Date对象
for (const [appId, appVersions] of Object.entries(data)) {
const versions = (appVersions as any[]).map(v => ({
...v,
createdAt: new Date(v.createdAt),
updatedAt: new Date(v.updatedAt)
}));
this.versions.set(appId, versions as Version[]);
}
} catch (e) {
console.error('Failed to load versions from storage:', e);
}
}
}
// 保存版本数据到本地存储
private saveToStorage(): void {
localStorage.setItem(this.storageKey, JSON.stringify(Object.fromEntries(this.versions)));
}
}2.3 版本管理组件
实现版本管理的可视化组件,包括版本列表、版本创建、版本详情等:
<template>
<div class="version-manager">
<div class="manager-header">
<h2>版本管理</h2>
<button class="create-btn" @click="showCreateModal = true">创建新版本</button>
</div>
<!-- 版本列表 -->
<div class="version-list">
<div
v-for="version in versions"
:key="version.id"
class="version-item"
:class="{ 'active': selectedVersion?.id === version.id }"
@click="selectVersion(version)"
>
<div class="version-info">
<div class="version-header">
<div class="version-name">{{ version.name }}</div>
<div class="version-number">{{ version.versionNumber }}</div>
</div>
<div class="version-meta">
<span class="status-badge" :class="version.status">{{ getStatusText(version.status) }}</span>
<span class="created-at">{{ formatDate(version.createdAt) }}</span>
<span class="created-by">创建人:{{ version.createdBy }}</span>
</div>
<div class="version-description">{{ version.description || '无描述' }}</div>
</div>
<div class="version-actions">
<button class="action-btn view-btn" @click.stop="viewVersion(version)">查看</button>
<button class="action-btn rollback-btn" @click.stop="rollbackVersion(version)">回滚</button>
<button class="action-btn delete-btn" @click.stop="deleteVersion(version)">删除</button>
</div>
</div>
<!-- 空状态 -->
<div class="empty-state" v-if="versions.length === 0">
<p>暂无版本,请点击"创建新版本"按钮创建第一个版本</p>
</div>
</div>
<!-- 版本详情 -->
<div class="version-detail" v-if="selectedVersion">
<div class="detail-header">
<h3>{{ selectedVersion.name }} ({{ selectedVersion.versionNumber }})</h3>
<button class="close-btn" @click="selectedVersion = null">×</button>
</div>
<div class="detail-content">
<div class="detail-section">
<h4>基本信息</h4>
<div class="info-item">
<label>版本号:</label>
<span>{{ selectedVersion.versionNumber }}</span>
</div>
<div class="info-item">
<label>状态:</label>
<span class="status-badge" :class="selectedVersion.status">{{ getStatusText(selectedVersion.status) }}</span>
</div>
<div class="info-item">
<label>创建时间:</label>
<span>{{ formatDate(selectedVersion.createdAt) }}</span>
</div>
<div class="info-item">
<label>创建人:</label>
<span>{{ selectedVersion.createdBy }}</span>
</div>
<div class="info-item">
<label>更新时间:</label>
<span>{{ formatDate(selectedVersion.updatedAt) }}</span>
</div>
</div>
<div class="detail-section">
<h4>版本描述</h4>
<div class="version-description-full">{{ selectedVersion.description || '无描述' }}</div>
</div>
<div class="detail-section">
<h4>变更日志</h4>
<div class="change-log">{{ selectedVersion.changeLog || '无变更日志' }}</div>
</div>
<div class="detail-section">
<h4>版本内容</h4>
<div class="version-content">
<div class="content-item">
<strong>页面数量:</strong>{{ selectedVersion.snapshot.pages.length }}
</div>
<div class="content-item">
<strong>组件数量:</strong>{{ selectedVersion.snapshot.components.length }}
</div>
<div class="content-item">
<strong>数据源数量:</strong>{{ selectedVersion.snapshot.dataSources.length }}
</div>
</div>
</div>
<div class="detail-actions">
<button class="primary-btn" @click="rollbackVersion(selectedVersion)">回滚到此版本</button>
<button class="secondary-btn" @click="publishVersion(selectedVersion)">发布此版本</button>
<button class="secondary-btn" @click="archiveVersion(selectedVersion)">归档此版本</button>
</div>
</div>
</div>
<!-- 创建版本模态框 -->
<div class="modal" v-if="showCreateModal">
<div class="modal-overlay" @click="showCreateModal = false"></div>
<div class="modal-content">
<div class="modal-header">
<h3>创建新版本</h3>
<button class="close-btn" @click="showCreateModal = false">×</button>
</div>
<div class="modal-body">
<form @submit.prevent="createVersion">
<div class="form-group">
<label>版本名称 *</label>
<input
type="text"
v-model="newVersion.name"
required
placeholder="请输入版本名称"
/>
</div>
<div class="form-group">
<label>版本描述</label>
<textarea
v-model="newVersion.description"
placeholder="请输入版本描述"
rows="3"
></textarea>
</div>
<div class="form-group">
<label>变更日志</label>
<textarea
v-model="newVersion.changeLog"
placeholder="请输入变更内容"
rows="4"
></textarea>
</div>
<div class="form-group">
<label>标签</label>
<input
type="text"
v-model="newVersion.tagsInput"
placeholder="请输入标签,用逗号分隔"
/>
</div>
<div class="form-actions">
<button type="button" class="cancel-btn" @click="showCreateModal = false">取消</button>
<button type="submit" class="save-btn" :disabled="isCreating">
{{ isCreating ? '创建中...' : '创建版本' }}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue';
import type { Version, VersionStatus } from './types';
import { VersionManager } from './services/VersionManager';
import { VersionStatus as VersionStatusEnum } from './types';
// 版本管理器实例
const versionManager = new VersionManager();
// 当前应用ID(实际项目中应该从路由或状态管理获取)
const appId = ref('app_123');
// 版本列表
const versions = ref<Version[]>([]);
// 选中的版本
const selectedVersion = ref<Version | null>(null);
// 显示创建版本模态框
const showCreateModal = ref(false);
// 是否正在创建版本
const isCreating = ref(false);
// 新版本表单数据
const newVersion = reactive({
name: '',
description: '',
changeLog: '',
tagsInput: ''
});
// 初始化
function init() {
versions.value = versionManager.getVersions(appId.value);
}
// 格式化日期
function formatDate(date: Date): string {
return new Date(date).toLocaleString('zh-CN');
}
// 获取状态文本
function getStatusText(status: VersionStatus): string {
const statusMap: Record<VersionStatus, string> = {
[VersionStatusEnum.DRAFT]: '草稿',
[VersionStatusEnum.PUBLISHED]: '已发布',
[VersionStatusEnum.ARCHIVED]: '已归档'
};
return statusMap[status] || status;
}
// 选择版本
function selectVersion(version: Version) {
selectedVersion.value = version;
}
// 查看版本
function viewVersion(version: Version) {
selectedVersion.value = version;
}
// 创建版本
async function createVersion() {
try {
isCreating.value = true;
// 生成标签数组
const tags = newVersion.tagsInput
? newVersion.tagsInput.split(',').map(tag => tag.trim()).filter(tag => tag)
: [];
// 模拟应用快照数据
const mockSnapshot = {
pages: [
{
id: 'page_1',
name: '首页',
path: '/',
structure: {},
config: {}
}
],
components: [
{
id: 'comp_1',
name: 'Header',
type: 'header',
config: {}
}
],
dataSources: [],
config: {
theme: 'default',
layout: 'vertical',
router: {},
settings: {}
}
};
// 创建版本
const createdVersion = versionManager.createVersion(
appId.value,
newVersion.name,
newVersion.description,
mockSnapshot
);
// 更新标签
createdVersion.tags = tags;
createdVersion.changeLog = newVersion.changeLog;
// 刷新版本列表
init();
// 关闭模态框
showCreateModal.value = false;
// 重置表单
Object.assign(newVersion, {
name: '',
description: '',
changeLog: '',
tagsInput: ''
});
alert('版本创建成功!');
} catch (error) {
console.error('Failed to create version:', error);
alert('版本创建失败!');
} finally {
isCreating.value = false;
}
}
// 回滚版本
function rollbackVersion(version: Version) {
if (confirm(`确定要回滚到版本"${version.name}"吗?当前的修改将会丢失。`)) {
// 模拟回滚操作
alert(`已回滚到版本:${version.name} (${version.versionNumber})`);
}
}
// 发布版本
function publishVersion(version: Version) {
versionManager.updateVersionStatus(version.id, VersionStatusEnum.PUBLISHED);
init();
alert(`版本"${version.name}"已发布!`);
}
// 归档版本
function archiveVersion(version: Version) {
versionManager.updateVersionStatus(version.id, VersionStatusEnum.ARCHIVED);
init();
alert(`版本"${version.name}"已归档!`);
}
// 删除版本
function deleteVersion(version: Version) {
if (confirm(`确定要删除版本"${version.name}"吗?此操作不可恢复。`)) {
versionManager.deleteVersion(version.id);
init();
if (selectedVersion.value?.id === version.id) {
selectedVersion.value = null;
}
alert(`版本"${version.name}"已删除!`);
}
}
onMounted(() => {
init();
});
</script>
<style scoped>
.version-manager {
display: flex;
flex-direction: column;
height: 100%;
font-family: Arial, sans-serif;
background-color: #f5f5f5;
}
/* 头部样式 */
.manager-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
background-color: white;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.manager-header h2 {
margin: 0;
font-size: 20px;
font-weight: 600;
color: #333;
}
.create-btn {
padding: 8px 16px;
background-color: #1976d2;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s ease;
}
.create-btn:hover {
background-color: #1565c0;
}
/* 版本列表样式 */
.version-list {
padding: 20px;
overflow-y: auto;
flex: 1;
}
.version-item {
display: flex;
justify-content: space-between;
align-items: center;
background-color: white;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 16px;
margin-bottom: 12px;
cursor: pointer;
transition: all 0.3s ease;
}
.version-item:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transform: translateY(-1px);
}
.version-item.active {
border-color: #1976d2;
box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.1);
}
.version-info {
flex: 1;
min-width: 0;
}
.version-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 8px;
}
.version-name {
font-size: 16px;
font-weight: 600;
color: #333;
}
.version-number {
font-size: 14px;
color: #1976d2;
background-color: rgba(25, 118, 210, 0.1);
padding: 2px 8px;
border-radius: 12px;
}
.version-meta {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 8px;
font-size: 12px;
color: #666;
}
.status-badge {
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
}
.status-badge.draft {
background-color: #fff3cd;
color: #856404;
}
.status-badge.published {
background-color: #d4edda;
color: #155724;
}
.status-badge.archived {
background-color: #e2e3e5;
color: #383d41;
}
.version-description {
font-size: 14px;
color: #666;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.version-actions {
display: flex;
gap: 8px;
}
.action-btn {
padding: 6px 12px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s ease;
}
.view-btn {
background-color: #2196f3;
color: white;
}
.view-btn:hover {
background-color: #1976d2;
}
.rollback-btn {
background-color: #ff9800;
color: white;
}
.rollback-btn:hover {
background-color: #f57c00;
}
.delete-btn {
background-color: #f44336;
color: white;
}
.delete-btn:hover {
background-color: #d32f2f;
}
/* 空状态样式 */
.empty-state {
text-align: center;
padding: 40px 20px;
color: #999;
}
/* 版本详情样式 */
.version-detail {
position: fixed;
top: 0;
right: 0;
width: 400px;
height: 100vh;
background-color: white;
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.1);
overflow-y: auto;
z-index: 100;
}
.detail-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid #e0e0e0;
}
.detail-header h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #333;
}
.close-btn {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #666;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
transition: all 0.3s ease;
}
.close-btn:hover {
background-color: #f5f5f5;
color: #333;
}
.detail-content {
padding: 20px;
}
.detail-section {
margin-bottom: 20px;
}
.detail-section h4 {
margin: 0 0 12px 0;
font-size: 16px;
font-weight: 600;
color: #333;
}
.info-item {
display: flex;
margin-bottom: 8px;
font-size: 14px;
}
.info-item label {
width: 80px;
font-weight: 500;
color: #666;
}
.info-item span {
flex: 1;
color: #333;
}
.version-description-full, .change-log {
background-color: #fafafa;
padding: 12px;
border-radius: 4px;
font-size: 14px;
line-height: 1.5;
color: #333;
}
.version-content {
background-color: #fafafa;
padding: 12px;
border-radius: 4px;
}
.content-item {
margin-bottom: 8px;
font-size: 14px;
color: #333;
}
.detail-actions {
display: flex;
gap: 12px;
margin-top: 24px;
}
.primary-btn, .secondary-btn {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
}
.primary-btn {
background-color: #1976d2;
color: white;
}
.primary-btn:hover {
background-color: #1565c0;
}
.secondary-btn {
background-color: #f5f5f5;
color: #333;
border: 1px solid #e0e0e0;
}
.secondary-btn:hover {
background-color: #e0e0e0;
}
/* 模态框样式 */
.modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
z-index: 200;
}
.modal-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
}
.modal-content {
position: relative;
width: 500px;
max-width: 90%;
max-height: 90vh;
overflow-y: auto;
background-color: white;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}
.modal-body {
padding: 20px;
}
/* 表单样式 */
.form-group {
margin-bottom: 16px;
}
.form-group label {
display: block;
margin-bottom: 6px;
font-weight: 500;
color: #333;
}
.form-group input,
.form-group textarea {
width: 100%;
padding: 10px 12px;
border: 1px solid #e0e0e0;
border-radius: 4px;
font-size: 14px;
transition: all 0.3s ease;
}
.form-group input:focus,
.form-group textarea:focus {
outline: none;
border-color: #1976d2;
box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.1);
}
.form-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 24px;
}
.cancel-btn, .save-btn {
padding: 10px 20px;
border: none;
border-radius: 4px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
}
.cancel-btn {
background-color: #f5f5f5;
color: #333;
}
.cancel-btn:hover {
background-color: #e0e0e0;
}
.save-btn {
background-color: #1976d2;
color: white;
}
.save-btn:hover:not(:disabled) {
background-color: #1565c0;
}
.save-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>2.4 版本回滚组件
实现版本回滚功能,允许用户将应用恢复到指定版本:
<template>
<div class="rollback-manager">
<h3>版本回滚</h3>
<p class="warning-text">警告:回滚操作会覆盖当前应用的所有修改,请谨慎操作!</p>
<!-- 版本选择 -->
<div class="version-selector">
<label>选择要回滚的版本:</label>
<select v-model="selectedVersionId" class="version-select">
<option value="">请选择版本</option>
<option
v-for="version in availableVersions"
:key="version.id"
:value="version.id"
>
{{ version.name }} ({{ version.versionNumber }}) - {{ formatDate(version.createdAt) }}
</option>
</select>
</div>
<!-- 版本预览 -->
<div class="version-preview" v-if="selectedVersion">
<h4>版本详情</h4>
<div class="preview-content">
<div class="preview-item">
<strong>版本名称:</strong>{{ selectedVersion.name }}
</div>
<div class="preview-item">
<strong>版本号:</strong>{{ selectedVersion.versionNumber }}
</div>
<div class="preview-item">
<strong>创建时间:</strong>{{ formatDate(selectedVersion.createdAt) }}
</div>
<div class="preview-item">
<strong>创建人:</strong>{{ selectedVersion.createdBy }}
</div>
<div class="preview-item description">
<strong>版本描述:</strong>{{ selectedVersion.description || '无描述' }}
</div>
</div>
</div>
<!-- 回滚确认 -->
<div class="rollback-actions" v-if="selectedVersion">
<div class="confirm-section">
<input type="checkbox" id="confirm" v-model="confirmRollback" />
<label for="confirm">我已了解风险,确认回滚</label>
</div>
<button
class="rollback-btn"
@click="performRollback"
:disabled="!confirmRollback || isRollingBack"
>
{{ isRollingBack ? '回滚中...' : '确认回滚' }}
</button>
</div>
<!-- 回滚日志 -->
<div class="rollback-log" v-if="rollbackLogs.length > 0">
<h4>回滚日志</h4>
<div class="log-list">
<div
v-for="(log, index) in rollbackLogs"
:key="index"
class="log-item"
:class="log.status"
>
<div class="log-time">{{ log.time }}</div>
<div class="log-content">{{ log.message }}</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue';
import type { Version } from './types';
import { VersionManager } from './services/VersionManager';
// 版本管理器实例
const versionManager = new VersionManager();
// 当前应用ID
const appId = ref('app_123');
// 可用版本列表(只显示已发布和草稿版本)
const availableVersions = ref<Version[]>([]);
// 选中的版本ID
const selectedVersionId = ref('');
// 是否确认回滚
const confirmRollback = ref(false);
// 是否正在回滚
const isRollingBack = ref(false);
// 回滚日志
const rollbackLogs = ref<Array<{ time: string; message: string; status: 'success' | 'error' | 'info' }>>([]);
// 选中的版本
const selectedVersion = computed(() => {
if (!selectedVersionId.value) return null;
return availableVersions.value.find(v => v.id === selectedVersionId.value) || null;
});
// 初始化
function init() {
const allVersions = versionManager.getVersions(appId.value);
// 只显示草稿和已发布版本
availableVersions.value = allVersions.filter(v => v.status !== 'archived');
}
// 格式化日期
function formatDate(date: Date): string {
return new Date(date).toLocaleString('zh-CN');
}
// 监听选中版本变化
watch(selectedVersionId, () => {
confirmRollback.value = false;
});
// 执行回滚
async function performRollback() {
if (!selectedVersion.value) return;
try {
isRollingBack.value = true;
confirmRollback.value = false;
// 清空日志
rollbackLogs.value = [];
// 添加开始日志
addLog('info', `开始回滚到版本:${selectedVersion.value.name} (${selectedVersion.value.versionNumber})`);
// 模拟回滚操作
await new Promise(resolve => setTimeout(resolve, 1500));
// 添加成功日志
addLog('success', `回滚成功!已恢复到版本:${selectedVersion.value.name} (${selectedVersion.value.versionNumber})`);
// 重置选中版本
selectedVersionId.value = '';
// 触发回滚成功事件(实际项目中应该使用事件总线或状态管理)
// emit('rollback-success', selectedVersion.value);
} catch (error) {
console.error('Rollback failed:', error);
addLog('error', `回滚失败:${error instanceof Error ? error.message : '未知错误'}`);
} finally {
isRollingBack.value = false;
}
}
// 添加日志
function addLog(status: 'success' | 'error' | 'info', message: string) {
rollbackLogs.value.push({
time: new Date().toLocaleTimeString('zh-CN'),
message,
status
});
}
init();
</script>
<style scoped>
.rollback-manager {
padding: 20px;
font-family: Arial, sans-serif;
background-color: white;
border-radius: 8px;
border: 1px solid #e0e0e0;
}
.rollback-manager h3 {
margin: 0 0 16px 0;
font-size: 18px;
font-weight: 600;
color: #333;
}
.warning-text {
color: #f57c00;
background-color: #fff3cd;
padding: 12px;
border-radius: 4px;
margin-bottom: 20px;
font-size: 14px;
line-height: 1.5;
}
.version-selector {
margin-bottom: 20px;
}
.version-selector label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #333;
font-size: 14px;
}
.version-select {
width: 100%;
padding: 10px 12px;
border: 1px solid #e0e0e0;
border-radius: 4px;
font-size: 14px;
transition: all 0.3s ease;
}
.version-select:focus {
outline: none;
border-color: #1976d2;
box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.1);
}
.version-preview {
background-color: #fafafa;
padding: 16px;
border-radius: 4px;
margin-bottom: 20px;
}
.version-preview h4 {
margin: 0 0 12px 0;
font-size: 16px;
font-weight: 600;
color: #333;
}
.preview-content {
display: flex;
flex-direction: column;
gap: 8px;
}
.preview-item {
font-size: 14px;
color: #333;
}
.preview-item.description {
margin-top: 4px;
line-height: 1.5;
}
.rollback-actions {
margin-bottom: 20px;
}
.confirm-section {
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
color: #333;
}
.rollback-btn {
padding: 10px 20px;
background-color: #ff9800;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
}
.rollback-btn:hover:not(:disabled) {
background-color: #f57c00;
}
.rollback-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.rollback-log {
margin-top: 20px;
}
.rollback-log h4 {
margin: 0 0 12px 0;
font-size: 16px;
font-weight: 600;
color: #333;
}
.log-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.log-item {
padding: 10px 12px;
border-radius: 4px;
font-size: 14px;
display: flex;
flex-direction: column;
gap: 4px;
}
.log-item.info {
background-color: #e3f2fd;
color: #1565c0;
}
.log-item.success {
background-color: #d4edda;
color: #155724;
}
.log-item.error {
background-color: #f8d7da;
color: #721c24;
}
.log-time {
font-size: 12px;
opacity: 0.8;
}
.log-content {
font-size: 14px;
line-height: 1.4;
}
</style>三、使用示例
3.1 基本使用
创建版本:
- 点击"创建新版本"按钮
- 输入版本名称和描述
- 填写变更日志
- 点击"创建版本"按钮
查看版本:
- 在版本列表中点击任意版本
- 在右侧面板查看版本详情
回滚版本:
- 选择要回滚的版本
- 勾选确认框
- 点击"确认回滚"按钮
- 查看回滚日志
发布版本:
- 在版本详情中点击"发布此版本"按钮
- 版本状态变为"已发布"
归档版本:
- 在版本详情中点击"归档此版本"按钮
- 版本状态变为"已归档"
3.2 集成到低代码平台
将版本管理功能集成到低代码平台中:
<template>
<div class="lowcode-platform">
<!-- 平台头部 -->
<header class="platform-header">
<h1>Vue 3低代码平台</h1>
<div class="header-actions">
<button class="action-btn" @click="showVersionManager = true">版本管理</button>
<!-- 其他操作按钮 -->
</div>
</header>
<!-- 平台主体 -->
<main class="platform-main">
<!-- 左侧组件面板 -->
<aside class="component-panel">
<!-- 组件列表 -->
</aside>
<!-- 中间画布区域 -->
<section class="canvas-area">
<!-- 设计画布 -->
</section>
<!-- 右侧属性面板 -->
<aside class="property-panel">
<!-- 属性配置 -->
</aside>
</main>
<!-- 版本管理侧边栏 -->
<aside
class="version-sidebar"
:class="{ 'open': showVersionManager }"
>
<VersionManager @close="showVersionManager = false" />
</aside>
<!-- 版本回滚模态框 -->
<div class="modal" v-if="showRollbackModal">
<div class="modal-overlay" @click="showRollbackModal = false"></div>
<div class="modal-content large">
<div class="modal-header">
<h3>版本回滚</h3>
<button class="close-btn" @click="showRollbackModal = false">×</button>
</div>
<div class="modal-body">
<RollbackManager @rollback-success="handleRollbackSuccess" />
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import VersionManager from './components/VersionManager.vue';
import RollbackManager from './components/RollbackManager.vue';
// 显示版本管理器
const showVersionManager = ref(false);
// 显示回滚模态框
const showRollbackModal = ref(false);
// 处理回滚成功
function handleRollbackSuccess(version: any) {
showRollbackModal.value = false;
// 刷新应用状态
console.log('Rollback successful to version:', version);
}
</script>
<style scoped>
/* 平台样式省略 */
</style>四、性能优化
4.1 快照优化
- 增量快照:只保存版本之间的差异,而不是完整快照
- 压缩存储:对快照数据进行压缩,减少存储空间
- 异步保存:版本创建过程异步执行,不阻塞用户操作
- 快照验证:创建版本后验证快照的完整性
4.2 版本列表优化
- 分页加载:大量版本时采用分页加载
- 虚拟滚动:长列表使用虚拟滚动技术
- 缓存机制:缓存已加载的版本数据
- 按需加载:版本详情按需加载,不自动加载所有版本的详细信息
4.3 回滚优化
- 预加载机制:回滚前预加载目标版本的快照
- 并行处理:多任务并行处理,提高回滚速度
- 进度反馈:提供详细的回滚进度反馈
- 回滚测试:在沙箱环境中测试回滚效果,确保回滚安全
五、最佳实践
5.1 版本命名规范
- 语义化版本号:使用语义化版本号(如:1.0.0)
- 清晰的版本名称:版本名称应能反映版本的主要功能或变更
- 详细的描述:每个版本应有详细的描述和变更日志
- 标签管理:使用标签对版本进行分类(如:beta、stable、feature-x)
5.2 版本管理策略
- 定期创建版本:建议在每次重要变更后创建版本
- 保留关键版本:保留所有已发布的版本,定期清理草稿版本
- 版本审核:重要版本发布前进行审核
- 回滚测试:定期测试回滚功能,确保其可用性
5.3 团队协作
- 分支管理:支持多分支开发,每个分支有独立的版本历史
- 权限控制:不同角色有不同的版本管理权限
- 协作日志:记录版本变更的协作信息
- 通知机制:版本变更时通知相关人员
六、总结
版本管理是低代码平台的重要组成部分,它为用户提供了应用状态的历史记录和回滚能力。在本集中,我们学习了:
- 版本管理的核心功能和架构设计
- 版本管理的数据模型设计
- 版本管理服务的实现
- 版本管理可视化组件的实现
- 版本回滚功能的实现
- 版本管理的集成使用
- 性能优化策略
- 最佳实践
通过合理设计和实现版本管理功能,我们可以构建出更可靠、更易用的低代码平台,使用户能够安全地管理和演进他们的应用。
在下一集中,我们将学习如何实现低代码平台的多租户架构设计,支持多个团队或组织共享同一个平台。