From d636050aaca229e4f4ef576c2b9059a1f51d7b2e Mon Sep 17 00:00:00 2001 From: wlkjyy Date: Tue, 15 Jul 2025 18:02:29 +0800 Subject: [PATCH] ACS --- .cursorrule | 196 +++ README.md | 82 +- components.md | 115 ++ index.html | 10 +- package.json | 8 +- pnpm-lock.yaml | 380 +++- public/logo.svg | 6 + src/App.vue | 121 +- src/api/domain.js | 25 + src/api/login.js | 10 + src/api/ticket.js | 70 + src/assets/404.svg | 31 + src/assets/logo.png | Bin 0 -> 23115 bytes src/assets/logo.svg | 6 + src/components/Charts/BarChart.vue | 109 ++ src/components/Charts/BaseEChart.vue | 88 + src/components/Charts/LineChart.vue | 109 ++ src/components/Charts/PieChart.vue | 88 + src/components/Container.vue | 17 + src/components/HelloWorld.vue | 43 - src/components/Qrcode.vue | 102 ++ src/components/TextTruncate.vue | 47 + src/components/layout/AdminLayout.vue | 282 +++ src/components/layout/Breadcrumb.vue | 125 ++ src/components/layout/SidebarMenuItem.vue | 103 ++ src/components/layout/TagsView.vue | 366 ++++ src/config/menus.js | 49 + src/main.js | 11 +- src/router/index.js | 220 ++- src/store/userStore.js | 14 + src/style.css | 135 ++ src/utils/acs/audit.js | 23 + src/utils/acs/message.js | 116 ++ src/utils/acs/mirror.js | 93 + src/utils/acs/order.js | 25 + src/utils/acs/pay.js | 32 + src/utils/acs/server.js | 455 +++++ src/utils/acs/setting.js | 40 + src/utils/acs/user-set.js | 45 + src/utils/acs/user.js | 501 ++++++ src/utils/acs/virtual.js | 171 ++ src/utils/hide.js | 51 + src/utils/request.js | 80 +- src/views/Login.vue | 409 +++++ src/views/NotFound.vue | 65 +- src/views/Redirect.vue | 21 + src/views/acs/images/ContainerImages.vue | 1281 ++++++++++++++ src/views/acs/images/ImageCategories.vue | 708 ++++++++ src/views/acs/images/ImageRequests.vue | 728 ++++++++ src/views/acs/images/VmImages.vue | 904 ++++++++++ src/views/acs/messages/Announcements.vue | 423 +++++ src/views/acs/messages/News.vue | 490 ++++++ src/views/acs/messages/Policies.vue | 498 ++++++ src/views/acs/nodes/Nodes.vue | 1036 +++++++++++ src/views/acs/nodes/components/VmList.vue | 314 ++++ .../acs/nodes/components/serverChart.vue | 340 ++++ src/views/acs/nodes/server.vue | 1373 +++++++++++++++ src/views/dashboard/Dashboard.vue | 892 ++++++++++ src/views/profile/ChangePassword.vue | 525 ++++++ src/views/profile/UserInfo.vue | 529 ++++++ src/views/system/DomainWhitelist.vue | 309 ++++ src/views/system/OperationLog.vue | 395 +++++ src/views/system/Users.vue | 578 ++++++ src/views/ticket/TicketChat.vue | 1561 +++++++++++++++++ vite.config.js | 9 +- 65 files changed, 17885 insertions(+), 103 deletions(-) create mode 100644 .cursorrule create mode 100644 components.md create mode 100644 public/logo.svg create mode 100644 src/api/domain.js create mode 100644 src/api/login.js create mode 100644 src/api/ticket.js create mode 100644 src/assets/404.svg create mode 100644 src/assets/logo.png create mode 100644 src/assets/logo.svg create mode 100644 src/components/Charts/BarChart.vue create mode 100644 src/components/Charts/BaseEChart.vue create mode 100644 src/components/Charts/LineChart.vue create mode 100644 src/components/Charts/PieChart.vue create mode 100644 src/components/Container.vue delete mode 100644 src/components/HelloWorld.vue create mode 100644 src/components/Qrcode.vue create mode 100644 src/components/TextTruncate.vue create mode 100644 src/components/layout/AdminLayout.vue create mode 100644 src/components/layout/Breadcrumb.vue create mode 100644 src/components/layout/SidebarMenuItem.vue create mode 100644 src/components/layout/TagsView.vue create mode 100644 src/config/menus.js create mode 100644 src/store/userStore.js create mode 100644 src/utils/acs/audit.js create mode 100644 src/utils/acs/message.js create mode 100644 src/utils/acs/mirror.js create mode 100644 src/utils/acs/order.js create mode 100644 src/utils/acs/pay.js create mode 100644 src/utils/acs/server.js create mode 100644 src/utils/acs/setting.js create mode 100644 src/utils/acs/user-set.js create mode 100644 src/utils/acs/user.js create mode 100644 src/utils/acs/virtual.js create mode 100644 src/utils/hide.js create mode 100644 src/views/Login.vue create mode 100644 src/views/Redirect.vue create mode 100644 src/views/acs/images/ContainerImages.vue create mode 100644 src/views/acs/images/ImageCategories.vue create mode 100644 src/views/acs/images/ImageRequests.vue create mode 100644 src/views/acs/images/VmImages.vue create mode 100644 src/views/acs/messages/Announcements.vue create mode 100644 src/views/acs/messages/News.vue create mode 100644 src/views/acs/messages/Policies.vue create mode 100644 src/views/acs/nodes/Nodes.vue create mode 100644 src/views/acs/nodes/components/VmList.vue create mode 100644 src/views/acs/nodes/components/serverChart.vue create mode 100644 src/views/acs/nodes/server.vue create mode 100644 src/views/dashboard/Dashboard.vue create mode 100644 src/views/profile/ChangePassword.vue create mode 100644 src/views/profile/UserInfo.vue create mode 100644 src/views/system/DomainWhitelist.vue create mode 100644 src/views/system/OperationLog.vue create mode 100644 src/views/system/Users.vue create mode 100644 src/views/ticket/TicketChat.vue diff --git a/.cursorrule b/.cursorrule new file mode 100644 index 0000000..c5360b4 --- /dev/null +++ b/.cursorrule @@ -0,0 +1,196 @@ +007 Admin UI项目,基于elementplus+vue3+vite+vue-router进行开发的企业级高端后台管理系统UI。 + +使用pnpm包管理器。 + +组件封装到src/components目录下。 +路由封装到src/router目录下。 +api封装到src/api目录下。 +store封装到src/store目录下。 +页面封装到src/views目录下。 + +当你实现一个通用组件后,应该在compoents.md文件中记录下来,记录组件名称、参数、事件、使用方法。 + +请保持整体风格样式统一,也就是说在实现一个页面或者组件之前你需要先阅读其他已经实现的页面。 +在没有实现具体页面之前禁止注册路由。 +在需要的情况下你需要注册侧边栏 +请使用vue3 setup语法糖开发,禁止使用scss。禁止使用ts。 + +注册侧边栏在/config/menus.js文件中。 + + + +## 1. 基础布局规范 +```css +/* 页面容器 */ +.page-container { + padding: 0; +} + +/* 筛选容器 */ +.filter-container { + margin-bottom: 20px; + border-radius: 8px; + box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05); +} + +/* 搜索表单 */ +.search-form { + margin-bottom: 15px; +} + +/* 操作栏 */ +.action-bar { + margin-top: 10px; + display: flex; + flex-wrap: wrap; + gap: 12px; +} + +/* 表格容器 */ +.table-container { + border-radius: 8px; + box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05); +} +``` + +## 2. 表格样式规范 +```css +/* 表格基础样式 */ +:deep(.el-table) { + border-radius: 8px; + overflow: hidden; + background: transparent; +} + +/* 表头样式 */ +:deep(.el-table th) { + background-color: #f8f9fb !important; + font-weight: 600; + color: #1f2937; + height: 50px; + padding: 8px 0; +} + +/* 单元格样式 */ +:deep(.el-table td) { + padding: 12px 0; +} + +/* 斑马纹样式 */ +:deep(.el-table--striped .el-table__body tr.el-table__row--striped td) { + background: #f8fafc; +} + +/* 行悬停效果 */ +:deep(.el-table__body tr:hover > td) { + background-color: #f1f5f9 !important; +} + +/* 行过渡动画 */ +:deep(.el-table__body tr) { + transition: all 0.3s ease; +} +``` + +## 3. 分页样式规范 +```css +/* 分页容器 */ +.pagination { + margin-top: 24px; + justify-content: flex-end; + padding: 0 16px; +} + +/* 分页基础样式 */ +:deep(.el-pagination) { + --el-pagination-hover-color: #1f2937; +} + +/* 禁用按钮样式 */ +:deep(.el-pagination button:disabled) { + background-color: #f1f5f9; +} + +/* 页码样式 */ +:deep(.el-pagination .el-pager li) { + border-radius: 4px; + margin: 0 2px; + transition: all 0.3s ease; +} + +/* 激活页码样式 */ +:deep(.el-pagination .el-pager li.active) { + background-color: #1f2937; + color: white; + font-weight: bold; +} + +/* 页码悬停效果 */ +:deep(.el-pagination .el-pager li:hover:not(.active)) { + background-color: #f1f5f9; +} +``` + +## 4. 对话框样式规范 +```css +/* 对话框底部 */ +.dialog-footer { + display: flex; + justify-content: flex-end; + gap: 12px; +} +``` + +## 5. 响应式设计规范 +```css +/* 移动端适配 */ +@media (max-width: 768px) { + .el-form-item { + margin-bottom: 12px; + } + + .action-bar { + flex-direction: column; + align-items: stretch; + } + + .action-bar .el-button { + width: 100%; + margin-left: 0 !important; + } + + :deep(.el-table th) { + padding: 6px 0; + } + + :deep(.el-table td) { + padding: 8px 0; + } +} +``` + +## 6. 颜色规范 +- 主色:#1f2937 +- 背景色:#f8f9fb +- 悬停色:#f1f5f9 +- 文字主色:#1f2937 +- 文字次色:#64748b + +## 7. 间距规范 +- 页面内边距:0 +- 卡片间距:20px +- 表单间距:15px +- 按钮间距:12px +- 表格内边距:8px-12px + +## 8. 圆角规范 +- 卡片圆角:8px +- 按钮圆角:4px +- 表格圆角:8px + +## 9. 阴影规范 +- 卡片阴影:0 2px 12px 0 rgba(0, 0, 0, 0.05) + +## 10. 动画规范 +- 过渡时间:0.3s +- 过渡效果:ease \ No newline at end of file diff --git a/README.md b/README.md index 9224482..52e6d73 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,81 @@ -# Vue 3 + Vite + ElementPlus +# 007UI 后台管理系统 -This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 ` \ No newline at end of file + +// 设置不同深度的主题色 +for (let i = 1; i <= 9; i++) { + useCssVar(`--el-color-primary-light-${i}`, document.documentElement).value = + i <= 5 + ? `rgba(24, 144, 255, ${1 - i * 0.1})` + : '#f0f9ff' + + if (i <= 2) { + useCssVar(`--el-color-primary-dark-${i}`, document.documentElement).value = + generateDarkColor(primaryColor, i) + } +} + + + \ No newline at end of file diff --git a/src/api/domain.js b/src/api/domain.js new file mode 100644 index 0000000..e6cddcd --- /dev/null +++ b/src/api/domain.js @@ -0,0 +1,25 @@ +import request from "@/utils/request.js"; + +// 获取域名白名单列表 +export function getDomainList(params) { + return request.get("/api/v1/admin/server/domain_withe/list",params) +} + +// 添加域名白名单 +export function addDomain(data) { + return request.post("/api/v1/admin/server/domain_withe/add",data) +} + +// 删除域名白名单 +export function deleteDomain(id) { + return request.post("/api/v1/admin/server/domain_withe/delete",{domain_id: id}) +} + +// 批量删除域名白名单 +export async function batchDeleteDomain(ids) { + let promises = [] + for (let id of ids) { + promises.push(deleteDomain(id)) + } + return await Promise.all(promises) +} \ No newline at end of file diff --git a/src/api/login.js b/src/api/login.js new file mode 100644 index 0000000..dccf8fd --- /dev/null +++ b/src/api/login.js @@ -0,0 +1,10 @@ +import request from "@/utils/request.js"; + + +export const userLogin = (username,password) => { + return request.post("/api/v1/user/login",{username,password}) +} + +export const getUserInfo = () => { + return request.get("/api/v1/users/info/info") +} \ No newline at end of file diff --git a/src/api/ticket.js b/src/api/ticket.js new file mode 100644 index 0000000..dfc61f5 --- /dev/null +++ b/src/api/ticket.js @@ -0,0 +1,70 @@ +import request from "@/utils/request.js"; +/** + * 获取工单列表 + * @param {Object} params 查询参数 + * @returns {Promise} + */ + +export function getTickerList(count, page, status) { + return request.get('/api/v1/admin/work_order/list', { count, page, status }) +} + +// 待处理 +export function getPendingTicketList(count, page) { + return getTickerList(count,page,0) +} + +// 进行中 +export function getProcessingTicketList(count, page) { + return getTickerList(count,page,1) +} + +//已回复 +export function getRepliedTicketList(count, page) { + return getTickerList(count,page,2) +} + +// 已解决 +export function getCompletedTicketList(count, page) { + return getTickerList(count,page,3) +} + +// 获取详情 +export function getTicketDetail(work_id) { + return request.get('/api/v1/admin/work_order/detail', { work_id }) +} + +// 回复 +export function replyTicket(work_id, content, files) { + return request.post('/api/v1/admin/work_order/reply', { work_id, content, files }) +} + +// 关闭工单 +export function closeTicket(work_id) { + return request.post('/api/v1/admin/work_order/close', { work_id }) +} + +export function getFile(file_id) { + return request.get('/api/v1/tool/file/down', { file_id }) +} + +// 获取用户头像 +export function getUserAvatar(user_id) { + // TODO: 实现获取用户头像的逻辑 + return `https://avatar.example.com/${user_id}` +} + +// 获取文件图片 +export async function getFileImage(file_id) { + let resp = await getFile(file_id) + console.log(resp.data.content) + return resp.data.content +} + +// 解析多个文件ID为图片URL数组 +export async function parseFilesToImages(files) { + if (!files || files === '') return [] + + const fileIds = files.split(',') + return await Promise.all(fileIds.map(async (id) => await getFileImage(id.trim()))) +} \ No newline at end of file diff --git a/src/assets/404.svg b/src/assets/404.svg new file mode 100644 index 0000000..151434a --- /dev/null +++ b/src/assets/404.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + 404 + + + + + + + + \ No newline at end of file diff --git a/src/assets/logo.png b/src/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..6bc8dad1ff7dcb55f8f19e86763e49d398da9c19 GIT binary patch literal 23115 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�!PAk)4_p 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 literal 0 HcmV?d00001 diff --git a/src/assets/logo.svg b/src/assets/logo.svg new file mode 100644 index 0000000..c5d1427 --- /dev/null +++ b/src/assets/logo.svg @@ -0,0 +1,6 @@ + + + + 007 + UI + \ No newline at end of file diff --git a/src/components/Charts/BarChart.vue b/src/components/Charts/BarChart.vue new file mode 100644 index 0000000..c3ca8b2 --- /dev/null +++ b/src/components/Charts/BarChart.vue @@ -0,0 +1,109 @@ + + + \ No newline at end of file diff --git a/src/components/Charts/BaseEChart.vue b/src/components/Charts/BaseEChart.vue new file mode 100644 index 0000000..4096b8c --- /dev/null +++ b/src/components/Charts/BaseEChart.vue @@ -0,0 +1,88 @@ + + + + + \ No newline at end of file diff --git a/src/components/Charts/LineChart.vue b/src/components/Charts/LineChart.vue new file mode 100644 index 0000000..fb73b29 --- /dev/null +++ b/src/components/Charts/LineChart.vue @@ -0,0 +1,109 @@ + + + \ No newline at end of file diff --git a/src/components/Charts/PieChart.vue b/src/components/Charts/PieChart.vue new file mode 100644 index 0000000..39030e1 --- /dev/null +++ b/src/components/Charts/PieChart.vue @@ -0,0 +1,88 @@ + + + \ No newline at end of file diff --git a/src/components/Container.vue b/src/components/Container.vue new file mode 100644 index 0000000..1673b63 --- /dev/null +++ b/src/components/Container.vue @@ -0,0 +1,17 @@ + + + + + \ No newline at end of file diff --git a/src/components/HelloWorld.vue b/src/components/HelloWorld.vue deleted file mode 100644 index 546ebbc..0000000 --- a/src/components/HelloWorld.vue +++ /dev/null @@ -1,43 +0,0 @@ - - - - - diff --git a/src/components/Qrcode.vue b/src/components/Qrcode.vue new file mode 100644 index 0000000..322ce61 --- /dev/null +++ b/src/components/Qrcode.vue @@ -0,0 +1,102 @@ + + + + + \ No newline at end of file diff --git a/src/components/TextTruncate.vue b/src/components/TextTruncate.vue new file mode 100644 index 0000000..982ea3e --- /dev/null +++ b/src/components/TextTruncate.vue @@ -0,0 +1,47 @@ + + + + + + \ No newline at end of file diff --git a/src/components/layout/AdminLayout.vue b/src/components/layout/AdminLayout.vue new file mode 100644 index 0000000..f983e31 --- /dev/null +++ b/src/components/layout/AdminLayout.vue @@ -0,0 +1,282 @@ + + + + + \ No newline at end of file diff --git a/src/components/layout/Breadcrumb.vue b/src/components/layout/Breadcrumb.vue new file mode 100644 index 0000000..639ffa3 --- /dev/null +++ b/src/components/layout/Breadcrumb.vue @@ -0,0 +1,125 @@ + + + + + \ No newline at end of file diff --git a/src/components/layout/SidebarMenuItem.vue b/src/components/layout/SidebarMenuItem.vue new file mode 100644 index 0000000..a88325d --- /dev/null +++ b/src/components/layout/SidebarMenuItem.vue @@ -0,0 +1,103 @@ + + + + + \ No newline at end of file diff --git a/src/components/layout/TagsView.vue b/src/components/layout/TagsView.vue new file mode 100644 index 0000000..d943a4c --- /dev/null +++ b/src/components/layout/TagsView.vue @@ -0,0 +1,366 @@ + + + + + \ No newline at end of file diff --git a/src/config/menus.js b/src/config/menus.js new file mode 100644 index 0000000..9ed7679 --- /dev/null +++ b/src/config/menus.js @@ -0,0 +1,49 @@ +export const menus = [ + { + path: '/dashboard', + title: '仪表盘', + icon: 'DataBoard' + }, + { + path : '/ticket', + title: '工单处理', + icon: 'DataBoard' + + }, + { + path: '/acs', + title: 'ACS管理', + icon: 'Monitor', + children: [ + { + path: '/acs/messages', + title: '消息管理', + children: [ + { path: '/acs/messages/announcements', title: '官方公告' }, + { path: '/acs/messages/policies', title: '官方政策' }, + { path: '/acs/messages/news', title: '新闻咨询' } + ] + }, + { + path: '/acs/images', + title: '镜像管理', + children: [ + { path: '/acs/images/vm', title: '虚拟机镜像' }, + { path: '/acs/images/container', title: '容器镜像' }, + { path: '/acs/images/categories', title: '镜像分类' } + ] + }, + { path: '/acs/nodes', title: '节点管理' } + ] + }, + { + path: '/system', + title: '系统管理', + icon: 'Setting', + children: [ + // { path: '/system/users', title: '用户管理' }, + // { path: '/system/operation-log', title: '操作日志' }, + { path: '/system/domain-whitelist', title: '域名白名单' } + ] + } +] \ No newline at end of file diff --git a/src/main.js b/src/main.js index b88abf1..3de95d3 100644 --- a/src/main.js +++ b/src/main.js @@ -2,10 +2,19 @@ import { createApp } from 'vue' import App from './App.vue' import router from './router' import ElementPlus from 'element-plus' +import * as ElementPlusIconsVue from '@element-plus/icons-vue' import 'element-plus/dist/index.css' import './style.css' +import {createPinia} from "pinia"; const app = createApp(App) + +// 注册所有图标 +for (const [key, component] of Object.entries(ElementPlusIconsVue)) { + app.component(key, component) +} +const pinia = createPinia() app.use(router) app.use(ElementPlus) -app.mount('#app') +app.use(pinia) +app.mount('#app') \ No newline at end of file diff --git a/src/router/index.js b/src/router/index.js index 172df83..7ecce2f 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,20 +1,217 @@ import { createRouter, createWebHistory } from 'vue-router' -import Home from '../views/Home.vue' +import AdminLayout from '../components/layout/AdminLayout.vue' +import OperationLog from '@/views/system/OperationLog.vue' const routes = [ { path: '/', - name: 'Home', - component: Home, + redirect: '/dashboard' + }, + { + path: '/redirect', + component: AdminLayout, + hidden: true, + children: [ + { + path: '/redirect/:path(.*)', + component: () => import('../views/Redirect.vue'), + meta: { title: '重定向' } + } + ] + }, + { + path: '/', + component: AdminLayout, + children: [ + { + path: 'dashboard', + name: 'Dashboard', + component: () => import('../views/dashboard/Dashboard.vue'), + meta: { + title: '仪表盘', + icon: 'DataBoard' + } + }, + { + path: 'ticket', + name: 'Ticket', + meta: { + title: '工单管理', + icon: 'Tickets' + }, + component: () => import('../views/ticket/TicketChat.vue'), + }, + // ACS管理路由 + { + path: 'acs', + name: 'ACS', + meta: { + title: 'ACS管理', + icon: 'Monitor' + }, + redirect: '/acs/messages/announcements', + children: [ + // 消息管理路由 + { + path: 'messages', + name: 'Messages', + meta: { + title: '消息管理' + }, + redirect: '/acs/messages/announcements', + children: [ + { + path: 'announcements', + name: 'Announcements', + component: () => import('../views/acs/messages/Announcements.vue'), + meta: { + title: '官方公告' + } + }, + { + path: 'policies', + name: 'Policies', + component: () => import('../views/acs/messages/Policies.vue'), + meta: { + title: '官方政策' + } + }, + { + path: 'news', + name: 'News', + component: () => import('../views/acs/messages/News.vue'), + meta: { + title: '新闻咨询' + } + } + ] + }, + // 镜像管理路由 + { + path: 'images', + name: 'Images', + meta: { + title: '镜像管理' + }, + redirect: '/acs/images/vm', + children: [ + { + path: 'vm', + name: 'VmImages', + component: () => import('../views/acs/images/VmImages.vue'), + meta: { + title: '虚拟机镜像' + } + }, + { + path: 'container', + name: 'ContainerImages', + component: () => import('../views/acs/images/ContainerImages.vue'), + meta: { + title: '容器镜像' + } + }, + { + path: 'categories', + name: 'ImageCategories', + component: () => import('../views/acs/images/ImageCategories.vue'), + meta: { + title: '镜像分类' + } + } + ] + }, + // 节点管理路由 + { + path: 'nodes', + name: 'Nodes', + component: () => import('../views/acs/nodes/Nodes.vue'), + meta: { + title: '节点管理' + } + } + ] + }, + { + path: 'system', + name: 'System', + meta: { + title: '系统管理', + icon: 'Setting' + }, + redirect: '/system/users', + children: [ + { + path: 'users', + name: 'Users', + component: () => import('../views/system/Users.vue'), + meta: { + title: '用户管理' + } + }, + { + path: 'operation-log', + name: 'OperationLog', + component: OperationLog, + meta: { title: '操作日志' } + }, + { + path: 'domain-whitelist', + name: 'DomainWhitelist', + component: () => import('../views/system/DomainWhitelist.vue'), + meta: { title: '域名白名单' } + } + ] + }, + // 个人中心路由 + { + path: 'profile', + name: 'Profile', + component: () => import('../views/profile/UserInfo.vue'), + meta: { + title: '个人信息', + hidden: true + } + }, + // 修改密码路由 + { + path: 'change-password', + name: 'ChangePassword', + component: () => import('../views/profile/ChangePassword.vue'), + meta: { + title: '修改密码', + hidden: true + } + }, + // 服务器详情页面路由 + { + path: 'servers/server', + name: 'ServerDetail', + component: () => import('../views/acs/nodes/server.vue'), + meta: { + title: '服务器详情', + hidden: true + } + } + ] + }, + // 登录页 + { + path: '/login', + name: 'Login', + component: () => import('../views/Login.vue'), meta: { - title: '首页' + title: '登录' } }, // 404 页面 { path: '/:pathMatch(.*)*', name: 'NotFound', - component: () => import('../views/NotFound.vue') + component: () => import('../views/NotFound.vue'), + meta: { + title: '页面不存在' + } } ] @@ -26,12 +223,19 @@ const router = createRouter({ // 全局前置守卫 router.beforeEach((to, from, next) => { // 设置页面标题 - document.title = to.meta.title || '默认标题' - next() + document.title = to.meta.title ? `${to.meta.title} - 007UI管理系统` : '007UI管理系统' + + // 这里可以添加登录验证逻辑 + const isAuthenticated = localStorage.getItem('token') + if (to.path !== '/login' && !isAuthenticated) { + next({ path: '/login' }) + } else { + next() + } }) // 全局后置钩子 -router.afterEach((to, from) => { +router.afterEach(() => { window.scrollTo(0, 0) }) diff --git a/src/store/userStore.js b/src/store/userStore.js new file mode 100644 index 0000000..87f521f --- /dev/null +++ b/src/store/userStore.js @@ -0,0 +1,14 @@ +import {defineStore} from "pinia"; +import {ref} from "vue"; + + +export const useUserStore = defineStore('userStore',() => { + + let userInfo = ref({}) + + function setUserInfo(u){ + userInfo.value = u + } + + return {userInfo,setUserInfo} +}) \ No newline at end of file diff --git a/src/style.css b/src/style.css index cca57b3..222de0c 100644 --- a/src/style.css +++ b/src/style.css @@ -1,5 +1,140 @@ +/* 全局样式 */ * { margin: 0; padding: 0; box-sizing: border-box; } + +html, body { + height: 100%; + width: 100%; +} + +body { + font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-size: 14px; + color: #303133; + background-color: #f5f7fa; +} + +#app { + height: 100%; +} + +/* 常用工具类 */ +.text-primary { + color: #1890ff; +} + +.text-success { + color: #52c41a; +} + +.text-warning { + color: #faad14; +} + +.text-danger { + color: #f5222d; +} + +.text-info { + color: #909399; +} + +.text-center { + text-align: center; +} + +.text-left { + text-align: left; +} + +.text-right { + text-align: right; +} + +.flex { + display: flex; +} + +.flex-column { + flex-direction: column; +} + +.justify-center { + justify-content: center; +} + +.justify-between { + justify-content: space-between; +} + +.items-center { + align-items: center; +} + +.w-full { + width: 100%; +} + +.h-full { + height: 100%; +} + +.mb-10 { + margin-bottom: 10px; +} + +.mt-10 { + margin-top: 10px; +} + +.mr-10 { + margin-right: 10px; +} + +.ml-10 { + margin-left: 10px; +} + +.p-10 { + padding: 10px; +} + +.py-10 { + padding-top: 10px; + padding-bottom: 10px; +} + +.px-10 { + padding-left: 10px; + padding-right: 10px; +} + +/* 响应式工具类 */ +@media (max-width: 768px) { + .hidden-xs { + display: none !important; + } +} + +@media (min-width: 768px) and (max-width: 992px) { + .hidden-sm { + display: none !important; + } +} + +@media (min-width: 992px) and (max-width: 1200px) { + .hidden-md { + display: none !important; + } +} + +@media (min-width: 1200px) { + .hidden-lg { + display: none !important; + } +} diff --git a/src/utils/acs/audit.js b/src/utils/acs/audit.js new file mode 100644 index 0000000..dc84395 --- /dev/null +++ b/src/utils/acs/audit.js @@ -0,0 +1,23 @@ +import {http2} from "@/utils/request.js"; + + +/**获取所有站点 */ +export const getSiteList = (data) => { + return http2.get(`/v1/admin/audit/list?page=${data.page}&count=${data.count}&key=${data.key}`) +} +/**手动触发站点审计 */ +export const auditSite = () => { + return http2.get(`/v1/admin/audit/start`) +} +/**删除违规网页审计 */ +export const delAudit = (data) => { + return http2.post(`/v1/admin/audit/delete`,data,{ + headers: { + 'Content-Type': 'multipart/form-data' + } + }) +} +/**获取违规网页审计列表 */ +export const getAuditList = (data) => { + return http2.get(`/v1/admin/audit/violation_list?page=${data.page}&count=${data.count}&key=${data.key}`) +} diff --git a/src/utils/acs/message.js b/src/utils/acs/message.js new file mode 100644 index 0000000..c6f4402 --- /dev/null +++ b/src/utils/acs/message.js @@ -0,0 +1,116 @@ +import {http2} from "@/utils/request.js"; +/**获取消息列表 */ +export const getMessageList = (data) => { + return http2.get(`/v1/messages/get_message_list?page=${data.page}&count=${data.count}&message_type=${data.message_type}`) +} +/**获取单条消息 */ +export const getMessage = (data) => { + return http2.get(`/v1/messages/get_message?message_id=${data}`) +} +/**添加消息 */ +export const addMessage = (data) => { + return http2.post(`/v1/messages/add_message`, data,{ +headers: { +'Content-Type': 'multipart/form-data' +} +}) +} +/**删除消息 */ +export const deleteMessage = (data) => { + return http2.post(`/v1/messages/delete_message`, data,{ +headers: { +'Content-Type': 'multipart/form-data' +} +}) +} +/**修改消息 */ +export const editMessage = (data) => { + return http2.post(`/v1/messages/update_message`, data,{ +headers: { +'Content-Type': 'multipart/form-data' +} +}) +} +/**获取附件列表 */ +export const getFileList = (data) => { + return http2.get(`/v1/attachment/get_attachment_list?page=${data.page}&count=${data.count}&key=${data.key}&user_type=${data.user_type}`) +} +/**上传附件 */ +export const uploadFile = (data) => { + return http2.post(`/v1/attachment/add_attachment`, data,{ +headers: { +'Content-Type': 'multipart/form-data' +} +}) +} +/**删除附件 */ +export const deleteFile = (data) => { + return http2.get(`/v1/attachment/delete_attachment?aid=${data}`) +} +/**用户获取消息列表 */ +export const getUserMessageList = (data) => { + return http2.get(`/v1/messages/get_message_list?page=${data.page}&count=${data.count}&message_type=${data.message_type}`) +} +/**用户获取单条消息 */ +export const getUserMessage = (data) => { + return http2.get(`/v1/messages/get_message?message_id=${data}`) +} + +/**获取消息详情 */ +export const getMessageDetail = (data) => { + return http2.get(`/v1/messages/get_message?message_id=${data.message_id}`) +} +/**修改图片大小 */ +export const compressAndConvertFileToBase64=async(file)=> { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + + reader.onload = function(e) { + const img = new Image(); + img.src = e.target.result; + + img.onload = function() { + const canvas = document.createElement('canvas'); + const MAX_WIDTH = 300; // 压缩的最大宽度 + const MAX_HEIGHT = 200; // 压缩的最大高度 + + let width = img.width; + let height = img.height; + + // 计算压缩比例 + if (width > height) { + if (width > MAX_WIDTH) { + height *= MAX_WIDTH / width; + width = MAX_WIDTH; + } + } else { + if (height > MAX_HEIGHT) { + width *= MAX_HEIGHT / height; + height = MAX_HEIGHT; + } + } + + canvas.width = width; + canvas.height = height; + + const ctx = canvas.getContext('2d'); + ctx.drawImage(img, 0, 0, width, height); + + // 将canvas内容转换为jpeg并压缩质量 + const dataUrl = canvas.toDataURL('image/jpeg', 0.8); // 0.8是压缩质量,范围0-1 + + resolve(dataUrl); + }; + + img.onerror = function(error) { + reject(error); + }; + }; + + reader.onerror = function(error) { + reject(error); + }; + + reader.readAsDataURL(file); + }); +} \ No newline at end of file diff --git a/src/utils/acs/mirror.js b/src/utils/acs/mirror.js new file mode 100644 index 0000000..f5b5c12 --- /dev/null +++ b/src/utils/acs/mirror.js @@ -0,0 +1,93 @@ +import {http2} from "@/utils/request.js"; +/**获取镜像列表 */ +export const getMirrorList = data => { + return http2.get(`/v1/image/list?server_id=${data}`); +}; +/*用户获取镜像列表 */ +export const getUserMirrorList = data => { + return http2.get( + `/v1/image/list?server_id=${data.server_id}&count=${data.count}&page=${data.page}&key=${data.key}` + ); +}; +/**上传镜像 */ +export const uploadMirror = data => { + return http2.post("/v1/image/pull", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**编辑镜像 */ +export const editMirror = data => { + return http2.post("/v1/image/update", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**删除镜像 */ +export const delMirror = data => { + return http2.post("/v1/image/delete", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**镜像同步 */ +export const syncMirror = data => { + return http2.get(`/v1/image/sync?server_id=${data}`); +}; +/**重新拉取镜像 */ +export const pullMirror = data => { + return http2.post(`/v1/image/repull`, data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; + +/**获取镜像信息 */ +export const Mirrorinfo = data => { + const serverType = data.server_type || "dockerContainer"; // 设置默认值 + return http2.get( + `/v1/image/info?image_id=${data.image_id}&server_type=${serverType}` + ); +}; + +export const addVirtualMirror = data => { + return http2.post("/v1/image/create", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; + + +export const getImageTypeList = (server_id) => { + return http2.get(`/v1/image/class_list?server_id=${server_id}`); +}; + +export const createImageType = (server_id,class_name,class_ico) => { + return http2.post("/v1/image/class_create", { + server_id, + class_name, + class_ico + },{ + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; + +export const updateImageType = (class_id,class_name,class_ico) => { + return http2.post("/v1/image/class_update", { + class_id, + class_name, + class_ico + },{ + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; + diff --git a/src/utils/acs/order.js b/src/utils/acs/order.js new file mode 100644 index 0000000..1e37294 --- /dev/null +++ b/src/utils/acs/order.js @@ -0,0 +1,25 @@ +import {http2} from "@/utils/request.js"; +/**获取订单列表 */ +export const getOrderList = (data) => { + return http2.get(`/v1/admin/trades/get_trades?page=${data.page}&count=${data.count}&key=${data.key}`) +} +/**编辑订单 */ +export const editOrder = (data) => { + return http2.post('/v1/admin/trades/update_trades',data,{ +headers: { +'Content-Type': 'multipart/form-data' +} +}) +} +/**删除订单 */ +export const deleteOrder = (data) => { + return http2.post('/v1/admin/trades/delete_trade',data,{ +headers: { +'Content-Type': 'multipart/form-data' +} +}) +} +/**用户获取订单列表 */ +export const getUserOrderList = (data) => { + return http2.get(`/v1/user/procedure/get_trade_list?page=${data.page}&count=${data.count}&key=${data.key}`) +} \ No newline at end of file diff --git a/src/utils/acs/pay.js b/src/utils/acs/pay.js new file mode 100644 index 0000000..8246250 --- /dev/null +++ b/src/utils/acs/pay.js @@ -0,0 +1,32 @@ +import {http2} from "@/utils/request.js"; +/**获取用户列表 */ +export const get_pay_code = data => { + return http2.get( + `https://yun.tdhly.love/submit.php?pid=2&type=${data.type}&out_trade_no={商户订单号}¬ify_url={服务器异步通知地址}&name={商品名称}&money=${data.money}&sign=9vP6xvcc93YYi9c6F3HiY9HFuyZizIxe&sign_type=MD5` + ); +}; +// /**email验证码 */ +// export const ask_update_user_email = data => { +// return http2.post("/v1/user/info/ask_update_user_email", data, { +// headers: { +// "Content-Type": "multipart/form-data" +// } +// }); +// }; +/**获取容器订单金额 */ +export const procedure_get_price = data => { + return http2.post("/v1/user/procedure/get_price", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; + +/**获取虚拟机订单金额 */ +export const procedure_vir_price = data => { + return http2.post("/v1/user/procedure/get_vm_price", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; diff --git a/src/utils/acs/server.js b/src/utils/acs/server.js new file mode 100644 index 0000000..cf06925 --- /dev/null +++ b/src/utils/acs/server.js @@ -0,0 +1,455 @@ +import {http2} from "@/utils/request.js"; + +/** 获取所有服务器 */ +export const getServer = (page, count, key, type = "dockerContainer") => { + return http2.get( + `/v1/admin/server/get_server_list?page=${page}&count=${count}&key=${key}&server_type=${type}` + ); +}; + +/**新增服务器 */ +export const addServer = data => { + return http2.post("/v1/admin/server/add_server", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**编辑服务器 */ +export const editServer = data => { + return http2.post("/v1/admin/server/update_server", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**删除服务器 */ +export const deleteServer = data => { + return http2.post("/v1/admin/server/delete_server", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**查询指定服务器 */ +export const selectServer = data => { + return http2.post("/v1/admin/server/select_server", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**获取服务器套餐列表*/ +export const getServerPlan = data => { + return http2.get( + `/v1/admin/container_plan/get_server_plan_list?server_id=${data.server_id}&count=${data.count}` + ); +}; +/**获取指定套餐 */ +export const selectServerPlan = data => { + return http2.post("/v1/admin/container_plan/get_server_plan_detail", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**修改套餐信息 */ +export const editServerPlan = data => { + return http2.post("/v1/admin/container_plan/update_server_plan", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**新增套餐 */ +export const addServerPlan = data => { + return http2.post("/v1/admin/container_plan/add_server_plan", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**删除套餐 */ +export const deleteServerPlan = data => { + return http2.post("/v1/admin/container_plan/delete_server_plan", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**获取容器列表 */ +export const getContainer = data => { + return http2.get( + `/v1/admin/container/get_container_list?server_id=${data.server_id}&user_id=${data.user_id}&page=${data.page}&count=${data.count}&key=${data.key}` + ); +}; +/**获取单个指定容器 */ +export const getOneContainer = data => { + return http2.post("/v1/admin/container/get_container_detail", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**查询指定虚拟机信息(管理员查询) */ +export const getVmAdminContainer = id => { + return http2.get(`/v1/admin/instance/detail/${id}`); +}; +// 暂停容器 +export const pauseContainer = data => { + return http2.post("/v1/admin/container/pause_container", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +// 暂停虚拟机 +export const pauseInstance = (data, id) => { + return http2.post(`/v1/admin/instance/pause/${id}`, data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**恢复虚拟机 */ +export const unpauseInstance = (id, data = "") => { + return http2.post(`/v1/admin/instance/resume/${id}`, data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; + +// 解除暂停 +export const unpauseContainer = data => { + return http2.post("/v1/admin/container/unpause_container", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**获取容器状态 */ +export const getContainerStatus = data => { + return http2.post("/v1/admin/container/get_container_status", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**获取虚拟机状态 */ +export const getInstanceStatus = id => { + return http2.get(`/v1/admin/instance/get_state/${id}`); +}; +/**查询服务器状态 */ +export const getServerStatus = id => { + return http2.get(`/v1/admin/server/send_server_status?server_id=${id}`); +}; +/**开通容器 */ +export const openContainer = data => { + return http2.post("/v1/admin/container/open_container", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**开通虚拟机 */ +export const openInstance = (id, data = "") => { + return http2.post(`/v1/admin/instance/approve/${id}`, data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**启动容器 */ +export const startContainer = data => { + return http2.post("/v1/admin/container/start_container", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**启动虚拟机 */ +export const startInstance = data => { + return http2.get(`/v1/admin/instance/start/${data}`); +}; +/**重装容器 */ +export const reinstallC = data => { + return http2.post("/v1/admin/container/reinstall_container", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**重装虚拟机 */ +export const reinstallI = (data, id) => { + return http2.post(`/v1/admin/instance/reinstall/${id}`, data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; + +/**获取容器日志 */ +export const getContainerLog = data => { + return http2.post(`/v1/admin/container/get_container_log`, data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**获取虚拟机操作日志 */ +export const getInstanceLog = (id, data) => { + return http2.get( + `/v1/admin/instance/log/${id}?page=${data.page}&count=${data.count}` + ); +}; + +/**重启容器 */ +export const restartContainer = data => { + return http2.post("/v1/admin/container/reboot_container", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**重启虚拟机 */ +export const restartInstance = data => { + return http2.get(`/v1/admin/instance/reboot/${data}`); +}; + +/**停止容器 */ +export const stopContainer = data => { + return http2.post("/v1/admin/container/stop_container", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**停止虚拟机 */ +export const stopInstance = data => { + return http2.get(`/v1/admin/instance/stop/${data}`); +}; + +/**删除容器 */ +export const deleteContainer = data => { + return http2.post("/v1/admin/container/delete_container", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**删除虚拟机 */ +export const deleteInstance = (id, data = "") => { + return http2.post(`/v1/admin/instance/delete/${id}`, data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**清除容器流量 */ +export const clearContainerTraffic = data => { + return http2.post("/v1/admin/container/clear_container_traffic", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**连接控制台 */ +export const connectConsole = data => { + return http2.post("/v1/admin/container/get_container_console", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**获取虚拟机控制台 */ +export const getInstanceConsole = data => { + return http2.get(`/v1/admin/instance/console/${data}`); +}; +/**查询容器所有卷信息 */ +export const getVolumeList = data => { + return http2.get(`/v1/admin/volume/get_volume_list?container_id=${data}`); +}; +/**查询虚拟机所有卷信息 */ +export const getInstanceVolumeList = data => { + return http2.get( + `/v1/admin/volume/get_volume_list?instance_id=${data.instance_id}&page=${data.page}&count=${data.count}` + ); +}; +/**新增卷 */ +export const addVolume = data => { + return http2.post("/v1/admin/volume/add_volume", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**修改卷大小 */ +export const updateVolume = data => { + return http2.post("/v1/admin/volume/update_volume_size", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**删除数据卷 */ +export const deleteVolume = data => { + return http2.post("/v1/admin/volume/delete_volume", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**获取容器网络信息 */ +export const getNetworkList = data => { + return http2.get( + `/v1/container/proxy/get_container_proxy?container_id=${data}` + ); +}; +/**获取虚拟机端口列表 */ +export const getInstancePortList = data => { + const params = new URLSearchParams(); + if (data.page !== undefined) params.append("page", data.page.toString()); + if (data.count !== undefined) params.append("count", data.count.toString()); + if (data.internal_port !== undefined) + params.append("internal_port", data.internal_port.toString()); + return http2.get( + `/v1/admin/instance_port/list?instance_id=${data.instance_id}&${params.toString()}` + ); +}; +/**添加容器网络 */ +export const addNetwork = data => { + return http2.post("/v1/container/proxy/add_container_proxy", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**创建端口 */ +export const addPort = data => { + return http2.post("/v1/admin/instance_port/create", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**获取浮动ip列表 */ +export const getFloatingIpList = data => { + return http2.get( + `/v1/admin/floating_ip/get_list?server_id=${data.server_id}&page=${data.page}&count=${data.count}` + ); +}; +/**新增浮动ip */ +export const addFloatingIp = data => { + return http2.post("/v1/admin/floating_ip/add", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**批量添加浮动ip */ +export const addFloatingIpBatch = data => { + return http2.post("/v1/admin/floating_ip/add_list", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**删除浮动ip */ +export const delFloatingIp = data => { + return http2.post("/v1/admin/floating_ip/delete", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; + +/**获取单个用户操作日志 */ +export const getUserLog = data => { + return http2.get( + `/v1/user/procedure/get_user_log?user_id=${data.user_id}&page=${data.page}&count=${data.count}` + ); +}; + +/**管理员修改头像 */ +export const editAvatar = data => { + return http2.post("/v1/admin/users/upload_user_avatar", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; + +/**获取服务器硬盘信息 */ +export const getDiskInfo = data => { + return http2.get(`/v1/admin/server/get_server_disk?server_id=${data}`); +}; +/**获取服务器实际划分硬盘信息 */ +export const getRealDisk = data => { + return http2.get(`/v1/admin/server/get_server_disk_info?server_id=${data}`); +}; +/**获取服务器流量信息 */ +export const getTraffic = data => { + return http2.get(`/v1/admin/server/get_server_bandwidth?server_id=${data}`); +}; +/**获取版本更新 */ +export const getVersion = () => { + return http2.get(`/v1/admin/version`); +}; + +// 管理员删除https网络 +export const AdminDelHttps = data => { + return http2.post("/v1/container/proxy/del_https_connet", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; + +// 管理员添加https网络 +export const AdminAddHttps = data => { + return http2.post("/v1/container/proxy/add_https_proxy", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**获取指定端口信息 */ +export const getPortInfo = data => { + return http2.get(`/v1/admin/instance_port/detail?port_id=${data}`); +}; +/**新增卷 */ +export const addVolumeMount = data => { + return http2.post("/v1/admin/volume/add_volume", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; + +/**进入救援系统 */ +export const rescueInstance = id => { + return http2.get(`/v1/admin/instance/rescue/enter/${id}`); +}; + +/**退出救援系统 */ +export const exitRescueInstance = id => { + return http2.get(`/v1/admin/instance/rescue/exit/${id}`); +}; + +/**修改虚拟机密码 */ +export const changeInstancePassword = (id, data) => { + return http2.post(`/v1/admin/instance/update_password/${id}`, data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**修改虚拟机密码(用户) */ +export const changeInstancePasswordUser = (id, data) => { + return http2.post(`/v1/user/instance/update_password/${id}`, data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; diff --git a/src/utils/acs/setting.js b/src/utils/acs/setting.js new file mode 100644 index 0000000..1df40b0 --- /dev/null +++ b/src/utils/acs/setting.js @@ -0,0 +1,40 @@ +import {http2} from "@/utils/request.js"; +/**获取全局配置 */ +export const getSetting = () => { + return http2.get('/v1/admin/settings/get_settings') +} +/**变更设置 */ +export const updateSetting = (data) => { + return http2.post('/v1/admin/settings/update_settings', data, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }) +} +/**新增设置 */ +export const addSetting = (data) => { + return http2.post('/v1/admin/settings/add_settings', data, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }) +} +/**删除设置 */ +export const deleteSetting = (data) => { + return http2.post('/v1/admin/settings/delete_settings', data,{ + headers: { + 'Content-Type': 'multipart/form-data' + } + }) +} +/**获取单项配置 */ +export const getOneSetting = (data) => { + return http2.get(`/v1/admin/settings/get_setting?name=${data}`) +} +/**获取多个配置 */ +export const getSettings = (data) => { + // return http2.get(`/v1/admin/settings/get_settings?names=${data}`); + const namesParam = data.join(','); + // 将处理后的namesParam放入URL中 + return http2.get(`/v1/admin/settings/get_setting?names=${encodeURIComponent(namesParam)}`); +} \ No newline at end of file diff --git a/src/utils/acs/user-set.js b/src/utils/acs/user-set.js new file mode 100644 index 0000000..840cdd9 --- /dev/null +++ b/src/utils/acs/user-set.js @@ -0,0 +1,45 @@ +import {http2} from "@/utils/request.js"; +/**获取用户列表 */ +export const ask_update_user_email11 = data => { + return http2.get(`/v1/user/info/ask_update_user_email?email=${data.email}`); +}; +/**email验证码 */ +export const ask_update_user_email = data => { + return http2.post("/v1/user/info/ask_update_user_email", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**email修改 */ +export const update_user_email = data => { + return http2.post("/v1/user/info/update_user_email", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**phone验证码 */ +export const ask_update_user_phone = data => { + return http2.post("/v1/user/info/ask_update_user_phone", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**phone修改 */ +export const update_user_phone = data => { + return http2.post("/v1/user/info/update_user_phone", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**密码修改 */ +export const update_user_password = data => { + return http2.post("/v1/user/info/update_user_password", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; diff --git a/src/utils/acs/user.js b/src/utils/acs/user.js new file mode 100644 index 0000000..e3980e9 --- /dev/null +++ b/src/utils/acs/user.js @@ -0,0 +1,501 @@ + +import {http2} from "@/utils/request.js"; +// import { getUserContainer } from './user'; + +// 获取图像验证码 +export const Captch = data => { + return http2.get(`/v1/user/check/get_code_img`); +}; + +/** 登录 */ +export const getLogin = data => { + return http2.post("/v1/user/login", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; + +// // /** 刷新token */ +// export const refreshTokenApi = (data?: object) => { +// return http.request("post", "/refresh-token", { data }); +// }; + +/**获取用户列表 */ +export const getUserList = data => { + return http2.get( + `/v1/admin/users/get_user_list?page=${data.page}&count=${data.count}&key=${data.key}` + ); +}; +/**添加用户 */ +export const addUser = data => { + return http2.post("/v1/admin/users/add_user", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**编辑用户信息 */ +export const editUser = data => { + return http2.post("/v1/admin/users/update_user", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**修改用户密码 */ +export const editPassword = data => { + return http2.post("/v1/admin/users/update_user_password", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**删除用户 */ +export const deleteUser = data => { + return http2.post("/v1/admin/users/del_user", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**查询单个用户 */ +export const userDetail = data => { + return http2.post("/v1/admin/users/select_user", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**修改用户余额 */ +export const editBalance = data => { + return http2.post("/v1/admin/users/update_user_balance", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**获取用户服务器 */ +export const getUserServer = (data = {}) => { + const serverType = data.server_type || "dockerContainer"; // 设置默认值 + return http2.get( + `/v1/user/procedure/get_server_list?server_type=${serverType}` + ); +}; +/**用户获取虚拟机列表 */ +export const getVirtualList = data => { + let url = `/v1/user/instance/list?page=${data.page}&count=${data.count}`; + if (data.key) { + url += `&key=${data.key}`; + } + if (data.server_id) { + url += `&server_id=${data.server_id}`; + } + return http2.get(url); +}; +/**用户获取服务器套餐 */ +export const getUserPackage = data => { + return http2.get(`/v1/user/procedure/get_server_plan_list?server_id=${data}`); +}; +/**获取用户容器列表 */ +export const getUserContainer = data => { + return http2.get( + `/v1/user/container/list?page=${data.page}&count=${data.count}` + ); +}; +/**用户按地区获取容器 */ +export const getUserContainerD = data => { + return http2.get( + `/v1/user/container/list?page=${data.page}&count=${data.count}&server_id=${data.server_id}` + ); +}; +/**获取用户操作日志 */ +export const get_user_log = () => { + return http2.get(`/v1/user/procedure/get_user_log`); +}; +/**获取用户自身信息 */ +export const getUserInfo = () => { + return http2.get(`/v1/user/procedure/get_user_info`); +}; +/**获取用户自身信息 */ +export const getUserInfoV1 = () => { + return http2.get(`/v1/user/info/get_user_info`); +}; +/**用户实名 */ +export const realName = data => { + return http2.post("/v1/external/real_name", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**发送手机验证码 */ +export const sendCode = data => { + return http2.post("/v1/external/send_message", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**发送邮箱验证码 */ +export const sendEmailCode = data => { + return http2.post("/v1/external/send_email", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**用户注册 */ +export const register = data => { + return http2.post("/v1/user/register", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**手机号修改校证码 */ +export const CodePhone = data => { + return http2.post("/v1/user/info/ask_update_user_phone", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**修改手机号码 */ +export const SetPhone = data => { + return http2.post("/v1/user/info/update_user_phone", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**邮箱修改校证码 */ +export const CodeEmail = data => { + return http2.post("/v1/user/info/ask_update_user_email", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**修改邮箱 */ +export const SetEmail = data => { + return http2.post("/v1/user/info/update_user_email", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**上传头像 */ +export const uploadAvatar = data => { + return http2.post("/v1/user/info/upload_user_avatar", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**手机号忘记密码 */ +export const forgetphone = data => { + return http2.post("/v1/user/info/forget_user_password_phone", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; + +/**邮箱忘记密码 */ +export const forgetemail = data => { + return http2.post("/v1/user/info/forget_user_password_email", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**管理员全局搜索 */ +export const Find = data => { + return http2.post("/v1/admin/search", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +// 管理员删除容器网络 +export const delContainer = data => { + return http2.post("/v1/user/container/delete_connect", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +// 删除端口 +export const delPort = data => { + return http2.post("/v1/admin/instance_port/delete", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +// 自定义容器价格 +export const Containerpay = data => { + return http2.post("/v1/admin/container/update_container_price", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +// 修改虚拟机续费价格 +export const Containerpaytime = (data, id) => { + return http2.post(`/v1/admin/instance/update_price/${id}`, data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +// 自定义容器到期时间 +export const Containertime = data => { + return http2.post("/v1/admin/container/update_container_expire_time", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +// 修改虚拟机续到期时间 +export const Containertimetime = (data, id) => { + return http2.post(`/v1/admin/instance/update_expire_time/${id}`, data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +// 修改虚拟机信息 +export const editContainer = (data, id) => { + return http2.post(`/v1/admin/instance/update/${id}`, data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; + +/***************************************容器管理 ************************/ + +/** 容器操作 */ +export const startUserContainer = (type, id) => { + return http2.post( + "/v1/user/container/" + type, + { + container_id: id + }, + { + headers: { + "Content-Type": "multipart/form-data" + } + } + ); +}; +/**用户容器退款 */ +export const backUserContainer = data => { + return http2.post("/v1/user/container/delete", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**重装容器 */ +export const reinContainer = data => { + return http2.post("/v1/user/container/reinstall", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**重装虚拟机 */ +export const reinVmContainer = (id, data) => { + return http2.post(`/v1/user/instance/reinstall/${id}`, data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/** 容器操作 */ +export const startAdminContainer = (type, id) => { + return http2.post( + "/v1/admin/container/" + type, + { + container_id: id + }, + { + headers: { + "Content-Type": "multipart/form-data" + } + } + ); +}; +/** 容器操作 */ +export const procedureUpdateContainerRenew = data => { + return http2.post("/v1/user/procedure/update_container_renew", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**获取容器完整信息 */ +export const getContainerDetail = id => { + return http2.get(`/v1/user/container/detail?container_id=${id}`); +}; +/**获取虚拟机完整信息 */ +export const getVmContainerDetail = id => { + return http2.get(`/v1/user/instance/detail/${id}`); +}; +/**容器操作信息 */ +export const containerLog = data => { + return http2.post("/v1/user/container/logs", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**虚拟机操作日志 */ +export const vmLog = data => { + return http2.get( + `/v1/user/instance/log/${data.id}?page=${data.page}&count=${data.count}` + ); +}; +/**获取容器状态 */ +export const getContainerStatus = data => { + return http2.post(`/v1/user/container/status`, data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**获取虚拟机状态 */ +export const getVmStatus = id => { + return http2.get(`/v1/user/instance/get_state/${id}`); +}; +/**获取容器运行日志 */ +export const getContainerLog = data => { + return http2.post(`/v1/user/container/run_logs`, data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**获取容器购买网络订单 */ +export const getContainerList = data => { + return http2.post(`/v1/user/procedure/add_network`, data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**计算容器网络价格 */ +export const getContainerPrice = data => { + return http2.post(`/v1/user/procedure/get_price_network`, data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/** 启动虚拟机 */ +export const start_vm = id => { + return http2.get(`/v1/user/instance/start/${id}`); +}; +/** 停止虚拟机(关机) */ +export const stop_vm = id => { + return http2.get(`/v1/user/instance/stop/${id}`); +}; +/**重启虚拟机 */ +export const restart_vm = id => { + return http2.get(`/v1/user/instance/reboot/${id}`); +}; +/**获取虚拟机控制台 */ +export const get_vm_console = id => { + return http2.get(`/v1/user/instance/console/${id}`); +}; +/**进入救援系统 */ +export const rescue_vm = id => { + return http2.get(`/v1/user/instance/rescue/enter/${id}`); +}; +/**退出救援系统 */ +export const unrescue_vm = id => { + return http2.get(`/v1/user/instance/rescue/exit/${id}`); +}; + +// ******************************* new +/** 提交充值订单 */ +export const user_update_container_recharge = data => { + return http2.post("/v1/user/procedure/update_container_recharge", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/** 提交容器订单 */ +export const user_update_plan_info = data => { + return http2.post("/v1/user/procedure/update_plan_info", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/** 提交虚拟机订单 */ +export const user_update_vm_info = data => { + return http2.post("/v1/user/procedure/create_vm_trade", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**获取订单简略信息 */ +export const getOrderDetail = id => { + return http2.get(`/v1/user/procedure/get_low_trade_info?trade_id=${id}`); +}; +/**支付请求 */ +export const pay_request = data => { + return http2.post("/v1/external/pay", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**用户删除容器网络 */ +export const deleteConNet = data => { + return http2.post("/v1/user/container/delete_connect", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; + +// 添加https +export const additionHttp = data => { + return http2.post("/v1/user/container/add_https_connet", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; + +// 删除https +export const DelHttp = data => { + return http2.post("/v1/user/container/del_https_connet", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**获取新增虚拟机端口价格 */ +export const getVmPortPrice = data => { + return http2.post("/v1/user/procedure/get_price_instance_port", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**提交新增虚拟机端口订单 */ +export const addVmPort = data => { + return http2.post("/v1/user/procedure/add_instance_port", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; diff --git a/src/utils/acs/virtual.js b/src/utils/acs/virtual.js new file mode 100644 index 0000000..b3e4522 --- /dev/null +++ b/src/utils/acs/virtual.js @@ -0,0 +1,171 @@ +import {http2} from "@/utils/request.js"; + + +/**获取虚拟机列表 */ +export const getVirtualList = data => { + let url = `/v1/admin/instance/list?page=${data.page}&count=${data.count}`; + if (data.key) { + url += `&key=${data.key}`; + } + if (data.user_id) { + url += `&user_id=${data.user_id}`; + } + if (data.server_id) { + url += `&server_id=${data.server_id}`; + } + return http2.get(url); +}; + +/**新增虚拟机 */ +export const addVirtual = data => { + return http2.post("/v1/admin/instance/create_vm", data); +}; + +/**迁移数据卷 */ +export const migrate_disk = data => { + return http2.post("/v1/admin/volume/migrate_volume", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; + +/**获取虚拟机访问控制列表 */ +export const getVirtualAccessList = data => { + let url = `/v1/admin/instance/access_control/list?page=${data.page}&count=${data.count}&instance_id=${data.instance_id}`; + return http2.get(url); +}; +/**获取虚拟机访问控制列表(用户) */ +export const getUserAccessList = data => { + let url = `/v1/user/instance/access_control/list?page=${data.page}&count=${data.count}&instance_id=${data.instance_id}`; + return http2.get(url); +}; + +/**创建访问控制 */ +export const createAccessControl = data => { + return http2.post("/v1/admin/instance/access_control/create", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**创建访问控制(用户) */ +export const createUserAccessControl = data => { + return http2.post("/v1/user/instance/access_control/create", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**删除访问控制 */ +export const deleteAccessControl = data => { + return http2.post("/v1/admin/instance/access_control/delete", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**删除访问控制(用户) */ +export const deleteUserAccessControl = data => { + return http2.post("/v1/user/instance/access_control/delete", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; + +/**获取虚拟机快照列表 */ +export const getSnapshotList = data => { + let url = `/v1/admin/instance/snapshot/list/${data.instance_id}?page=${data.page}&count=${data.count}`; + return http2.get(url); +}; +/**获取虚拟机快照列表(用户) */ +export const getUserSnapshotList = data => { + let url = `/v1/user/instance/snapshot/list/${data.instance_id}?page=${data.page}&count=${data.count}`; + return http2.get(url); +}; +/**创建虚拟机快照 */ +export const createSnapshot = (data, id) => { + return http2.post(`/v1/admin/instance/snapshot/create/${id}`, data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**创建虚拟机快照(用户) */ +export const createUserSnapshot = (data, id) => { + return http2.post(`/v1/user/instance/snapshot/create/${id}`, data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; + +/**删除虚拟机快照 */ +export const deleteSnapshot = (data, id) => { + return http2.post(`/v1/admin/instance/snapshot/delete/${id}`, data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; + +/**恢复虚拟机快照 */ +export const recoverSnapshot = (data, id) => { + return http2.post(`/v1/admin/instance/snapshot/restore/${id}`, data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**恢复虚拟机快照(用户) */ +export const recoverUserSnapshot = (data, id) => { + return http2.post(`/v1/user/instance/snapshot/restore/${id}`, data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +/**获取实时监控 */ +export const getVirtualLog = data => { + return http2.get( + `/v1/admin/instance/run_logs/${data.id}?start_time=${data.start_time}&end_time=${data.end_time}` + ); +}; +/**获取实时监控(用户) */ +export const getUserVirtualLog = data => { + return http2.get( + `/v1/user/instance/run_logs/${data.id}?start_time=${data.start_time}&end_time=${data.end_time}` + ); +}; + +/**获取新增虚拟机快照数量价格 */ +export const getSnapshotPrice = data => { + return http2.get(`/v1/user/procedure/get_price_snapshot?num=${data}`); +}; + +/**提交虚拟机购买快照订单 */ +export const submitSnapshotOrder = data => { + return http2.post("/v1/user/procedure/update_container_snapshot", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; +// 获取购买虚拟机数据卷价格 +export const getVolumePrice = data => { + return http2.post("/v1/user/procedure/get_price_volume", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; + +// 提交虚拟机数据卷订单 +export const submitVolumeOrder = data => { + return http2.post("/v1/user/procedure/update_container_volume", data, { + headers: { + "Content-Type": "multipart/form-data" + } + }); +}; diff --git a/src/utils/hide.js b/src/utils/hide.js new file mode 100644 index 0000000..f208e01 --- /dev/null +++ b/src/utils/hide.js @@ -0,0 +1,51 @@ +/** + * 复制文本到剪贴板 + * @param {string} text 要复制的文本 + * @returns {Promise} 是否复制成功 + */ +export const copyDomText = (text) => { + // 优先使用 navigator.clipboard API (现代浏览器) + if (navigator.clipboard && window.isSecureContext) { + return navigator.clipboard.writeText(text) + .then(() => { + return true; + }) + .catch((error) => { + console.error('复制到剪贴板失败:', error); + return fallbackCopyTextToClipboard(text); + }); + } else { + // 回退方案 + return fallbackCopyTextToClipboard(text); + } +}; + +/** + * 使用传统方法复制文本到剪贴板 + * @param {string} text 要复制的文本 + * @returns {boolean} 是否复制成功 + */ +const fallbackCopyTextToClipboard = (text) => { + try { + const textArea = document.createElement('textarea'); + textArea.value = text; + + // 避免滚动到底部 + textArea.style.top = '0'; + textArea.style.left = '0'; + textArea.style.position = 'fixed'; + textArea.style.opacity = '0'; + + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + + const successful = document.execCommand('copy'); + document.body.removeChild(textArea); + + return successful; + } catch (err) { + console.error('复制到剪贴板失败:', err); + return false; + } +}; \ No newline at end of file diff --git a/src/utils/request.js b/src/utils/request.js index dbb97ce..a9a91d3 100644 --- a/src/utils/request.js +++ b/src/utils/request.js @@ -1,4 +1,26 @@ import axios from 'axios' +import { ElMessage } from 'element-plus' +import router from '@/router' + +// 基础URL +const baseUrl = 'https://apiservertest.s1f.ren' + +// 检查URL是否需要认证 +const urlNeedAuth = (url) => { + // 这里可以添加不需要认证的URL列表 + const noAuthUrls = ['/v1/user/login', '/v1/user/check/get_code_img', '/v1/user/register'] + return !noAuthUrls.some(noAuthUrl => url.includes(noAuthUrl)) +} + +// 检查token是否过期 +const isTokenExpired = () => { + const token = localStorage.getItem('token') + if (!token) return true + + // 这里可以添加token过期检查逻辑,如果有JWT可以解析它 + // 简单实现,仅检查token是否存在 + return false +} class Request { constructor(config = {}) { @@ -14,10 +36,10 @@ class Request { (config) => { // 在发送请求之前做些什么 // 例如:添加 token - // const token = localStorage.getItem('token') - // if (token) { - // config.headers.Authorization = `Bearer ${token}` - // } + const token = localStorage.getItem('token') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } return config }, (error) => { @@ -49,7 +71,7 @@ class Request { // break // } // } - return Promise.reject(error) + return error.response.data } ) } @@ -82,11 +104,55 @@ class Request { // 创建默认实例 const request = new Request({ - baseURL: 'http://localhost:3000', + baseURL: baseUrl, timeout: 5000, headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'multipart/form-data' } }) +export const mainUrl = baseUrl + "/acs" + +export const http2 = axios.create({ + baseURL: baseUrl, + timeout: 5000, + headers: {}, +}); + +http2.interceptors.request.use(config => { + const token = localStorage.getItem('token'); // 假设 token 存储在 localStorage + if(urlNeedAuth(config.url) && isTokenExpired()){ + if (token){ + localStorage.removeItem('token'); + ElMessage.warning('登陆过期,请重新登陆') + } + router.push('/login') + return Promise.reject(); + } + config.headers.Authorization = `Bearer ${token}`; + + config.url = '/acs' + config.url + return config +}) + +http2.interceptors.response.use( + response => response, // 正常响应时直接返回 + error => { + console.log('出现错误', error.response); + if (error.response == undefined) { + ElMessage.error("服务器错误,请稍后再试"); + return Promise.reject(error); + } + const { status } = error.response; + if (status === 401) { + localStorage.removeItem('token'); + ElMessage.warning('登陆过期,请重新登陆') + router.push('/login') + return Promise.reject(); + } + // 其他错误处理(可选) + return Promise.reject(error); + } +); + export default request \ No newline at end of file diff --git a/src/views/Login.vue b/src/views/Login.vue new file mode 100644 index 0000000..f202058 --- /dev/null +++ b/src/views/Login.vue @@ -0,0 +1,409 @@ + + + + + \ No newline at end of file diff --git a/src/views/NotFound.vue b/src/views/NotFound.vue index bc19912..f15d170 100644 --- a/src/views/NotFound.vue +++ b/src/views/NotFound.vue @@ -1,34 +1,61 @@ - \ No newline at end of file diff --git a/src/views/Redirect.vue b/src/views/Redirect.vue new file mode 100644 index 0000000..fb20ae8 --- /dev/null +++ b/src/views/Redirect.vue @@ -0,0 +1,21 @@ + + + \ No newline at end of file diff --git a/src/views/acs/images/ContainerImages.vue b/src/views/acs/images/ContainerImages.vue new file mode 100644 index 0000000..96bb108 --- /dev/null +++ b/src/views/acs/images/ContainerImages.vue @@ -0,0 +1,1281 @@ + + + + + \ No newline at end of file diff --git a/src/views/acs/images/ImageCategories.vue b/src/views/acs/images/ImageCategories.vue new file mode 100644 index 0000000..9838c1d --- /dev/null +++ b/src/views/acs/images/ImageCategories.vue @@ -0,0 +1,708 @@ + + + + + \ No newline at end of file diff --git a/src/views/acs/images/ImageRequests.vue b/src/views/acs/images/ImageRequests.vue new file mode 100644 index 0000000..d705efa --- /dev/null +++ b/src/views/acs/images/ImageRequests.vue @@ -0,0 +1,728 @@ + + + + + \ No newline at end of file diff --git a/src/views/acs/images/VmImages.vue b/src/views/acs/images/VmImages.vue new file mode 100644 index 0000000..63dd130 --- /dev/null +++ b/src/views/acs/images/VmImages.vue @@ -0,0 +1,904 @@ + + + + + + \ No newline at end of file diff --git a/src/views/acs/messages/Announcements.vue b/src/views/acs/messages/Announcements.vue new file mode 100644 index 0000000..a8d6051 --- /dev/null +++ b/src/views/acs/messages/Announcements.vue @@ -0,0 +1,423 @@ + + + + + \ No newline at end of file diff --git a/src/views/acs/messages/News.vue b/src/views/acs/messages/News.vue new file mode 100644 index 0000000..ead8aa4 --- /dev/null +++ b/src/views/acs/messages/News.vue @@ -0,0 +1,490 @@ + + + + + \ No newline at end of file diff --git a/src/views/acs/messages/Policies.vue b/src/views/acs/messages/Policies.vue new file mode 100644 index 0000000..f2e0f15 --- /dev/null +++ b/src/views/acs/messages/Policies.vue @@ -0,0 +1,498 @@ + + + + + \ No newline at end of file diff --git a/src/views/acs/nodes/Nodes.vue b/src/views/acs/nodes/Nodes.vue new file mode 100644 index 0000000..81cc90d --- /dev/null +++ b/src/views/acs/nodes/Nodes.vue @@ -0,0 +1,1036 @@ + + + + + \ No newline at end of file diff --git a/src/views/acs/nodes/components/VmList.vue b/src/views/acs/nodes/components/VmList.vue new file mode 100644 index 0000000..2de28a2 --- /dev/null +++ b/src/views/acs/nodes/components/VmList.vue @@ -0,0 +1,314 @@ + + + + + \ No newline at end of file diff --git a/src/views/acs/nodes/components/serverChart.vue b/src/views/acs/nodes/components/serverChart.vue new file mode 100644 index 0000000..d398f0b --- /dev/null +++ b/src/views/acs/nodes/components/serverChart.vue @@ -0,0 +1,340 @@ + + + + + \ No newline at end of file diff --git a/src/views/acs/nodes/server.vue b/src/views/acs/nodes/server.vue new file mode 100644 index 0000000..fa9f259 --- /dev/null +++ b/src/views/acs/nodes/server.vue @@ -0,0 +1,1373 @@ + + + + + \ No newline at end of file diff --git a/src/views/dashboard/Dashboard.vue b/src/views/dashboard/Dashboard.vue new file mode 100644 index 0000000..963bf5d --- /dev/null +++ b/src/views/dashboard/Dashboard.vue @@ -0,0 +1,892 @@ + + + + + \ No newline at end of file diff --git a/src/views/profile/ChangePassword.vue b/src/views/profile/ChangePassword.vue new file mode 100644 index 0000000..1fc43ff --- /dev/null +++ b/src/views/profile/ChangePassword.vue @@ -0,0 +1,525 @@ + + + + + \ No newline at end of file diff --git a/src/views/profile/UserInfo.vue b/src/views/profile/UserInfo.vue new file mode 100644 index 0000000..a4e080e --- /dev/null +++ b/src/views/profile/UserInfo.vue @@ -0,0 +1,529 @@ + + + + + \ No newline at end of file diff --git a/src/views/system/DomainWhitelist.vue b/src/views/system/DomainWhitelist.vue new file mode 100644 index 0000000..e1a84ef --- /dev/null +++ b/src/views/system/DomainWhitelist.vue @@ -0,0 +1,309 @@ + + + + + \ No newline at end of file diff --git a/src/views/system/OperationLog.vue b/src/views/system/OperationLog.vue new file mode 100644 index 0000000..8de1ab0 --- /dev/null +++ b/src/views/system/OperationLog.vue @@ -0,0 +1,395 @@ + + + + + \ No newline at end of file diff --git a/src/views/system/Users.vue b/src/views/system/Users.vue new file mode 100644 index 0000000..3ea12b8 --- /dev/null +++ b/src/views/system/Users.vue @@ -0,0 +1,578 @@ + + + + + \ No newline at end of file diff --git a/src/views/ticket/TicketChat.vue b/src/views/ticket/TicketChat.vue new file mode 100644 index 0000000..8aca872 --- /dev/null +++ b/src/views/ticket/TicketChat.vue @@ -0,0 +1,1561 @@ + + + + + \ No newline at end of file diff --git a/vite.config.js b/vite.config.js index bbcf80c..6bdfa39 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,7 +1,12 @@ import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' - +import { resolve } from 'path' // https://vite.dev/config/ export default defineConfig({ plugins: [vue()], -}) + resolve: { + alias: { + '@': resolve(__dirname, 'src') + } + } +}) \ No newline at end of file