728 lines
21 KiB
Vue
728 lines
21 KiB
Vue
<template>
|
||
<div class="image-requests-container">
|
||
<div class="page-header">
|
||
<h2>申请镜像</h2>
|
||
<div class="header-actions">
|
||
<el-button type="primary" @click="handleAdd">
|
||
<el-icon><plus /></el-icon>申请镜像
|
||
</el-button>
|
||
<el-button @click="handleRefresh">
|
||
<el-icon><refresh /></el-icon>刷新
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 提示信息 -->
|
||
<el-alert
|
||
type="info"
|
||
show-icon
|
||
:closable="false"
|
||
class="info-alert"
|
||
>
|
||
<el-icon><info-filled /></el-icon>
|
||
申请的镜像需要经过安全审核,审核通过后可在创建云电脑或容器时使用,审核结果将通过站内信通知。
|
||
</el-alert>
|
||
|
||
<!-- 搜索区域 -->
|
||
<el-card class="search-card">
|
||
<el-form :inline="true" :model="searchForm" class="search-form">
|
||
<el-form-item label="镜像名称">
|
||
<el-input v-model="searchForm.name" placeholder="请输入镜像名称" clearable />
|
||
</el-form-item>
|
||
<el-form-item label="镜像类型">
|
||
<el-select v-model="searchForm.type" placeholder="请选择镜像类型" clearable>
|
||
<el-option label="Docker镜像" value="docker" />
|
||
<el-option label="Windows镜像" value="windows" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="申请状态">
|
||
<el-select v-model="searchForm.status" placeholder="请选择状态" clearable>
|
||
<el-option label="已通过" value="approved" />
|
||
<el-option label="审核中" value="pending" />
|
||
<el-option label="已拒绝" value="rejected" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" @click="handleSearch">
|
||
<el-icon><search /></el-icon>搜索
|
||
</el-button>
|
||
<el-button @click="resetSearch">
|
||
<el-icon><refresh /></el-icon>重置
|
||
</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
</el-card>
|
||
|
||
<!-- 数据表格 -->
|
||
<el-card class="table-card">
|
||
<el-table
|
||
v-loading="loading"
|
||
:data="tableData"
|
||
border
|
||
style="width: 100%"
|
||
row-key="id"
|
||
>
|
||
<el-table-column prop="id" label="申请ID" width="150" align="center" />
|
||
<el-table-column prop="name" label="镜像名称" min-width="180" show-overflow-tooltip />
|
||
<el-table-column prop="type" label="类型" width="120" align="center">
|
||
<template #default="scope">
|
||
<el-tag :type="scope.row.type === 'docker' ? 'success' : 'primary'">
|
||
{{ scope.row.type === 'docker' ? 'Docker' : 'Windows' }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="requestTime" label="申请时间" width="180" align="center" />
|
||
<el-table-column prop="status" label="状态" width="100" align="center">
|
||
<template #default="scope">
|
||
<el-tag :type="getStatusType(scope.row.status)">
|
||
{{ getStatusText(scope.row.status) }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="220" align="center" fixed="right">
|
||
<template #default="scope">
|
||
<el-button type="primary" link @click="handleView(scope.row)">
|
||
<el-icon><view /></el-icon>查看详情
|
||
</el-button>
|
||
<el-button
|
||
v-if="scope.row.status === 'rejected'"
|
||
type="primary"
|
||
link
|
||
@click="handleResubmit(scope.row)"
|
||
>
|
||
<el-icon><refresh /></el-icon>重新提交
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<!-- 分页 -->
|
||
<div class="pagination-container">
|
||
<el-pagination
|
||
v-model:current-page="pagination.currentPage"
|
||
v-model:page-size="pagination.pageSize"
|
||
:page-sizes="[10, 20, 50, 100]"
|
||
:total="pagination.total"
|
||
layout="total, sizes, prev, pager, next, jumper"
|
||
@size-change="handleSizeChange"
|
||
@current-change="handleCurrentChange"
|
||
/>
|
||
</div>
|
||
</el-card>
|
||
|
||
<!-- 申请镜像对话框 -->
|
||
<el-dialog
|
||
v-model="requestDialogVisible"
|
||
title="申请镜像"
|
||
width="700px"
|
||
:before-close="handleDialogClose"
|
||
>
|
||
<el-form :model="requestForm" label-width="120px" :rules="rules" ref="requestFormRef">
|
||
<el-form-item label="镜像类型" prop="type">
|
||
<el-radio-group v-model="requestForm.type">
|
||
<el-radio label="docker">Docker镜像</el-radio>
|
||
<el-radio label="windows">Windows镜像</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="镜像名称" prop="name">
|
||
<el-input v-model="requestForm.name" placeholder="请输入镜像名称,例如:MySQL 8.0" />
|
||
</el-form-item>
|
||
|
||
<template v-if="requestForm.type === 'docker'">
|
||
<el-form-item label="Docker镜像地址" prop="dockerImage">
|
||
<el-input v-model="requestForm.dockerImage" placeholder="请输入Docker镜像地址,例如:mysql:8.0">
|
||
<template #prepend>
|
||
<el-select v-model="requestForm.dockerSource" style="width: 120px">
|
||
<el-option label="Docker Hub" value="dockerhub" />
|
||
<el-option label="自定义" value="custom" />
|
||
</el-select>
|
||
</template>
|
||
</el-input>
|
||
<div class="form-tip">Docker Hub格式:mysql:8.0;自建仓库格式:namespace/repo:tag</div>
|
||
</el-form-item>
|
||
|
||
<el-divider content-position="left">环境变量配置</el-divider>
|
||
|
||
<div class="env-vars-container">
|
||
<div class="env-vars-header">
|
||
<div class="env-var-name">变量名</div>
|
||
<div class="env-var-value">变量值</div>
|
||
<div class="env-var-action"></div>
|
||
</div>
|
||
|
||
<div v-for="(env, index) in requestForm.envVars" :key="index" class="env-vars-item">
|
||
<el-input v-model="env.key" placeholder="KEY" />
|
||
<el-input v-model="env.value" placeholder="VALUE" />
|
||
<el-button circle type="danger" @click="removeEnvVar(index)">
|
||
<el-icon><delete /></el-icon>
|
||
</el-button>
|
||
</div>
|
||
|
||
<el-button type="primary" plain @click="addEnvVar" class="add-env-btn">
|
||
<el-icon><plus /></el-icon>添加环境变量
|
||
</el-button>
|
||
</div>
|
||
|
||
<el-divider content-position="left">暴露端口</el-divider>
|
||
|
||
<div class="ports-container">
|
||
<div class="ports-header">
|
||
<div class="port-container">容器端口</div>
|
||
<div class="port-protocol">协议</div>
|
||
<div class="port-desc">描述</div>
|
||
<div class="port-action"></div>
|
||
</div>
|
||
|
||
<div v-for="(port, index) in requestForm.ports" :key="index" class="ports-item">
|
||
<el-input-number v-model="port.containerPort" :min="1" :max="65535" controls-position="right" />
|
||
<el-select v-model="port.protocol">
|
||
<el-option label="TCP" value="TCP" />
|
||
<el-option label="UDP" value="UDP" />
|
||
</el-select>
|
||
<el-input v-model="port.description" placeholder="请填写用途描述" />
|
||
<el-button circle type="danger" @click="removePort(index)">
|
||
<el-icon><delete /></el-icon>
|
||
</el-button>
|
||
</div>
|
||
|
||
<el-button type="primary" plain @click="addPort" class="add-port-btn">
|
||
<el-icon><plus /></el-icon>添加端口
|
||
</el-button>
|
||
</div>
|
||
</template>
|
||
|
||
<template v-else>
|
||
<el-form-item label="Windows版本" prop="windowsVersion">
|
||
<el-select v-model="requestForm.windowsVersion" placeholder="请选择Windows版本" style="width: 100%">
|
||
<el-option label="Windows Server 2019" value="2019" />
|
||
<el-option label="Windows Server 2022" value="2022" />
|
||
<el-option label="Windows 10" value="10" />
|
||
<el-option label="Windows 11" value="11" />
|
||
</el-select>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="镜像链接" prop="windowsImageUrl">
|
||
<el-input v-model="requestForm.windowsImageUrl" placeholder="请输入Windows镜像的下载链接" />
|
||
<div class="form-tip">提供ISO镜像的下载链接,支持微软官方、MSDN等其他合法渠道的镜像</div>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="激活方式" prop="activationMethod">
|
||
<el-radio-group v-model="requestForm.activationMethod">
|
||
<el-radio label="kms">KMS激活</el-radio>
|
||
<el-radio label="key">产品密钥</el-radio>
|
||
<el-radio label="none">不需要激活</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
</template>
|
||
|
||
<el-form-item label="申请理由" prop="reason">
|
||
<el-input
|
||
v-model="requestForm.reason"
|
||
type="textarea"
|
||
:rows="4"
|
||
placeholder="请详细说明申请该镜像的用途和理由"
|
||
/>
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<el-button @click="handleDialogClose">取消</el-button>
|
||
<el-button type="primary" @click="submitForm">提交申请</el-button>
|
||
</div>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 查看详情对话框 -->
|
||
<el-dialog
|
||
v-model="detailDialogVisible"
|
||
title="申请详情"
|
||
width="700px"
|
||
:before-close="handleDialogClose"
|
||
>
|
||
<div v-if="currentRequest.id" class="request-detail">
|
||
<el-descriptions :column="2" border>
|
||
<el-descriptions-item label="申请ID" :span="2">{{ currentRequest.id }}</el-descriptions-item>
|
||
<el-descriptions-item label="镜像名称">{{ currentRequest.name }}</el-descriptions-item>
|
||
<el-descriptions-item label="镜像类型">
|
||
<el-tag :type="currentRequest.type === 'docker' ? 'success' : 'primary'">
|
||
{{ currentRequest.type === 'docker' ? 'Docker' : 'Windows' }}
|
||
</el-tag>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="申请时间">{{ currentRequest.requestTime }}</el-descriptions-item>
|
||
<el-descriptions-item label="状态">
|
||
<el-tag :type="getStatusType(currentRequest.status)">
|
||
{{ getStatusText(currentRequest.status) }}
|
||
</el-tag>
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
|
||
<el-divider content-position="left">详细信息</el-divider>
|
||
|
||
<template v-if="currentRequest.type === 'docker'">
|
||
<el-descriptions :column="1" border>
|
||
<el-descriptions-item label="Docker镜像地址">
|
||
{{ currentRequest.dockerSource === 'dockerhub' ? 'Docker Hub: ' : '自定义: ' }}{{ currentRequest.dockerImage }}
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
|
||
<div v-if="currentRequest.envVars && currentRequest.envVars.length > 0">
|
||
<el-divider content-position="left">环境变量</el-divider>
|
||
<el-table :data="currentRequest.envVars" border style="width: 100%">
|
||
<el-table-column prop="key" label="变量名" />
|
||
<el-table-column prop="value" label="变量值" />
|
||
</el-table>
|
||
</div>
|
||
|
||
<div v-if="currentRequest.ports && currentRequest.ports.length > 0">
|
||
<el-divider content-position="left">端口配置</el-divider>
|
||
<el-table :data="currentRequest.ports" border style="width: 100%">
|
||
<el-table-column prop="containerPort" label="容器端口" width="120" />
|
||
<el-table-column prop="protocol" label="协议" width="100" />
|
||
<el-table-column prop="description" label="描述" />
|
||
</el-table>
|
||
</div>
|
||
</template>
|
||
|
||
<template v-else>
|
||
<el-descriptions :column="1" border>
|
||
<el-descriptions-item label="Windows版本">
|
||
{{ getWindowsVersionText(currentRequest.windowsVersion) }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="镜像链接">
|
||
{{ currentRequest.windowsImageUrl }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="激活方式">
|
||
{{ getActivationMethodText(currentRequest.activationMethod) }}
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
</template>
|
||
|
||
<el-divider content-position="left">申请理由</el-divider>
|
||
<div class="request-reason">{{ currentRequest.reason }}</div>
|
||
|
||
<template v-if="currentRequest.reviewComment">
|
||
<el-divider content-position="left">审核意见</el-divider>
|
||
<div class="review-comment">{{ currentRequest.reviewComment }}</div>
|
||
</template>
|
||
</div>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, reactive, onMounted } from 'vue'
|
||
import {
|
||
Plus, Refresh, Search, View, Delete, InfoFilled
|
||
} from '@element-plus/icons-vue'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
|
||
// 搜索表单
|
||
const searchForm = reactive({
|
||
name: '',
|
||
type: '',
|
||
status: ''
|
||
})
|
||
|
||
// 重置搜索
|
||
const resetSearch = () => {
|
||
searchForm.name = ''
|
||
searchForm.type = ''
|
||
searchForm.status = ''
|
||
handleSearch()
|
||
}
|
||
|
||
// 表格数据
|
||
const loading = ref(false)
|
||
const tableData = ref([])
|
||
|
||
// 分页
|
||
const pagination = reactive({
|
||
currentPage: 1,
|
||
pageSize: 10,
|
||
total: 0
|
||
})
|
||
|
||
// 处理页码变化
|
||
const handleCurrentChange = (val) => {
|
||
pagination.currentPage = val
|
||
fetchData()
|
||
}
|
||
|
||
// 处理每页条数变化
|
||
const handleSizeChange = (val) => {
|
||
pagination.pageSize = val
|
||
fetchData()
|
||
}
|
||
|
||
// 对话框相关
|
||
const requestDialogVisible = ref(false)
|
||
const detailDialogVisible = ref(false)
|
||
const currentRequest = ref({})
|
||
|
||
// 表单对象和规则
|
||
const requestFormRef = ref(null)
|
||
const requestForm = reactive({
|
||
type: 'docker',
|
||
name: '',
|
||
dockerSource: 'dockerhub',
|
||
dockerImage: '',
|
||
windowsVersion: '',
|
||
windowsImageUrl: '',
|
||
activationMethod: 'kms',
|
||
reason: '',
|
||
envVars: [],
|
||
ports: []
|
||
})
|
||
|
||
const rules = {
|
||
type: [{ required: true, message: '请选择镜像类型', trigger: 'change' }],
|
||
name: [{ required: true, message: '请输入镜像名称', trigger: 'blur' }],
|
||
dockerImage: [{ required: true, message: '请输入Docker镜像地址', trigger: 'blur' }],
|
||
windowsVersion: [{ required: true, message: '请选择Windows版本', trigger: 'change' }],
|
||
windowsImageUrl: [{ required: true, message: '请输入镜像链接', trigger: 'blur' }],
|
||
activationMethod: [{ required: true, message: '请选择激活方式', trigger: 'change' }],
|
||
reason: [{ required: true, message: '请输入申请理由', trigger: 'blur' }]
|
||
}
|
||
|
||
// 状态标签样式
|
||
const getStatusType = (status) => {
|
||
const map = {
|
||
approved: 'success',
|
||
pending: 'warning',
|
||
rejected: 'danger'
|
||
}
|
||
return map[status] || 'info'
|
||
}
|
||
|
||
const getStatusText = (status) => {
|
||
const map = {
|
||
approved: '已通过',
|
||
pending: '审核中',
|
||
rejected: '已拒绝'
|
||
}
|
||
return map[status] || '未知'
|
||
}
|
||
|
||
// Windows版本文本
|
||
const getWindowsVersionText = (version) => {
|
||
const map = {
|
||
'2019': 'Windows Server 2019',
|
||
'2022': 'Windows Server 2022',
|
||
'10': 'Windows 10',
|
||
'11': 'Windows 11'
|
||
}
|
||
return map[version] || '未知'
|
||
}
|
||
|
||
// 激活方式文本
|
||
const getActivationMethodText = (method) => {
|
||
const map = {
|
||
kms: 'KMS激活',
|
||
key: '产品密钥',
|
||
none: '不需要激活'
|
||
}
|
||
return map[method] || '未知'
|
||
}
|
||
|
||
// 处理搜索
|
||
const handleSearch = () => {
|
||
pagination.currentPage = 1
|
||
fetchData()
|
||
}
|
||
|
||
// 刷新数据
|
||
const handleRefresh = () => {
|
||
fetchData()
|
||
}
|
||
|
||
// 添加环境变量
|
||
const addEnvVar = () => {
|
||
requestForm.envVars.push({ key: '', value: '' })
|
||
}
|
||
|
||
// 移除环境变量
|
||
const removeEnvVar = (index) => {
|
||
requestForm.envVars.splice(index, 1)
|
||
}
|
||
|
||
// 添加端口
|
||
const addPort = () => {
|
||
requestForm.ports.push({ containerPort: 80, protocol: 'TCP', description: '' })
|
||
}
|
||
|
||
// 移除端口
|
||
const removePort = (index) => {
|
||
requestForm.ports.splice(index, 1)
|
||
}
|
||
|
||
// 获取数据
|
||
const fetchData = () => {
|
||
loading.value = true
|
||
|
||
// 模拟API请求
|
||
setTimeout(() => {
|
||
// 这里应该是真实的API请求
|
||
const mockData = [
|
||
{
|
||
id: 'REQ20240501001',
|
||
name: 'MySQL 8.0',
|
||
type: 'docker',
|
||
requestTime: '2024-05-01 10:23:45',
|
||
status: 'approved',
|
||
dockerSource: 'dockerhub',
|
||
dockerImage: 'mysql:8.0',
|
||
envVars: [
|
||
{ key: 'MYSQL_ROOT_PASSWORD', value: 'password' },
|
||
{ key: 'MYSQL_DATABASE', value: 'testdb' }
|
||
],
|
||
ports: [
|
||
{ containerPort: 3306, protocol: 'TCP', description: 'MySQL默认端口' }
|
||
],
|
||
reason: '用于开发测试环境,需要MySQL数据库服务',
|
||
reviewComment: '审核通过,已添加到镜像列表'
|
||
},
|
||
{
|
||
id: 'REQ20240502001',
|
||
name: 'Windows Server 2022',
|
||
type: 'windows',
|
||
requestTime: '2024-05-02 14:30:12',
|
||
status: 'pending',
|
||
windowsVersion: '2022',
|
||
windowsImageUrl: 'https://example.com/windows-server-2022.iso',
|
||
activationMethod: 'kms',
|
||
reason: '用于测试Windows Server 2022的新功能和兼容性'
|
||
},
|
||
{
|
||
id: 'REQ20240503001',
|
||
name: 'Redis 7.0',
|
||
type: 'docker',
|
||
requestTime: '2024-05-03 09:15:36',
|
||
status: 'rejected',
|
||
dockerSource: 'dockerhub',
|
||
dockerImage: 'redis:7.0',
|
||
envVars: [],
|
||
ports: [
|
||
{ containerPort: 6379, protocol: 'TCP', description: 'Redis默认端口' }
|
||
],
|
||
reason: '用于缓存服务',
|
||
reviewComment: '镜像存在安全漏洞,请使用Redis 7.0.2或更高版本'
|
||
}
|
||
]
|
||
|
||
tableData.value = mockData
|
||
pagination.total = mockData.length
|
||
loading.value = false
|
||
}, 500)
|
||
}
|
||
|
||
// 申请镜像
|
||
const handleAdd = () => {
|
||
requestForm.type = 'docker'
|
||
requestForm.name = ''
|
||
requestForm.dockerSource = 'dockerhub'
|
||
requestForm.dockerImage = ''
|
||
requestForm.windowsVersion = ''
|
||
requestForm.windowsImageUrl = ''
|
||
requestForm.activationMethod = 'kms'
|
||
requestForm.reason = ''
|
||
requestForm.envVars = []
|
||
requestForm.ports = []
|
||
requestDialogVisible.value = true
|
||
}
|
||
|
||
// 查看详情
|
||
const handleView = (row) => {
|
||
currentRequest.value = { ...row }
|
||
detailDialogVisible.value = true
|
||
}
|
||
|
||
// 重新提交
|
||
const handleResubmit = (row) => {
|
||
// 复制原有申请的信息到表单
|
||
requestForm.type = row.type
|
||
requestForm.name = row.name
|
||
|
||
if (row.type === 'docker') {
|
||
requestForm.dockerSource = row.dockerSource
|
||
requestForm.dockerImage = row.dockerImage
|
||
requestForm.envVars = [...row.envVars]
|
||
requestForm.ports = [...row.ports]
|
||
} else {
|
||
requestForm.windowsVersion = row.windowsVersion
|
||
requestForm.windowsImageUrl = row.windowsImageUrl
|
||
requestForm.activationMethod = row.activationMethod
|
||
}
|
||
|
||
requestForm.reason = row.reason
|
||
requestDialogVisible.value = true
|
||
}
|
||
|
||
// 关闭对话框
|
||
const handleDialogClose = () => {
|
||
requestDialogVisible.value = false
|
||
detailDialogVisible.value = false
|
||
if (requestFormRef.value) {
|
||
requestFormRef.value.resetFields()
|
||
}
|
||
}
|
||
|
||
// 提交表单
|
||
const submitForm = async () => {
|
||
if (!requestFormRef.value) return
|
||
|
||
await requestFormRef.value.validate((valid) => {
|
||
if (valid) {
|
||
// 这里应该是API请求
|
||
ElMessage.success('申请提交成功,请等待审核')
|
||
requestDialogVisible.value = false
|
||
fetchData()
|
||
}
|
||
})
|
||
}
|
||
|
||
// 初始加载
|
||
onMounted(() => {
|
||
fetchData()
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.image-requests-container {
|
||
padding: 20px;
|
||
}
|
||
|
||
.page-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.header-actions {
|
||
display: flex;
|
||
gap: 10px;
|
||
}
|
||
|
||
.info-alert {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.search-card {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.search-form {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.table-card {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.pagination-container {
|
||
margin-top: 20px;
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
/* 表单样式 */
|
||
.form-tip {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
margin-top: 5px;
|
||
}
|
||
|
||
/* 环境变量配置样式 */
|
||
.env-vars-container {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.env-vars-header {
|
||
display: flex;
|
||
margin-bottom: 10px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.env-vars-item {
|
||
display: flex;
|
||
margin-bottom: 10px;
|
||
gap: 10px;
|
||
}
|
||
|
||
.env-var-name {
|
||
flex: 1;
|
||
}
|
||
|
||
.env-var-value {
|
||
flex: 1;
|
||
}
|
||
|
||
.env-var-action {
|
||
width: 40px;
|
||
}
|
||
|
||
.add-env-btn {
|
||
margin-top: 10px;
|
||
}
|
||
|
||
/* 端口配置样式 */
|
||
.ports-container {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.ports-header {
|
||
display: flex;
|
||
margin-bottom: 10px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.ports-item {
|
||
display: flex;
|
||
margin-bottom: 10px;
|
||
gap: 10px;
|
||
}
|
||
|
||
.port-container {
|
||
width: 150px;
|
||
}
|
||
|
||
.port-protocol {
|
||
width: 120px;
|
||
}
|
||
|
||
.port-desc {
|
||
flex: 1;
|
||
}
|
||
|
||
.port-action {
|
||
width: 40px;
|
||
}
|
||
|
||
.add-port-btn {
|
||
margin-top: 10px;
|
||
}
|
||
|
||
/* 详情样式 */
|
||
.request-detail {
|
||
padding: 0 10px;
|
||
}
|
||
|
||
.request-reason {
|
||
background-color: #f8f8f8;
|
||
padding: 15px;
|
||
border-radius: 4px;
|
||
margin-top: 10px;
|
||
white-space: pre-wrap;
|
||
}
|
||
|
||
.review-comment {
|
||
background-color: #f0f9eb;
|
||
padding: 15px;
|
||
border-radius: 4px;
|
||
margin-top: 10px;
|
||
white-space: pre-wrap;
|
||
border-left: 4px solid #67c23a;
|
||
}
|
||
</style> |