containerDetail.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. <template>
  2. <div class="app-container">
  3. <el-row justify="space-between">
  4. <el-col :span="12">
  5. <el-form :model="queryParams" ref="queryRef" :inline="true" label-width="72px">
  6. <el-form-item label="容器名称">
  7. <el-input v-model="queryParams.containerName" placeholder="请输入容器名称" clearable style="width: 240px"
  8. @keyup.enter="getModelListTable"/>
  9. </el-form-item>
  10. <el-form-item>
  11. <el-button type="primary" icon="Search" @click="getModelListTable">查询</el-button>
  12. </el-form-item>
  13. </el-form>
  14. </el-col>
  15. <el-button type="primary" @click="reg" icon="Plus">创建容器</el-button>
  16. </el-row>
  17. <el-table
  18. :data="tableData"
  19. height="69vh"
  20. :cell-style="{ padding: '5px' }"
  21. :header-cell-style="{fontSize: '14px', height: heightAll * 0.01 + 'px' }"
  22. :row-style="{ fontSize: '1rem', textAlign:'center' }"
  23. border>
  24. <el-table-column type="index" label="序号" width="80">
  25. <template #default="{ $index }">
  26. <div style="text-align: center;">{{ $index + 1 }}</div>
  27. </template>
  28. </el-table-column>
  29. <el-table-column prop="containerName" label="模型名称"/>
  30. <el-table-column prop="name" label="容器名称"/>
  31. <el-table-column prop="imageName" label="容器镜像" width="220"/>
  32. <el-table-column prop="publishedPorts" label="开放端口" width="180">
  33. <template v-slot:header>
  34. 开放端口
  35. <el-popover title="开放端口" content="开放端口:内置端口" placement="top-start">
  36. <template #reference>
  37. <QuestionFilled style="width: 1em; height: 1em;"></QuestionFilled>
  38. </template>
  39. </el-popover>
  40. </template>
  41. </el-table-column>
  42. <el-table-column prop="status" align="center" label="模型状态" width="120">
  43. <template #default="scope">
  44. <el-tag v-if="scope.row.status==='RUNNING'">
  45. {{ getContainerStatus(scope.row.status) }}
  46. </el-tag>
  47. <el-tag v-if="scope.row.status==='STOPPED'" type="danger">
  48. {{ getContainerStatus(scope.row.status) }}
  49. </el-tag>
  50. <el-tag v-if="scope.row.status==='EXITED'" type="warning">
  51. {{ getContainerStatus(scope.row.status) }}
  52. </el-tag>
  53. </template>
  54. </el-table-column>
  55. <el-table-column align="center" label="操作" width="300">
  56. <template #default="scope">
  57. <div style="display: flex;justify-content: space-between;">
  58. <el-button type="primary" @click="handleEdit(scope.row)" size="small" text>编辑</el-button>
  59. <el-button v-if="scope.row.status!=='RUNNING'" @click="startContainers(scope.row.id)" type="primary"
  60. text
  61. size="small">运行
  62. </el-button>
  63. <el-button v-if="scope.row.status==='RUNNING'" @click="stopContainers(scope.row.id)" type="danger" text
  64. size="small">停止
  65. </el-button>
  66. <el-button v-if="scope.row.status==='RUNNING'" @click="restartContainers(scope.row.id)" type="warning"
  67. text
  68. size="small">重启
  69. </el-button>
  70. <el-button type="danger" @click="handleDelete(scope.row)" text size="small">删除</el-button>
  71. </div>
  72. </template>
  73. </el-table-column>
  74. </el-table>
  75. <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
  76. v-model:limit="queryParams.pageSize"/>
  77. <el-dialog v-model="dialogVisible" :title="dialogTitle" @close="clearForm" destroy-on-close>
  78. <el-form :model="form" label-position="right" ref="formRef" label-width="120px" :rules="rulesJi">
  79. <el-form-item label="容器名称:" prop="containerName">
  80. <el-input v-model="form.containerName"/>
  81. </el-form-item>
  82. <el-form-item label="容器英文名称:" prop="name">
  83. <el-input v-model="form.name" :disabled="form.id"/>
  84. </el-form-item>
  85. <el-form-item label="镜像:" prop="imageName">
  86. <el-select v-model="form.imageName" :disabled="form.id">
  87. <el-option v-for="item in imageList" :key="item.id" :label="item.imageName + ':' + item.tag"
  88. :value="item.imageName + ':' + item.tag"></el-option>
  89. </el-select>
  90. </el-form-item>
  91. <el-form-item>
  92. <template v-slot:label>
  93. <div style="display: flex;align-items: center;">
  94. 开放端口
  95. <el-popover title="开放端口" content="开放端口:内置端口" placement="top-start">
  96. <template #reference>
  97. <QuestionFilled style="width: 1em; height: 1em; margin: 0 5px;"></QuestionFilled>
  98. </template>
  99. </el-popover>
  100. :
  101. </div>
  102. </template>
  103. <el-input v-model="form.publishedPorts" :disabled="form.id"/>
  104. </el-form-item>
  105. <el-form-item label="容器说明">
  106. <el-input v-model="form.remark" type="textarea" placeholder="请输入容器说明"/>
  107. </el-form-item>
  108. </el-form>
  109. <template #footer>
  110. <el-button @click="dialogVisible = false">取消</el-button>
  111. <el-button type="primary" @click="submit">提交</el-button>
  112. </template>
  113. </el-dialog>
  114. </div>
  115. </template>
  116. <script setup>
  117. import {nextTick, onMounted, reactive, ref} from 'vue';
  118. import {getModelContainerById, getModelContainerPageList, saveModelContainer} from "@/api/container/modelContainer.js";
  119. import {deleteContainer, restartContainer, startContainer, stopContainer} from "@/api/container/containerOperation.js";
  120. import Pagination from "@/components/Pagination/index.vue";
  121. import {getModelContainerImageList} from "@/api/container/modelContainerImage.js";
  122. const {proxy} = getCurrentInstance();
  123. const queryParams = ref({
  124. pageNum: 1,
  125. pageSize: 20,
  126. containerName: '',
  127. })
  128. let tableData = ref([])
  129. const total = ref(0)
  130. function getContainerStatus(status) {
  131. switch (status) {
  132. case 'RUNNING':
  133. return '运行中'
  134. case 'STOPPED':
  135. return '已停止'
  136. case 'EXITED':
  137. return '审核未通过'
  138. default:
  139. return ''
  140. }
  141. }
  142. function startContainers(id) {
  143. startContainer(id).then(() => {
  144. proxy.$modal.msgSuccess('启动成功')
  145. getModelListTable()
  146. })
  147. }
  148. function stopContainers(id) {
  149. stopContainer(id).then(() => {
  150. proxy.$modal.msgSuccess('关闭成功')
  151. getModelListTable()
  152. })
  153. }
  154. function restartContainers(id) {
  155. restartContainer(id).then(() => {
  156. proxy.$modal.msgSuccess('重启成功')
  157. getModelListTable()
  158. })
  159. }
  160. async function handleEdit(row) {
  161. dialogVisible.value = true
  162. dialogTitle.value = `编辑【${row.containerName}】容器`
  163. await nextTick()
  164. getModelContainerById(row.id).then(res => {
  165. form.value = res.data
  166. })
  167. }
  168. function handleDelete(row) {
  169. proxy.$modal.confirm('是否确认删除?').then(function () {
  170. return deleteContainer(row.id);
  171. }).then(() => {
  172. getModelListTable();
  173. proxy.$modal.msgSuccess("删除成功");
  174. }).catch(() => {
  175. });
  176. }
  177. const dialogVisible = ref(false)
  178. const dialogTitle = ref('新建容器')
  179. const imageList = ref([])
  180. const formRef = ref();
  181. const form = ref({
  182. name: '',
  183. imageName: '',
  184. publishedPorts: '',
  185. });
  186. const rulesJi = reactive({
  187. name: [
  188. {required: true, message: '请输入容器名称', trigger: 'blur'},
  189. {
  190. pattern: /^[a-z][a-z0-9_-]{3,49}$/,
  191. message: '名称格式不正确,以字母开头,允许使用小写字母、数字、下划线和连字符。',
  192. trigger: 'blur'
  193. },
  194. ],
  195. imageName: [{required: true, message: '必填', trigger: 'blur'}],
  196. });
  197. const heightAll = window.innerHeight
  198. async function submit() {
  199. formRef.value.validate((valid) => {
  200. if (valid) {
  201. if (!form.value.id) {
  202. let [imageName, tag] = form.value.imageName.split(':');
  203. let image = imageList.value.find(item => item.imageName === imageName && item.tag === tag);
  204. if (image) {
  205. form.value.imageId = image.imageId
  206. }
  207. }
  208. saveModelContainer(form.value).then(() => {
  209. proxy.$modal.msgSuccess("保存成功");
  210. dialogVisible.value = false
  211. getModelListTable()
  212. })
  213. }
  214. })
  215. }
  216. function clearForm() {
  217. form.value = {
  218. name: '',
  219. imageName: '',
  220. publishedPorts: ''
  221. }
  222. }
  223. function reg() {
  224. dialogVisible.value = true
  225. }
  226. function getModelListTable() {
  227. getModelContainerPageList(queryParams.value).then(res => {
  228. tableData.value = res.rows
  229. total.value = res.total
  230. })
  231. }
  232. function getImageList() {
  233. getModelContainerImageList().then(res => {
  234. imageList.value = res.data
  235. })
  236. }
  237. getImageList()
  238. onMounted(() => {
  239. getModelListTable()
  240. });
  241. </script>
  242. <style scoped>
  243. .pagination-container {
  244. margin-top: 15px;
  245. }
  246. .type-container {
  247. padding-right: 10px;
  248. .type-item {
  249. padding: 10px;
  250. border-radius: 10px;
  251. text-align: center;
  252. background-color: #fff;
  253. color: #409EFF;
  254. cursor: pointer;
  255. &.active {
  256. background-color: #409EFF;
  257. color: #fff;
  258. }
  259. }
  260. }
  261. :deep(.treeLeft) .el-tree-node__content {
  262. display: flex !important;
  263. height: 28px; /* 按设计稿调整高度 */
  264. align-items: center;
  265. padding-top: 0 !important;
  266. }
  267. :deep(.treeLeft) .el-tree-node__content:hover {
  268. background-color: #e9e9eb;
  269. }
  270. :deep(.treeLeft) .el-tree-node__content:active {
  271. background-color: rgka(69, 157, 255, 0.1) !important;
  272. }
  273. /* 选中态(Active) */
  274. :deep(.treeLeft) .el-tree-node.is-current > .el-tree-node__content {
  275. background-color: #c6e2ff !important;
  276. }
  277. .tabs-wrapper {
  278. position: relative; /* 确保内容层在伪元素上方 */
  279. z-index: 1; /* 关键:高于背景图 */
  280. }
  281. .tabs-wrapper :deep(.el-tabs),
  282. .tabs-wrapper :deep(.el-tabs__content) {
  283. background-color: red !important;
  284. }
  285. :deep(.el-tabs) {
  286. background-color: transparent !important;
  287. }
  288. :deep(.el-tabs__content) {
  289. background-color: transparent !important;
  290. }
  291. :deep(.custom-dialog-bg) {
  292. z-index: 1000;
  293. background-image: url('@/assets/images/backDia.jpg') !important;
  294. background-position-x: left;
  295. background-position-y: bottom;
  296. background-size: initial;
  297. background-repeat: repeat-x;
  298. background-attachment: initial;
  299. background-origin: initial;
  300. background-clip: initial;
  301. background-color: rgb(255, 255, 255);
  302. }
  303. :deep(.custom-dialog-bg .el-dialog__header) {
  304. }
  305. :deep(.custom-dialog-bg .el-dialog__body) {
  306. color: #ecf0f1 !important;; /* 内容文字颜色 */
  307. }
  308. /* 横向排列单选框标签和输入框 */
  309. .custom-input-wrapper {
  310. display: flex;
  311. align-items: center;
  312. gap: 10px; /* 调整间距 */
  313. }
  314. /* 输入框仅显示底部横线 */
  315. .underline-input :deep(.el-input__wrapper) {
  316. padding: 0;
  317. box-shadow: none !important;
  318. border-bottom: 1px solid #dcdfe6; /* 横线颜色 */
  319. border-radius: 0;
  320. background: transparent;
  321. }
  322. .underline-input :deep(.el-input__inner) {
  323. height: 24px;
  324. padding: 0 5px;
  325. }
  326. :deep(.el-table__body tr:hover > td) {
  327. background-color: #eaf7ff !important;
  328. }
  329. .drag-handle {
  330. cursor: move;
  331. }
  332. .ghost {
  333. opacity: 0.5;
  334. background: #c8ebfb;
  335. }
  336. /* 防止文字选中 */
  337. :deep(.el-table__row) {
  338. user-select: none;
  339. -webkit-user-select: none;
  340. }
  341. </style>
  342. <style scoped lang="scss">
  343. .el-table .el-table__row td {
  344. height: 60px !important; /* 行高 */
  345. }
  346. .custom-tree-node {
  347. display: flex; /* 启用 Flex 布局 */
  348. align-items: center; /* 垂直居中 */
  349. gap: 8px; /* 图标与文字间距 */
  350. }
  351. :deep(.svg-icon) {
  352. outline: none;
  353. }
  354. :deep(.svg-icon svg) {
  355. stroke: none;
  356. }
  357. </style>