init
This commit is contained in:
262
src/views/infra/config/index.vue
Normal file
262
src/views/infra/config/index.vue
Normal file
@@ -0,0 +1,262 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #menu-left="{ size }">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
:size="size"
|
||||
icon="el-icon-download"
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['infra:config:export']"
|
||||
>导出</el-button
|
||||
>
|
||||
</template>
|
||||
<template #accountCount="scope">
|
||||
<el-tag>{{ scope.row.accountCount }}</el-tag>
|
||||
</template>
|
||||
<template #visible="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.visible !== undefined"
|
||||
:type="DICT_TYPE.INFRA_BOOLEAN_STRING"
|
||||
:value="scope.row.visible"
|
||||
/>
|
||||
</template>
|
||||
<template #type="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.type !== undefined"
|
||||
:type="DICT_TYPE.INFRA_CONFIG_TYPE"
|
||||
:value="scope.row.type"
|
||||
/>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter, getSearchDate } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as ConfigApi from '@/api/infra/config'
|
||||
|
||||
defineOptions({ name: 'InfraConfig' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
span: 12,
|
||||
dialogWidth: '50%',
|
||||
menuWidth: 180,
|
||||
column: {
|
||||
category: {
|
||||
label: '参数分类',
|
||||
width: 90,
|
||||
rules: [{ required: true, message: '参数分类不能为空', trigger: 'blur' }]
|
||||
},
|
||||
name: {
|
||||
label: '参数名称',
|
||||
search: true,
|
||||
minWidth: 120,
|
||||
rules: [{ required: true, message: '参数名称不能为空', trigger: 'blur' }]
|
||||
},
|
||||
key: {
|
||||
label: '参数键名',
|
||||
search: true,
|
||||
minWidth: 120,
|
||||
rules: [{ required: true, message: '参数键名不能为空', trigger: 'blur' }]
|
||||
},
|
||||
value: {
|
||||
label: '参数键值',
|
||||
rules: [{ required: true, message: '参数键值不能为空', trigger: 'blur' }]
|
||||
},
|
||||
visible: {
|
||||
label: '是否可见',
|
||||
type: 'radio',
|
||||
width: 85,
|
||||
dicData: getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING),
|
||||
rules: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
|
||||
value: true
|
||||
},
|
||||
type: {
|
||||
label: '系统内置',
|
||||
search: true,
|
||||
width: 95,
|
||||
type: 'select',
|
||||
dicData: getIntDictOptions(DICT_TYPE.INFRA_CONFIG_TYPE),
|
||||
display: false
|
||||
},
|
||||
remark: {
|
||||
label: '备注',
|
||||
type: 'textarea',
|
||||
minRows: 2,
|
||||
span: 24,
|
||||
maxRows: 4,
|
||||
overHidden: true
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
search: true,
|
||||
searchRange: true,
|
||||
display: false,
|
||||
type: 'date',
|
||||
searchType: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
width: 160,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<{ id?: number }>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref<any>({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const permission = getCurrPermi(['infra:config'])
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
|
||||
if (searchObj.createTime?.length) {
|
||||
searchObj.createTime = getSearchDate(searchObj.createTime)
|
||||
} else delete searchObj.createTime
|
||||
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await ConfigApi.getConfigPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
tableForm.value = await ConfigApi.getConfig(tableForm.value.id)
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
let bool = await ConfigApi.createConfig(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
let bool = await ConfigApi.updateConfig(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await ConfigApi.deleteConfig(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
let searchObj = { ...tableSearch.value }
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
const data = await ConfigApi.exportConfig(searchObj)
|
||||
download.excel(data, '配置管理列表.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
268
src/views/infra/dataSourceConfig/index.vue
Normal file
268
src/views/infra/dataSourceConfig/index.vue
Normal file
@@ -0,0 +1,268 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<!-- 自定义操作栏 -->
|
||||
<template #menu="{ row, index }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="crudRef.rowEdit(row, index)"
|
||||
v-hasPermi="['infra:data-source-config:update']"
|
||||
v-if="row.id !== 0"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="crudRef.rowDel(row, index)"
|
||||
v-hasPermi="['infra:data-source-config:delete']"
|
||||
v-if="row.id !== 0"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #dbCode="{ row }">
|
||||
<span> {{ row.dbCode }} </span>
|
||||
<el-tag
|
||||
size="small"
|
||||
:type="row.isConnect == 'Y' ? 'primary' : 'danger'"
|
||||
effect="dark"
|
||||
class="pos-absolute right-2px top-2px"
|
||||
>
|
||||
{{ row.isConnect == 'Y' ? '已连接' : '已断开' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as DataSourceConfigApi from '@/api/infra/dataSourceConfig'
|
||||
|
||||
defineOptions({ name: 'InfraDataSourceConfig' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
|
||||
const DBList = [
|
||||
{
|
||||
label: 'MySQL 5.7+',
|
||||
value: 'MySQL',
|
||||
driverClass: 'com.mysql.cj.jdbc.Driver',
|
||||
url: 'jdbc:mysql://127.0.0.1:3306/jeelowcode?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai'
|
||||
},
|
||||
{
|
||||
label: 'Oracle',
|
||||
value: 'Oracle',
|
||||
driverClass: 'oracle.jdbc.OracleDriver',
|
||||
url: 'jdbc:oracle:thin:@127.0.0.1:1521:ORCL'
|
||||
},
|
||||
{
|
||||
label: 'postgresql',
|
||||
value: 'postgresql',
|
||||
driverClass: 'org.postgresql.Driver',
|
||||
url: 'jdbc:postgresql://127.0.0.1:5432/jeelowcode'
|
||||
},
|
||||
{
|
||||
label: '达梦',
|
||||
value: 'DM',
|
||||
driverClass: 'dm.jdbc.driver.DmDriver',
|
||||
url: 'jdbc:dm://127.0.0.1:5236/?jeelowcode&zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8'
|
||||
}
|
||||
]
|
||||
|
||||
const tableOption = reactive({
|
||||
editBtn: false,
|
||||
delBtn: false,
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
labelWidth: 120,
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
column: {
|
||||
dbCode: {
|
||||
label: '数据源编码',
|
||||
minWidth: 120,
|
||||
rules: [{ required: true, message: '数据源编码不能为空', trigger: 'blur' }],
|
||||
editDisabled: true
|
||||
},
|
||||
name: {
|
||||
label: '数据源名称',
|
||||
minWidth: 120,
|
||||
rules: [{ required: true, message: '数据源名称不能为空', trigger: 'blur' }]
|
||||
},
|
||||
dbType: {
|
||||
label: '数据库类型',
|
||||
type: 'select',
|
||||
dicData: DBList,
|
||||
width: 120,
|
||||
rules: [{ required: true, message: '数据库类型不能为空', trigger: 'change' }],
|
||||
change: ({ item }) => {
|
||||
if (item) {
|
||||
tableForm.value.driverClass = item.driverClass
|
||||
tableForm.value.url = item.url
|
||||
}
|
||||
}
|
||||
},
|
||||
driverClass: {
|
||||
label: '驱动类',
|
||||
hide: true,
|
||||
rules: [{ required: true, message: '驱动类不能为空', trigger: 'blur' }]
|
||||
},
|
||||
url: {
|
||||
label: '数据源连接',
|
||||
type: 'textarea',
|
||||
minRows: 1,
|
||||
maxRows: 3,
|
||||
overHidden: true,
|
||||
minWidth: 120,
|
||||
rules: [{ required: true, message: '数据源连接不能为空', trigger: 'blur' }]
|
||||
},
|
||||
username: {
|
||||
label: '用户名',
|
||||
width: 120,
|
||||
rules: [{ required: true, message: '用户名不能为空', trigger: 'blur' }]
|
||||
},
|
||||
password: {
|
||||
label: '密码',
|
||||
rules: [{ required: true, message: '密码不能为空', trigger: 'blur' }],
|
||||
hide: true
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
searchRange: true,
|
||||
display: false,
|
||||
type: 'datetime',
|
||||
width: 160,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<any>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const permission = getCurrPermi(['infra:data-source-config'])
|
||||
|
||||
const crudRef = ref()
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await DataSourceConfigApi.getDataSourceConfigList()
|
||||
tableData.value = data
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
tableForm.value = await DataSourceConfigApi.getDataSourceConfig(tableForm.value.id)
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
let bool = await DataSourceConfigApi.createDataSourceConfig(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
let bool = await DataSourceConfigApi.updateDataSourceConfig(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await DataSourceConfigApi.deleteDataSourceConfig(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
57
src/views/infra/dbDoc/index.vue
Normal file
57
src/views/infra/dbDoc/index.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<ContentWrap title="数据库文档">
|
||||
<div class="mb-10px">
|
||||
<el-button type="primary" plain @click="handleExport('HTML')">
|
||||
<Icon icon="ep:download" /> 导出 HTML
|
||||
</el-button>
|
||||
<el-button type="primary" plain @click="handleExport('Word')">
|
||||
<Icon icon="ep:download" /> 导出 Word
|
||||
</el-button>
|
||||
<el-button type="primary" plain @click="handleExport('Markdown')">
|
||||
<Icon icon="ep:download" /> 导出 Markdown
|
||||
</el-button>
|
||||
</div>
|
||||
<IFrame v-if="!loading" v-loading="loading" :src="src" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import download from '@/utils/download'
|
||||
import * as DbDocApi from '@/api/infra/dbDoc'
|
||||
|
||||
defineOptions({ name: 'InfraDBDoc' })
|
||||
|
||||
const loading = ref(true) // 是否加载中
|
||||
const src = ref('') // HTML 的地址
|
||||
|
||||
/** 页面加载 */
|
||||
const init = async () => {
|
||||
try {
|
||||
const data = await DbDocApi.exportHtml()
|
||||
const blob = new Blob([data], { type: 'text/html' })
|
||||
src.value = window.URL.createObjectURL(blob)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理导出 */
|
||||
const handleExport = async (type: string) => {
|
||||
if (type === 'HTML') {
|
||||
const res = await DbDocApi.exportHtml()
|
||||
download.html(res, '数据库文档.html')
|
||||
}
|
||||
if (type === 'Word') {
|
||||
const res = await DbDocApi.exportWord()
|
||||
download.word(res, '数据库文档.doc')
|
||||
}
|
||||
if (type === 'Markdown') {
|
||||
const res = await DbDocApi.exportMarkdown()
|
||||
download.markdown(res, '数据库文档.md')
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
await init()
|
||||
})
|
||||
</script>
|
||||
25
src/views/infra/druid/index.vue
Normal file
25
src/views/infra/druid/index.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<ContentWrap :bodyStyle="{ padding: '0px' }" class="!mb-0">
|
||||
<IFrame v-if="!loading" v-loading="loading" :src="url" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as ConfigApi from '@/api/infra/config'
|
||||
|
||||
defineOptions({ name: 'InfraDruid' })
|
||||
|
||||
const loading = ref(true) // 是否加载中
|
||||
const url = ref(import.meta.env.VITE_BASE_URL + '/druid/index.html')
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const data = await ConfigApi.getConfigKey('url.druid')
|
||||
if (data && data.length > 0) {
|
||||
url.value = data
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
103
src/views/infra/file/FileForm.vue
Normal file
103
src/views/infra/file/FileForm.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<DesignPopup v-model="dialogVisible" title="上传文件" width="40%" :is-footer="true">
|
||||
<div class="p-20px">
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
v-model:file-list="fileList"
|
||||
:action="uploadUrl"
|
||||
:auto-upload="false"
|
||||
:data="data"
|
||||
:disabled="formLoading"
|
||||
:limit="1"
|
||||
:on-change="handleFileChange"
|
||||
:on-error="submitFormError"
|
||||
:on-exceed="handleExceed"
|
||||
:on-success="submitFormSuccess"
|
||||
:http-request="httpRequest"
|
||||
accept=".jpg, .png, .gif"
|
||||
drag
|
||||
>
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text"> 将文件拖到此处,或 <em>点击上传</em></div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip" style="color: red">
|
||||
提示:仅允许导入 jpg、png、gif 格式文件!
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitFileForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</DesignPopup>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useUpload } from '@/components/UploadFile/src/useUpload'
|
||||
|
||||
defineOptions({ name: 'InfraFileForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const fileList = ref([]) // 文件列表
|
||||
const data = ref({ path: '' })
|
||||
const uploadRef = ref()
|
||||
|
||||
const { uploadUrl, httpRequest } = useUpload()
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async () => {
|
||||
dialogVisible.value = true
|
||||
resetForm()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 处理上传的文件发生变化 */
|
||||
const handleFileChange = (file) => {
|
||||
data.value.path = file.name
|
||||
}
|
||||
|
||||
/** 提交表单 */
|
||||
const submitFileForm = () => {
|
||||
if (fileList.value.length == 0) {
|
||||
message.error('请上传文件')
|
||||
return
|
||||
}
|
||||
unref(uploadRef)?.submit()
|
||||
}
|
||||
|
||||
/** 文件上传成功处理 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitFormSuccess = () => {
|
||||
// 清理
|
||||
dialogVisible.value = false
|
||||
formLoading.value = false
|
||||
unref(uploadRef)?.clearFiles()
|
||||
// 提示成功,并刷新
|
||||
setTimeout(() => {
|
||||
message.success(t('common.createSuccess'))
|
||||
emit('success')
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
/** 上传错误提示 */
|
||||
const submitFormError = (): void => {
|
||||
message.error('上传失败,请您重新上传!')
|
||||
formLoading.value = false
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
// 重置上传状态和文件
|
||||
formLoading.value = false
|
||||
uploadRef.value?.clearFiles()
|
||||
}
|
||||
|
||||
/** 文件数超出提示 */
|
||||
const handleExceed = (): void => {
|
||||
message.error('最多只能上传一个文件!')
|
||||
}
|
||||
</script>
|
||||
210
src/views/infra/file/index.vue
Normal file
210
src/views/infra/file/index.vue
Normal file
@@ -0,0 +1,210 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button type="primary" plain @click="openForm">
|
||||
<Icon icon="ep:upload" class="mr-5px" /> 上传文件
|
||||
</el-button>
|
||||
</template>
|
||||
<template #urlValue="scope">
|
||||
{{ scope.row.url }}
|
||||
</template>
|
||||
<template #url="{ row }">
|
||||
<el-image
|
||||
v-if="row.type.includes('image')"
|
||||
class="h-80px w-80px"
|
||||
lazy
|
||||
:src="row.url"
|
||||
:preview-src-list="[row.url]"
|
||||
preview-teleported
|
||||
fit="cover"
|
||||
/>
|
||||
<el-link
|
||||
v-else-if="row.type.includes('pdf')"
|
||||
type="primary"
|
||||
:href="row.url"
|
||||
:underline="false"
|
||||
target="_blank"
|
||||
>预览</el-link
|
||||
>
|
||||
<el-link v-else type="primary" download :href="row.url" :underline="false" target="_blank"
|
||||
>下载</el-link
|
||||
>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<FileForm ref="formRef" @success="getTableData" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { fileSizeFormatter } from '@/utils'
|
||||
import { dateFormatter, getSearchDate } from '@/utils/formatTime'
|
||||
import * as FileApi from '@/api/infra/file'
|
||||
import FileForm from './FileForm.vue'
|
||||
|
||||
defineOptions({ name: 'InfraFile' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
addBtn: false,
|
||||
editBtn: false,
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
menuWidth: 120,
|
||||
column: {
|
||||
name: {
|
||||
label: '文件名',
|
||||
minWidth: 120,
|
||||
overHidden: true
|
||||
},
|
||||
path: {
|
||||
label: '文件路径',
|
||||
minWidth: 120,
|
||||
search: true,
|
||||
overHidden: true
|
||||
},
|
||||
urlValue: {
|
||||
label: 'URL',
|
||||
minWidth: 100,
|
||||
overHidden: true
|
||||
},
|
||||
size: {
|
||||
label: '文件大小',
|
||||
minWidth: 100,
|
||||
formatter: fileSizeFormatter,
|
||||
width: '120px'
|
||||
},
|
||||
type: {
|
||||
label: '文件类型',
|
||||
minWidth: 120,
|
||||
search: true,
|
||||
overHidden: true
|
||||
},
|
||||
url: {
|
||||
label: '文件内容',
|
||||
width: 120
|
||||
},
|
||||
createTime: {
|
||||
label: '上传时间',
|
||||
searchRange: true,
|
||||
search: true,
|
||||
type: 'date',
|
||||
searchType: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
width: 160,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<{ id?: number }>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref<any>({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const permission = getCurrPermi(['infra:file'])
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
|
||||
if (searchObj.createTime?.length) {
|
||||
searchObj.createTime = getSearchDate(searchObj.createTime)
|
||||
} else delete searchObj.createTime
|
||||
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await FileApi.getFilePage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await FileApi.deleteFile(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = () => {
|
||||
formRef.value.open()
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
452
src/views/infra/fileConfig/index.vue
Normal file
452
src/views/infra/fileConfig/index.vue
Normal file
@@ -0,0 +1,452 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<!-- 表格 -->
|
||||
<template #storage="scope">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.INFRA_FILE_STORAGE"
|
||||
:value="scope.row.storage ? scope.row.storage : ''"
|
||||
/>
|
||||
</template>
|
||||
<template #master="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.master !== undefined"
|
||||
:type="DICT_TYPE.INFRA_BOOLEAN_STRING"
|
||||
:value="scope.row.master"
|
||||
/>
|
||||
</template>
|
||||
<!-- 表单 -->
|
||||
<!-- 自定义操作栏 -->
|
||||
<template #menu="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
class="is-text"
|
||||
icon="el-icon-operation"
|
||||
:disabled="row.master"
|
||||
@click="handleMaster(row.id)"
|
||||
v-hasPermi="['infra:file-config:update']"
|
||||
>
|
||||
主配置
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
class="is-text"
|
||||
icon="el-icon-tickets"
|
||||
type="primary"
|
||||
@click="handleTest(row.id)"
|
||||
>
|
||||
测试
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter, getSearchDate } from '@/utils/formatTime'
|
||||
import * as FileConfigApi from '@/api/infra/fileConfig'
|
||||
|
||||
defineOptions({ name: 'InfraFileConfig' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
menuWidth: 300,
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
labelWidth: 100,
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
column: {
|
||||
id: {
|
||||
label: '编号',
|
||||
width: 80,
|
||||
display: false
|
||||
},
|
||||
name: {
|
||||
label: '配置名',
|
||||
search: true,
|
||||
minWidth: 90,
|
||||
rules: [{ required: true, message: '配置名不能为空', trigger: 'blur' }]
|
||||
},
|
||||
remark: {
|
||||
label: '备注'
|
||||
},
|
||||
storage: {
|
||||
label: '存储器',
|
||||
type: 'select',
|
||||
disabled: false,
|
||||
search: true,
|
||||
span: 8,
|
||||
minWidth: 90,
|
||||
dicData: getIntDictOptions(DICT_TYPE.INFRA_FILE_STORAGE),
|
||||
rules: [{ required: true, message: '存储器不能为空', trigger: 'blur' }],
|
||||
change: ({ value, column }) => {
|
||||
let {
|
||||
basePath,
|
||||
host,
|
||||
port,
|
||||
username,
|
||||
password,
|
||||
mode,
|
||||
endpoint,
|
||||
bucket,
|
||||
accessKey,
|
||||
accessSecret,
|
||||
domain
|
||||
} = tableOption.column
|
||||
if (value) {
|
||||
domain.display = true
|
||||
if (value === 20) {
|
||||
domain.rules = []
|
||||
} else {
|
||||
domain.rules = [{ required: true, message: '自定义域名不能为空', trigger: 'blur' }]
|
||||
}
|
||||
} else {
|
||||
domain.display = false
|
||||
}
|
||||
if (value >= 10 && value <= 12) {
|
||||
basePath.display = true
|
||||
} else {
|
||||
basePath.display = false
|
||||
}
|
||||
if (value >= 11 && value <= 12) {
|
||||
host.display = true
|
||||
port.display = true
|
||||
username.display = true
|
||||
password.display = true
|
||||
} else {
|
||||
host.display = false
|
||||
port.display = false
|
||||
username.display = false
|
||||
password.display = false
|
||||
}
|
||||
if (value === 11) {
|
||||
mode.display = true
|
||||
} else {
|
||||
mode.display = false
|
||||
}
|
||||
if (value === 20) {
|
||||
endpoint.display = true
|
||||
bucket.display = true
|
||||
accessKey.display = true
|
||||
accessSecret.display = true
|
||||
} else {
|
||||
endpoint.display = false
|
||||
bucket.display = false
|
||||
accessKey.display = false
|
||||
accessSecret.display = false
|
||||
}
|
||||
}
|
||||
},
|
||||
master: {
|
||||
label: '主配置',
|
||||
type: 'select',
|
||||
display: false,
|
||||
span: 8,
|
||||
minWidth: 90,
|
||||
dicData: getIntDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)
|
||||
},
|
||||
basePath: {
|
||||
label: '基础路径',
|
||||
hide: true,
|
||||
display: false,
|
||||
rules: [{ required: true, message: '基础路径不能为空', trigger: 'blur' }]
|
||||
},
|
||||
host: {
|
||||
label: '主机地址',
|
||||
hide: true,
|
||||
display: false,
|
||||
rules: [{ required: true, message: '主机地址不能为空', trigger: 'blur' }]
|
||||
},
|
||||
port: {
|
||||
label: '主机端口',
|
||||
type: 'number',
|
||||
span: 8,
|
||||
hide: true,
|
||||
display: false,
|
||||
rules: [{ required: true, message: '主机端口不能为空', trigger: 'blur' }]
|
||||
},
|
||||
username: {
|
||||
label: '用户名',
|
||||
hide: true,
|
||||
display: false,
|
||||
rules: [{ required: true, message: '用户名不能为空', trigger: 'blur' }]
|
||||
},
|
||||
password: {
|
||||
label: '密码',
|
||||
hide: true,
|
||||
display: false,
|
||||
rules: [{ required: true, message: '密码不能为空', trigger: 'blur' }]
|
||||
},
|
||||
mode: {
|
||||
label: '连接模式',
|
||||
type: 'radio',
|
||||
hide: true,
|
||||
display: false,
|
||||
dicData: [
|
||||
{ label: '主动模式', value: 'Active' },
|
||||
{ label: '被动模式', value: 'Passive' }
|
||||
],
|
||||
rules: [{ required: true, message: '连接模式不能为空', trigger: 'blur' }]
|
||||
},
|
||||
endpoint: {
|
||||
label: '节点地址',
|
||||
hide: true,
|
||||
display: false,
|
||||
rules: [{ required: true, message: '节点地址不能为空', trigger: 'blur' }]
|
||||
},
|
||||
bucket: {
|
||||
label: '存储 bucket',
|
||||
hide: true,
|
||||
display: false,
|
||||
rules: [{ required: true, message: '存储 bucket不能为空', trigger: 'blur' }]
|
||||
},
|
||||
accessKey: {
|
||||
label: 'accessKey',
|
||||
hide: true,
|
||||
display: false,
|
||||
rules: [{ required: true, message: 'accessKey不能为空', trigger: 'blur' }]
|
||||
},
|
||||
accessSecret: {
|
||||
label: 'accessSecret',
|
||||
hide: true,
|
||||
display: false,
|
||||
rules: [{ required: true, message: 'accessSecret不能为空', trigger: 'blur' }]
|
||||
},
|
||||
domain: {
|
||||
label: '自定义域名',
|
||||
hide: true,
|
||||
display: false,
|
||||
rules: [{ required: true, message: '自定义域名不能为空', trigger: 'blur' }]
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
searchRange: true,
|
||||
search: true,
|
||||
display: false,
|
||||
type: 'date',
|
||||
searchType: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<{ id?: number }>({})
|
||||
let tableChildForm: string[] = reactive([]) // 保存参数,方便提交
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref<any>({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const permission = getCurrPermi(['infra:file-config'])
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
|
||||
if (searchObj.createTime?.length) {
|
||||
searchObj.createTime = getSearchDate(searchObj.createTime)
|
||||
} else delete searchObj.createTime
|
||||
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await FileConfigApi.getFileConfigPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
tableOption.column.storage.disabled = true
|
||||
let data = await FileConfigApi.getFileConfig(tableForm.value.id)
|
||||
tableChildForm = Object.keys(data.config)
|
||||
// 数据处理
|
||||
data = {
|
||||
storage: data.storage,
|
||||
remark: data.remark,
|
||||
name: data.name,
|
||||
master: data.master,
|
||||
id: data.id,
|
||||
createTime: data.createTime,
|
||||
...data.config
|
||||
}
|
||||
tableForm.value = data
|
||||
} else {
|
||||
tableOption.column.storage.disabled = false
|
||||
}
|
||||
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
// 删除多余表单
|
||||
Object.keys(tableOption.column).forEach((item) => {
|
||||
if (tableOption.column[item].display != undefined && !tableOption.column[item].display)
|
||||
delete form[item]
|
||||
})
|
||||
|
||||
// 添加config参数
|
||||
let config = {}
|
||||
Object.keys(form).forEach((item) => {
|
||||
if (item !== 'storage' && item !== 'remark' && item !== 'name') {
|
||||
config[item] = form[item]
|
||||
delete form[item]
|
||||
}
|
||||
})
|
||||
form.config = config
|
||||
// 发送请求
|
||||
let bool = await FileConfigApi.createFileConfig(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
// 添加config参数
|
||||
let config = {}
|
||||
tableChildForm.forEach((item) => {
|
||||
if (form[item]) {
|
||||
config[item] = form[item]
|
||||
}
|
||||
})
|
||||
Object.keys(form).forEach((item) => {
|
||||
if (
|
||||
item !== 'storage' &&
|
||||
item !== 'remark' &&
|
||||
item !== 'name' &&
|
||||
item !== 'master' &&
|
||||
item !== 'id' &&
|
||||
item !== 'createTime'
|
||||
)
|
||||
delete form[item] //清除多余参数(已经放在config中)
|
||||
})
|
||||
form.config = config
|
||||
|
||||
let bool = await FileConfigApi.updateFileConfig(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await FileConfigApi.deleteFileConfig(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 主配置按钮操作 */
|
||||
const handleMaster = async (id) => {
|
||||
try {
|
||||
await message.confirm('是否确认修改配置编号为"' + id + '"的数据项为主配置?')
|
||||
await FileConfigApi.updateFileConfigMaster(id)
|
||||
message.success(t('common.updateSuccess'))
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 测试按钮操作 */
|
||||
const handleTest = async (id) => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await FileConfigApi.testFileConfig(id)
|
||||
message.alert(
|
||||
`<div>测试通过,上传文件成功!访问地址:</div>
|
||||
<div style="word-break: break-word;">${response}</div>
|
||||
`,
|
||||
'',
|
||||
{
|
||||
dangerouslyUseHTMLString: true
|
||||
}
|
||||
)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
398
src/views/infra/job/index.vue
Normal file
398
src/views/infra/job/index.vue
Normal file
@@ -0,0 +1,398 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
v-model="tableForm"
|
||||
ref="crudRef"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #menu-left="{ size }">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
:size="size"
|
||||
icon="el-icon-download"
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['infra:job:export']"
|
||||
>导出</el-button
|
||||
>
|
||||
<el-button type="info" plain @click="handleJobLog()" v-hasPermi="['infra:job:query']">
|
||||
<Icon icon="ep:zoom-in" class="mr-5px" /> 执行日志
|
||||
</el-button>
|
||||
</template>
|
||||
<template #menu="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
class="is-text"
|
||||
:icon="
|
||||
scope.row.status === InfraJobStatusEnum.STOP
|
||||
? 'el-icon-video-play'
|
||||
: 'el-icon-video-pause'
|
||||
"
|
||||
@click="handleChangeStatus(scope.row)"
|
||||
v-hasPermi="['infra:job:update']"
|
||||
>
|
||||
{{ scope.row.status === InfraJobStatusEnum.STOP ? '开启' : '暂停' }}
|
||||
</el-button>
|
||||
<el-dropdown
|
||||
@command="(command) => handleCommand(command, scope.row)"
|
||||
v-hasPermi="['infra:job:trigger', 'infra:job:query']"
|
||||
>
|
||||
<div class="pt-3px pr-4px pb-3px pl-4px cursor-pointer">
|
||||
<el-text type="primary">
|
||||
<span>更多</span>
|
||||
<Icon :size="16" icon="iconamoon:arrow-down-2-light" />
|
||||
</el-text>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="handleRun" v-if="checkPermi(['infra:job:trigger'])">
|
||||
执行一次
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
@click="crudRef.rowView(scope.row, scope.index)"
|
||||
v-if="checkPermi(['infra:job:query'])"
|
||||
>
|
||||
任务详细
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="handleJobLog" v-if="checkPermi(['infra:job:query'])">
|
||||
调度日志
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
<template #accountCount="scope">
|
||||
<el-tag>{{ scope.row.accountCount }}</el-tag>
|
||||
</template>
|
||||
<template #status="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.status !== undefined"
|
||||
:type="DICT_TYPE.INFRA_JOB_STATUS"
|
||||
:value="scope.row.status"
|
||||
/>
|
||||
</template>
|
||||
<template #cronExpression-form="{ type }">
|
||||
<div v-if="type == 'view'">{{ tableForm.cronExpression }}</div>
|
||||
<Crontab v-else v-model="tableForm.cronExpression" />
|
||||
</template>
|
||||
<template #stayus-form="{ value }">
|
||||
<dict-tag v-if="value" :type="DICT_TYPE.INFRA_JOB_STATUS" :value="value" />
|
||||
</template>
|
||||
<template #executionTime-form>
|
||||
<div class="pt-10px">
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="(nextTime, index) in nextTimes"
|
||||
:key="index"
|
||||
:timestamp="formatDate(nextTime)"
|
||||
>
|
||||
第 {{ index + 1 }} 次
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</div>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
<DesignPopup v-model="logPopup.show" :title="logPopup.title" width="80%" controlType="drawer">
|
||||
<div class="p-20px">
|
||||
<InfraJobLog ref="logRef" :jobId="logPopup.jobId"></InfraJobLog>
|
||||
</div>
|
||||
</DesignPopup>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { checkPermi } from '@/utils/permission'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as JobApi from '@/api/infra/job'
|
||||
import { InfraJobStatusEnum } from '@/utils/constants'
|
||||
import InfraJobLog from './logger/index.vue'
|
||||
|
||||
defineOptions({ name: 'SystemTenant' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
const nextTimes = ref([]) // 下一轮执行时间的数组
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
searchLabelWidth: 120,
|
||||
menuWidth: 300,
|
||||
labelSuffix: ' ',
|
||||
labelWidth: 120,
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
column: {
|
||||
name: {
|
||||
label: '任务名称',
|
||||
minWidth: 110,
|
||||
search: true,
|
||||
rules: [{ required: true, message: '任务名称不能为空', trigger: 'blur' }]
|
||||
},
|
||||
status: {
|
||||
label: '任务状态',
|
||||
width: 90,
|
||||
search: true,
|
||||
type: 'select',
|
||||
display: false,
|
||||
dicData: getIntDictOptions(DICT_TYPE.INFRA_JOB_STATUS)
|
||||
},
|
||||
handlerName: {
|
||||
label: '处理器的名字',
|
||||
minWidth: 100,
|
||||
search: true,
|
||||
editDisabled: true,
|
||||
rules: [{ required: true, message: '处理器的名字不能为空', trigger: 'blur' }]
|
||||
},
|
||||
handlerParam: {
|
||||
label: '处理器的参数',
|
||||
minWidth: 110
|
||||
},
|
||||
cronExpression: {
|
||||
label: 'CRON 表达式',
|
||||
minWidth: 110,
|
||||
rules: [{ required: true, message: 'CRON 表达式不能为空', trigger: 'blur' }]
|
||||
},
|
||||
retryCount: {
|
||||
label: '重试次数',
|
||||
hide: true,
|
||||
rules: [{ required: true, message: '重试次数不能为空', trigger: 'blur' }],
|
||||
placeholder: '请输入重试次数。设置为 0 时,不进行重试'
|
||||
},
|
||||
retryInterval: {
|
||||
label: '重试间隔',
|
||||
hide: true,
|
||||
rules: [{ required: true, message: '重试间隔不能为空', trigger: 'blur' }],
|
||||
placeholder: '请输入重试间隔,单位:毫秒。设置为 0 时,无需间隔'
|
||||
},
|
||||
monitorTimeout: {
|
||||
label: '监控超时时间',
|
||||
placeholder: '请输入监控超时时间,单位:毫秒',
|
||||
hide: true
|
||||
},
|
||||
executionTime: {
|
||||
label: '后续执行时间',
|
||||
display: false,
|
||||
hide: true
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<any>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const logPopup = ref({ show: false, title: '', jobId: 0 })
|
||||
const permission = getCurrPermi(['infra:job'])
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
const crudRef = ref()
|
||||
const logRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await JobApi.getJobPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
loading.value = true
|
||||
tableForm.value = await JobApi.getJob(tableForm.value.id)
|
||||
if (type == 'view') nextTimes.value = await JobApi.getJobNextTimes(tableForm.value.id!)
|
||||
loading.value = false
|
||||
}
|
||||
if (type === 'view') {
|
||||
tableOption.column.status.display = true
|
||||
tableOption.column.executionTime.display = true
|
||||
} else {
|
||||
tableOption.column.status.display = false
|
||||
tableOption.column.executionTime.display = false
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
let bool = await JobApi.createJob(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
let bool = await JobApi.updateJob(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await JobApi.deleteJob(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 修改状态操作 */
|
||||
const handleChangeStatus = async (row: JobApi.JobVO) => {
|
||||
try {
|
||||
// 修改状态的二次确认
|
||||
const text = row.status === InfraJobStatusEnum.STOP ? '开启' : '关闭'
|
||||
await message.confirm(
|
||||
'确认要' + text + '定时任务编号为"' + row.id + '"的数据项?',
|
||||
t('common.reminder')
|
||||
)
|
||||
const status =
|
||||
row.status === InfraJobStatusEnum.STOP ? InfraJobStatusEnum.NORMAL : InfraJobStatusEnum.STOP
|
||||
await JobApi.updateJobStatus(row.id, status)
|
||||
message.success(text + '成功')
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {
|
||||
// 取消后,进行恢复按钮
|
||||
row.status =
|
||||
row.status === InfraJobStatusEnum.NORMAL ? InfraJobStatusEnum.STOP : InfraJobStatusEnum.NORMAL
|
||||
}
|
||||
}
|
||||
|
||||
/** '更多'操作按钮 */
|
||||
const handleCommand = (command, row) => {
|
||||
switch (command) {
|
||||
case 'handleRun':
|
||||
handleRun(row)
|
||||
break
|
||||
case 'handleJobLog':
|
||||
handleJobLog(row)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/** 执行一次 */
|
||||
const handleRun = async (row: JobApi.JobVO) => {
|
||||
try {
|
||||
// 二次确认
|
||||
await message.confirm('确认要立即执行一次' + row.name + '?', t('common.reminder'))
|
||||
// 提交执行
|
||||
await JobApi.runJob(row.id)
|
||||
message.success('执行成功')
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 跳转执行日志 */
|
||||
const handleJobLog = (row?) => {
|
||||
logPopup.value = {
|
||||
show: true,
|
||||
title: row ? `${row.name} 调度日志` : '所有的调度日志',
|
||||
jobId: row?.id || 0
|
||||
}
|
||||
setTimeout(() => {
|
||||
if (logRef.value) logRef.value.resetChange()
|
||||
}, 30)
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
let searchObj = { ...tableSearch.value }
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
const data = await JobApi.exportJob(searchObj)
|
||||
download.excel(data, '定时任务列表.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-dropdown {
|
||||
padding: 4px 2px;
|
||||
}
|
||||
</style>
|
||||
230
src/views/infra/job/logger/index.vue
Normal file
230
src/views/infra/job/logger/index.vue
Normal file
@@ -0,0 +1,230 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #menu-left="{ size }">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
:size="size"
|
||||
icon="el-icon-download"
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['system:tenant:export']"
|
||||
>导出</el-button
|
||||
>
|
||||
</template>
|
||||
<template #status="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.status !== undefined"
|
||||
:type="DICT_TYPE.INFRA_JOB_LOG_STATUS"
|
||||
:value="scope.row.status"
|
||||
/>
|
||||
</template>
|
||||
<template #beginTimeText-form>
|
||||
{{ formatDate(tableForm.beginTime) + ' ~ ' + formatDate(tableForm.endTime) }}
|
||||
</template>
|
||||
<template #duration-form="{ value }">
|
||||
{{ value + ' 毫秒' }}
|
||||
</template>
|
||||
<template #status-form="{ value }">
|
||||
<dict-tag
|
||||
v-if="value !== undefined"
|
||||
:type="DICT_TYPE.INFRA_JOB_LOG_STATUS"
|
||||
:value="value"
|
||||
/>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { formatDate, getSearchDate } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as JobLogApi from '@/api/infra/jobLog'
|
||||
|
||||
defineOptions({ name: 'InfraJobLog' })
|
||||
|
||||
interface Props {
|
||||
jobId?: number
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
addBtn: false,
|
||||
editBtn: false,
|
||||
delBtn: false,
|
||||
viewBtn: true,
|
||||
viewBtnText: '详情',
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
searchLabelWidth: 100,
|
||||
labelSuffix: ' ',
|
||||
labelWidth: 120,
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
menuWidth: 120,
|
||||
column: {
|
||||
jobId: {
|
||||
label: '任务编号',
|
||||
width: 90
|
||||
},
|
||||
handlerName: {
|
||||
label: '处理器的名字',
|
||||
search: true,
|
||||
searchSpan: 5
|
||||
},
|
||||
beginTime: {
|
||||
label: '执行时间',
|
||||
hide: true,
|
||||
display: false,
|
||||
search: true,
|
||||
type: 'date',
|
||||
searchRange: true,
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
startPlaceholder: '开始执行时间',
|
||||
endPlaceholder: '结束执行时间',
|
||||
searchSpan: 8,
|
||||
},
|
||||
handlerParam: {
|
||||
label: '处理器的参数'
|
||||
},
|
||||
executeIndex: {
|
||||
label: '第几次执行',
|
||||
width: 100
|
||||
},
|
||||
beginTimeText: {
|
||||
label: '执行时间',
|
||||
html: true,
|
||||
width: 160,
|
||||
formatter: (row) => {
|
||||
return `<div>
|
||||
<div>${formatDate(row.beginTime)}</div>
|
||||
<div>${formatDate(row.endTime)}</div>
|
||||
</div>`
|
||||
}
|
||||
},
|
||||
duration: {
|
||||
label: '执行时长',
|
||||
width: 100,
|
||||
formatter: (row) => {
|
||||
return row.duration + '毫秒'
|
||||
}
|
||||
},
|
||||
status: {
|
||||
label: '任务状态',
|
||||
searchSpan: 5,
|
||||
search: true,
|
||||
width: 90,
|
||||
type: 'select',
|
||||
dicData: getIntDictOptions(DICT_TYPE.INFRA_JOB_LOG_STATUS)
|
||||
},
|
||||
result: {
|
||||
label: '执行结果',
|
||||
hide: true
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<any>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
let searchObj: any = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
if (searchObj.beginTime?.length) {
|
||||
const dateArr = getSearchDate(searchObj.beginTime)
|
||||
searchObj.beginTime = dateArr[0]
|
||||
searchObj.endTime = dateArr[1]
|
||||
} else delete searchObj.beginTime
|
||||
if (props.jobId !== 0) searchObj['jobId'] = props.jobId
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await JobLogApi.getJobLogPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
let searchObj: any = { ...tableSearch.value }
|
||||
if (props.jobId) searchObj.jobId = props.jobId
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
|
||||
const data = await JobLogApi.exportJobLog(searchObj)
|
||||
download.excel(data, '调度日志列表.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
|
||||
defineExpose({ resetChange })
|
||||
</script>
|
||||
265
src/views/infra/redis/index.vue
Normal file
265
src/views/infra/redis/index.vue
Normal file
@@ -0,0 +1,265 @@
|
||||
<template>
|
||||
<el-scrollbar height="calc(100vh - 88px - 40px - 50px)">
|
||||
<el-row>
|
||||
<!-- 基本信息 -->
|
||||
<el-col :span="24" class="card-box" shadow="hover">
|
||||
<el-card>
|
||||
<el-descriptions title="基本信息" :column="6" border>
|
||||
<el-descriptions-item label="Redis版本 :">
|
||||
{{ cache?.info?.redis_version }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="运行模式 :">
|
||||
{{ cache?.info?.redis_mode == 'standalone' ? '单机' : '集群' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="端口 :">
|
||||
{{ cache?.info?.tcp_port }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="客户端数 :">
|
||||
{{ cache?.info?.connected_clients }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="运行时间(天) :">
|
||||
{{ cache?.info?.uptime_in_days }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="使用内存 :">
|
||||
{{ cache?.info?.used_memory_human }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="使用CPU :">
|
||||
{{ cache?.info ? parseFloat(cache?.info?.used_cpu_user_children).toFixed(2) : '' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="内存配置 :">
|
||||
{{ cache?.info?.maxmemory_human }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="AOF是否开启 :">
|
||||
{{ cache?.info?.aof_enabled == '0' ? '否' : '是' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="RDB是否成功 :">
|
||||
{{ cache?.info?.rdb_last_bgsave_status }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="Key数量 :">
|
||||
{{ cache?.dbSize }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="网络入口/出口 :">
|
||||
{{ cache?.info?.instantaneous_input_kbps }}kps/
|
||||
{{ cache?.info?.instantaneous_output_kbps }}kps
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<!-- 命令统计 -->
|
||||
<el-col :span="12" class="mt-3">
|
||||
<el-card :gutter="12" shadow="hover">
|
||||
<Echart :options="commandStatsRefChika" :height="420" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
<!-- 内存使用量统计 -->
|
||||
<el-col :span="12" class="mt-3">
|
||||
<el-card class="ml-3" :gutter="12" shadow="hover">
|
||||
<Echart :options="usedmemoryEchartChika" :height="420" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as RedisApi from '@/api/infra/redis'
|
||||
import { RedisMonitorInfoVO } from '@/api/infra/redis/types'
|
||||
const cache = ref<RedisMonitorInfoVO>()
|
||||
|
||||
// 基本信息
|
||||
const readRedisInfo = async () => {
|
||||
const data = await RedisApi.getCache()
|
||||
cache.value = data
|
||||
}
|
||||
|
||||
// 内存使用情况
|
||||
const usedmemoryEchartChika = reactive<any>({
|
||||
title: {
|
||||
// 仪表盘标题。
|
||||
text: '内存使用情况',
|
||||
left: 'center',
|
||||
show: true, // 是否显示标题,默认 true。
|
||||
offsetCenter: [0, '20%'], //相对于仪表盘中心的偏移位置,数组第一项是水平方向的偏移,第二项是垂直方向的偏移。可以是绝对的数值,也可以是相对于仪表盘半径的百分比。
|
||||
color: 'yellow', // 文字的颜色,默认 #333。
|
||||
fontSize: 20 // 文字的字体大小,默认 15。
|
||||
},
|
||||
toolbox: {
|
||||
show: false,
|
||||
feature: {
|
||||
restore: { show: true },
|
||||
saveAsImage: { show: true }
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '峰值',
|
||||
type: 'gauge',
|
||||
min: 0,
|
||||
max: 50,
|
||||
splitNumber: 10,
|
||||
//这是指针的颜色
|
||||
color: '#F5C74E',
|
||||
radius: '85%',
|
||||
center: ['50%', '50%'],
|
||||
startAngle: 225,
|
||||
endAngle: -45,
|
||||
axisLine: {
|
||||
// 坐标轴线
|
||||
lineStyle: {
|
||||
// 属性lineStyle控制线条样式
|
||||
color: [
|
||||
[0.2, '#7FFF00'],
|
||||
[0.8, '#00FFFF'],
|
||||
[1, '#FF0000']
|
||||
],
|
||||
//width: 6 外框的大小(环的宽度)
|
||||
width: 10
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
// 坐标轴小标记
|
||||
//里面的线长是5(短线)
|
||||
length: 5, // 属性length控制线长
|
||||
lineStyle: {
|
||||
// 属性lineStyle控制线条样式
|
||||
color: '#76D9D7'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
// 分隔线
|
||||
length: 20, // 属性length控制线长
|
||||
lineStyle: {
|
||||
// 属性lineStyle(详见lineStyle)控制线条样式
|
||||
color: '#76D9D7'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#76D9D7',
|
||||
distance: 15,
|
||||
fontSize: 15
|
||||
},
|
||||
pointer: {
|
||||
// 指针的大小
|
||||
width: 7,
|
||||
show: true
|
||||
},
|
||||
detail: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
// 里面文字下的数值大小(50)
|
||||
fontSize: 15,
|
||||
color: '#FFFFFF'
|
||||
},
|
||||
valueAnimation: true
|
||||
},
|
||||
progress: {
|
||||
show: true
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// 指令使用情况
|
||||
const commandStatsRefChika = reactive({
|
||||
title: {
|
||||
text: '命令统计',
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{a} <br/>{b} : {c} ({d}%)'
|
||||
},
|
||||
legend: {
|
||||
type: 'scroll',
|
||||
orient: 'vertical',
|
||||
right: 30,
|
||||
top: 10,
|
||||
bottom: 20,
|
||||
data: [] as any[],
|
||||
textStyle: {
|
||||
color: '#a1a1a1'
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '命令',
|
||||
type: 'pie',
|
||||
radius: [20, 120],
|
||||
center: ['40%', '60%'],
|
||||
data: [] as any[],
|
||||
roseType: 'radius',
|
||||
label: {
|
||||
show: true
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true
|
||||
},
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
/** 加载数据 */
|
||||
const getSummary = () => {
|
||||
// 初始化命令图表
|
||||
initCommandStatsChart()
|
||||
usedMemoryInstance()
|
||||
}
|
||||
|
||||
/** 命令使用情况 */
|
||||
const initCommandStatsChart = async () => {
|
||||
usedmemoryEchartChika.series[0].data = []
|
||||
// 发起请求
|
||||
try {
|
||||
const data = await RedisApi.getCache()
|
||||
cache.value = data
|
||||
// 处理数据
|
||||
const commandStats = [] as any[]
|
||||
const nameList = [] as string[]
|
||||
data.commandStats.forEach((row) => {
|
||||
commandStats.push({
|
||||
name: row.command,
|
||||
value: row.calls
|
||||
})
|
||||
nameList.push(row.command)
|
||||
})
|
||||
commandStatsRefChika.legend.data = nameList
|
||||
commandStatsRefChika.series[0].data = commandStats
|
||||
} catch {}
|
||||
}
|
||||
const usedMemoryInstance = async () => {
|
||||
try {
|
||||
const data = await RedisApi.getCache()
|
||||
cache.value = data
|
||||
// 仪表盘详情,用于显示数据。
|
||||
usedmemoryEchartChika.series[0].detail = {
|
||||
show: true, // 是否显示详情,默认 true。
|
||||
offsetCenter: [0, '50%'], // 相对于仪表盘中心的偏移位置,数组第一项是水平方向的偏移,第二项是垂直方向的偏移。可以是绝对的数值,也可以是相对于仪表盘半径的百分比。
|
||||
color: 'auto', // 文字的颜色,默认 auto。
|
||||
fontSize: 30, // 文字的字体大小,默认 15。
|
||||
formatter: cache.value!.info.used_memory_human // 格式化函数或者字符串
|
||||
}
|
||||
|
||||
usedmemoryEchartChika.series[0].data[0] = {
|
||||
value: cache.value!.info.used_memory_human,
|
||||
name: '内存消耗'
|
||||
}
|
||||
usedmemoryEchartChika.tooltip = {
|
||||
formatter: '{b} <br/>{a} : ' + cache.value!.info.used_memory_human
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
// 读取 redis 信息
|
||||
readRedisInfo()
|
||||
// 加载数据
|
||||
getSummary()
|
||||
})
|
||||
</script>
|
||||
27
src/views/infra/server/index.vue
Normal file
27
src/views/infra/server/index.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
|
||||
<ContentWrap :bodyStyle="{ padding: '0px' }" class="!mb-0">
|
||||
<IFrame v-if="!loading" v-loading="loading" :src="src" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as ConfigApi from '@/api/infra/config'
|
||||
|
||||
defineOptions({ name: 'InfraAdminServer' })
|
||||
|
||||
const loading = ref(true) // 是否加载中
|
||||
const src = ref(import.meta.env.VITE_BASE_URL + '/admin/applications')
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
try {
|
||||
// 友情提示:如果访问出现 404 问题:
|
||||
const data = await ConfigApi.getConfigKey('url.spring-boot-admin')
|
||||
if (data && data.length > 0) {
|
||||
src.value = data
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
25
src/views/infra/skywalking/index.vue
Normal file
25
src/views/infra/skywalking/index.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<ContentWrap :bodyStyle="{ padding: '0px' }" class="!mb-0">
|
||||
<IFrame v-if="!loading" v-loading="loading" :src="src" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as ConfigApi from '@/api/infra/config'
|
||||
|
||||
defineOptions({ name: 'InfraSkyWalking' })
|
||||
|
||||
const loading = ref(true) // 是否加载中
|
||||
const src = ref('')
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const data = await ConfigApi.getConfigKey('url.skywalking')
|
||||
if (data && data.length > 0) {
|
||||
src.value = data
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
26
src/views/infra/swagger/index.vue
Normal file
26
src/views/infra/swagger/index.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<ContentWrap :bodyStyle="{ padding: '0px' }" class="!mb-0">
|
||||
<IFrame v-if="!loading" v-loading="loading" :src="src" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as ConfigApi from '@/api/infra/config'
|
||||
|
||||
defineOptions({ name: 'InfraSwagger' })
|
||||
|
||||
const loading = ref(true) // 是否加载中
|
||||
const src = ref(import.meta.env.VITE_BASE_URL + '/doc.html') // Knife4j UI
|
||||
// const src = ref(import.meta.env.VITE_BASE_URL + '/swagger-ui') // Swagger UI
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const data = await ConfigApi.getConfigKey('url.swagger')
|
||||
if (data && data.length > 0) {
|
||||
src.value = data
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
184
src/views/infra/webSocket/index.vue
Normal file
184
src/views/infra/webSocket/index.vue
Normal file
@@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<div class="flex">
|
||||
<!-- 左侧:建立连接、发送消息 -->
|
||||
<el-card :gutter="12" class="w-1/2" shadow="always">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>连接</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex items-center">
|
||||
<span class="mr-4 text-lg font-medium"> 连接状态: </span>
|
||||
<el-tag :color="getTagColor">{{ status }}</el-tag>
|
||||
</div>
|
||||
<hr class="my-4" />
|
||||
<div class="flex">
|
||||
<el-input v-model="server" disabled>
|
||||
<template #prepend>服务地址</template>
|
||||
</el-input>
|
||||
<el-button :type="getIsOpen ? 'danger' : 'primary'" @click="toggleConnectStatus">
|
||||
{{ getIsOpen ? '关闭连接' : '开启连接' }}
|
||||
</el-button>
|
||||
</div>
|
||||
<p class="mt-4 text-lg font-medium">消息输入框</p>
|
||||
<hr class="my-4" />
|
||||
<el-input
|
||||
v-model="sendText"
|
||||
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||
:disabled="!getIsOpen"
|
||||
clearable
|
||||
placeholder="请输入你要发送的消息"
|
||||
type="textarea"
|
||||
/>
|
||||
<el-select v-model="sendUserId" class="mt-4" placeholder="请选择发送人">
|
||||
<el-option key="" label="所有人" value="" />
|
||||
<el-option
|
||||
v-for="user in userList"
|
||||
:key="user.id"
|
||||
:label="user.nickname"
|
||||
:value="user.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button :disabled="!getIsOpen" block class="ml-2 mt-4" type="primary" @click="handlerSend">
|
||||
发送
|
||||
</el-button>
|
||||
</el-card>
|
||||
<!-- 右侧:消息记录 -->
|
||||
<el-card :gutter="12" class="w-1/2" shadow="always">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>消息记录</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="max-h-80 overflow-auto">
|
||||
<ul>
|
||||
<li v-for="msg in messageReverseList" :key="msg.time" class="mt-2">
|
||||
<div class="flex items-center">
|
||||
<span class="text-primary mr-2 font-medium">收到消息:</span>
|
||||
<span>{{ formatDate(new Date(msg.time)) }}</span>
|
||||
</div>
|
||||
<div>
|
||||
{{ msg.text }}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import { useWebSocket } from '@vueuse/core'
|
||||
import { getRefreshToken } from '@/utils/auth'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
|
||||
defineOptions({ name: 'InfraWebSocket' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const server = ref(
|
||||
(import.meta.env.VITE_BASE_URL + '/infra/ws').replace('http', 'ws') +
|
||||
'?token=' +
|
||||
getRefreshToken() // 使用 getRefreshToken() 方法,而不使用 getAccessToken() 方法的原因:WebSocket 无法方便的刷新访问令牌
|
||||
) // WebSocket 服务地址
|
||||
const getIsOpen = computed(() => status.value === 'OPEN') // WebSocket 连接是否打开
|
||||
const getTagColor = computed(() => (getIsOpen.value ? 'success' : 'red')) // WebSocket 连接的展示颜色
|
||||
|
||||
/** 发起 WebSocket 连接 */
|
||||
const { status, data, send, close, open } = useWebSocket(server.value, {
|
||||
autoReconnect: true,
|
||||
heartbeat: true
|
||||
})
|
||||
|
||||
/** 监听接收到的数据 */
|
||||
const messageList = ref([] as { time: number; text: string }[]) // 消息列表
|
||||
const messageReverseList = computed(() => messageList.value.slice().reverse())
|
||||
watchEffect(() => {
|
||||
if (!data.value) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
// 1. 收到心跳
|
||||
if (data.value === 'pong') {
|
||||
// state.recordList.push({
|
||||
// text: '【心跳】',
|
||||
// time: new Date().getTime()
|
||||
// })
|
||||
return
|
||||
}
|
||||
|
||||
// 2.1 解析 type 消息类型
|
||||
const jsonMessage = JSON.parse(data.value)
|
||||
const type = jsonMessage.type
|
||||
const content = JSON.parse(jsonMessage.content)
|
||||
if (!type) {
|
||||
message.error('未知的消息类型:' + data.value)
|
||||
return
|
||||
}
|
||||
// 2.2 消息类型:demo-message-receive
|
||||
if (type === 'demo-message-receive') {
|
||||
const single = content.single
|
||||
if (single) {
|
||||
messageList.value.push({
|
||||
text: `【单发】用户编号(${content.fromUserId}):${content.text}`,
|
||||
time: new Date().getTime()
|
||||
})
|
||||
} else {
|
||||
messageList.value.push({
|
||||
text: `【群发】用户编号(${content.fromUserId}):${content.text}`,
|
||||
time: new Date().getTime()
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
// 2.3 消息类型:notice-push
|
||||
if (type === 'notice-push') {
|
||||
messageList.value.push({
|
||||
text: `【系统通知】:${content.title}`,
|
||||
time: new Date().getTime()
|
||||
})
|
||||
return
|
||||
}
|
||||
message.error('未处理消息:' + data.value)
|
||||
} catch (error) {
|
||||
message.error('处理消息发生异常:' + data.value)
|
||||
console.error(error)
|
||||
}
|
||||
})
|
||||
|
||||
/** 发送消息 */
|
||||
const sendText = ref('') // 发送内容
|
||||
const sendUserId = ref('') // 发送人
|
||||
const handlerSend = () => {
|
||||
// 1.1 先 JSON 化 message 消息内容
|
||||
const messageContent = JSON.stringify({
|
||||
text: sendText.value,
|
||||
toUserId: sendUserId.value
|
||||
})
|
||||
// 1.2 再 JSON 化整个消息
|
||||
const jsonMessage = JSON.stringify({
|
||||
type: 'demo-message-send',
|
||||
content: messageContent
|
||||
})
|
||||
// 2. 最后发送消息
|
||||
send(jsonMessage)
|
||||
sendText.value = ''
|
||||
}
|
||||
|
||||
/** 切换 websocket 连接状态 */
|
||||
const toggleConnectStatus = () => {
|
||||
if (getIsOpen.value) {
|
||||
close()
|
||||
} else {
|
||||
open()
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
const userList = ref<any[]>([]) // 用户列表
|
||||
onMounted(async () => {
|
||||
// 获取用户列表
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
})
|
||||
|
||||
</script>
|
||||
Reference in New Issue
Block a user