提交 53de5a7a authored 作者: 刘旭's avatar 刘旭

初次提交

上级
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
'extends': [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-typescript',
'@vue/eslint-config-prettier/skip-formatting'
],
parserOptions: {
ecmaVersion: 'latest'
}
}
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"tabWidth": 2,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "none"
}
\ No newline at end of file
{
"recommendations": [
"Vue.volar",
"Vue.vscode-typescript-vue-plugin",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}
# demo
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
## Type Support for `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
1. Disable the built-in TypeScript Extension
1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette
2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
## Customize configuration
See [Vite Configuration Reference](https://vitejs.dev/config/).
## Project Setup
```sh
pnpm install
```
### Compile and Hot-Reload for Development
```sh
pnpm dev
```
### Type-Check, Compile and Minify for Production
```sh
pnpm build
```
### Lint with [ESLint](https://eslint.org/)
```sh
pnpm lint
```
/// <reference types="vite/client" />
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>管理中心</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
<style></style>
{
"name": "demo",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit --skipLibCheck && vite build",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build --force",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"format": "prettier --write src/"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"axios": "^1.6.5",
"element-plus": "^2.5.3",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1",
"unplugin-auto-import": "^0.17.3",
"unplugin-icons": "^0.18.2",
"unplugin-vue-components": "^0.26.0",
"vue": "^3.3.11",
"vue-clipboard3": "^2.0.0",
"vue-router": "^4.2.5"
},
"devDependencies": {
"@iconify-json/ep": "^1.1.14",
"@rushstack/eslint-patch": "^1.3.3",
"@tsconfig/node18": "^18.2.2",
"@types/node": "^18.19.3",
"@vitejs/plugin-vue": "^4.5.2",
"@vue/eslint-config-prettier": "^8.0.0",
"@vue/eslint-config-typescript": "^12.0.0",
"@vue/tsconfig": "^0.5.0",
"eslint": "^8.49.0",
"eslint-plugin-vue": "^9.17.0",
"npm-run-all2": "^6.1.1",
"prettier": "^3.0.3",
"typescript": "~5.3.0",
"vite": "^5.0.10",
"vue-tsc": "^1.8.25"
}
}
差异被折叠。
<script setup lang="ts">
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
</script>
<template>
<el-config-provider :locale="zhCn">
<router-view></router-view>
</el-config-provider>
</template>
<style scoped>
header {
line-height: 1.5;
max-height: 100vh;
}
.logo {
display: block;
margin: 0 auto 2rem;
}
nav {
width: 100%;
font-size: 12px;
text-align: center;
margin-top: 2rem;
}
nav a.router-link-exact-active {
color: var(--color-text);
}
nav a.router-link-exact-active:hover {
background-color: transparent;
}
nav a {
display: inline-block;
padding: 0 1rem;
border-left: 1px solid var(--color-border);
}
nav a:first-of-type {
border: 0;
}
@media (min-width: 1024px) {
header {
display: flex;
place-items: center;
padding-right: calc(var(--section-gap) / 2);
}
.logo {
margin: 0 2rem 0 0;
}
header .wrapper {
display: flex;
place-items: flex-start;
flex-wrap: wrap;
}
nav {
text-align: left;
margin-left: -1rem;
font-size: 1rem;
padding: 1rem 0;
margin-top: 1rem;
}
}
</style>
import axios, {
AxiosInstance,
AxiosError,
AxiosRequestConfig,
InternalAxiosRequestConfig,
AxiosResponse
} from 'axios'
export interface CustomAxiosRequestConfig extends InternalAxiosRequestConfig {
loading?: boolean
Authorization?: boolean
}
// 数据返回的接口
// 定义请求响应参数,不含data
interface Result {
code: number
msg: string
}
// 请求响应参数,包含data
interface ResultData<T = any> extends Result {
data?: T
}
export let baseURL: string = ''
enum RequestEnums {
TIMEOUT = 20000, // 请求超时
OVERDUE = 600, // 登录失败
FAIL = 999, // 请求失败
SUCCESS = 200, // 请求成功
EXPIRE = 501 // token过期
}
let IsError = false
const config = {
// 默认地址
baseURL,
// 设置超时时间
timeout: RequestEnums.TIMEOUT as number,
// 跨域时候允许携带凭证
withCredentials: true
}
class RequestHttp {
// 定义成员变量并指定类型
service: AxiosInstance
public constructor(config: AxiosRequestConfig) {
// 实例化axios
this.service = axios.create(config)
/**
* 请求拦截器
* 客户端发送请求 -> [请求拦截器] -> 服务器
* token校验(JWT) : 接受服务器返回的token,存储到本地储存当中
*/
this.service.interceptors.request.use(
(config: CustomAxiosRequestConfig) => {
// const globalStore = useGlobalStore()
// let globalStore: any
// if (config.headers && globalStore.token) {
// config.headers.set('Authorization', globalStore.token)
// }
return config
},
// 请求报错
(error: AxiosError) => {
return Promise.reject(error)
}
),
/**
* 响应拦截器
* 服务器换返回信息 -> [拦截统一处理] -> 客户端JS获取到信息
*/
this.service.interceptors.response.use(
(response: AxiosResponse) => {
const { data, config } = response // 解构
if (IsError || data.code === 502) return data
// 未携带token或token失效
if (data.code === RequestEnums.OVERDUE) {
// 登录信息失效,应跳转到登录页面,并清空本地的token
ElMessage.error(data.msg)
return Promise.reject(data)
}
// 全局错误信息拦截(防止下载文件得时候返回数据流,没有code,直接报错)
if (data.code && data.code !== RequestEnums.SUCCESS) {
ElMessage.error(data.msg) // 此处也可以使用组件提示报错信息
return Promise.reject(data)
}
// * 成功请求(在页面上除非特殊情况,否则不用在页面处理失败逻辑)
return data
},
(error: AxiosError) => {
const { response } = error
// 请求超时 && 网络错误单独判断,没有 response
if (error.message.indexOf('timeout') !== -1) ElMessage.error('请求超时!请您稍后重试')
if (error.message.indexOf('Network Error') !== -1)
ElMessage.error('网络错误!请您稍后重试')
// 根据响应的错误状态码,做不同的处理
if (response) this.handleCode(response.status)
// 服务器结果都没有返回(可能服务器错误可能客户端断网),断网处理:可以跳转到断网页面
// if (!window.navigator.onLine) router.replace('/500')
return Promise.reject(error)
}
)
}
handleCode(code: number): void {
switch (code) {
case 401:
ElMessage.error('登录失败,请重新登录')
break
default:
ElMessage.error('请求失败')
break
}
}
// 常用方法封装
get<T>(url: string, params?: Object, _object = {}): Promise<ResultData<T>> {
return this.service.get(url, { params, ..._object })
}
post<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
return this.service.post(url, params, _object)
}
put<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
return this.service.put(url, params, _object)
}
delete<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
return this.service.delete(url, { params, ..._object })
}
download(url: string, params?: object, _object = {}): Promise<BlobPart> {
return this.service.post(url, params, { ..._object, responseType: 'blob' })
}
}
// 导出一个实例对象
export default new RequestHttp(config)
import request from './config'
export const getTenantList = () => {
return request.get('/admin/docker/listTenant')
}
export const getListAll = (tenantId: any) => {
return request.get('/admin/docker/listAll', tenantId)
}
export const getMessage = (tenantId: any) => {
return request.get('/admin/docker/message', { tenantId })
}
export const getMessageList = (data: any) => {
return request.get('/admin/docker/message/list', data)
}
export const startAll = (tenantId?: any) => {
return request.post('/admin/docker/startAll', { tenantId })
}
export const updateAll = (tenantId?: any) => {
return request.post('/admin/docker/updateAll', { tenantId })
}
export const cleanAll = (tenantId?: any) => {
return request.post('/admin/docker/clean', { tenantId })
}
export const getMac = () => {
return request.post('/admin/api/lic/info/mac')
}
export const startOne = (data: any) => {
return request.post('/admin/docker/startOne', data)
}
export const updateOne = (data: any) => {
return request.post('/admin/docker/updateOne', data)
}
export const restartOne = (data: any) => {
return request.post('/admin/docker/restart', data)
}
export const cleanOne = (data: any) => {
return request.post('/admin/docker/cleanOne', data)
}
export const startBatch = (data: any) => {
return request.post('/admin/docker/start', data)
}
export const updateBatch = (data: any) => {
return request.post('/admin/docker/update', data)
}
export const cleanBatch = (data: any) => {
return request.post('/admin/docker/clean', data)
}
export const getJarList = () => {
return request.get('/admin/docker/jar/list')
}
差异被折叠。
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
<template>
<el-menu
active-text-color="#ffd04b"
background-color="#545c64"
:default-active="active"
text-color="#fff"
router
>
<!-- <div style="margin-bottom: 10px">
<el-select v-model="tenantId" @change="selectChange" placeholder="请选择租户">
<el-option
v-for="item in tenantList"
:key="item.tenantId"
:label="item.tenantName"
:value="item.tenantId"
/>
</el-select>
</div> -->
<el-menu-item v-for="item in mentList" :index="item.index" :route="item.path">
<span>{{ item.name }}</span>
</el-menu-item>
</el-menu>
</template>
<script setup lang="ts">
import { useTenantStore } from '@/stores/modules/tenant'
const route = useRoute()
const tenantStore = useTenantStore()
const tenantList = computed(() => tenantStore.getTenantList)
const tenantId = computed(() => tenantStore.getTenantId)
const active: any = ref(1)
const mentList = ref([
{
index: 1,
path: '/tenant',
name: '租户管理'
},
{
index: 2,
path: '/logs',
name: '日志管理'
},
{
index: 3,
path: '/imageManagement',
name: '容器管理'
},
{
index: 4,
path: '/jarManagement',
name: 'jar包管理'
}
])
watch(
() => route.path,
(newPath: string) => {
active.value = mentList.value.find((item: any) => newPath === item.path)?.index
},
{ immediate: true }
)
const selectChange = (tenantId: any) => {
tenantStore.setTenantId(tenantId)
}
</script>
<style scoped>
.el-menu {
height: 100%;
}
</style>
<template>
<div class="headers">
<h2>管理中心</h2>
<div style="width: 200px">
<el-select v-model="tenantId" @change="selectChange" placeholder="请选择租户">
<el-option
v-for="item in tenantList"
:key="item.tenantId"
:label="item.tenantName"
:value="item.tenantId"
/>
</el-select>
</div>
</div>
</template>
<script setup lang="ts">
import { useTenantStore } from '@/stores/modules/tenant'
const tenantStore = useTenantStore()
const tenantList = computed(() => tenantStore.getTenantList)
const tenantId = computed(() => tenantStore.getTenantId)
const selectChange = (tenantId: any) => {
tenantStore.setTenantId(tenantId || null)
}
</script>
<style scoped>
.headers {
display: flex;
align-items: center;
height: 100%;
}
h2 {
font-size: 20px;
font-weight: bolder;
color: #ffffff;
width: 100px;
text-align: justify;
text-align-last: justify;
margin-right: 80px;
}
</style>
<template>
<el-container>
<el-header><Headers /></el-header>
<el-container>
<el-aside width="200px">
<Aside />
</el-aside>
<el-main>
<keep-alive>
<router-view />
</keep-alive>
</el-main>
</el-container>
</el-container>
</template>
<script setup lang="ts">
import Aside from './components/aside.vue'
import Headers from './components/header.vue'
</script>
<style scoped>
.el-container {
height: 100%;
}
.el-main {
--el-main-padding: 30px;
}
.el-header {
background-color: #545c64;
border-bottom: 1px solid #ffffff;
box-sizing: border-box;
height: 50px;
}
</style>
import { createApp } from 'vue'
import store from '@/stores'
import App from './App.vue'
import router from './router'
import '@/styles/base.css'
const app = createApp(App)
app.use(router)
app.use(store)
app.mount('#app')
import { createRouter, createWebHashHistory } from 'vue-router'
import login from '@/views/login/index.vue'
import layout from '@/layout/index.vue'
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
name: 'login',
component: login
},
{
path: '/center',
component: layout,
redirect: '/tenant',
children: [
{
path: '/tenant',
component: () => import('@/views/tenant/index.vue')
},
{
path: '/logs',
component: () => import('@/views/logs/index.vue')
},
{
path: '/imageManagement',
component: () => import('@/views/imageManagement/index.vue')
},
{
path: '/jarManagement',
component: () => import('@/views/jarManagement/index.vue')
}
]
}
]
})
export default router
import { PersistedStateOptions } from 'pinia-plugin-persistedstate'
/**
* @description pinia 持久化参数配置
* @param {String} key 存储到持久化的 name
* @param {Array} paths 需要持久化的 state name
* @return persist
* */
const piniaPersistConfig = (key: string, paths?: string[]) => {
const persist: PersistedStateOptions = {
key,
storage: localStorage,
// storage: sessionStorage,
paths
}
return persist
}
export default piniaPersistConfig
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
// pinia persist
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
export default pinia
import { defineStore } from 'pinia'
import piniaPersistConfig from '../helper/piniaPersist'
import { getTenantList } from '@/api'
export const useTenantStore = defineStore('tenant', {
// 相当于data
state: () => {
return {
tenantId: '',
tenantList: [] as any
}
},
// 相当于计算属性
getters: {
getTenantList: (state) => state.tenantList,
getTenantId: (state) => state.tenantId
},
actions: {
async initTenantList() {
const res: any = await getTenantList()
if (res.code === 200) {
this.tenantList = res.data
this.tenantId = res.data?.length ? res.data[0].tenantId : ''
}
},
setTenantId(tenantId: any) {
this.tenantId = tenantId
}
},
persist: piniaPersistConfig('tenant')
})
/* Reset style sheet */
/* 因为使用富文本编辑器才注释掉,如果你项目没有使用富文本编辑器,可以取消注释 */
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio {
padding: 0;
margin: 0;
font: inherit;
font-size: 100%;
vertical-align: baseline;
border: 0;
font-family:
Source Han Sans CN,
Source Han Sans CN;
}
/* HTML5 display-role reset for older browsers */
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
display: block;
}
body {
padding: 0;
margin: 0;
}
ol,
ul {
list-style: none;
}
blockquote,
q {
quotes: none;
}
blockquote::before,
blockquote::after,
q::before,
q::after {
content: '';
content: none;
}
table {
border-spacing: 0;
border-collapse: collapse;
}
html,
body,
#app {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
/* 解决 h1 标签在 webkit 内核浏览器中文字大小失效问题 */
:-webkit-any(article, aside, nav, section) h1 {
font-size: 2em;
}
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
export {}
declare global {
const EffectScope: typeof import('vue')['EffectScope']
const ElMessage: typeof import('element-plus/es')['ElMessage']
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
const computed: typeof import('vue')['computed']
const createApp: typeof import('vue')['createApp']
const createPinia: typeof import('pinia')['createPinia']
const customRef: typeof import('vue')['customRef']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComponent: typeof import('vue')['defineComponent']
const defineStore: typeof import('pinia')['defineStore']
const effectScope: typeof import('vue')['effectScope']
const getActivePinia: typeof import('pinia')['getActivePinia']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const h: typeof import('vue')['h']
const inject: typeof import('vue')['inject']
const isProxy: typeof import('vue')['isProxy']
const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const mapActions: typeof import('pinia')['mapActions']
const mapGetters: typeof import('pinia')['mapGetters']
const mapState: typeof import('pinia')['mapState']
const mapStores: typeof import('pinia')['mapStores']
const mapWritableState: typeof import('pinia')['mapWritableState']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onDeactivated: typeof import('vue')['onDeactivated']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onMounted: typeof import('vue')['onMounted']
const onRenderTracked: typeof import('vue')['onRenderTracked']
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const onScopeDispose: typeof import('vue')['onScopeDispose']
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const provide: typeof import('vue')['provide']
const reactive: typeof import('vue')['reactive']
const readonly: typeof import('vue')['readonly']
const ref: typeof import('vue')['ref']
const resolveComponent: typeof import('vue')['resolveComponent']
const setActivePinia: typeof import('pinia')['setActivePinia']
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const storeToRefs: typeof import('pinia')['storeToRefs']
const toRaw: typeof import('vue')['toRaw']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const toValue: typeof import('vue')['toValue']
const triggerRef: typeof import('vue')['triggerRef']
const unref: typeof import('vue')['unref']
const useAttrs: typeof import('vue')['useAttrs']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVars: typeof import('vue')['useCssVars']
const useLink: typeof import('vue-router')['useLink']
const useRoute: typeof import('vue-router')['useRoute']
const useRouter: typeof import('vue-router')['useRouter']
const useSlots: typeof import('vue')['useSlots']
const watch: typeof import('vue')['watch']
const watchEffect: typeof import('vue')['watchEffect']
const watchPostEffect: typeof import('vue')['watchPostEffect']
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
}
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
import('vue')
}
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
declare module 'vue' {
export interface GlobalComponents {
ElAside: typeof import('element-plus/es')['ElAside']
ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
ElCol: typeof import('element-plus/es')['ElCol']
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput']
ElLink: typeof import('element-plus/es')['ElLink']
ElMain: typeof import('element-plus/es')['ElMain']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElRow: typeof import('element-plus/es')['ElRow']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTag: typeof import('element-plus/es')['ElTag']
ElTimeline: typeof import('element-plus/es')['ElTimeline']
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
ElUpload: typeof import('element-plus/es')['ElUpload']
IEpRefreshRight: typeof import('~icons/ep/refresh-right')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}
export interface ComponentCustomProperties {
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
}
}
declare module 'element-plus/dist/locale/zh-cn.mjs'
<template>
<div class="lic-container" v-loading="state.loading">
<div style="display: flex; justify-content: space-between">
<div style="margin-bottom: 20px; display: flex; flex-direction: row; align-items: center">
<el-button type="primary" size="mini" @click="onStart" style="margin-left: 20px"
>启动
</el-button>
<el-button type="primary" size="mini" @click="onRestartOne" style="margin-left: 20px"
>重启
</el-button>
<el-button type="primary" size="mini" @click="onUpdate" style="margin-left: 20px"
>更新
</el-button>
<el-button type="primary" size="mini" @click="onClean" style="margin-left: 20px"
>清理
</el-button>
<!-- <el-button type="primary" size="mini" @click="onMac" style="margin-left: 20px"
>MAC
</el-button> -->
<el-button type="primary" size="mini" @click="onMessages" style="margin-left: 20px"
>刷新
</el-button>
</div>
<!-- <el-select v-model="tenantId.value" @change="selectChange" placeholder="请选择租户">
<el-option
v-for="item in state.listTenant"
:key="item.tenantId"
:label="item.tenantName"
:value="item.tenantId"
/>
</el-select> -->
</div>
<el-table
:data="tableData"
style="width: 100%"
stripe
border
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="id" label="序号" width="80" align="center" />
<el-table-column prop="name" label="服务" />
<el-table-column prop="version" label="版本" width="100" align="center" />
<el-table-column prop="stats" label="状态" width="100" align="center">
<template #default="scope">
<el-tag
:type="scope.row.stats === '运行中' ? 'success' : 'warning'"
disable-transitions
>{{ scope.row.stats }}</el-tag
>
</template>
</el-table-column>
<!-- <el-table-column label="操作" align="center" width="255">
<template #default="scope">
<el-link type="primary" :underline="false" @click="onStartOne(scope.row)">启动</el-link>
<el-divider direction="vertical" />
<el-link type="primary" :underline="false" @click="onRestartOne(scope.row)">重启</el-link>
<el-divider direction="vertical" />
<el-link type="primary" :underline="false" @click="onUpdateOne(scope.row)">更新</el-link>
<el-divider direction="vertical" />
<el-link type="error" :underline="false" @click="onCleanOne(scope.row)">清除</el-link>
</template>
</el-table-column> -->
</el-table>
<div v-show="state.messages.length" class="timeline" v-loading="state.messageLoading">
<el-icon size="28" color="#409EFC" class="RefreshRight" @click="initMessage"
><i-ep-RefreshRight
/></el-icon>
<el-timeline>
<el-timeline-item v-for="(item, index) in state.messages" :key="index">
{{ item }}
</el-timeline-item>
</el-timeline>
</div>
</div>
</template>
<script setup lang="ts">
import {
getListAll,
getMessage,
cleanAll,
getMac,
startOne,
updateOne,
restartOne,
cleanOne,
startBatch,
updateBatch,
cleanBatch
} from '@/api'
import { useTenantStore } from '@/stores/modules/tenant'
import clipboard3 from 'vue-clipboard3'
const tenantStore = useTenantStore()
const tenantId = computed(() => tenantStore.getTenantId)
const state = reactive({
visible: false,
loading: true,
messageLoading: true,
action: '/admin/api/lic/info/file/import',
tableData: [],
messages: [] as any,
dialogVisible: false,
environment: '',
selectRows: [] as any
})
const tableData = ref()
const handleSelectionChange = (rows: any) => {
state.selectRows = rows.map((item: any) => item.name)
state.selectRows = [...state.selectRows]
console.log(state.selectRows)
}
const onStart = () => {
if (!state.selectRows.length) return ElMessage.warning('请先选择数据')
ElMessageBox.confirm('确认启动?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res: any = await startBatch({ names: state.selectRows, tenantId: tenantId.value })
if (res.code === 200) ElMessage.success('启动成功')
else ElMessage.error(res.msg)
} finally {
onMessages()
}
})
}
const onUpdate = () => {
if (!state.selectRows.length) return ElMessage.warning('请先选择数据')
ElMessageBox.confirm('确认更新?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
const res: any = await updateBatch({ names: state.selectRows, tenantId: tenantId.value })
try {
if (res.code === 200) ElMessage.success('更新成功')
else ElMessage.error(res.msg)
} finally {
onMessages()
}
})
}
const onClean = () => {
if (!state.selectRows.length) return ElMessage.warning('请先选择数据')
ElMessageBox.confirm('确认清理?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
const res: any = await cleanBatch({ names: state.selectRows, tenantId: tenantId.value })
try {
if (res.code === 200) ElMessage.success('清理成功')
else ElMessage.error(res.msg)
} finally {
onMessages()
}
})
}
const onMac = async () => {
const res: any = await getMac()
if (res.code === 200) {
if (res.data.macAddress)
ElMessageBox.confirm(`mac地址:${res.data.macAddress}`, '提示', {
confirmButtonText: '复制',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
const { toClipboard } = clipboard3()
try {
await toClipboard(res.data.macAddress)
ElMessage.success('mac: ' + res.data.macAddress + ' 复制成功!!')
} catch (error) {
ElMessage.warning('复制失败!!')
}
})
else {
ElMessage.warning('mac地址为空')
}
} else ElMessage.error(res.msg)
}
const onMessages = async () => {
state.loading = true
await initListAll()
await initMessage().then(() => {
state.loading = false
})
}
const onStartOne = (row: any) => {
ElMessageBox.confirm(`确认启动 ${row.name}?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
const res: any = await startOne({ name: row.name, tenantId: tenantId.value })
if (res.code === 200) ElMessage.success('启动成功')
else ElMessage.error(res.msg)
})
}
const onUpdateOne = (row: any) => {
ElMessageBox.confirm(`确认更新 ${row.name}?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
const res: any = await updateOne({ name: row.name, tenantId: tenantId.value })
if (res.code === 200) ElMessage.success('更新成功')
else ElMessage.error(res.msg)
})
}
const onRestartOne = () => {
if (!state.selectRows.length) return ElMessage.warning('请先选择数据')
if (state.selectRows.length > 1) return ElMessage.warning('一次只能重启一条数据')
ElMessageBox.confirm(`确认重启 ?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res: any = await restartOne({
name: state.selectRows[0].name,
tenantId: tenantId.value
})
if (res.code === 200) ElMessage.success('重启成功')
else ElMessage.error(res.msg)
} finally {
onMessages()
}
})
}
const onCleanOne = (row: any) => {
ElMessageBox.confirm(`确认清除 ${row.name}?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
const res: any = await cleanOne({ name: row.name, tenantId: tenantId.value })
if (res.code === 200) ElMessage.success('清除成功')
else ElMessage.error(res.msg)
})
}
const tenantChange = (tenantId: any) => {
state.loading = true
initListAll(tenantId).then(() => {
state.loading = false
})
}
const initListAll = async (tenantId: number | string = '') => {
const res = await getListAll({ tenantId })
if (res.code === 200) {
tableData.value = res.data
}
}
const initMessage = async () => {
state.messageLoading = true
const res = await getMessage(tenantId.value)
if (res.code === 200) {
state.messages = res.data
setTimeout(() => {
state.messageLoading = false
}, 300)
}
}
watch(
() => tenantId.value,
(newTenantId) => {
tenantChange(newTenantId)
},
{ immediate: true }
)
const init = async () => {
await initListAll()
await initMessage().then(() => {
state.loading = false
})
}
init()
</script>
<style scoped>
.el-select {
width: 200px;
}
.timeline {
position: relative;
margin-top: 20px;
width: 500px;
border: 1px solid var(--el-border-color);
padding: 20px;
border-radius: 10px;
}
.RefreshRight {
position: absolute;
top: 6px;
right: 6px;
cursor: pointer;
}
.el-timeline-item {
padding-bottom: 10px;
}
</style>
<template>
<div v-loading="loading">
<el-table :data="tableData" stripe border>
<el-table-column type="index" label="#" align="center" />
<el-table-column prop="name" label="jar包名称" />
<el-table-column prop="stats" label="状态" align="center" width="100px">
<template #default="scope">
<el-tag :type="scope.row.stats ? 'success' : 'error'" disable-transitions>{{
scope.row.stats ? '成功' : '失败'
}}</el-tag>
</template>
</el-table-column>
<el-table-column prop="exist" label="存在" align="center" width="100px">
<template #default="scope">
<el-tag :type="scope.row.exist ? 'success' : 'error'" disable-transitions>{{
scope.row.exist ? '是' : '否'
}}</el-tag>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script setup lang="ts">
import { getJarList } from '@/api'
const loading = ref(true)
const tableData = ref()
const init = async () => {
const res: any = await getJarList()
console.log(res)
if (res.code === 200) {
tableData.value = res.data
loading.value = false
}
}
init()
</script>
<style scoped></style>
<template>
<div id="login">
<el-card>
<el-form class="login-container" label-position="left" label-width="0px">
<h2 class="login_title">系统登录</h2>
<el-form-item>
<el-input
type="text"
v-model="loginForm.username"
auto-complete="off"
placeholder="账号"
></el-input>
</el-form-item>
<el-form-item>
<el-input
type="password"
v-model="loginForm.password"
auto-complete="off"
placeholder="密码"
></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" style="width: 120px" @click="login">登录</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<script setup lang="ts">
import { useTenantStore } from '@/stores/modules/tenant'
const tenantStore = useTenantStore()
const loginForm = ref({
username: '',
password: ''
})
const router = useRouter()
const login = () => {
tenantStore.initTenantList()
router.push('/tenant')
}
</script>
<style scoped>
#login {
background: url('@/assets/login-background.jpg') no-repeat;
background-position: center;
height: 100%;
width: 100%;
background-size: cover;
position: fixed;
}
.el-card {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 400px;
border-radius: 15px;
}
.el-form {
display: flex;
flex-direction: column;
align-items: center;
}
h2 {
font-size: 20px;
font-weight: bolder;
margin: 10px 0 20px 0;
}
.el-form-item {
width: 250px;
}
:deep(.el-form-item__content) {
display: flex;
justify-content: center;
}
</style>
<template>
<div v-loading="loading">
<el-upload :action="action" :show-file-list="false" :on-success="onUpload">
<el-button type="primary" size="mini">新增租户</el-button>
</el-upload>
<el-table :data="tableData" stripe border>
<el-table-column type="index" label="#" />
<el-table-column prop="tenantId" label="租户id" />
<el-table-column prop="tenantName" label="租户名称" />
<el-table-column prop="domainName" label="租户域名" />
</el-table>
</div>
</template>
<script setup lang="ts">
import { useTenantStore } from '@/stores/modules/tenant'
const tenantStore = useTenantStore()
const tableData = ref()
const loading = ref(true)
const action = '/admin/api/lic/info/file/import'
const onUpload = (res: any) => {
try {
if (res.code === 200) ElMessage.success('导入成功')
else ElMessage.error(res.msg)
} finally {
init()
}
}
const init = () => {
loading.value = true
tableData.value = tenantStore.getTenantList
loading.value = false
}
init()
</script>
<style setup></style>
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
"lib": ["esnext", "dom", "dom.iterable", "scripthost"]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx",
"src/types/auto-imports.d.ts",
"images.d.ts"
],
"exclude": ["node_modules"]
}
{
"extends": "@tsconfig/node18/tsconfig.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"nightwatch.conf.*",
"playwright.config.*"
],
"compilerOptions": {
"composite": true,
"noEmit": true,
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
}
}
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// 配置@别名
import { resolve } from 'path'
// 按需自动引入导入element plus。
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
// 自动导入icon图标
import Icons from 'unplugin-icons/vite'
import IconsResolver from 'unplugin-icons/resolver'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
AutoImport({
imports: ['vue', 'vue-router', 'pinia'],
resolvers: [
ElementPlusResolver(),
IconsResolver({
prefix: 'Icon'
})
],
//存放的位置
dts: 'src/types/auto-import.d.ts'
}),
Components({
resolvers: [
ElementPlusResolver(),
IconsResolver({
enabledCollections: ['ep']
})
],
dts: 'src/types/components.d.ts'
}),
Icons({
autoInstall: true
})
],
resolve: {
alias: {
'@': resolve(__dirname, './src')
}
},
server: {
port: 4003, //设置服务启动端口号,是一个可选项,不要设置为本机的端口号,可能会发生冲突
open: true, //是否自动打开浏览器,可选项
cors: true, //允许跨域。
host: '0.0.0.0',
// 设置代理
proxy: {
'/admin': {
target: 'http://192.168.1.88:8080/'
}
}
}
})
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论