|
|
@@ -0,0 +1,308 @@
|
|
|
+<template>
|
|
|
+ <div class="package-upload">
|
|
|
+ <div class="upload-header">
|
|
|
+ <span class="title">部署包管理</span>
|
|
|
+ </div>
|
|
|
+ <div class="upload-content">
|
|
|
+ <a-card title="上传部署包" :bordered="true" style="width: 100%">
|
|
|
+ <a-form :label-col="{ span: 4 }" :wrapper-col="{ span: 16 }">
|
|
|
+ <a-form-item label="部署包类型">
|
|
|
+ <a-radio-group v-model:value="packageType">
|
|
|
+ <a-radio value="frontend">前端部署包 (zip)</a-radio>
|
|
|
+ <a-radio value="backend">后端部署包 (jar)</a-radio>
|
|
|
+ </a-radio-group>
|
|
|
+ </a-form-item>
|
|
|
+ <a-form-item label="选择文件">
|
|
|
+ <a-alert type="warning" show-icon style="margin-bottom: 12px">
|
|
|
+ <template #message>
|
|
|
+ <template v-if="packageType === 'frontend'">
|
|
|
+ <span style="font-weight: bold">前端包文件命名规则:</span>
|
|
|
+ <div>
|
|
|
+ dist + 端口号.zip,例如一通三防智能管控平台前端包为
|
|
|
+ <b>dist8092.zip</b>
|
|
|
+ </div>
|
|
|
+ <div style="margin-top: 4px; font-weight: bold">上传包时请严格按照命名规则,否则会导致部署失败或引起其他系统问题!!!</div>
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <span style="font-weight: bold">后端包文件命名规则:</span>
|
|
|
+ <div style="margin-top: 4px">
|
|
|
+ <div>jeecg-cloud-gateway-3.2.0.jar</div>
|
|
|
+ <div>jeecg-cloud-nacos-3.2.0.jar</div>
|
|
|
+ <div>jeecg-cloud-system-start-3.2.0.jar</div>
|
|
|
+ <div>ventanaly-monitor-3.2.0.jar</div>
|
|
|
+ <div>ventanaly-model-3.2.0.jar</div>
|
|
|
+ <div>ventanaly-jingtaifengliang-3.2.0.jar</div>
|
|
|
+ <div>ventanaly-sharefile-3.2.0.jar</div>
|
|
|
+ <div>ventanaly-province-3.2.0.jar</div>
|
|
|
+ <div>ventanaly-ai-service-3.2.0.jar</div>
|
|
|
+ <div>ventanaly-company-3.2.0.jar</div>
|
|
|
+ </div>
|
|
|
+ <div style="margin-top: 4px; font-weight: bold">上传包时请严格按照命名规则,否则会导致部署失败或引起其他系统问题!!!</div>
|
|
|
+ </template>
|
|
|
+ </template>
|
|
|
+ </a-alert>
|
|
|
+ <a-upload :before-upload="beforeUpload" :file-list="fileList" :remove="handleRemove" :max-count="1">
|
|
|
+ <a-button type="primary">
|
|
|
+ <template #icon><upload-outlined /></template>
|
|
|
+ 选择文件
|
|
|
+ </a-button>
|
|
|
+ </a-upload>
|
|
|
+ <div class="file-tip" v-if="fileList.length > 0">
|
|
|
+ <span>已选择: {{ fileList[0]?.name }}</span>
|
|
|
+ <span style="margin-left: 12px; color: #999">({{ formatFileSize(fileList[0]?.size) }})</span>
|
|
|
+ <span v-if="nameWarning" class="name-warning">{{ nameWarning }}</span>
|
|
|
+ </div>
|
|
|
+ </a-form-item>
|
|
|
+ <a-form-item :wrapper-col="{ offset: 4, span: 16 }">
|
|
|
+ <a-button type="primary" danger :loading="deploying" :disabled="fileList.length === 0" @click="handleUploadAndDeploy">
|
|
|
+ <template #icon><cloud-upload-outlined /></template>
|
|
|
+ 上传并部署
|
|
|
+ </a-button>
|
|
|
+ </a-form-item>
|
|
|
+ </a-form>
|
|
|
+ </a-card>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 全屏loading -->
|
|
|
+ <div class="fullscreen-loading" v-if="deploying">
|
|
|
+ <a-spin size="large" />
|
|
|
+ <span class="loading-text">上传并部署中,请勿关闭或操作页面</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 部署结果弹框 -->
|
|
|
+ <a-modal v-model:visible="resultVisible" :title="deployIsSuccess ? '部署成功' : '部署失败'" :footer="null" width="600px">
|
|
|
+ <div class="deploy-status-icon" :class="deployIsSuccess ? 'is-success' : 'is-fail'">
|
|
|
+ <template v-if="deployIsSuccess">
|
|
|
+ <span class="icon-check"></span>
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <span class="icon-cross"></span>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ <div class="deploy-msg">{{ deployMsg }}</div>
|
|
|
+ </a-modal>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+ import { ref, watch } from 'vue';
|
|
|
+ import { UploadOutlined, CloudUploadOutlined } from '@ant-design/icons-vue';
|
|
|
+ import { message } from 'ant-design-vue';
|
|
|
+ import { uploadProgramApi } from './package.api';
|
|
|
+
|
|
|
+ const packageType = ref<string>('frontend');
|
|
|
+ const fileList = ref<any[]>([]);
|
|
|
+ const deploying = ref<boolean>(false);
|
|
|
+ const resultVisible = ref<boolean>(false);
|
|
|
+ const deployIsSuccess = ref<boolean>(false);
|
|
|
+ const deployMsg = ref<string>('');
|
|
|
+ const nameWarning = ref<string>('');
|
|
|
+
|
|
|
+ const acceptMap: Record<string, string> = {
|
|
|
+ frontend: '.zip',
|
|
|
+ backend: '.jar',
|
|
|
+ };
|
|
|
+
|
|
|
+ const backendJarNames = [
|
|
|
+ 'jeecg-cloud-gateway-3.2.0.jar',
|
|
|
+ 'jeecg-cloud-nacos-3.2.0.jar',
|
|
|
+ 'jeecg-cloud-system-start-3.2.0.jar',
|
|
|
+ 'ventanaly-monitor-3.2.0.jar',
|
|
|
+ 'ventanaly-model-3.2.0.jar',
|
|
|
+ 'ventanaly-jingtaifengliang-3.2.0.jar',
|
|
|
+ 'ventanaly-sharefile-3.2.0.jar',
|
|
|
+ 'ventanaly-province-3.2.0.jar',
|
|
|
+ 'ventanaly-ai-service-3.2.0.jar',
|
|
|
+ 'ventanaly-company-3.2.0.jar',
|
|
|
+ ];
|
|
|
+
|
|
|
+ watch(packageType, () => {
|
|
|
+ fileList.value = [];
|
|
|
+ nameWarning.value = '';
|
|
|
+ });
|
|
|
+
|
|
|
+ function beforeUpload(file) {
|
|
|
+ const accept = acceptMap[packageType.value];
|
|
|
+ const acceptExts = accept.split(',');
|
|
|
+ const ext = '.' + file.name.split('.').pop().toLowerCase();
|
|
|
+ if (!acceptExts.includes(ext)) {
|
|
|
+ message.error(`当前类型仅支持 ${accept} 格式的文件`);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (packageType.value === 'frontend') {
|
|
|
+ nameWarning.value = /^dist\d+\.zip$/i.test(file.name) ? '' : '包名不符合命名规则,请检查!!!';
|
|
|
+ } else {
|
|
|
+ nameWarning.value = backendJarNames.includes(file.name) ? '' : '包名不符合命名规则,请检查!!!';
|
|
|
+ }
|
|
|
+ fileList.value = [file];
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleRemove() {
|
|
|
+ fileList.value = [];
|
|
|
+ nameWarning.value = '';
|
|
|
+ }
|
|
|
+
|
|
|
+ function formatFileSize(size?: number): string {
|
|
|
+ if (!size) return '0 B';
|
|
|
+ const units = ['B', 'KB', 'MB', 'GB'];
|
|
|
+ let i = 0;
|
|
|
+ let s = size;
|
|
|
+ while (s >= 1024 && i < units.length - 1) {
|
|
|
+ s /= 1024;
|
|
|
+ i++;
|
|
|
+ }
|
|
|
+ return s.toFixed(2) + ' ' + units[i];
|
|
|
+ }
|
|
|
+
|
|
|
+ async function handleUploadAndDeploy() {
|
|
|
+ if (fileList.value.length === 0) {
|
|
|
+ message.warning('请先选择文件');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ deploying.value = true;
|
|
|
+ try {
|
|
|
+ const res = await uploadProgramApi(fileList.value[0]);
|
|
|
+ const result = res?.result || {};
|
|
|
+ deployIsSuccess.value = result.isSuccess === true;
|
|
|
+ deployMsg.value = result.msg || res?.message || '未知结果';
|
|
|
+ resultVisible.value = true;
|
|
|
+ if (result.isSuccess) {
|
|
|
+ fileList.value = [];
|
|
|
+ }
|
|
|
+ } catch (e: any) {
|
|
|
+ deployIsSuccess.value = false;
|
|
|
+ deployMsg.value = e?.message || '上传请求失败,请检查网络连接';
|
|
|
+ resultVisible.value = true;
|
|
|
+ } finally {
|
|
|
+ deploying.value = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="less" scoped>
|
|
|
+ @import '/@/design/theme.less';
|
|
|
+
|
|
|
+ .package-upload {
|
|
|
+ padding: 20px;
|
|
|
+ height: 100%;
|
|
|
+ overflow-y: auto;
|
|
|
+
|
|
|
+ :deep(.zxm-card-head-title),
|
|
|
+ :deep(.zxm-form-item-label > label) {
|
|
|
+ color: #fff !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.anticon-exclamation-circle) {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .upload-header {
|
|
|
+ margin-bottom: 20px;
|
|
|
+
|
|
|
+ .title {
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #fff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .file-tip {
|
|
|
+ margin-top: 8px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #fff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .name-warning {
|
|
|
+ margin-left: 12px;
|
|
|
+ color: #ff4d4f;
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .fullscreen-loading {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background: rgba(0, 0, 0, 0.45);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ z-index: 9999;
|
|
|
+
|
|
|
+ .loading-text {
|
|
|
+ margin-top: 16px;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 16px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .deploy-status-icon {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 48px;
|
|
|
+ height: 48px;
|
|
|
+ border-radius: 50%;
|
|
|
+ margin: 8px auto 20px;
|
|
|
+
|
|
|
+ &.is-success {
|
|
|
+ background: #52c41a;
|
|
|
+
|
|
|
+ .icon-check {
|
|
|
+ display: block;
|
|
|
+ width: 20px;
|
|
|
+ height: 36px;
|
|
|
+ border: solid #fff;
|
|
|
+ border-width: 0 5px 5px 0;
|
|
|
+ transform: rotate(45deg) translate(-3px, -3px);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &.is-fail {
|
|
|
+ background: #ff4d4f;
|
|
|
+
|
|
|
+ .icon-cross {
|
|
|
+ display: block;
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ &::before,
|
|
|
+ &::after {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ width: 5px;
|
|
|
+ height: 32px;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 2px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &::before {
|
|
|
+ transform: translate(-50%, -50%) rotate(45deg);
|
|
|
+ }
|
|
|
+
|
|
|
+ &::after {
|
|
|
+ transform: translate(-50%, -50%) rotate(-45deg);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .deploy-msg {
|
|
|
+ white-space: pre-wrap;
|
|
|
+ word-break: break-all;
|
|
|
+ max-height: 400px;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: 12px;
|
|
|
+ border-radius: 4px;
|
|
|
+ font-size: 20px;
|
|
|
+ line-height: 1.6;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+</style>
|