mirror of
https://gitee.com/niucloud-team/niucloud.git
synced 2026-02-26 09:10:26 +00:00
1461 lines
58 KiB
Vue
1461 lines
58 KiB
Vue
<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>
|
||
</div>
|
||
|
||
<el-tabs v-model="activeName" class="mt-[10px]">
|
||
<el-tab-pane :label="t('installLabel')" name="installed"></el-tab-pane>
|
||
<el-tab-pane :label="t('uninstalledLabel')" name="uninstalled"></el-tab-pane>
|
||
<el-tab-pane :label="t('buyLabel')" name="all"></el-tab-pane>
|
||
<el-tab-pane :label="t('recentlyUpdated')" name="recentlyUpdated">
|
||
<template #label>
|
||
<span class="custom-tabs-label">
|
||
<span>{{ t('recentlyUpdated') }}</span>
|
||
<span v-if="localList['recentlyUpdated'].length > 0" class="w-[15px] h-[15px] bg-[#DA203E] absolute text-[#fff] text-[11px] flex items-center justify-center rounded-full top-[3px] right-[-12px]">{{ localList['recentlyUpdated'].length }}</span>
|
||
</span>
|
||
</template>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
<div class="flex justify-between my-[10px]">
|
||
<div class="flex items-center search-form">
|
||
<el-input class="!w-[192px] !h-[32px] rounded-[4px]" :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>
|
||
<el-select v-model="search_type" placeholder="请选择类型" class="!w-[192px] !h-[32px] rounded-[4px] ml-[20px] " >
|
||
<el-option :label="t('全部')" value="" />
|
||
<el-option v-for="(label, value) in typeList" :key="value" :label="label" :value="value"></el-option>
|
||
</el-select>
|
||
<el-button type="primary" @click="query" class="ml-[20px]">{{ t("搜索") }}</el-button>
|
||
</div>
|
||
<div>
|
||
<el-button type="primary" v-show="activeName === 'recentlyUpdated'" @click="batchUpgrade" :loading="upgradeRef?.loading" :disabled="authLoading">{{ t("batchUpgrade") }}</el-button>
|
||
<!-- <el-button type="primary" @click="handleCloudBuild" :loading="cloudBuildRef?.loading" :disabled="authLoading">{{ t("cloudBuild") }}</el-button> -->
|
||
</div>
|
||
|
||
</div>
|
||
<div class="relative">
|
||
<el-table v-if="localList[activeName].length && !loading" :tree-props="{ children: 'children' }" :default-expand-all="true" :data="info[activeName]" row-key="key" size="large" @selection-change="handleSelectionChange">
|
||
<el-table-column width="24">
|
||
<template #default="{ row }">
|
||
<div class="tree-child-cell" :class="{ 'is-tree-parent': row.children?.length, 'is-tree-child': typeof row.support_app === 'string' && row.support_app !== '' && visibleRowKeys.has(row.support_app)}">
|
||
<span style="opacity: 0;">.</span>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column type="selection" v-if="activeName === 'recentlyUpdated'" />
|
||
<el-table-column :label="t('appName')" align="left" width="500">
|
||
<template #default="{ row }">
|
||
<div class="flex items-center cursor-pointer relative left-[-10px]">
|
||
<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-one.png" alt="" />
|
||
</div>
|
||
</template>
|
||
</el-image>
|
||
<div class="flex-1 w-0 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] flex flex-nowrap">
|
||
<el-tag type="danger" size="small" v-if="activeName == 'recentlyUpdated' && row.install_info && Object.keys(row.install_info)?.length && row.install_info.version != row.version">{{ t("newVersion") }}{{ row.version }}</el-tag>
|
||
<el-tooltip v-if="versionJudge(row)" effect="dark" :content="`该插件适配框架版本为${ row.support_version },与已安装框架版本${frameworkVersion}不完全兼容`" placement="top-start">
|
||
<el-tag type="warning" size="small" class="ml-[3px]">该插件适配框架版本为{{ row.support_version }},与已安装框架版本{{frameworkVersion}}不完全兼容</el-tag>
|
||
</el-tooltip>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<el-table-column align="left" min-width="150">
|
||
<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 :label="t('introduction')" align="left" min-width="250">
|
||
<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="80">
|
||
<template #default="{ row }">
|
||
<span class="font-500 text-[13px] multi-hidden">{{ row.type === "app" ? t("app") : t("addon") }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<el-table-column :label="t('author')" align="left" min-width="80">
|
||
<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="250">
|
||
<template #default="{ row }">
|
||
<el-button class="!text-[13px]" v-if="activeName == 'recentlyUpdated' && 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="getAddonDetailFn(row)">{{ t("detail") }}</el-button>
|
||
<el-button class="!text-[13px]" type="primary" link @click="updateInformationFn(row)">更新信息</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="data-loading" v-if="loading || !localList[activeName].length">
|
||
<el-table :data="[]" size="large" class="pt-[5px]">
|
||
<el-table-column :label="t('appName')" align="left" width="320" />
|
||
<el-table-column align="left" min-width="120" />
|
||
<el-table-column :label="t('introduction')" align="left" min-width="200" />
|
||
<el-table-column :label="t('type')" align="left" min-width="100" />
|
||
<el-table-column :label="t('author')" align="left" min-width="100" />
|
||
<el-table-column :label="t('operation')" fixed="right" align="right" width="150" />
|
||
<template #empty>
|
||
<span></span>
|
||
</template>
|
||
</el-table>
|
||
<div class="h-[100px]" v-loading="loading" v-if="loading"></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>
|
||
<el-empty class="mx-auto overview-empty" v-if="!localList.recentlyUpdated.length && !loading && authinfo && activeName == 'recentlyUpdated' && !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("recentlyUpdatedEmpty") }}</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" class="number-of-steps" process-status="process" align-center v-if="installStep != 2 && !errorDialog ">
|
||
<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 == 0" 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 v-if="!installCheckResult.is_pass" class="mt-[10px] mx-[20px] text-[14px] cursor-pointer text-primary flex items-center justify-between bg-[#EFF6FF] rounded-[4px] p-[10px]" @click="cloudBuildCheckDirFn">
|
||
<div class="flex items-center">
|
||
<el-icon :size="17"><QuestionFilled /></el-icon>
|
||
<span class="ml-[5px] leading-[20px]">编译权限错误,查看解决方案</span></div>
|
||
<div class="border-[1px] border-primary rounded-[3px] w-[72px] h-[26px] leading-[25px] text-center">立即查看</div>
|
||
</div>
|
||
<div class="px-[20px] pt-[10px] text-[14px]">
|
||
<el-row class="py-[10px] items table-head-bg pl-[15px] mb-[10px]">
|
||
<el-col :span="18">
|
||
<span>{{ t("path") }}</span>
|
||
</el-col>
|
||
<el-col :span="3">
|
||
<span>{{ t("demand") }}</span>
|
||
</el-col>
|
||
<el-col :span="3">
|
||
<span>{{ t("status") }}</span>
|
||
</el-col>
|
||
</el-row>
|
||
<el-scrollbar style="height: calc(300px); overflow: auto">
|
||
<el-row class="pb-[10px] items pl-[15px]" v-for="(item, index) in installCheckResult.dir.is_readable" :key="index">
|
||
<el-col :span="18">
|
||
<span>{{ item.dir }}</span>
|
||
</el-col>
|
||
<el-col :span="3">
|
||
<span>{{ t("readable") }}</span>
|
||
</el-col>
|
||
<el-col :span="3" >
|
||
<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="18">
|
||
<span>{{ item.dir }}</span>
|
||
</el-col>
|
||
<el-col :span="3">
|
||
<span>{{ t("write") }}</span>
|
||
</el-col>
|
||
<el-col :span="3">
|
||
<span v-if="item.status" class="text-right">
|
||
<el-icon color="green">
|
||
<Select />
|
||
</el-icon>
|
||
</span>
|
||
<span v-else>
|
||
<el-icon color="red">
|
||
<CloseBold />
|
||
</el-icon>
|
||
</span>
|
||
</el-col>
|
||
</el-row>
|
||
</el-scrollbar>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- </el-scrollbar> -->
|
||
<div class="flex justify-end">
|
||
<el-tooltip effect="dark" placement="top">
|
||
<template #content>
|
||
<div class="w-[400px]">
|
||
{{t("installTips")}}
|
||
</div>
|
||
</template>
|
||
<el-button :disabled="!installCheckResult.is_pass || cloudInstalling" :loading="localInstalling" @click="handleInstall">{{ t("localInstall") }}</el-button>
|
||
</el-tooltip>
|
||
<el-tooltip effect="dark" placement="top">
|
||
<template #content>
|
||
<div class="w-[400px]">
|
||
{{t("cloudInstallTips")}}
|
||
</div>
|
||
</template>
|
||
<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 == 1 && !errorDialog" class="h-[50vh] mt-[20px]">
|
||
<terminal ref="terminalRef" :name="`install-${terminalId}`" :context="currAddon" :init-log="null" :show-header="false" :show-log-time="true" @exec-cmd="onExecCmd" />
|
||
</div>
|
||
<div v-show="installStep == 2" 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> -->
|
||
<el-result icon="success" :title="t('addonInstallSuccess')">
|
||
<template #icon>
|
||
<img src="@/app/assets/images/success_icon.png" alt="">
|
||
</template>
|
||
<template #extra>
|
||
<div v-for="(item, index) in installAfterTips" class="mb-[10px]" :key="index">
|
||
<div class="text-[16px] text-[#4F516D] mt-[5px]">{{ item }}</div>
|
||
</div>
|
||
<div class="text-[16px] text-[#9699B6] mt-[10px]" v-if="upgradeDuration>0">本次安装用时{{ formatUpgradeDuration }}</div>
|
||
<div class="mt-[20px]">
|
||
<el-button @click="handleBack()" v-if="installType=='cloud'" class="!w-[90px]">返回</el-button>
|
||
<el-button @click="installShowDialog=false" type="primary" class="!w-[90px]">完成</el-button>
|
||
</div>
|
||
</template>
|
||
</el-result>
|
||
</div>
|
||
<div class="h-[50vh] mt-[20px] flex flex-col" v-show="errorDialog">
|
||
<el-result icon="error" :title="t('安装失败')">
|
||
<template #icon>
|
||
<img src="@/app/assets/images/error_icon.png" alt="">
|
||
</template>
|
||
<template #extra>
|
||
<el-scrollbar class="max-h-[120px] !overflow-auto text-[15px] text-[#4F516D] mb-[15px] mt-[-15px]">
|
||
{{errorMsg}}
|
||
</el-scrollbar>
|
||
<el-button @click="handleBack()" v-if="installType=='cloud'" class="!w-[90px]">错误信息</el-button>
|
||
<el-button @click="installShowDialog=false" type="primary" class="!w-[90px]">完成</el-button>
|
||
</template>
|
||
</el-result>
|
||
</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="18">
|
||
<span>{{ t("path") }}</span>
|
||
</el-col>
|
||
<el-col :span="3">
|
||
<span>{{ t("demand") }}</span>
|
||
</el-col>
|
||
<el-col :span="3">
|
||
<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="18">
|
||
<span>{{ item.dir }}</span>
|
||
</el-col>
|
||
<el-col :span="3">
|
||
<span>{{ t("readable") }}</span>
|
||
</el-col>
|
||
<el-col :span="3">
|
||
<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="18">
|
||
<span>{{ item.dir }}</span>
|
||
</el-col>
|
||
<el-col :span="3">
|
||
<span>{{ t("write") }}</span>
|
||
</el-col>
|
||
<el-col :span="3" >
|
||
<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-log :upgradeKey="upgradeKey" ref="upgradeLogRef" />
|
||
<upgrade ref="upgradeRef" @complete="localListFn" @cloudbuild="handleCloudBuild" />
|
||
<cloud-build ref="cloudBuildRef" />
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import { ref, reactive, watch, h ,computed } from 'vue'
|
||
import { t } from '@/lang'
|
||
import {
|
||
getAddonLocal,
|
||
uninstallAddon,
|
||
installAddon,
|
||
preInstallCheck,
|
||
cloudInstallAddon,
|
||
getAddonInstalltask,
|
||
getAddonCloudInstallLog,
|
||
preUninstallCheck,
|
||
cancelInstall,
|
||
getAddonInit
|
||
} from '@/app/api/addon'
|
||
import { deleteAddonDevelop } from '@/app/api/tools'
|
||
import { downloadVersion, getAuthInfo, setAuthInfo } from '@/app/api/module'
|
||
import { getVersions } from '@/app/api/auth'
|
||
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'
|
||
import UpgradeLog from '@/app/components/upgrade-log/index.vue'
|
||
|
||
const router = useRouter()
|
||
const route = useRoute()
|
||
const terminalId = ref(Date.now());
|
||
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 frameworkVersion = ref('')
|
||
const upgradeLogRef = ref<any>(null)
|
||
getVersions().then((res) => {
|
||
frameworkVersion.value = res.data.version.version
|
||
})
|
||
|
||
const typeList = ref({})
|
||
const getAddonInitFn = () => {
|
||
getAddonInit().then((res) => {
|
||
typeList.value = res.data.type_list
|
||
})
|
||
}
|
||
getAddonInitFn()
|
||
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
|
||
}
|
||
})
|
||
|
||
/**
|
||
* 本地下载的插件列表
|
||
*/
|
||
const search_name = ref('')
|
||
const search_type = ref('')
|
||
// 表格展示数据
|
||
const info = ref({
|
||
installed: [],
|
||
uninstalled: [],
|
||
all: [],
|
||
recentlyUpdated: []
|
||
})
|
||
const buildInfo = (list: any[]) => {
|
||
const map = new Map()
|
||
const result: any[] = []
|
||
|
||
// 所有插件都先放进 map,初始化 children
|
||
list.forEach(item => {
|
||
map.set(item.key, { ...item, children: [] })
|
||
})
|
||
|
||
// 第二次遍历构建父子关系
|
||
list.forEach(item => {
|
||
if (item.support_app && map.has(item.support_app)) {
|
||
const parent = map.get(item.support_app)
|
||
parent.children.push(map.get(item.key)) // 直接取已经构建好的对象
|
||
}
|
||
})
|
||
|
||
// 最终收集那些没有作为子插件挂载出去的插件(即顶层插件)
|
||
map.forEach((item: any) => {
|
||
if (!item.support_app || !map.has(item.support_app)) {
|
||
result.push(item)
|
||
}
|
||
})
|
||
|
||
return result
|
||
}
|
||
// const query = () => {
|
||
// if (search_name.value == '' || search_name.value == null) {
|
||
// info.value.installed = buildInfo(localList.value.installed)
|
||
// info.value.uninstalled = buildInfo(localList.value.uninstalled)
|
||
// info.value.all = buildInfo(localList.value.all)
|
||
// info.value.recentlyUpdated = buildInfo(localList.value.recentlyUpdated)
|
||
// return false
|
||
// }
|
||
|
||
// const filteredInstalled = localList.value.installed.filter((el: any) => el.title.indexOf(search_name.value) != -1)
|
||
// const filteredUninstalled = localList.value.uninstalled.filter((el: any) => el.title.indexOf(search_name.value) != -1)
|
||
// const filteredAll = localList.value.all.filter((el: any) => el.title.indexOf(search_name.value) != -1)
|
||
// const filteredRecentlyUpdated = localList.value.recentlyUpdated.filter((el: any) => el.title.indexOf(search_name.value) != -1)
|
||
|
||
// // 构建父子关系
|
||
// info.value.installed = buildInfo(filteredInstalled)
|
||
// info.value.uninstalled = buildInfo(filteredUninstalled)
|
||
// info.value.all = buildInfo(filteredAll)
|
||
// info.value.recentlyUpdated = buildInfo(filteredRecentlyUpdated)
|
||
// }
|
||
const query = () => {
|
||
const name = search_name.value
|
||
const type = search_type.value
|
||
|
||
// 如果没填搜索关键词也没选类型,重置所有列表
|
||
if ((!name || name === '') && (type === '' || type == null)) {
|
||
info.value.installed = buildInfo(localList.value.installed)
|
||
info.value.uninstalled = buildInfo(localList.value.uninstalled)
|
||
info.value.all = buildInfo(localList.value.all)
|
||
info.value.recentlyUpdated = buildInfo(localList.value.recentlyUpdated)
|
||
return
|
||
}
|
||
|
||
// 公共筛选函数
|
||
const filterList = (list: any[]) => {
|
||
return list.filter((el: any) => {
|
||
const matchName = !name || el.title.includes(name)
|
||
const matchType = !type || el.type === type
|
||
return matchName && matchType
|
||
})
|
||
}
|
||
|
||
info.value.installed = buildInfo(filterList(localList.value.installed))
|
||
info.value.uninstalled = buildInfo(filterList(localList.value.uninstalled))
|
||
info.value.all = buildInfo(filterList(localList.value.all))
|
||
info.value.recentlyUpdated = buildInfo(filterList(localList.value.recentlyUpdated))
|
||
}
|
||
|
||
const localList = ref({
|
||
installed: [],
|
||
uninstalled: [],
|
||
all: [],
|
||
recentlyUpdated: [],
|
||
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 = []
|
||
localList.value.recentlyUpdated = []
|
||
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])
|
||
if (data[i].install_info.version != data[i].version) {
|
||
localList.value.recentlyUpdated.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 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(0)
|
||
|
||
// 安装检测结果
|
||
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 = 0
|
||
isBack.value = false
|
||
errorDialog.value = false
|
||
installType.value = ''
|
||
installShowDialog.value = true
|
||
installAfterTips.value = []
|
||
installCheckResult.value = res.data
|
||
userStore.clearRouters()
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 获取正在进行的安装任务
|
||
*/
|
||
const upgradeStartTime = ref<number | null>(null)
|
||
const upgradeDuration = ref(0) // 单位:秒
|
||
let upgradeTimer: ReturnType<typeof setInterval> | null = null
|
||
let notificationEl = null
|
||
|
||
const getInstallTask = (first: boolean = true) => {
|
||
getAddonInstalltask().then((res) => {
|
||
if (res.data) {
|
||
upgradeStartTime.value = Date.now()
|
||
upgradeDuration.value = 0
|
||
if (upgradeTimer) clearInterval(upgradeTimer)
|
||
upgradeTimer = setInterval(() => {
|
||
upgradeDuration.value++
|
||
}, 1000)
|
||
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) {
|
||
terminalRef.value.pushMessage({ content: res.data.error, class: 'error' })
|
||
errorMsg.value = res.data.error
|
||
errorDialog.value = true
|
||
if (upgradeTimer) {
|
||
clearInterval(upgradeTimer)
|
||
upgradeTimer = null
|
||
}
|
||
// ElMessage({ message: '插件安装失败', type: 'error', duration: 5000 })
|
||
return
|
||
}
|
||
if (res.data.mode == 'cloud') {
|
||
getCloudInstallLog()
|
||
}
|
||
setTimeout(() => {
|
||
getInstallTask(false)
|
||
}, 2000)
|
||
|
||
} else {
|
||
if (!first) {
|
||
installStep.value = 2
|
||
if (upgradeTimer) {
|
||
clearInterval(upgradeTimer)
|
||
upgradeTimer = null
|
||
}
|
||
localListFn()
|
||
userStore.clearRouters()
|
||
notificationEl.close()
|
||
}
|
||
}
|
||
}).catch((e) => {
|
||
terminalRef.value.pushMessage({ content: e.message, class: 'error' })
|
||
})
|
||
}
|
||
|
||
getInstallTask()
|
||
|
||
const isBack = ref(false)
|
||
const handleBack = () => {
|
||
isBack.value = true
|
||
installStep.value = 1
|
||
errorDialog.value = false
|
||
}
|
||
|
||
const formatUpgradeDuration = computed(() => {
|
||
const s = upgradeDuration.value
|
||
const h = Math.floor(s / 3600)
|
||
const m = Math.floor((s % 3600) / 60)
|
||
const sec = s % 60
|
||
return [
|
||
h > 0 ? `${h}小时` : '',
|
||
m > 0 ? `${m}分钟` : '',
|
||
`${sec}秒`
|
||
].filter(Boolean).join('')
|
||
})
|
||
|
||
const checkInstallTask = () => {
|
||
installShowDialog.value = true
|
||
installStep.value = 1
|
||
}
|
||
|
||
const localInstalling = ref(false)
|
||
/**
|
||
* 安装插件
|
||
*/
|
||
const installType = ref('')
|
||
const handleInstall = () => {
|
||
if (!installCheckResult.value.is_pass || localInstalling.value) return
|
||
installType.value = 'local'
|
||
localInstalling.value = true
|
||
upgradeStartTime.value = Date.now()
|
||
upgradeDuration.value = 0
|
||
if (upgradeTimer) clearInterval(upgradeTimer)
|
||
upgradeTimer = setInterval(() => {
|
||
upgradeDuration.value++
|
||
}, 1000)
|
||
|
||
installAddon({ addon: currAddon.value }).then((res) => {
|
||
installStep.value = 2
|
||
if (upgradeTimer) {
|
||
clearInterval(upgradeTimer)
|
||
upgradeTimer = null
|
||
}
|
||
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
|
||
installType.value = 'cloud'
|
||
|
||
cloudInstallAddon({ addon: currAddon.value }).then((res) => {
|
||
installStep.value = 1
|
||
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')
|
||
}
|
||
})
|
||
}
|
||
const errorDialog = ref(false)
|
||
const errorMsg = ref('')
|
||
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)
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 插件升级
|
||
* @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 == 1 && !isBack.value && !errorDialog.value) {
|
||
ElMessageBox.confirm(t('installShowDialogCloseTips'), t('warning'), {
|
||
confirmButtonText: t('confirm'),
|
||
cancelButtonText: t('cancel'),
|
||
type: 'warning'
|
||
}).then(() => {
|
||
cancelInstall(currAddon.value)
|
||
if (upgradeTimer) {
|
||
clearInterval(upgradeTimer)
|
||
upgradeTimer = null
|
||
}
|
||
isBack.value = false
|
||
installType.value = ''
|
||
errorDialog.value = false
|
||
done()
|
||
})
|
||
} else if (installStep.value == 2) {
|
||
activeNameTabFn('installed')
|
||
location.reload()
|
||
} else {
|
||
done()
|
||
}
|
||
|
||
flashInterval && clearInterval(flashInterval)
|
||
}
|
||
|
||
// 插件详情
|
||
const appStoreShowDialog = ref(false)
|
||
const appStoreInfo = ref({})
|
||
const getAddonDetailFn = (data: any) => {
|
||
appStoreShowDialog.value = true
|
||
appStoreInfo.value = data
|
||
}
|
||
// 更新信息
|
||
|
||
const upgradeKey = ref<string>('')
|
||
const updateInformationFn = (data: any) => {
|
||
// updateInformationDialog.value = true
|
||
upgradeKey.value = data.key
|
||
upgradeLogRef.value?.open()
|
||
}
|
||
// 授权
|
||
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 cloudBuildCheckDirFn = () => {
|
||
window.open('https://doc.niucloud.com/v6.html?keywords=/chang-jian-wen-ti-chu-li/er-shi-wu-3001-sheng-7ea7-yun-bian-yi-mu-lu-du-xie-quan-xian-zhuang-tai-bu-tong-guo-ru-he-chu-li')
|
||
}
|
||
|
||
const deleteAddonFn = (key: string) => {
|
||
ElMessageBox.confirm(t('deleteAddonTips'), t('warning'), {
|
||
confirmButtonText: t('confirm'),
|
||
cancelButtonText: t('cancel'),
|
||
type: 'warning'
|
||
}).then(() => {
|
||
deleteAddonDevelop(key).then(() => {
|
||
localListFn()
|
||
})
|
||
})
|
||
}
|
||
|
||
const versionJudge = (row: any) => {
|
||
if (!row.support_version) return false
|
||
const supportVersionApp = row.support_version.split('.')
|
||
const frameworkVersionArr = frameworkVersion.value.split('.')
|
||
if (parseFloat(`${ supportVersionApp[0] }.${ supportVersionApp[1] }`) < parseFloat(`${ frameworkVersionArr[0] }.${ frameworkVersionArr[1] }`)) return true
|
||
return false
|
||
}
|
||
|
||
let batchUpgradeApp = []
|
||
const handleSelectionChange = (e: any) => {
|
||
batchUpgradeApp = e.map(item => item.key)
|
||
}
|
||
|
||
const batchUpgrade = () => {
|
||
if (!batchUpgradeApp.length) {
|
||
ElMessage({ message: '请先勾选要升级的插件', type: 'error', duration: 5000 })
|
||
return
|
||
}
|
||
|
||
upgradeAddonFn(batchUpgradeApp.toString())
|
||
}
|
||
|
||
const visibleRowKeys = computed(() => {
|
||
return new Set((info.value[activeName.value] || []).map(row => row.key));
|
||
});
|
||
</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;
|
||
}
|
||
|
||
// 插件安装-弹窗-表格样式
|
||
.table-head-bg {
|
||
background: #f5f7f9;
|
||
}
|
||
|
||
html.dark .table-head-bg {
|
||
background: #141414;
|
||
}
|
||
|
||
.el-alert .el-alert__title {
|
||
font-size: 16px;
|
||
line-height: 18px;
|
||
}
|
||
|
||
:deep(.terminal .t-log-box span) {
|
||
white-space: pre-wrap;
|
||
}
|
||
|
||
:deep(.data-loading) {
|
||
.el-table__body-wrapper {
|
||
display: none !important;
|
||
}
|
||
}
|
||
:deep(.hide-expand .el-table__expand-icon>.el-icon){
|
||
visibility: hidden;
|
||
pointer-events: none;
|
||
}
|
||
:deep(.el-input__wrapper){
|
||
box-shadow: none !important;
|
||
border-radius: 4px !important;
|
||
border: 1px solid #D1D5DB !important;
|
||
height: 32px !important;
|
||
}
|
||
:deep(.el-select__wrapper){
|
||
box-shadow: none !important;
|
||
border-radius: 4px !important;
|
||
border: 1px solid #D1D5DB !important;
|
||
height: 32px !important;
|
||
}
|
||
:deep(.el-button){
|
||
border-radius: 4px !important;
|
||
}
|
||
/* 设置 el-select 的 placeholder 颜色 */
|
||
:deep(.search-form .el-select__placeholder.is-transparent) {
|
||
color: #C4C7DA;
|
||
font-size: 12px;
|
||
}
|
||
|
||
/* 设置 el-select 选中后的颜色 */
|
||
:deep(.search-form .el-select__placeholder) {
|
||
color: #4F516D;
|
||
font-size: 12px;
|
||
|
||
}
|
||
/* 设置 el-input 的 placeholder 颜色 */
|
||
:deep(.search-form .el-input__inner::placeholder) {
|
||
color: #C4C7DA;
|
||
font-size: 12px;
|
||
|
||
}
|
||
/* 设置 el-input 输入内容后的颜色 */
|
||
:deep(.search-form .el-input__inner) {
|
||
color: #4F516D;
|
||
font-size: 12px;
|
||
|
||
}
|
||
/* 设置 el-date-picker 的 placeholder 颜色 */
|
||
:deep(.search-form .el-date-editor .el-range-input::placeholder) {
|
||
color: #C4C7DA;
|
||
font-size: 12px;
|
||
}
|
||
|
||
/* 设置 el-date-picker 的输入内容颜色 */
|
||
:deep(.search-form .el-date-editor .el-range-input) {
|
||
color: #4F516D;
|
||
font-size: 12px;
|
||
}
|
||
|
||
|
||
:deep(.el-table tr td:first-child) {
|
||
border-bottom: none;
|
||
// background-color: inherit !important;
|
||
height: 100px;
|
||
}
|
||
:deep(.el-table__body tr:hover td:first-child) {
|
||
// border-bottom: 1px solid var(--el-table-border-color);
|
||
}
|
||
:deep(.el-table__body tr) {
|
||
position: relative;
|
||
}
|
||
:deep(.el-table__body td:first-child::before) {
|
||
opacity: 0;
|
||
content: '';
|
||
position: absolute;
|
||
top: -1px;
|
||
left: 0;
|
||
right: 0;
|
||
height: 1px;
|
||
background-color: var(--el-table-border-color);
|
||
// transition: opacity 0.2s;
|
||
z-index: 1;
|
||
}
|
||
:deep(.el-table__body td:first-child) {
|
||
position: relative;
|
||
}
|
||
|
||
:deep(.el-table__body tr:hover td:first-child::before) {
|
||
opacity: 1;
|
||
|
||
}
|
||
/* 创建伪元素当作 hover 边框线,默认隐藏 */
|
||
:deep(.el-table__body td:first-child::after) {
|
||
content: '';
|
||
position: absolute;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
height: 1px;
|
||
background-color: var(--el-table-border-color);
|
||
opacity: 0;
|
||
// transition: opacity 0.2s ease;
|
||
pointer-events: none;
|
||
z-index: 1;
|
||
}
|
||
|
||
/* 悬浮时显示这条伪边框线 */
|
||
:deep(.el-table__body tr:hover td:first-child::after) {
|
||
opacity: 1;
|
||
}
|
||
|
||
:deep(.el-table__fixed-body-wrapper .el-table__row .el-table__cell) {
|
||
overflow: visible;
|
||
}
|
||
:deep(.el-table .el-table__expand-icon){
|
||
position: relative;
|
||
top: 12.5px;
|
||
left: -13px;
|
||
z-index: 99;
|
||
margin: 3px;
|
||
overflow: hidden;
|
||
}
|
||
:deep(.el-table__fixed-body-wrapper .el-table__cell:first-child) {
|
||
background-color: inherit !important; /* 从行继承背景色 */
|
||
}
|
||
|
||
:deep(.el-table tr td:nth-child(1)::before){
|
||
overflow: hidden !important;
|
||
}
|
||
:deep(.tree-child-cell) {
|
||
position: relative;
|
||
height: 100%;
|
||
}
|
||
:deep(.el-table .cell){
|
||
overflow: visible !important;
|
||
}
|
||
:deep(.tree-child-cell.is-tree-child::before) {
|
||
content: '';
|
||
position: absolute;
|
||
left: -5px;
|
||
top: -99px;
|
||
bottom: 0;
|
||
width: 1px;
|
||
height: 100px;
|
||
background-color: #F5F5F5;
|
||
}
|
||
|
||
:deep(.tree-child-cell.is-tree-child::after) {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: -5px;
|
||
width: 8px;
|
||
height: 1px;
|
||
background-color: #F5F5F5;
|
||
}
|
||
|
||
:deep(.hidden-selection-column .cell) {
|
||
display: none;
|
||
}
|
||
:deep(.el-dialog__title){
|
||
font-size: 20px;
|
||
font-weight: bold;
|
||
}
|
||
:deep(.el-result__title p){
|
||
font-size: 25px;
|
||
color: #1D1F3A;
|
||
font-weight: 500;
|
||
}
|
||
:deep(.el-result__subtitle p){
|
||
font-size: 15px;
|
||
color: #4F516D;
|
||
font-weight: 500;
|
||
word-break: break-all;
|
||
text-overflow: ellipsis;
|
||
overflow: hidden;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
}
|
||
:deep(.number-of-steps) {
|
||
.el-step__line {
|
||
margin: 0 25px;
|
||
background: #dddddd;
|
||
}
|
||
|
||
.el-step__head {
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.is-success {
|
||
color: var(--el-color-primary);
|
||
border-color: var(--el-color-primary);
|
||
|
||
.el-step__icon {
|
||
background: var(--el-color-primary);
|
||
color: #fff;
|
||
// box-shadow: 0 0 0 4px var(--el-color-primary-light-9);
|
||
|
||
i {
|
||
color: #fff;
|
||
}
|
||
}
|
||
|
||
.el-step__line {
|
||
margin: 0 25px;
|
||
background: var(--el-color-primary);
|
||
}
|
||
}
|
||
.is-finish {
|
||
color: var(--el-color-primary);
|
||
border-color: var(--el-color-primary);
|
||
|
||
.el-step__icon {
|
||
background: var(--el-color-primary)!important;
|
||
color: #fff !important;
|
||
// box-shadow: 0 0 0 4px var(--el-color-primary-light-9);
|
||
|
||
i {
|
||
color: #fff;
|
||
}
|
||
}
|
||
|
||
.el-step__line {
|
||
margin: 0 25px;
|
||
background: var(--el-color-primary);
|
||
}
|
||
}
|
||
|
||
.is-process {
|
||
color: var(--el-color-primary);
|
||
font-weight: inherit;
|
||
|
||
// font-size: 18px;
|
||
.el-step__icon {
|
||
padding: 10px;
|
||
border: 1px solid var(--el-color-primary);
|
||
background: var(--el-color-primary)!important;
|
||
color: #fff !important;
|
||
// box-shadow: 0 0 0 4px var(--el-color-primary-light-9);
|
||
}
|
||
}
|
||
|
||
.is-wait {
|
||
color: #333;
|
||
}
|
||
}
|
||
|
||
</style>
|
||
|
||
<style>
|
||
.el-empty .el-empty__image {
|
||
width: auto;
|
||
}
|
||
</style>
|