feat: 授权管理页面+路由,IM状态30s轮询,移除接入文档,ops去除appKey/平台列,Jenkinsfile固定main分支
这个提交包含在:
父节点
b153521a91
当前提交
88e5a70d87
44
Jenkinsfile
vendored
44
Jenkinsfile
vendored
@ -2,28 +2,34 @@ pipeline {
|
||||
agent any
|
||||
|
||||
parameters {
|
||||
string(name: 'BRANCH', defaultValue: 'main', description: 'Git 分支名')
|
||||
choice(name: 'APP', choices: ['tenant-platform', 'ops-platform'], description: '要构建的 Web 应用')
|
||||
string(name: 'IMAGE_TAG', defaultValue: 'latest', description: '镜像 Tag')
|
||||
booleanParam(name: 'DEPLOY', defaultValue: true, description: '构建后是否自动部署')
|
||||
}
|
||||
|
||||
environment {
|
||||
ACR_REGISTRY = 'crpi-n44qjpuucgjt8e8c.cn-beijing.personal.cr.aliyuncs.com'
|
||||
ACR_NAMESPACE = 'xuqmgroup'
|
||||
ACR_USERNAME = 'xuqinmin12'
|
||||
PROD_HOST = '106.54.23.149'
|
||||
PROD_USER = 'ubuntu'
|
||||
COMPOSE_FILE = '/opt/xuqm/deploy/compose.production.yaml'
|
||||
ACR_REGISTRY = 'crpi-n44qjpuucgjt8e8c.cn-beijing.personal.cr.aliyuncs.com'
|
||||
ACR_NAMESPACE = 'xuqmgroup'
|
||||
ACR_USERNAME = 'xuqinmin12'
|
||||
PROD_HOST = '106.54.23.149'
|
||||
PROD_USER = 'ubuntu'
|
||||
COMPOSE_FILE = '/opt/xuqm/deploy/compose.production.yaml'
|
||||
DOCKER_BUILDKIT = '1'
|
||||
}
|
||||
|
||||
options {
|
||||
timeout(time: 20, unit: 'MINUTES')
|
||||
buildDiscarder(logRotator(numToKeepStr: '20'))
|
||||
disableConcurrentBuilds()
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Checkout') {
|
||||
steps {
|
||||
checkout([$class: 'GitSCM',
|
||||
branches: [[name: "*/${params.BRANCH}"]],
|
||||
extensions: [],
|
||||
checkout([
|
||||
$class: 'GitSCM',
|
||||
branches: [[name: 'main']],
|
||||
extensions: [[$class: 'CleanBeforeCheckout']],
|
||||
userRemoteConfigs: scm.userRemoteConfigs
|
||||
])
|
||||
}
|
||||
@ -34,16 +40,16 @@ pipeline {
|
||||
script {
|
||||
switch (params.APP) {
|
||||
case 'tenant-platform':
|
||||
env.IMAGE_NAME = 'tenant-web'
|
||||
env.DOCKERFILE = 'Dockerfile.tenant'
|
||||
env.IMAGE_NAME = 'tenant-web'
|
||||
env.DOCKERFILE = 'Dockerfile.tenant'
|
||||
env.DEPLOY_SERVICE = 'tenant-web'
|
||||
env.BUILD_ARGS = '--build-arg TENANT_APP_BASE=/ --build-arg TENANT_API_BASE_URL=/api'
|
||||
env.BUILD_ARGS = '--build-arg TENANT_APP_BASE=/ --build-arg TENANT_API_BASE_URL=/api'
|
||||
break
|
||||
case 'ops-platform':
|
||||
env.IMAGE_NAME = 'ops-web'
|
||||
env.DOCKERFILE = 'Dockerfile.ops'
|
||||
env.IMAGE_NAME = 'ops-web'
|
||||
env.DOCKERFILE = 'Dockerfile.ops'
|
||||
env.DEPLOY_SERVICE = 'ops-web'
|
||||
env.BUILD_ARGS = '--build-arg OPS_APP_BASE=/ --build-arg OPS_API_BASE_URL=/api'
|
||||
env.BUILD_ARGS = '--build-arg OPS_APP_BASE=/ --build-arg OPS_API_BASE_URL=/api'
|
||||
break
|
||||
default:
|
||||
error("Unsupported APP: ${params.APP}")
|
||||
@ -59,8 +65,8 @@ pipeline {
|
||||
def fullImage = "${env.ACR_REGISTRY}/${env.ACR_NAMESPACE}/${env.IMAGE_NAME}:${params.IMAGE_TAG}"
|
||||
bat """
|
||||
docker login ${env.ACR_REGISTRY} -u ${env.ACR_USERNAME} -p %ACR_PASS%
|
||||
docker pull ${fullImage} || exit 0
|
||||
docker build -f ${env.DOCKERFILE} ${env.BUILD_ARGS} --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from ${fullImage} -t ${fullImage} .
|
||||
docker pull --platform=linux/amd64 ${fullImage} || echo Pull failed, will build fresh
|
||||
docker build --platform=linux/amd64 -f ${env.DOCKERFILE} ${env.BUILD_ARGS} --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from ${fullImage} -t ${fullImage} .
|
||||
docker push ${fullImage}
|
||||
docker rmi ${fullImage} || exit 0
|
||||
"""
|
||||
@ -76,7 +82,7 @@ pipeline {
|
||||
script {
|
||||
def fullImage = "${env.ACR_REGISTRY}/${env.ACR_NAMESPACE}/${env.IMAGE_NAME}:${params.IMAGE_TAG}"
|
||||
bat """
|
||||
ssh -i "%SSH_KEY%" -o StrictHostKeyChecking=no ${env.PROD_USER}@${env.PROD_HOST} "docker pull ${fullImage} && docker compose -f ${env.COMPOSE_FILE} up -d --no-deps ${env.DEPLOY_SERVICE} && docker image prune -f"
|
||||
ssh -i "%SSH_KEY%" -o StrictHostKeyChecking=no ${env.PROD_USER}@${env.PROD_HOST} "docker pull ${fullImage} && docker compose -f ${env.COMPOSE_FILE} up -d --no-deps --force-recreate ${env.DEPLOY_SERVICE} && docker image prune -f"
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,28 +2,40 @@ pipeline {
|
||||
agent any
|
||||
|
||||
parameters {
|
||||
string(name: 'BRANCH', defaultValue: 'main', description: 'Git 分支名')
|
||||
string(name: 'IMAGE_TAG', defaultValue: 'latest', description: '镜像 Tag')
|
||||
booleanParam(name: 'DEPLOY', defaultValue: true, description: '构建后是否自动部署')
|
||||
}
|
||||
|
||||
environment {
|
||||
ACR_REGISTRY = 'crpi-n44qjpuucgjt8e8c.cn-beijing.personal.cr.aliyuncs.com'
|
||||
ACR_NAMESPACE = 'xuqmgroup'
|
||||
ACR_USERNAME = 'xuqinmin12'
|
||||
PROD_HOST = '106.54.23.149'
|
||||
PROD_USER = 'ubuntu'
|
||||
COMPOSE_FILE = '/opt/xuqm/deploy/compose.production.yaml'
|
||||
ACR_REGISTRY = 'crpi-n44qjpuucgjt8e8c.cn-beijing.personal.cr.aliyuncs.com'
|
||||
ACR_NAMESPACE = 'xuqmgroup'
|
||||
ACR_USERNAME = 'xuqinmin12'
|
||||
PROD_HOST = '106.54.23.149'
|
||||
PROD_USER = 'ubuntu'
|
||||
COMPOSE_FILE = '/opt/xuqm/deploy/compose.production.yaml'
|
||||
DOCKER_BUILDKIT = '1'
|
||||
IMAGE_NAME = 'ops-web'
|
||||
DOCKERFILE = 'Dockerfile.ops'
|
||||
DEPLOY_SERVICE = 'ops-web'
|
||||
BUILD_ARGS = '--build-arg OPS_APP_BASE=/ --build-arg OPS_API_BASE_URL=/api'
|
||||
IMAGE_NAME = 'ops-web'
|
||||
DOCKERFILE = 'Dockerfile.ops'
|
||||
DEPLOY_SERVICE = 'ops-web'
|
||||
BUILD_ARGS = '--build-arg OPS_APP_BASE=/ --build-arg OPS_API_BASE_URL=/api'
|
||||
}
|
||||
|
||||
options {
|
||||
timeout(time: 20, unit: 'MINUTES')
|
||||
buildDiscarder(logRotator(numToKeepStr: '20'))
|
||||
disableConcurrentBuilds()
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Checkout') {
|
||||
steps { checkout scm }
|
||||
steps {
|
||||
checkout([
|
||||
$class: 'GitSCM',
|
||||
branches: [[name: 'main']],
|
||||
extensions: [[$class: 'CleanBeforeCheckout']],
|
||||
userRemoteConfigs: scm.userRemoteConfigs
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
stage('Docker Build & Push') {
|
||||
@ -50,7 +62,7 @@ pipeline {
|
||||
script {
|
||||
def fullImage = "${env.ACR_REGISTRY}/${env.ACR_NAMESPACE}/${env.IMAGE_NAME}:${params.IMAGE_TAG}"
|
||||
bat """
|
||||
ssh -i "%SSH_KEY%" -o StrictHostKeyChecking=no ${env.PROD_USER}@${env.PROD_HOST} "docker pull ${fullImage} && docker compose -f ${env.COMPOSE_FILE} up -d --no-deps ${env.DEPLOY_SERVICE} && docker image prune -f"
|
||||
ssh -i "%SSH_KEY%" -o StrictHostKeyChecking=no ${env.PROD_USER}@${env.PROD_HOST} "docker pull ${fullImage} && docker compose -f ${env.COMPOSE_FILE} up -d --no-deps --force-recreate ${env.DEPLOY_SERVICE} && docker image prune -f"
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,28 +2,40 @@ pipeline {
|
||||
agent any
|
||||
|
||||
parameters {
|
||||
string(name: 'BRANCH', defaultValue: 'main', description: 'Git 分支名')
|
||||
string(name: 'IMAGE_TAG', defaultValue: 'latest', description: '镜像 Tag')
|
||||
booleanParam(name: 'DEPLOY', defaultValue: true, description: '构建后是否自动部署')
|
||||
}
|
||||
|
||||
environment {
|
||||
ACR_REGISTRY = 'crpi-n44qjpuucgjt8e8c.cn-beijing.personal.cr.aliyuncs.com'
|
||||
ACR_NAMESPACE = 'xuqmgroup'
|
||||
ACR_USERNAME = 'xuqinmin12'
|
||||
PROD_HOST = '106.54.23.149'
|
||||
PROD_USER = 'ubuntu'
|
||||
COMPOSE_FILE = '/opt/xuqm/deploy/compose.production.yaml'
|
||||
ACR_REGISTRY = 'crpi-n44qjpuucgjt8e8c.cn-beijing.personal.cr.aliyuncs.com'
|
||||
ACR_NAMESPACE = 'xuqmgroup'
|
||||
ACR_USERNAME = 'xuqinmin12'
|
||||
PROD_HOST = '106.54.23.149'
|
||||
PROD_USER = 'ubuntu'
|
||||
COMPOSE_FILE = '/opt/xuqm/deploy/compose.production.yaml'
|
||||
DOCKER_BUILDKIT = '1'
|
||||
IMAGE_NAME = 'tenant-web'
|
||||
DOCKERFILE = 'Dockerfile.tenant'
|
||||
DEPLOY_SERVICE = 'tenant-web'
|
||||
BUILD_ARGS = '--build-arg TENANT_APP_BASE=/ --build-arg TENANT_API_BASE_URL=/api'
|
||||
IMAGE_NAME = 'tenant-web'
|
||||
DOCKERFILE = 'Dockerfile.tenant'
|
||||
DEPLOY_SERVICE = 'tenant-web'
|
||||
BUILD_ARGS = '--build-arg TENANT_APP_BASE=/ --build-arg TENANT_API_BASE_URL=/api'
|
||||
}
|
||||
|
||||
options {
|
||||
timeout(time: 20, unit: 'MINUTES')
|
||||
buildDiscarder(logRotator(numToKeepStr: '20'))
|
||||
disableConcurrentBuilds()
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Checkout') {
|
||||
steps { checkout scm }
|
||||
steps {
|
||||
checkout([
|
||||
$class: 'GitSCM',
|
||||
branches: [[name: 'main']],
|
||||
extensions: [[$class: 'CleanBeforeCheckout']],
|
||||
userRemoteConfigs: scm.userRemoteConfigs
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
stage('Docker Build & Push') {
|
||||
@ -50,7 +62,7 @@ pipeline {
|
||||
script {
|
||||
def fullImage = "${env.ACR_REGISTRY}/${env.ACR_NAMESPACE}/${env.IMAGE_NAME}:${params.IMAGE_TAG}"
|
||||
bat """
|
||||
ssh -i "%SSH_KEY%" -o StrictHostKeyChecking=no ${env.PROD_USER}@${env.PROD_HOST} "docker pull ${fullImage} && docker compose -f ${env.COMPOSE_FILE} up -d --no-deps ${env.DEPLOY_SERVICE} && docker image prune -f"
|
||||
ssh -i "%SSH_KEY%" -o StrictHostKeyChecking=no ${env.PROD_USER}@${env.PROD_HOST} "docker pull ${fullImage} && docker compose -f ${env.COMPOSE_FILE} up -d --no-deps --force-recreate ${env.DEPLOY_SERVICE} && docker image prune -f"
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,6 @@
|
||||
</div>
|
||||
|
||||
<el-table :data="requests" v-loading="loading">
|
||||
<el-table-column prop="appKey" label="AppKey" width="180" />
|
||||
<el-table-column label="租户信息" min-width="220" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<div>
|
||||
@ -27,11 +26,6 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="platform" label="平台" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small">{{ row.platform }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="serviceType" label="服务类型" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" type="info">{{ row.serviceType }}</el-tag>
|
||||
|
||||
@ -97,6 +97,10 @@ const router = createRouter({
|
||||
path: 'services/update/:appKey?',
|
||||
component: () => import('@/views/update/VersionManagementView.vue'),
|
||||
},
|
||||
{
|
||||
path: 'services/license/:appKey?',
|
||||
component: () => import('@/views/license/LicenseManagementView.vue'),
|
||||
},
|
||||
{
|
||||
path: 'accounts',
|
||||
component: () => import('@/views/accounts/SubAccountView.vue'),
|
||||
|
||||
@ -814,7 +814,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { appApi, type App } from '@/api/app'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
@ -850,6 +850,14 @@ const checkingService = ref(false)
|
||||
const showActivationDialog = ref(false)
|
||||
const activationReason = ref('')
|
||||
const submittingActivation = ref(false)
|
||||
let pollingTimer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
function clearPolling() {
|
||||
if (pollingTimer !== null) {
|
||||
clearInterval(pollingTimer)
|
||||
pollingTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
async function checkServiceEnabled(key: string) {
|
||||
checkingService.value = true
|
||||
@ -859,8 +867,21 @@ async function checkServiceEnabled(key: string) {
|
||||
const enabled = res.data.data.some(s => s.serviceType === 'IM' && s.enabled)
|
||||
serviceEnabled.value = enabled
|
||||
if (enabled) {
|
||||
clearPolling()
|
||||
loadStats()
|
||||
loadUsers()
|
||||
} else if (pollingTimer === null) {
|
||||
pollingTimer = setInterval(async () => {
|
||||
const currentKey = appKey.value
|
||||
if (!currentKey) { clearPolling(); return }
|
||||
const r = await appApi.getServices(currentKey).catch(() => null)
|
||||
if (r && r.data.data.some(s => s.serviceType === 'IM' && s.enabled)) {
|
||||
serviceEnabled.value = true
|
||||
clearPolling()
|
||||
loadStats()
|
||||
loadUsers()
|
||||
}
|
||||
}, 30000)
|
||||
}
|
||||
} catch {
|
||||
serviceEnabled.value = false
|
||||
@ -1976,8 +1997,10 @@ function handleOperationLogPageChange(page: number) {
|
||||
}
|
||||
|
||||
watch(appKey, (key) => {
|
||||
if (isServicesPortal.value && key) {
|
||||
checkServiceEnabled(key)
|
||||
if (isServicesPortal.value) {
|
||||
clearPolling()
|
||||
serviceEnabled.value = null
|
||||
if (key) checkServiceEnabled(key)
|
||||
}
|
||||
})
|
||||
|
||||
@ -1992,6 +2015,10 @@ onMounted(() => {
|
||||
loadUsers()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearPolling()
|
||||
})
|
||||
|
||||
function logActionLabel(action: string) {
|
||||
return {
|
||||
REGISTER_USER: '注册用户',
|
||||
|
||||
@ -20,9 +20,9 @@
|
||||
<el-menu-item index="/services/im"><el-icon><ChatDotRound /></el-icon><span>即时通讯</span></el-menu-item>
|
||||
<el-menu-item index="/services/push"><el-icon><Bell /></el-icon><span>离线推送</span></el-menu-item>
|
||||
<el-menu-item index="/services/update"><el-icon><Upload /></el-icon><span>版本管理</span></el-menu-item>
|
||||
<el-menu-item index="/services/license"><el-icon><Key /></el-icon><span>授权管理</span></el-menu-item>
|
||||
</el-sub-menu>
|
||||
<el-menu-item index="/security"><el-icon><Lock /></el-icon><span>安全中心</span></el-menu-item>
|
||||
<el-menu-item index="/docs"><el-icon><Document /></el-icon><span>接入文档</span></el-menu-item>
|
||||
<el-menu-item index="/operation-logs"><el-icon><Document /></el-icon><span>操作日志</span></el-menu-item>
|
||||
<el-menu-item v-if="auth.user?.type === 'MAIN'" index="/accounts"><el-icon><User /></el-icon><span>子账号管理</span></el-menu-item>
|
||||
</el-menu>
|
||||
@ -55,9 +55,9 @@
|
||||
<el-menu-item index="/services/im"><el-icon><ChatDotRound /></el-icon><span>即时通讯</span></el-menu-item>
|
||||
<el-menu-item index="/services/push"><el-icon><Bell /></el-icon><span>离线推送</span></el-menu-item>
|
||||
<el-menu-item index="/services/update"><el-icon><Upload /></el-icon><span>版本管理</span></el-menu-item>
|
||||
<el-menu-item index="/services/license"><el-icon><Key /></el-icon><span>授权管理</span></el-menu-item>
|
||||
</el-sub-menu>
|
||||
<el-menu-item index="/security"><el-icon><Lock /></el-icon><span>安全中心</span></el-menu-item>
|
||||
<el-menu-item index="/docs"><el-icon><Document /></el-icon><span>接入文档</span></el-menu-item>
|
||||
<el-menu-item index="/operation-logs"><el-icon><Document /></el-icon><span>操作日志</span></el-menu-item>
|
||||
<el-menu-item v-if="auth.user?.type === 'MAIN'" index="/accounts"><el-icon><User /></el-icon><span>子账号管理</span></el-menu-item>
|
||||
</el-menu>
|
||||
@ -106,7 +106,7 @@
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { Bell, ChatDotRound, Document, Grid, List, Lock, Menu, Odometer, Upload, User } from '@element-plus/icons-vue'
|
||||
import { Bell, ChatDotRound, Document, Grid, Key, List, Lock, Menu, Odometer, Upload, User } from '@element-plus/icons-vue'
|
||||
|
||||
const auth = useAuthStore()
|
||||
const route = useRoute()
|
||||
|
||||
@ -9,7 +9,27 @@
|
||||
<el-page-header v-else @back="$router.back()" :content="`授权管理 - ${appName}`" style="margin-bottom:20px" />
|
||||
<el-empty v-if="isServicesPortal && !appKey" description="请选择一个应用" style="margin-top:80px" />
|
||||
|
||||
<template v-if="!isServicesPortal || appKey">
|
||||
<div v-if="isServicesPortal && appKey && checkingService" v-loading="true" style="min-height:200px" />
|
||||
|
||||
<template v-if="isServicesPortal && appKey && serviceEnabled === false">
|
||||
<el-empty :image-size="80" description="当前应用未开通授权管理服务" style="margin-top:60px">
|
||||
<el-button type="primary" @click="showActivationDialog = true">申请开通</el-button>
|
||||
</el-empty>
|
||||
<el-dialog v-model="showActivationDialog" title="申请开通授权管理" width="460px">
|
||||
<el-form label-width="80px">
|
||||
<el-form-item label="服务">授权管理</el-form-item>
|
||||
<el-form-item label="申请理由">
|
||||
<el-input v-model="activationReason" type="textarea" :rows="3" placeholder="请描述您的业务场景" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showActivationDialog = false">取消</el-button>
|
||||
<el-button type="primary" :loading="submittingActivation" @click="submitActivation">提交申请</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<template v-if="!isServicesPortal || (appKey && serviceEnabled === true)">
|
||||
<el-row :gutter="16" class="stat-grid">
|
||||
<el-col :xs="24" :sm="12" :md="6" v-for="item in statCards" :key="item.label">
|
||||
<el-card shadow="never">
|
||||
@ -111,9 +131,15 @@ const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const isMobile = ref(false)
|
||||
const appKey = computed(() => route.query.appKey as string || route.params.appKey as string)
|
||||
const appKey = computed(() => (route.params.appKey as string) || (route.query.appKey as string) || '')
|
||||
const isServicesPortal = computed(() => route.path.startsWith('/services/'))
|
||||
|
||||
const serviceEnabled = ref<boolean | null>(null)
|
||||
const checkingService = ref(false)
|
||||
const showActivationDialog = ref(false)
|
||||
const activationReason = ref('')
|
||||
const submittingActivation = ref(false)
|
||||
|
||||
const portalApps = ref<App[]>([])
|
||||
const currentApp = ref<App | null>(null)
|
||||
const license = ref<AppLicense | null>(null)
|
||||
@ -189,8 +215,41 @@ function parseFilename(disposition?: string) {
|
||||
return plain ? decodeURIComponent(plain) : null
|
||||
}
|
||||
|
||||
async function checkServiceEnabled(key: string) {
|
||||
checkingService.value = true
|
||||
serviceEnabled.value = null
|
||||
try {
|
||||
const res = await appApi.getServices(key)
|
||||
const enabled = res.data.data.some(s => s.serviceType === 'LICENSE' && s.enabled)
|
||||
serviceEnabled.value = enabled
|
||||
if (enabled) loadData()
|
||||
} catch {
|
||||
serviceEnabled.value = false
|
||||
} finally {
|
||||
checkingService.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function submitActivation() {
|
||||
if (!activationReason.value.trim()) {
|
||||
ElMessage.warning('请填写申请理由')
|
||||
return
|
||||
}
|
||||
submittingActivation.value = true
|
||||
try {
|
||||
await appApi.requestActivation(appKey.value, 'LICENSE', activationReason.value.trim())
|
||||
ElMessage.success('申请已提交,等待运营审核')
|
||||
showActivationDialog.value = false
|
||||
activationReason.value = ''
|
||||
} catch {
|
||||
ElMessage.error('提交失败')
|
||||
} finally {
|
||||
submittingActivation.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function switchApp(key: string) {
|
||||
router.push({ path: route.path, query: { ...route.query, appKey: key } })
|
||||
router.push(`/services/license/${key}`)
|
||||
}
|
||||
|
||||
function formatDate(d: string | number) {
|
||||
@ -202,20 +261,26 @@ function updateViewport() {
|
||||
isMobile.value = window.innerWidth < 768
|
||||
}
|
||||
|
||||
watch(appKey, () => {
|
||||
loadData()
|
||||
watch(appKey, (key) => {
|
||||
if (isServicesPortal.value) {
|
||||
if (key) checkServiceEnabled(key)
|
||||
} else {
|
||||
loadData()
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
updateViewport()
|
||||
window.addEventListener('resize', updateViewport)
|
||||
loadData()
|
||||
if (isServicesPortal.value) {
|
||||
appApi.list().then(res => {
|
||||
portalApps.value = res.data.data || []
|
||||
currentApp.value = portalApps.value.find(item => item.appKey === appKey.value) ?? currentApp.value
|
||||
})
|
||||
if (appKey.value) checkServiceEnabled(appKey.value)
|
||||
return
|
||||
}
|
||||
loadData()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户