<!DOCTYPE html>
|
<html lang="zh-CN">
|
|
<head>
|
<meta charset="utf-8">
|
<title>APK打包管理</title>
|
<meta name="renderer" content="webkit">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
<link rel="stylesheet" href="../../static/vue/element/element.css">
|
<style>
|
* {
|
margin: 0;
|
padding: 0;
|
box-sizing: border-box;
|
}
|
|
body {
|
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;
|
background: #f5f7fa;
|
padding: 15px;
|
}
|
|
.app-container {
|
background: #fff;
|
border-radius: 8px;
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
padding: 20px;
|
}
|
|
.header-section {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 20px;
|
flex-wrap: wrap;
|
gap: 10px;
|
}
|
|
.search-section {
|
display: flex;
|
gap: 10px;
|
flex-wrap: wrap;
|
align-items: center;
|
}
|
|
.action-buttons {
|
display: flex;
|
gap: 10px;
|
}
|
|
.auto-refresh-indicator {
|
display: flex;
|
align-items: center;
|
font-size: 12px;
|
color: #909399;
|
margin-left: 10px;
|
}
|
|
.auto-refresh-indicator.active {
|
color: #67c23a;
|
}
|
|
.auto-refresh-indicator .dot {
|
width: 8px;
|
height: 8px;
|
border-radius: 50%;
|
background: #909399;
|
margin-right: 6px;
|
}
|
|
.auto-refresh-indicator.active .dot {
|
background: #67c23a;
|
animation: pulse 1.5s infinite;
|
}
|
|
@keyframes pulse {
|
|
0%,
|
100% {
|
opacity: 1;
|
transform: scale(1);
|
}
|
|
50% {
|
opacity: 0.5;
|
transform: scale(0.8);
|
}
|
}
|
|
.status-tag {
|
font-weight: 500;
|
}
|
|
.building-animation {
|
animation: blink 1s infinite;
|
}
|
|
@keyframes blink {
|
|
0%,
|
100% {
|
opacity: 1;
|
}
|
|
50% {
|
opacity: 0.4;
|
}
|
}
|
|
.table-section {
|
margin-top: 15px;
|
}
|
|
.pagination-section {
|
margin-top: 20px;
|
display: flex;
|
justify-content: flex-end;
|
}
|
|
.dialog-footer {
|
text-align: right;
|
}
|
|
.form-tip {
|
font-size: 12px;
|
color: #909399;
|
margin-top: 5px;
|
}
|
|
.install-textarea {
|
width: 100%;
|
min-height: 120px;
|
}
|
|
.detail-row {
|
margin-bottom: 15px;
|
}
|
|
.detail-label {
|
font-weight: 500;
|
color: #606266;
|
margin-bottom: 5px;
|
}
|
|
.detail-value {
|
color: #303133;
|
word-break: break-all;
|
}
|
|
.error-text {
|
color: #f56c6c;
|
}
|
</style>
|
</head>
|
|
<body>
|
<div id="app">
|
<div class="app-container" v-loading="tableLoading">
|
<!-- 头部区域 -->
|
<div class="header-section">
|
<!-- 搜索区域 -->
|
<div class="search-section">
|
<el-select v-model="searchForm.status" placeholder="全部状态" clearable size="small"
|
style="width: 120px;">
|
<el-option label="等待中" value="0"></el-option>
|
<el-option label="打包中" value="1"></el-option>
|
<el-option label="成功" value="2"></el-option>
|
<el-option label="失败" value="3"></el-option>
|
</el-select>
|
<el-input v-model="searchForm.repo_alias" placeholder="仓库别名" size="small" style="width: 150px;"
|
clearable></el-input>
|
<el-input v-model="searchForm.branch" placeholder="分支名称" size="small" style="width: 120px;"
|
clearable></el-input>
|
<el-button type="primary" size="small" icon="el-icon-search" @click="handleSearch">搜索</el-button>
|
<el-button size="small" icon="el-icon-refresh-left" @click="handleReset">重置</el-button>
|
</div>
|
|
<!-- 操作按钮 -->
|
<div class="action-buttons">
|
<el-button type="primary" size="small" icon="el-icon-plus" @click="showBuildDialog">新建打包</el-button>
|
<el-button type="success" size="small" icon="el-icon-refresh" @click="refreshAllTasks"
|
:loading="refreshing">刷新状态</el-button>
|
<el-button type="danger" size="small" icon="el-icon-delete" @click="handleBatchDelete"
|
:disabled="selectedRows.length === 0">删除</el-button>
|
<div class="auto-refresh-indicator" :class="{ active: autoRefreshEnabled }">
|
<span class="dot"></span>
|
<span>{{ autoRefreshEnabled ? '自动刷新中' : '自动刷新已关闭' }}</span>
|
</div>
|
</div>
|
</div>
|
|
<!-- 表格区域 -->
|
<div class="table-section">
|
<el-table :data="tableData" border stripe @selection-change="handleSelectionChange" style="width: 100%;"
|
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }">
|
<el-table-column type="selection" width="50" align="center"></el-table-column>
|
<el-table-column prop="id" label="ID" width="60" align="center"></el-table-column>
|
<el-table-column prop="taskId" label="任务ID" min-width="200" show-overflow-tooltip></el-table-column>
|
<el-table-column prop="repoAlias" label="仓库别名" width="120" align="center"></el-table-column>
|
<el-table-column prop="branch" label="分支" width="100" align="center"></el-table-column>
|
<el-table-column prop="buildType" label="类型" width="80" align="center">
|
<template slot-scope="scope">
|
<el-tag size="mini" :type="scope.row.buildType === 'release' ? 'success' : 'warning'">
|
{{ scope.row.buildType }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column prop="status" label="状态" width="100" align="center">
|
<template slot-scope="scope">
|
<el-tag size="small" :type="getStatusType(scope.row.status)"
|
:class="{ 'building-animation': scope.row.status === 1 }">
|
{{ getStatusText(scope.row.status) }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column prop="projectName" label="项目名称" min-width="150"
|
show-overflow-tooltip></el-table-column>
|
<el-table-column prop="createdAt" label="创建时间" width="160" align="center">
|
<template slot-scope="scope">
|
{{ formatDate(scope.row.createdAt) }}
|
</template>
|
</el-table-column>
|
<el-table-column label="操作" width="180" align="center" fixed="right">
|
<template slot-scope="scope">
|
<el-button type="text" size="small" @click="showDetail(scope.row)">详情</el-button>
|
<el-button type="text" size="small" v-if="scope.row.status === 2 && scope.row.apkPath"
|
@click="downloadApk(scope.row)" :loading="scope.row.downloading">下载</el-button>
|
<el-button type="text" size="small" style="color: #67c23a;"
|
v-if="scope.row.status === 2 && scope.row.apkPath"
|
@click="showInstallDialog(scope.row)">安装</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
</div>
|
|
<!-- 分页 -->
|
<div class="pagination-section">
|
<el-pagination background layout="total, sizes, prev, pager, next, jumper" :total="total"
|
:page-size="pageSize" :current-page="currentPage" :page-sizes="[10, 20, 50, 100]"
|
@size-change="handleSizeChange" @current-change="handlePageChange">
|
</el-pagination>
|
</div>
|
</div>
|
|
<!-- 新建打包对话框 -->
|
<el-dialog title="新建打包任务" :visible.sync="buildDialogVisible" width="450px" :close-on-click-modal="false">
|
<el-form :model="buildForm" :rules="buildRules" ref="buildFormRef" label-width="100px">
|
<el-form-item label="打包类型" prop="buildType">
|
<el-radio-group v-model="buildForm.buildType">
|
<el-radio label="release">Release</el-radio>
|
<el-radio label="debug">Debug</el-radio>
|
</el-radio-group>
|
</el-form-item>
|
<el-form-item label="目标类型" prop="android_target">
|
<el-select v-model="buildForm.android_target" placeholder="请选择目标类型" style="width: 100%;">
|
<el-option label="TV" value="tv"></el-option>
|
<el-option label="PDA" value="pda"></el-option>
|
</el-select>
|
</el-form-item>
|
<el-form-item label="仓库别名" prop="repoAlias">
|
<el-input v-model="buildForm.repoAlias" placeholder="请输入仓库别名"></el-input>
|
</el-form-item>
|
<el-form-item label="分支名称" prop="branch">
|
<el-input v-model="buildForm.branch" placeholder="请输入分支名称"></el-input>
|
<div class="form-tip">默认为 master 分支</div>
|
</el-form-item>
|
<el-form-item label="服务器地址" prop="server_url">
|
<el-input v-model="buildForm.server_url" placeholder="请输入 server_url"></el-input>
|
<div class="form-tip">TV 包必填</div>
|
</el-form-item>
|
</el-form>
|
<div slot="footer" class="dialog-footer">
|
<el-button @click="buildDialogVisible = false">取消</el-button>
|
<el-button type="primary" @click="submitBuild" :loading="buildSubmitting">提交打包</el-button>
|
</div>
|
</el-dialog>
|
|
<!-- 任务详情对话框 -->
|
<el-dialog title="任务详情" :visible.sync="detailDialogVisible" width="550px">
|
<div v-if="currentDetail">
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<div class="detail-row">
|
<div class="detail-label">任务ID</div>
|
<div class="detail-value">{{ currentDetail.taskId }}</div>
|
</div>
|
</el-col>
|
<el-col :span="12">
|
<div class="detail-row">
|
<div class="detail-label">状态</div>
|
<div class="detail-value">
|
<el-tag size="small" :type="getStatusType(currentDetail.status)">
|
{{ getStatusText(currentDetail.status) }}
|
</el-tag>
|
</div>
|
</div>
|
</el-col>
|
</el-row>
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<div class="detail-row">
|
<div class="detail-label">仓库别名</div>
|
<div class="detail-value">{{ currentDetail.repoAlias }}</div>
|
</div>
|
</el-col>
|
<el-col :span="12">
|
<div class="detail-row">
|
<div class="detail-label">分支</div>
|
<div class="detail-value">{{ currentDetail.branch }}</div>
|
</div>
|
</el-col>
|
</el-row>
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<div class="detail-row">
|
<div class="detail-label">打包类型</div>
|
<div class="detail-value">{{ currentDetail.buildType }}</div>
|
</div>
|
</el-col>
|
<el-col :span="12">
|
<div class="detail-row">
|
<div class="detail-label">项目名称</div>
|
<div class="detail-value">{{ currentDetail.projectName || '-' }}</div>
|
</div>
|
</el-col>
|
</el-row>
|
<div class="detail-row">
|
<div class="detail-label">APK路径</div>
|
<div class="detail-value">{{ currentDetail.apkPath || '未下载' }}</div>
|
</div>
|
<el-row :gutter="20">
|
<el-col :span="8">
|
<div class="detail-row">
|
<div class="detail-label">创建时间</div>
|
<div class="detail-value">{{ formatDate(currentDetail.createdAt) }}</div>
|
</div>
|
</el-col>
|
<el-col :span="8">
|
<div class="detail-row">
|
<div class="detail-label">开始时间</div>
|
<div class="detail-value">{{ formatDate(currentDetail.startedAt) || '-' }}</div>
|
</div>
|
</el-col>
|
<el-col :span="8">
|
<div class="detail-row">
|
<div class="detail-label">完成时间</div>
|
<div class="detail-value">{{ formatDate(currentDetail.finishedAt) || '-' }}</div>
|
</div>
|
</el-col>
|
</el-row>
|
<div class="detail-row" v-if="currentDetail.error">
|
<div class="detail-label">错误信息</div>
|
<div class="detail-value error-text">{{ currentDetail.error }}</div>
|
</div>
|
</div>
|
<div slot="footer" class="dialog-footer">
|
<el-button @click="detailDialogVisible = false">关闭</el-button>
|
</div>
|
</el-dialog>
|
|
<!-- 安装对话框 -->
|
<el-dialog title="安装APK到设备" :visible.sync="installDialogVisible" width="450px" :close-on-click-modal="false">
|
<el-form label-width="80px">
|
<el-form-item label="设备IP">
|
<el-input type="textarea" v-model="installIps" class="install-textarea"
|
placeholder="请输入设备IP地址,多个设备请换行分隔 例如: 192.168.1.100 192.168.1.101">
|
</el-input>
|
<div class="form-tip">支持批量安装,每行一个IP地址</div>
|
</el-form-item>
|
</el-form>
|
<div slot="footer" class="dialog-footer">
|
<el-button @click="installDialogVisible = false">取消</el-button>
|
<el-button type="primary" @click="submitInstall" :loading="installing">确认安装</el-button>
|
</div>
|
</el-dialog>
|
</div>
|
|
<script src="../../static/vue/js/vue.min.js"></script>
|
<script src="../../static/vue/element/element.js"></script>
|
<script src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
|
<script src="../../static/js/common.js"></script>
|
<script>
|
new Vue({
|
el: '#app',
|
data: {
|
// 表格数据
|
tableData: [],
|
tableLoading: false,
|
total: 0,
|
currentPage: 1,
|
pageSize: 10,
|
selectedRows: [],
|
|
// 搜索表单
|
searchForm: {
|
status: '',
|
repo_alias: '',
|
branch: ''
|
},
|
|
// 自动刷新
|
autoRefreshEnabled: false,
|
autoRefreshTimer: null,
|
refreshing: false,
|
|
// 新建打包对话框
|
buildDialogVisible: false,
|
buildSubmitting: false,
|
buildForm: {
|
buildType: 'release',
|
android_target: 'tv',
|
repoAlias: 'zy-monitor',
|
branch: 'master',
|
server_url: ''
|
},
|
buildRules: {
|
android_target: [{ required: true, message: '请选择目标类型', trigger: 'change' }],
|
repoAlias: [{ required: true, message: '请输入仓库别名', trigger: 'blur' }]
|
},
|
|
// 详情对话框
|
detailDialogVisible: false,
|
currentDetail: null,
|
|
// 安装对话框
|
installDialogVisible: false,
|
installIps: '',
|
installing: false,
|
currentInstallTask: null
|
},
|
|
created() {
|
this.loadData();
|
this.buildForm.server_url = this.getServerUrl();
|
},
|
|
beforeDestroy() {
|
this.stopAutoRefresh();
|
},
|
|
methods: {
|
// 获取请求头
|
getHeaders() {
|
return { 'token': localStorage.getItem('token') };
|
},
|
getServerUrl() {
|
const { protocol, host } = window.location;
|
return `${protocol}//${host}/monitor`;
|
},
|
|
// 加载数据
|
loadData() {
|
this.tableLoading = true;
|
const params = {
|
curr: this.currentPage,
|
limit: this.pageSize,
|
...this.searchForm
|
};
|
|
$.ajax({
|
url: baseUrl + '/apkBuildTask/list/auth',
|
headers: this.getHeaders(),
|
data: params,
|
success: (res) => {
|
this.tableLoading = false;
|
if (res.code === 200) {
|
this.tableData = res.data.records || [];
|
this.total = res.data.total || 0;
|
this.checkAutoRefresh();
|
} else if (res.code === 403) {
|
top.location.href = baseUrl + '/';
|
} else {
|
this.$message.error(res.msg || '加载失败');
|
}
|
},
|
error: () => {
|
this.tableLoading = false;
|
this.$message.error('请求失败');
|
}
|
});
|
},
|
|
// 检查是否需要自动刷新
|
checkAutoRefresh() {
|
const hasPending = this.tableData.some(item => item.status === 0 || item.status === 1);
|
if (hasPending && !this.autoRefreshEnabled) {
|
this.startAutoRefresh();
|
} else if (!hasPending && this.autoRefreshEnabled) {
|
this.stopAutoRefresh();
|
}
|
},
|
|
// 开启自动刷新
|
startAutoRefresh() {
|
if (this.autoRefreshTimer) return;
|
this.autoRefreshEnabled = true;
|
this.autoRefreshTimer = setInterval(() => {
|
this.silentRefresh();
|
}, 5000);
|
},
|
|
// 停止自动刷新
|
stopAutoRefresh() {
|
if (this.autoRefreshTimer) {
|
clearInterval(this.autoRefreshTimer);
|
this.autoRefreshTimer = null;
|
}
|
this.autoRefreshEnabled = false;
|
},
|
|
// 静默刷新
|
silentRefresh() {
|
$.ajax({
|
url: baseUrl + '/apkBuildTask/refreshAll/auth',
|
headers: this.getHeaders(),
|
method: 'POST',
|
success: (res) => {
|
if (res.code === 200) {
|
this.loadData();
|
}
|
}
|
});
|
},
|
|
// 手动刷新所有任务
|
refreshAllTasks() {
|
this.refreshing = true;
|
$.ajax({
|
url: baseUrl + '/apkBuildTask/refreshAll/auth',
|
headers: this.getHeaders(),
|
method: 'POST',
|
success: (res) => {
|
this.refreshing = false;
|
if (res.code === 200) {
|
this.$message.success(`刷新完成,更新了 ${res.data.refreshedCount} 个任务`);
|
this.loadData();
|
} else if (res.code === 403) {
|
top.location.href = baseUrl + '/';
|
} else {
|
this.$message.error(res.msg || '刷新失败');
|
}
|
},
|
error: () => {
|
this.refreshing = false;
|
this.$message.error('请求失败');
|
}
|
});
|
},
|
|
// 搜索
|
handleSearch() {
|
this.currentPage = 1;
|
this.loadData();
|
},
|
|
// 重置
|
handleReset() {
|
this.searchForm = { status: '', repo_alias: '', branch: '' };
|
this.currentPage = 1;
|
this.loadData();
|
},
|
|
// 分页
|
handlePageChange(page) {
|
this.currentPage = page;
|
this.loadData();
|
},
|
|
handleSizeChange(size) {
|
this.pageSize = size;
|
this.currentPage = 1;
|
this.loadData();
|
},
|
|
// 选择行
|
handleSelectionChange(selection) {
|
this.selectedRows = selection;
|
},
|
|
// 批量删除
|
handleBatchDelete() {
|
if (this.selectedRows.length === 0) return;
|
|
this.$confirm(`确定删除选中的 ${this.selectedRows.length} 条记录吗?`, '提示', {
|
type: 'warning'
|
}).then(() => {
|
const ids = this.selectedRows.map(row => row.id);
|
$.ajax({
|
url: baseUrl + '/apkBuildTask/delete/auth',
|
headers: this.getHeaders(),
|
method: 'POST',
|
data: { ids: ids },
|
traditional: true,
|
success: (res) => {
|
if (res.code === 200) {
|
this.$message.success('删除成功');
|
this.loadData();
|
} else {
|
this.$message.error(res.msg || '删除失败');
|
}
|
}
|
});
|
}).catch(() => { });
|
},
|
|
// 显示新建打包对话框
|
showBuildDialog() {
|
this.buildForm = {
|
buildType: 'release',
|
android_target: 'tv',
|
repoAlias: 'zy-monitor',
|
branch: 'master',
|
server_url: this.getServerUrl()
|
};
|
this.buildDialogVisible = true;
|
},
|
|
// 提交打包
|
submitBuild() {
|
this.$refs.buildFormRef.validate((valid) => {
|
if (!valid) return;
|
|
this.buildSubmitting = true;
|
$.ajax({
|
url: baseUrl + '/apkBuildTask/build/auth',
|
headers: this.getHeaders(),
|
method: 'POST',
|
contentType: 'application/json;charset=UTF-8',
|
data: JSON.stringify(this.buildForm),
|
success: (res) => {
|
this.buildSubmitting = false;
|
if (res.code === 200) {
|
this.$message.success('打包任务已创建');
|
this.buildDialogVisible = false;
|
this.loadData();
|
} else if (res.code === 403) {
|
top.location.href = baseUrl + '/';
|
} else {
|
this.$message.error(res.msg || '创建失败');
|
}
|
},
|
error: () => {
|
this.buildSubmitting = false;
|
this.$message.error('请求失败');
|
}
|
});
|
});
|
},
|
|
// 显示详情
|
showDetail(row) {
|
this.currentDetail = row;
|
this.detailDialogVisible = true;
|
},
|
|
// 下载APK
|
downloadApk(row) {
|
this.$set(row, 'downloading', true);
|
$.ajax({
|
url: baseUrl + '/apkBuildTask/downloadFile/' + row.id + '/auth',
|
headers: this.getHeaders(),
|
method: 'GET',
|
xhrFields: { responseType: 'blob' },
|
success: (data, status, xhr) => {
|
this.$set(row, 'downloading', false);
|
const contentType = xhr.getResponseHeader('Content-Type') || '';
|
if (contentType.indexOf('application/json') !== -1) {
|
const reader = new FileReader();
|
reader.onload = () => {
|
try {
|
const res = JSON.parse(reader.result);
|
this.$message.error(res.msg || '下载失败');
|
} catch (e) {
|
this.$message.error('下载失败');
|
}
|
};
|
reader.readAsText(data);
|
return;
|
}
|
const disposition = xhr.getResponseHeader('Content-Disposition') || '';
|
const fileNameMatch = disposition.match(/filename="?([^"]+)"?/);
|
let fileName = fileNameMatch ? decodeURIComponent(fileNameMatch[1]) : '';
|
if (!fileName) {
|
fileName = (row.projectName ? row.projectName : row.id) + '.apk';
|
}
|
const blob = new Blob([data], { type: contentType || 'application/octet-stream' });
|
const link = document.createElement('a');
|
link.href = URL.createObjectURL(blob);
|
link.download = fileName;
|
document.body.appendChild(link);
|
link.click();
|
document.body.removeChild(link);
|
URL.revokeObjectURL(link.href);
|
this.$message.success('APK已开始下载');
|
},
|
error: (xhr) => {
|
this.$set(row, 'downloading', false);
|
if (xhr && xhr.response) {
|
const reader = new FileReader();
|
reader.onload = () => {
|
try {
|
const res = JSON.parse(reader.result);
|
this.$message.error(res.msg || '下载失败');
|
} catch (e) {
|
this.$message.error('请求失败');
|
}
|
};
|
reader.readAsText(xhr.response);
|
} else {
|
this.$message.error('请求失败');
|
}
|
}
|
});
|
},
|
|
// 显示安装对话框
|
showInstallDialog(row) {
|
this.currentInstallTask = row;
|
this.installIps = '';
|
this.installDialogVisible = true;
|
},
|
|
// 提交安装
|
submitInstall() {
|
if (!this.installIps.trim()) {
|
this.$message.warning('请输入设备IP地址');
|
return;
|
}
|
|
const ips = this.installIps.split('\n')
|
.map(ip => ip.trim())
|
.filter(ip => ip !== '');
|
|
if (ips.length === 0) {
|
this.$message.warning('请输入有效的设备IP地址');
|
return;
|
}
|
|
this.installing = true;
|
const taskId = this.currentInstallTask.id;
|
|
if (ips.length === 1) {
|
// 单设备安装
|
$.ajax({
|
url: baseUrl + '/apkBuildTask/install/' + taskId + '/auth',
|
headers: this.getHeaders(),
|
method: 'POST',
|
contentType: 'application/json;charset=UTF-8',
|
data: JSON.stringify({ deviceIp: ips[0] }),
|
success: (res) => {
|
this.installing = false;
|
this.installDialogVisible = false;
|
if (res.code === 200) {
|
this.$alert(res.data.result, '安装结果', { type: 'success' });
|
} else {
|
this.$message.error(res.msg || '安装失败');
|
}
|
},
|
error: () => {
|
this.installing = false;
|
this.$message.error('请求失败');
|
}
|
});
|
} else {
|
// 批量安装
|
$.ajax({
|
url: baseUrl + '/apkBuildTask/installBatch/' + taskId + '/auth',
|
headers: this.getHeaders(),
|
method: 'POST',
|
contentType: 'application/json;charset=UTF-8',
|
data: JSON.stringify({ deviceIps: ips }),
|
success: (res) => {
|
this.installing = false;
|
this.installDialogVisible = false;
|
if (res.code === 200) {
|
this.$alert(res.data.join('\n'), '批量安装结果', { type: 'success' });
|
} else {
|
this.$message.error(res.msg || '安装失败');
|
}
|
},
|
error: () => {
|
this.installing = false;
|
this.$message.error('请求失败');
|
}
|
});
|
}
|
},
|
|
// 获取状态类型
|
getStatusType(status) {
|
const types = { 0: 'info', 1: '', 2: 'success', 3: 'danger' };
|
return types[status] || 'info';
|
},
|
|
// 获取状态文本
|
getStatusText(status) {
|
const texts = { 0: '等待中', 1: '打包中', 2: '成功', 3: '失败' };
|
return texts[status] || '未知';
|
},
|
|
// 格式化日期
|
formatDate(timestamp) {
|
if (!timestamp) return '';
|
const date = new Date(timestamp);
|
const year = date.getFullYear();
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
const day = String(date.getDate()).padStart(2, '0');
|
const hours = String(date.getHours()).padStart(2, '0');
|
const minutes = String(date.getMinutes()).padStart(2, '0');
|
const seconds = String(date.getSeconds()).padStart(2, '0');
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
}
|
}
|
});
|
</script>
|
</body>
|
|
</html>
|