全栈小学生 bf224633d5 update
2024-08-17 09:35:06 +08:00

995 lines
41 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<!--应用市场-->
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center">
<span class="text-page-title">{{ t('localAppText') }}</span>
<el-input class="!w-[250px]" :placeholder="t('search')" v-model.trim="search_name" @keyup.enter="query">
<template #suffix>
<el-icon class="el-input__icon cursor-pointer" size="14px" @click="query">
<search />
</el-icon>
</template>
</el-input>
</div>
<div class="flex justify-between my-[20px]">
<div class="flex">
<div :class="['flex items-center text-[14px] h-[32px] text-[#a6a9ad] border-[1px] border-solid my-[3px] border-[var(--el-color-info-light-8)] rounded-full px-[20px] mr-[24px] cursor-pointer hover:bg-[var(--el-color-info-light-8)]', { '!text-[#fff] !bg-[#000] !border-[#000]': activeName === 'installed' }]" @click="activeNameTabFn('installed')">
{{ t('installLabel') }}
</div>
<div :class="['flex items-center text-[14px] h-[32px] text-[#a6a9ad] border-[1px] border-solid my-[3px] border-[var(--el-color-info-light-8)] rounded-full px-[20px] mr-[24px] cursor-pointer hover:bg-[var(--el-color-info-light-8)]', { '!text-[#fff] !bg-[#000] !border-[#000]': activeName === 'uninstalled' }]" @click="activeNameTabFn('uninstalled')">
{{ t('uninstalledLabel') }}
</div>
<div :class="['flex items-center text-[14px] h-[32px] text-[#a6a9ad] border-[1px] border-solid my-[3px] border-[var(--el-color-info-light-8)] rounded-full px-[20px] mr-[24px] cursor-pointer hover:bg-[var(--el-color-info-light-8)]', { '!text-[#fff] !bg-[#000] !border-[#000]': activeName === 'all' }]" @click="activeNameTabFn('all')">
{{ t('buyLabel') }}
</div>
</div>
<el-button type="primary" round @click="handleCloudBuild" :loading="cloudBuildRef?.loading">{{ t('cloudBuild') }}</el-button>
</div>
<div>
<el-table v-if="localList[activeName].length&&!authLoading" :data="info[activeName]" size="large" class="pt-[5px]">
<el-table-column :label="t('appName')" align="left" width="320">
<template #default="{ row }">
<div class="flex items-center cursor-pointer" @click = "handleTips">
<el-image class="w-[54px] h-[54px]" :src="row.icon" fit="contain">
<template #error>
<div class="flex items-center w-full h-full">
<img class="max-w-full max-h-full" src="@/app/assets/images/icon-addon.png" alt="">
</div>
</template>
</el-image>
<div class="flex flex-col justify-center pl-[20px] font-500 text-[13px]">
<div class="w-[236px] truncate leading-[18px]">{{ row.title }}</div>
<div class="w-[236px] truncate leading-[18px] mt-[6px]" v-if="row.install_info && Object.keys(row.install_info)?.length">{{ row.install_info.version }}</div>
<div class="w-[236px] truncate leading-[18px] mt-[6px]" v-else>{{ row.version }}</div>
<div class="mt-[3px]" v-if="row.install_info && Object.keys(row.install_info)?.length && row.install_info.version != row.version">
<el-tag type="danger" size="small">{{ t('newVersion') }}{{ row.version }}</el-tag>
</div>
</div>
</div>
</template>
</el-table-column>
<el-table-column align="left" min-width="120">
<template #header>
<div class="flex items-center">
<span class="font-500 text-[13px] mr-[5px]">{{ t('appIdentification') }}</span>
<el-tooltip class="box-item" effect="light" :content="t('tipText')" placement="bottom">
<el-icon class="cursor-pointer text-[16px] text-[#a9a9a9]">
<QuestionFilled />
</el-icon>
</el-tooltip>
</div>
</template>
<template #default="{ row }">
<span class="font-500 text-[13px]">{{ row.key }}</span>
</template>
</el-table-column>
<el-table-column prop="" :label="t('introduction')" align="left" min-width="200">
<template #default="{ row }">
<span class="font-500 text-[13px] multi-hidden">{{ row.desc }}</span>
</template>
</el-table-column>
<el-table-column :label="t('type')" align="left" min-width="100">
<template #default="{ row }">
<span class="font-500 text-[13px]">{{ row.type === 'app' ? t('app') : t('addon') }}</span>
</template>
</el-table-column>
<el-table-column prop="" :label="t('author')" align="left" min-width="100">
<template #default="{ row }">
<span class="font-500 text-[13px]">{{ row.author }}</span>
</template>
</el-table-column>
<el-table-column :label="t('operation')" fixed="right" align="right" width="200">
<template #default="{ row }">
<el-button class="!text-[13px]" v-if="row.install_info && Object.keys(row.install_info)?.length && row.install_info.version != row.version" type="primary" link @click="upgradeAddonFn(row.key)">{{ t('upgrade') }}</el-button>
<el-button class="!text-[13px]" v-if="row.install_info && Object.keys(row.install_info)?.length" type="primary" link @click="uninstallAddonFn(row.key)">{{ t('unload') }}</el-button>
<template v-if="row.is_download && (!row.install_info || !Object.keys(row.install_info).length)">
<el-button class="!text-[13px]" type="primary" link @click="installAddonFn(row.key)">{{ t('install') }}</el-button>
<el-button class="!text-[13px]" type="primary" link @click="deleteAddonFn(row.key)">{{ t('delete') }}</el-button>
</template>
<el-button class="!text-[13px]" v-if="!row.is_download" :loading="downloading == row.key" :disabled="downloading != ''" type="primary" link @click.stop="downEvent(row)">
<span>{{ t('down') }}</span>
</el-button>
<el-button class="!text-[13px]" type="primary" link @click="getAddonDetialFn(row)">{{ t('detail') }}</el-button>
</template>
</el-table-column>
</el-table>
<div class="data-loading" v-if="authLoading || !localList[activeName].length">
<el-table :data="[]" size="large" class="pt-[5px]">
<el-table-column :label="t('appName')" align="left" width="320"></el-table-column>
<el-table-column align="left" min-width="120"></el-table-column>
<el-table-column prop="" :label="t('introduction')" align="left" min-width="200"></el-table-column>
<el-table-column :label="t('type')" align="left" min-width="100"></el-table-column>
<el-table-column prop="" :label="t('author')" align="left" min-width="100"></el-table-column>
<el-table-column :label="t('operation')" fixed="right" align="right" width="150"></el-table-column>
<template #empty><span></span></template>
</el-table>
<div class="h-[100px]" v-loading="authLoading" v-if="authLoading">
</div>
</div>
<el-empty class="mx-auto overview-empty" v-if="!localList.installed.length && !loading && activeName == 'installed'&&!authLoading">
<template #image>
<div class="w-[230px] mx-auto">
<img src="@/app/assets/images/index/apply_empty.png" class="max-w-full" alt="">
</div>
</template>
<template #description>
<p class="flex items-center">{{ t('installed-empty') }}</p>
</template>
</el-empty>
<el-empty class="mx-auto overview-empty" v-if="!localList.uninstalled.length && !loading && activeName == 'uninstalled'&&!authLoading">
<template #image>
<div class="w-[230px] mx-auto">
<img src="@/app/assets/images/index/apply_empty.png" class="max-w-full" alt="">
</div>
</template>
<template #description>
<p class="flex items-center">
<span>{{ t('descriptionLeft') }}</span>
<el-link type="primary" @click="goRouter" class="mx-[5px]">{{ t('link') }}</el-link>
<span>{{ t('descriptionRight') }}</span>
</p>
</template>
</el-empty>
<div v-if="!localList.all.length && !loading && !authinfo && activeName == 'all' && !authLoading" class="mx-auto overview-empty flex flex-col items-center pt-14 pb-6">
<div class="mb-[20px] text-sm text-[#888]">检测到当前账号尚未绑定授权,请先绑定授权!</div>
<div class="flex flex-1 flex-wrap justify-center relative">
<el-button class="w-[154px] !h-[48px] mt-[8px]" type="primary" @click="authCodeApproveFn">授权码认证</el-button>
<el-popover ref="getAuthCodeDialog" placement="bottom" :width="478" trigger="click" class="mt-[8px]">
<div class="px-[18px] py-[8px]">
<p class="leading-[32px] text-[14px]">
您在官方应用市场购买任意一款应用,即可获得授权码。输入正确授权码认证通过后,即可支持在线升级和其它相关服务</p>
<div class="flex justify-end mt-[36px]">
<el-button class="w-[182px] !h-[48px]" plain @click="market">去应用市场逛逛</el-button>
<el-button class="w-[100px] !h-[48px]" plain @click="getAuthCodeDialog.hide()">关闭</el-button>
</div>
</div>
<template #reference>
<el-button class="w-[154px] !h-[48px] mt-[8px] !text-[var(--el-color-primary)] hover:!text-[var(--el-color-primary)] !bg-transparent" plain type="primary">如何获取授权码?</el-button>
</template>
</el-popover>
</div>
</div>
<el-empty class="mx-auto overview-empty" v-if="!localList.all.length && !loading && authinfo && activeName == 'all' && !authLoading">
<template #image>
<div class="w-[230px] mx-auto">
<img src="@/app/assets/images/index/apply_empty.png" class="max-w-full" alt="">
</div>
</template>
<template #description>
<p class="flex items-center">
<span>{{ t('buyDescriptionLeft') }}</span>
<el-link type="primary" @click="goRouter" class="mx-[5px]">{{ t('link') }}</el-link>
<span>{{ t('descriptionRight') }}</span>
</p>
</template>
</el-empty>
</div>
<el-dialog v-model="authCodeApproveDialog" title="授权码认证" width="400px">
<el-form :model="formData" label-width="0" ref="formRef" :rules="formRules" class="page-form">
<el-card class="box-card !border-none" shadow="never">
<el-form-item prop="auth_code">
<el-input v-model.trim="formData.auth_code" :placeholder="t('authCodePlaceholder')" class="input-width" clearable size="large" />
</el-form-item>
<div class="mt-[20px]">
<el-form-item prop="auth_secret">
<el-input v-model.trim="formData.auth_secret" clearable :placeholder="t('authSecretPlaceholder')" class="input-width" size="large" />
</el-form-item>
</div>
<div class="text-sm mt-[10px] text-info">{{ t('authInfoTips') }}</div>
<div class="mt-[20px]">
<el-button type="primary" class="w-full" size="large" :loading="saveLoading" @click="save(formRef)">{{ t('confirm') }}</el-button>
</div>
<div class="mt-[10px] text-right">
<el-button type="primary" link @click="market">{{ t('notHaveAuth') }}</el-button>
</div>
</el-card>
</el-form>
</el-dialog>
<!-- 详情 -->
<el-dialog v-model="appStoreShowDialog" :title="t('plugDetail')" width="500px" :destroy-on-close="true">
<el-form :model="appStoreInfo" label-width="120px" ref="formRef" class="page-form">
<el-form-item :label="t('title')">
<div class="input-width"> {{ appStoreInfo.title }} </div>
</el-form-item>
<el-form-item :label="t('desc')">
<div class="input-width"> {{ appStoreInfo.desc }} </div>
</el-form-item>
<el-form-item :label="t('author')">
<div class="input-width"> {{ appStoreInfo.author }} </div>
</el-form-item>
<el-form-item :label="t('version')">
<div class="input-width"> {{ appStoreInfo.version }} </div>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" @click="appStoreShowDialog = false">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
<!-- 安装弹窗 -->
<el-dialog v-model="installShowDialog" :title="t('addonInstall')" width="850px" :close-on-click-modal="false" :close-on-press-escape="false" :before-close="installShowDialogClose">
<el-steps :space="200" :active="installStep" finish-status="success" align-center>
<el-step :title="t('envCheck')" class="flex-1" />
<el-step :title="t('installProgress')" class="flex-1" />
<el-step :title="t('installComplete')" class="flex-1" />
</el-steps>
<div v-show="installStep == 1" v-loading="!installCheckResult.dir">
<el-scrollbar max-height="50vh">
<div class="min-h-[150px]">
<div class="my-3" v-if="installCheckResult.dir">
<p class="pt-[20px] pl-[20px] ">{{ t('dirPermission') }}</p>
<div class="px-[20px] pt-[10px] text-[14px]">
<el-row class="py-[10px] items table-head-bg pl-[15px] mb-[10px]">
<el-col :span="12">
<span>{{ t('path') }}</span>
</el-col>
<el-col :span="6">
<span>{{ t('demand') }}</span>
</el-col>
<el-col :span="6">
<span>{{ t('status') }}</span>
</el-col>
</el-row>
<el-row class="pb-[10px] items pl-[15px]" v-for="(item,index) in installCheckResult.dir.is_readable" :key="index">
<el-col :span="12">
<span>{{ item.dir }}</span>
</el-col>
<el-col :span="6">
<span>{{ t('readable') }}</span>
</el-col>
<el-col :span="6">
<span v-if="item.status"><el-icon color="green"><Select /></el-icon></span>
<span v-else>
<el-icon color="red">
<CloseBold />
</el-icon>
</span>
</el-col>
</el-row>
<el-row class="pb-[10px] items pl-[15px]" v-for="(item,index) in installCheckResult.dir.is_write" :key="index">
<el-col :span="12">
<span>{{ item.dir }}</span>
</el-col>
<el-col :span="6">
<span>{{ t('write') }}</span>
</el-col>
<el-col :span="6">
<span v-if="item.status"><el-icon color="green"><Select /></el-icon></span>
<span v-else>
<el-icon color="red">
<CloseBold />
</el-icon>
</span>
</el-col>
</el-row>
</div>
</div>
</div>
</el-scrollbar>
<div class="flex justify-end">
<el-tooltip effect="dark" :content="t('installTips')" placement="top">
<el-button type="default" :disabled="!installCheckResult.is_pass || cloudInstalling" :loading="localInstalling" @click="handleInstall">{{ t('localInstall') }}</el-button>
</el-tooltip>
<el-tooltip effect="dark" :content="t('cloudInstallTips')" placement="top">
<el-button type="primary" :disabled="!installCheckResult.is_pass || localInstalling" :loading="cloudInstalling" @click="handleCloudInstall">{{ t('cloudInstall') }}</el-button>
</el-tooltip>
</div>
</div>
<div v-show="installStep == 2" class="h-[50vh] mt-[20px]">
<terminal ref="terminalRef" :context="currAddon" :init-log="null" :show-header="false" :show-log-time="true" @exec-cmd="onExecCmd"/>
</div>
<div v-show="installStep == 3" class="h-[50vh] mt-[20px] flex flex-col">
<el-result icon="success" :title="t('addonInstallSuccess')"></el-result>
<!-- 提示信息 -->
<div v-for="(item,index) in installAfterTips" class="mb-[10px]" :key="index">
<el-alert :title="item" type="error" :closable="false" />
</div>
</div>
</el-dialog>
<el-dialog v-model="uninstallShowDialog" :title="t('addonUninstall')" width="850px" :close-on-click-modal="false" :close-on-press-escape="false">
<el-scrollbar max-height="50vh">
<div class="min-h-[150px]">
<div class="bg-[#fff] my-3" v-if="uninstallCheckResult.dir">
<p class="pt-[20px] pl-[20px] ">{{ t('dirPermission') }}</p>
<div class="px-[20px] pt-[10px] text-[14px]">
<el-row class="py-[10px] items table-head-bg pl-[15px] mb-[10px]">
<el-col :span="12">
<span>{{ t('path') }}</span>
</el-col>
<el-col :span="6">
<span>{{ t('demand') }}</span>
</el-col>
<el-col :span="6">
<span>{{ t('status') }}</span>
</el-col>
</el-row>
<el-row class="pb-[10px] items pl-[15px]" v-for="(item,index) in uninstallCheckResult.dir.is_readable" :key="index">
<el-col :span="12">
<span>{{ item.dir }}</span>
</el-col>
<el-col :span="6">
<span>{{ t('readable') }}</span>
</el-col>
<el-col :span="6">
<span v-if="item.status"><el-icon color="green"><Select /></el-icon></span>
<span v-else>
<el-icon color="red">
<CloseBold />
</el-icon>
</span>
</el-col>
</el-row>
<el-row class="pb-[10px] items pl-[15px]" v-for="(item,index) in uninstallCheckResult.dir.is_write" :key="index">
<el-col :span="12">
<span>{{ item.dir }}</span>
</el-col>
<el-col :span="6">
<span>{{ t('write') }}</span>
</el-col>
<el-col :span="6">
<span v-if="item.status"><el-icon color="green"><Select /></el-icon></span>
<span v-else>
<el-icon color="red">
<CloseBold />
</el-icon>
</span>
</el-col>
</el-row>
</div>
</div>
</div>
</el-scrollbar>
</el-dialog>
<!-- 下载提示 -->
<el-dialog v-model="unloadHintDialog" title="下载提示" width="30%">
<span>本地已经存在该插件/应用再次下载会覆盖该插件/应用</span>
<template #footer>
<span class="dialog-footer">
<el-button @click="unloadHintDialog = false">取消</el-button>
<el-button type="primary" @click="downEventHintFn">确定</el-button>
</span>
</template>
</el-dialog>
</el-card>
</div>
<upgrade ref="upgradeRef" @complete="localListFn" @cloudbuild="handleCloudBuild"/>
<cloud-build ref="cloudBuildRef" />
</template>
<script lang="ts" setup>
import { ref, reactive, watch, h } from 'vue'
import { t } from '@/lang'
import { getAddonLocal, uninstallAddon, installAddon, preInstallCheck, cloudInstallAddon, getAddonInstalltask, getAddonCloudInstallLog, preUninstallCheck, cancelInstall } from '@/app/api/addon'
import { deleteAddonDevelop } from '@/app/api/tools'
import { downloadVersion, getAuthInfo, setAuthInfo } from '@/app/api/module'
import { ElMessage, ElMessageBox, ElNotification, FormInstance, FormRules } from 'element-plus'
import 'vue-web-terminal/lib/theme/dark.css'
import { Terminal, TerminalFlash } from 'vue-web-terminal'
import { findFirstValidRoute } from '@/router/routers'
import storage from '@/utils/storage'
import { useRouter, useRoute } from 'vue-router'
import useUserStore from '@/stores/modules/user'
import Upgrade from '@/app/components/upgrade/index.vue'
import CloudBuild from '@/app/components/cloud-build/index.vue'
const router = useRouter()
const route = useRoute()
const activeName = ref(storage.get('storeActiveName') || 'installed')
const upgradeRef = ref(null)
const cloudBuildRef = ref(null)
const loading = ref<Boolean>(true)
const downloading = ref('')
const installAfterTips = ref<string[]>([])
const userStore = useUserStore()
const unloadHintDialog = ref(false)
const terminalRef = ref(null)
const currDownData = ref()
const downEventHintFn = () => {
downEvent(currDownData.value, true)
}
const activeNameTabFn = (data:any) => {
activeName.value = data
storage.set({ key: 'storeActiveName', data })
}
if (route.query.id) {
activeNameTabFn(route.query.id)
}
const downEvent = (param: Record<string, any>, isDown = false) => {
if (param.is_download && activeName.value == 'all' && !isDown) {
unloadHintDialog.value = true
currDownData.value = param
return false
}
if (downloading.value) return
downloading.value = param.key
downloadVersion({ addon: param.key, version: param.version }).then(() => {
unloadHintDialog.value = false
installAddonFn(param.key)
localListFn()
downloading.value = ''
}).catch(() => {
downloading.value = ''
})
}
const authCode = ref('')
getAuthInfo().then(res => {
if (res.data.data && res.data.data.auth_code) {
authCode.value = res.data.data.auth_code
}
}).catch(() => {
})
/**
* 本地下载的插件列表
*/
const search_name = ref('')
// 表格展示数据
const info = ref({
installed: [],
uninstalled: [],
all: []
})
const query = () => {
if (search_name.value == '' || search_name.value == null) {
info.value.installed = localList.value.installed
info.value.uninstalled = localList.value.uninstalled
info.value.all = localList.value.all
return false
}
info.value.installed = localList.value.installed.filter((el: any) => el.title.indexOf(search_name.value) != -1)
info.value.uninstalled = localList.value.uninstalled.filter((el: any) => el.title.indexOf(search_name.value) != -1)
info.value.all = localList.value.all.filter((el: any) => el.title.indexOf(search_name.value) != -1)
}
const localList = ref({
installed: [],
uninstalled: [],
all: [],
error: ''
})
const localListFn = () => {
loading.value = true
getAddonLocal({}).then(res => {
const data = res.data.list
localList.value.error = res.data.error
localList.value.installed = []
localList.value.uninstalled = []
localList.value.all = []
for (const i in data) {
if (data[i].is_local == false) localList.value.all.push(data[i])
if (data[i].install_info && Object.keys(data[i].install_info)?.length) {
localList.value.installed.push(data[i])
} else {
if (data[i].is_download == true) localList.value.uninstalled.push(data[i])
}
}
query()
userStore.routers.forEach((item, index) => {
if (item.children && item.children.length) {
item.name = findFirstValidRoute(item.children)
appLink.value[item.meta.app] = findFirstValidRoute(item.children)
} else {
appLink.value[item.meta.app] = item.name
}
})
loading.value = false
}).catch(() => {
loading.value = false
})
}
localListFn()
// 点击应用提示到站点
const handleTips = () => {
ElMessage('请在站点中运行程序!')
}
// 点击应用可以进系统
const appLink: any = ref({})
const itemPath = (data: any) => {
if (data.type == 'app' && Object.keys(data.install_info).length) {
storage.set({ key: 'menuAppStorage', data: data.key })
storage.set({ key: 'plugMenuTypeStorage', data: '' })
const appMenuList = userStore.appMenuList
appMenuList.push(data.key)
userStore.setAppMenuList(appMenuList)
const name: any = appLink.value[data.key]
router.push({ name })
}
}
const currAddon = ref('')
// 安装面板弹窗
const installShowDialog = ref(false)
// 安装步骤
const installStep = ref(1)
// 安装检测结果
const installCheckResult = ref({})
let flashInterval = null
const terminalFlash = new TerminalFlash()
const onExecCmd = (key, command, success, failed, name)=> {
if (command == '开始安装插件') {
success(terminalFlash)
const frames = makeIterator(['/', '——', '\\', '|'])
flashInterval = setInterval(() => {
terminalFlash.flush('> ' + frames.next().value)
}, 150)
}
}
function makeIterator(array: string[]) {
let nextIndex = 0
return {
next() {
if ((nextIndex + 1) == array.length) {
nextIndex = 0
}
return { value: array[nextIndex++] }
}
}
}
/**
* 安装
* @param key
*/
const installAddonFn = (key: string) => {
currAddon.value = key
preInstallCheck(key).then(res => {
installStep.value = 1
installShowDialog.value = true
installAfterTips.value = []
installCheckResult.value = res.data
userStore.clearRouters()
}).catch(() => { })
}
/**
* 获取正在进行的安装任务
*/
let notificationEl = null
const getInstallTask = (first: boolean = true) => {
getAddonInstalltask().then(res => {
if (res.data) {
if (first) {
installLog = []
currAddon.value = res.data.addon
if (!installShowDialog.value) {
notificationEl = ElNotification.success({
title: t('warning'),
dangerouslyUseHTMLString: true,
message: h('div', {}, [
t('installingTips'),
h('span', { class: 'text-primary cursor-pointer', onClick: checkInstallTask }, [t('installPercent')])
]),
duration: 0,
showClose: false
})
}
}
if (res.data.error) {
ElMessage({ message: '插件安装失败', type: 'error', duration: 5000 })
return
}
if (res.data.mode == 'cloud') {
getCloudInstallLog()
}
setTimeout(() => {
getInstallTask(false)
}, 2000)
} else {
if (!first) {
installStep.value = 3
localListFn()
userStore.clearRouters()
notificationEl.close()
}
}
}).catch((e) => {
terminalRef.value.pushMessage({ content: e.message, class: 'error' })
})
}
getInstallTask()
const checkInstallTask = () => {
installShowDialog.value = true
installStep.value = 2
}
const localInstalling = ref(false)
/**
* 安装插件
*/
const handleInstall = () => {
if (!installCheckResult.value.is_pass || localInstalling.value) return
localInstalling.value = true
installAddon({ addon: currAddon.value }).then(res => {
installStep.value = 3
localListFn()
localInstalling.value = false
if (res.data.length) installAfterTips.value = res.data
}).catch((res) => {
localInstalling.value = false
})
}
const cloudInstalling = ref(false)
/**
* 云安装插件
*/
const handleCloudInstall = () => {
if (!authCode.value) {
authElMessageBox()
return
}
if (!installCheckResult.value.is_pass || cloudInstalling.value) return
cloudInstalling.value = true
cloudInstallAddon({ addon: currAddon.value }).then(res => {
installStep.value = 2
terminalRef.value.execute('clear')
terminalRef.value.execute('开始安装插件')
getInstallTask()
cloudInstalling.value = false
}).catch((res) => {
cloudInstalling.value = false
})
}
const authElMessageBox = () => {
ElMessageBox.confirm(
t('authTips'),
t('warning'),
{
distinguishCancelAndClose: true,
confirmButtonText: t('toBind'),
cancelButtonText: t('toNiucloud')
}
).then(() => {
authCodeApproveFn()
}).catch((action: string) => {
if (action === 'cancel') {
window.open('https://www.niucloud.com/app')
}
})
}
let installLog: string[] = []
const getCloudInstallLog = () => {
getAddonCloudInstallLog(currAddon.value).then(res => {
const data = res.data.data ?? []
if (data[0] && data[0].length && installShowDialog.value == true) {
data[0].forEach(item => {
if (!installLog.includes(item.action)) {
terminalRef.value.pushMessage({ content: `正在执行:${ item.action }` })
installLog.push(item.action)
if (item.code == 0) {
terminalRef.value.pushMessage({ content: item.msg, class: 'error' })
}
}
})
}
}).catch(() => {
notificationEl?.close()
})
}
watch(currAddon, (nval) => {
installCheckResult.value = {}
})
// 卸载面板弹窗
const uninstallShowDialog = ref(false)
// 卸载环境检测结果
const uninstallCheckResult = ref({})
/**
* 卸载
* @param key
*/
const uninstallAddonFn = (key: string) => {
ElMessageBox.confirm(
t('uninstallTips'),
t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}
).then(() => {
handleUninstallAddon(key)
}).catch(() => { })
}
/**
* 插件升级
* @param key
*/
const upgradeAddonFn = (key: string) => {
upgradeRef.value?.open(key)
}
/**
* 云编译
*/
const handleCloudBuild = () => {
if (!authCode.value) {
authElMessageBox()
return
}
if (cloudBuildRef.value.cloudBuildTask) {
cloudBuildRef.value?.open()
return
}
ElMessageBox.confirm(t('cloudBuildTips'), t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}
).then(() => {
cloudBuildRef.value?.open()
})
}
const handleUninstallAddon = (key: string) => {
preUninstallCheck(key).then(({ data }) => {
if (data.is_pass) {
uninstallAddon({ addon: key }).then(res => {
localListFn()
userStore.clearRouters()
loading.value = false
}).catch(() => {
loading.value = false
})
} else {
uninstallCheckResult.value = data
uninstallShowDialog.value = true
}
})
}
const market = () => {
window.open('https://www.niucloud.com/app')
}
/**
* 安装弹窗关闭提示
* @param done
*/
const installShowDialogClose = (done: () => {}) => {
if (installStep.value == 2) {
ElMessageBox.confirm(
t('installShowDialogCloseTips'),
t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}
).then(() => {
cancelInstall(currAddon.value)
done()
}).catch(() => {
})
} else if (installStep.value == 3) {
activeNameTabFn('installed')
location.reload()
} else {
done()
}
flashInterval && clearInterval(flashInterval)
}
// 插件详情
const appStoreShowDialog = ref(false)
const appStoreInfo = ref<AnyObject>({})
const getAddonDetialFn = (data: AnyObject) => {
appStoreShowDialog.value = true
appStoreInfo.value = data
}
// 授权
const authCodeApproveDialog = ref(false)
const authinfo = ref('')
const getAuthCodeDialog = ref(null)
const saveLoading = ref(false)
const authLoading = ref(true)
const checkAppMange = () => {
authLoading.value = true
getAuthInfo()
.then((res) => {
authLoading.value = false
if (res.data.data && res.data.data.length != 0) {
authinfo.value = res.data.data
}
})
.catch(() => {
authLoading.value = false
authCodeApproveDialog.value = false
})
}
checkAppMange()
const authCodeApproveFn = () => {
authCodeApproveDialog.value = true
}
const formData = reactive<Record<string, string>>({
auth_code: '',
auth_secret: ''
})
const formRef = ref<FormInstance>()
// 表单验证规则
const formRules = reactive<FormRules>({
auth_code: [
{ required: true, message: t('authCodePlaceholder'), trigger: 'blur' }
],
auth_secret: [
{ required: true, message: t('authSecretPlaceholder'), trigger: 'blur' }
]
})
const save = async (formEl: FormInstance | undefined) => {
if (saveLoading.value || !formEl) return
await formEl.validate(async(valid) => {
if (valid) {
saveLoading.value = true
setAuthInfo(formData).then(() => {
saveLoading.value = false
setTimeout(() => {
location.reload()
}, 1000)
}).catch(() => {
saveLoading.value = false
})
}
})
}
const goRouter = () => {
window.open('https://www.niucloud.com/app')
}
const deleteAddonFn = (key: string) => {
ElMessageBox.confirm(
t('deleteAddonTips'),
t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}
).then(() => {
deleteAddonDevelop(key).then(() => {
localListFn()
})
}).catch(() => { })
}
</script>
<style lang="scss" scoped>
/* 多行超出隐藏 */
.multi-hidden {
word-break: break-all;
text-overflow: ellipsis;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.demo-tabs>.el-tabs__content {
padding: 32px;
color: #6b778c;
font-size: 32px;
font-weight: 600;
}
.plug-item {
.plug-item-operate {
color: var(--el-color-primary);
border-color: var(--el-color-primary);
font-size: var(--el-font-size-extra-small);
}
}
:deep(.t-container) {
box-shadow: none !important;
.t-window {
padding: 10px 20px !important;
}
}
.switch-btn.active {
border-color: var(--el-color-primary);
color: #fff;
background-color: var(--el-color-primary);
}
.plug-large {
.plug-item-operate {
color: var(--el-color-primary);
border-color: var(--el-color-primary);
font-size: var(--el-font-size-extra-small);
}
}
.app-text {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
}
// 插件安装-弹窗-表格样式
.table-head-bg {
background: #f5f7f9;
}
html.dark .table-head-bg {
background: #141414;
}
.el-alert .el-alert__title {
font-size: 16px;
line-height: 18px;
}
.app-store {
height: calc(100vh - 120px);
box-sizing: border-box;
}
:deep(.terminal .t-log-box span) {
white-space: pre-wrap;
}
:deep(.data-loading) {
.el-table__body-wrapper {
display: none!important;
}
}
</style>
<style>
.el-empty .el-empty__image {
width: auto;
}
</style>