feat: add vue3(element-plus)

This commit is contained in:
xingyu
2022-07-18 19:06:37 +08:00
parent c6b58dca52
commit 80a3ae8d74
423 changed files with 41039 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
import { reactive } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
import { DICT_TYPE } from '@/utils/dict'
const { t } = useI18n() // 国际化
// CrudSchema
const crudSchemas = reactive<CrudSchema[]>([
{
label: t('common.index'),
field: 'id',
type: 'index'
},
{
label: '链路追踪',
field: 'traceId'
},
{
label: '用户编号',
field: 'userId',
search: {
show: true
}
},
{
label: '用户类型',
field: 'userType',
dictType: DICT_TYPE.USER_TYPE,
search: {
show: true
}
},
{
label: '应用名',
field: 'applicationName',
search: {
show: true
}
},
{
label: '请求方法名',
field: 'requestMethod'
},
{
label: '请求地址',
field: 'requestUrl',
search: {
show: true
}
},
{
label: '请求时间',
field: 'beginTime'
},
{
label: '执行时长',
field: 'duration'
},
{
label: '操作结果',
field: 'resultCode',
search: {
show: true
}
},
{
label: t('table.action'),
field: 'action',
width: '300px',
form: {
show: false
},
detail: {
show: false
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@@ -0,0 +1,99 @@
<script setup lang="ts">
import { ref } from 'vue'
import dayjs from 'dayjs'
import { DICT_TYPE } from '@/utils/dict'
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import type { ApiAccessLogVO } from '@/api/infra/apiAccessLog/types'
import { allSchemas } from './apiAccessLog.data'
import * as ApiAccessLogApi from '@/api/infra/apiAccessLog'
const { t } = useI18n() // 国际化
// ========== 列表相关 ==========
const { register, tableObject, methods } = useTable<PageResult<ApiAccessLogVO>, ApiAccessLogVO>({
getListApi: ApiAccessLogApi.getApiAccessLogPageApi
})
const { getList, setSearchParams } = methods
// ========== 详情相关 ==========
const detailRef = ref() // 详情 Ref
const dialogVisible = ref(false) // 是否显示弹出层
const dialogTitle = ref('') // 弹出层标题
// 详情操作
const handleDetail = (row: ApiAccessLogVO) => {
// 设置数据
detailRef.value = row
dialogTitle.value = t('action.detail')
dialogVisible.value = true
}
// ========== 初始化 ==========
getList()
</script>
<template>
<!-- 搜索工作区 -->
<ContentWrap>
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
</ContentWrap>
<ContentWrap>
<!-- 列表 -->
<Table
:columns="allSchemas.tableColumns"
:selection="false"
:data="tableObject.tableList"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
@register="register"
>
<template #userType="{ row }">
<DictTag :type="DICT_TYPE.USER_TYPE" :value="row.userType" />
</template>
<template #beginTime="{ row }">
<span>{{ dayjs(row.beginTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #duration="{ row }">
<span>{{ row.duration + 'ms' }}</span>
</template>
<template #resultCode="{ row }">
<span>{{ row.resultCode === 0 ? '成功' : '失败(' + row.resultMsg + ')' }}</span>
</template>
<template #action="{ row }">
<el-button
link
type="primary"
v-hasPermi="['infra:api-access-log:query']"
@click="handleDetail(row)"
>
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
</el-button>
</template>
</Table>
</ContentWrap>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<!-- 对话框(详情) -->
<Descriptions :schema="allSchemas.detailSchema" :data="detailRef">
<template #userType="{ row }">
<DictTag :type="DICT_TYPE.USER_TYPE" :value="row.userType" />
</template>
<template #beginTime="{ row }">
<span>{{ dayjs(row.beginTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #duration="{ row }">
<span>{{ row.duration + 'ms' }}</span>
</template>
<template #resultCode="{ row }">
<span>{{ row.resultCode === 0 ? '成功' : '失败(' + row.resultMsg + ')' }}</span>
</template>
</Descriptions>
<!-- 操作按钮 -->
<template #footer>
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
</template>
</Dialog>
</template>

View File

@@ -0,0 +1,87 @@
import { reactive } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
import { DICT_TYPE } from '@/utils/dict'
const { t } = useI18n() // 国际化
// CrudSchema
const crudSchemas = reactive<CrudSchema[]>([
{
label: t('common.index'),
field: 'id',
type: 'index'
},
{
label: '链路追踪',
field: 'traceId'
},
{
label: '用户编号',
field: 'userId',
search: {
show: true
}
},
{
label: '用户类型',
field: 'userType',
dictType: DICT_TYPE.USER_TYPE,
search: {
show: true
}
},
{
label: '应用名',
field: 'applicationName',
search: {
show: true
}
},
{
label: '请求方法名',
field: 'requestMethod'
},
{
label: '请求地址',
field: 'requestUrl',
search: {
show: true
}
},
{
label: '异常发生时间',
field: 'exceptionTime'
},
{
label: '异常名',
field: 'exceptionName'
},
{
label: '处理状态',
field: 'processStatus',
dictType: DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS,
search: {
show: true
}
},
{
label: '处理人',
field: 'processUserId'
},
{
label: '处理时间',
field: 'processTime'
},
{
label: t('table.action'),
field: 'action',
width: '300px',
form: {
show: false
},
detail: {
show: false
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@@ -0,0 +1,141 @@
<script setup lang="ts">
import { ref } from 'vue'
import dayjs from 'dayjs'
import { DICT_TYPE } from '@/utils/dict'
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import type { ApiErrorLogVO } from '@/api/infra/apiErrorLog/types'
import { allSchemas } from './apiErrorLog.data'
import * as ApiErrorLogApi from '@/api/infra/apiErrorLog'
import { InfraApiErrorLogProcessStatusEnum } from '@/utils/constants'
import { ElMessage, ElMessageBox } from 'element-plus'
const { t } = useI18n() // 国际化
// ========== 列表相关 ==========
const { register, tableObject, methods } = useTable<PageResult<ApiErrorLogVO>, ApiErrorLogVO>({
getListApi: ApiErrorLogApi.getApiErrorLogPageApi,
exportListApi: ApiErrorLogApi.exportApiErrorLogApi
})
const { getList, setSearchParams, exportList } = methods
// ========== 详情相关 ==========
const detailRef = ref() // 详情 Ref
const dialogVisible = ref(false) // 是否显示弹出层
const dialogTitle = ref('') // 弹出层标题
// 导出操作
const handleExport = async () => {
await exportList('用户数据.xls')
}
// 详情操作
const handleDetail = (row: ApiErrorLogVO) => {
// 设置数据
detailRef.value = row
dialogTitle.value = t('action.detail')
dialogVisible.value = true
}
// 异常处理操作
const handleProcessClick = (row: ApiErrorLogVO, processSttatus: number, type: string) => {
ElMessageBox.confirm('确认标记为' + type + '?', t('common.reminder'), {
confirmButtonText: t('common.ok'),
cancelButtonText: t('common.cancel'),
type: 'warning'
})
.then(async () => {
ApiErrorLogApi.updateApiErrorLogPageApi(row.id, processSttatus).then(() => {
ElMessage.success(t('common.updateSuccess'))
getList()
})
})
.catch(() => {})
}
// ========== 初始化 ==========
getList()
</script>
<template>
<!-- 搜索工作区 -->
<ContentWrap>
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
</ContentWrap>
<ContentWrap>
<el-button v-hasPermi="['infra:api-error-log:export']" @click="handleExport">
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') }}
</el-button>
<!-- 列表 -->
<Table
:columns="allSchemas.tableColumns"
:selection="false"
:data="tableObject.tableList"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
@register="register"
>
<template #userType="{ row }">
<DictTag :type="DICT_TYPE.USER_TYPE" :value="row.userType" />
</template>
<template #processStatus="{ row }">
<DictTag :type="DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS" :value="row.processStatus" />
</template>
<template #exceptionTime="{ row }">
<span>{{ dayjs(row.exceptionTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #processTime="{ row }">
<span v-if="row.processTime">{{
dayjs(row.processTime).format('YYYY-MM-DD HH:mm:ss')
}}</span>
</template>
<template #action="{ row }">
<el-button
link
type="primary"
v-hasPermi="['infra:api-error-log:export']"
@click="handleDetail(row)"
>
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
</el-button>
<el-button
link
type="primary"
v-if="row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT"
v-hasPermi="['infra:api-error-log:update-status']"
@click="handleProcessClick(row, InfraApiErrorLogProcessStatusEnum.DONE, '已处理')"
>
<Icon icon="ep:cpu" class="mr-5px" /> 已处理
</el-button>
<el-button
link
type="primary"
v-if="row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT"
v-hasPermi="['infra:api-error-log:update-status']"
@click="handleProcessClick(row, InfraApiErrorLogProcessStatusEnum.IGNORE, '已忽略')"
>
<Icon icon="ep:mute-notification" class="mr-5px" /> 已忽略
</el-button>
</template>
</Table>
</ContentWrap>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<!-- 对话框(详情) -->
<Descriptions :schema="allSchemas.detailSchema" :data="detailRef">
<template #userType="{ row }">
<DictTag :type="DICT_TYPE.USER_TYPE" :value="row.userType" />
</template>
<template #processStatus="{ row }">
<DictTag :type="DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS" :value="row.processStatus" />
</template>
<template #exceptionTime="{ row }">
<span>{{ dayjs(row.exceptionTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
</Descriptions>
<!-- 操作按钮 -->
<template #footer>
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
</template>
</Dialog>
</template>

View File

@@ -0,0 +1,7 @@
<script setup lang="ts"></script>
<template>
<div>index</div>
</template>
<style scoped></style>

View File

@@ -0,0 +1,67 @@
<script setup lang="ts">
import { ref, unref, onMounted } from 'vue'
import { ContentDetailWrap } from '@/components/ContentDetailWrap'
import BasicInfoForm from './components/BasicInfoForm.vue'
import CloumInfoFormVue from './components/CloumInfoForm.vue'
import GenInfoFormVue from './components/GenInfoForm.vue'
import { ElTabs, ElTabPane, ElButton } from 'element-plus'
import { getCodegenTableApi } from '@/api/infra/codegen'
import { useRouter, useRoute } from 'vue-router'
import { useI18n } from '@/hooks/web/useI18n'
import { CodegenColumnVO, CodegenTableVO } from '@/api/infra/codegen/types'
const { t } = useI18n()
const { push } = useRouter()
const { query } = useRoute()
const tableCurrentRow = ref<Nullable<CodegenTableVO>>(null)
const cloumCurrentRow = ref<CodegenColumnVO[]>()
const getList = async () => {
const id = query.id as unknown as number
if (id) {
// 获取表详细信息
const res = await getCodegenTableApi(id)
tableCurrentRow.value = res.table
cloumCurrentRow.value = res.columns
}
}
const loading = ref(false)
const activeName = ref('cloum')
const basicInfoRef = ref<ComponentRef<typeof BasicInfoForm>>()
const genInfoRef = ref<ComponentRef<typeof GenInfoFormVue>>()
// TODO: 提交
const submitForm = async () => {
const basicInfo = unref(basicInfoRef)
const genInfo = unref(genInfoRef)
const basicValidate = await basicInfo?.elFormRef?.validate()?.catch(() => {})
const genValidate = await genInfo?.elFormRef?.validate()?.catch(() => {})
if (basicValidate && genValidate) {
const basicInfoData = (await basicInfo?.getFormData()) as CodegenTableVO
const genInfoData = (await genInfo?.getFormData()) as CodegenTableVO
console.info(basicInfoData)
console.info(genInfoData)
}
console.info(1)
}
onMounted(() => {
getList()
})
</script>
<template>
<ContentDetailWrap title="代码生成" @back="push('/infra/codegen')">
<el-tabs v-model="activeName">
<el-tab-pane label="基本信息" name="basic">
<BasicInfoForm ref="basicInfoRef" :current-row="tableCurrentRow" />
</el-tab-pane>
<el-tab-pane label="字段信息" name="cloum">
<CloumInfoFormVue ref="cloumInfoRef" :current-row="cloumCurrentRow" />
</el-tab-pane>
<el-tab-pane label="生成信息" name="genInfo">
<GenInfoFormVue ref="basicInfoRef" :current-row="tableCurrentRow" />
</el-tab-pane>
</el-tabs>
<template #right>
<el-button type="primary" :loading="loading" @click="submitForm">
{{ t('action.save') }}
</el-button>
</template>
</ContentDetailWrap>
</template>

View File

@@ -0,0 +1,74 @@
import { reactive } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { required } from '@/utils/formRules'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
const { t } = useI18n() // 国际化
// 表单校验
export const rules = reactive({
title: [required],
type: [required],
status: [required]
})
// CrudSchema
const crudSchemas = reactive<CrudSchema[]>([
{
label: t('common.index'),
field: 'id',
type: 'index',
form: {
show: false
},
detail: {
show: false
}
},
{
label: '表名称',
field: 'tableName',
search: {
show: true
}
},
{
label: '表描述',
field: 'tableComment',
search: {
show: true
}
},
{
label: '实体',
field: 'className',
search: {
show: true
}
},
{
label: t('common.createTime'),
field: 'createTime',
form: {
show: false
}
},
{
label: t('common.updateTime'),
field: 'updateTime',
form: {
show: false
}
},
{
label: t('table.action'),
field: 'action',
width: '500px',
form: {
show: false
},
detail: {
show: false
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@@ -0,0 +1,88 @@
<script setup lang="ts">
import { PropType, reactive, watch } from 'vue'
import { required } from '@/utils/formRules'
import { CodegenTableVO } from '@/api/infra/codegen/types'
import { Form } from '@/components/Form'
import { useForm } from '@/hooks/web/useForm'
const props = defineProps({
currentRow: {
type: Object as PropType<Nullable<CodegenTableVO>>,
default: () => null
}
})
const rules = reactive({
tableName: [required],
tableComment: [required],
className: [required],
author: [required]
})
const schema = reactive<FormSchema[]>([
{
label: '表名称',
field: 'tableName',
component: 'Input',
colProps: {
span: 12
}
},
{
label: '表描述',
field: 'tableComment',
component: 'Input',
colProps: {
span: 12
}
},
{
label: '实体类名称',
field: 'className',
component: 'Input',
colProps: {
span: 12
}
},
{
label: '作者',
field: 'author',
component: 'Input',
colProps: {
span: 12
}
},
{
label: '备注',
field: 'remark',
component: 'Input',
componentProps: {
type: 'textarea',
rows: 4
},
colProps: {
span: 12
}
}
])
const { register, methods, elFormRef } = useForm({
schema
})
watch(
() => props.currentRow,
(currentRow) => {
if (!currentRow) return
const { setValues } = methods
setValues(currentRow)
},
{
deep: true,
immediate: true
}
)
defineExpose({
elFormRef,
getFormData: methods.getFormData
})
</script>
<template>
<Form :rules="rules" @register="register" />
</template>

View File

@@ -0,0 +1,137 @@
<script setup lang="ts">
import { ElTable, ElTableColumn, ElInput, ElSelect, ElOption, ElCheckbox } from 'element-plus'
import { onMounted, PropType, ref } from 'vue'
import { CodegenColumnVO } from '@/api/infra/codegen/types'
import { listSimpleDictTypeApi } from '@/api/system/dict/dict.type'
import { DictTypeVO } from '@/api/system/dict/types'
defineProps({
currentRow: {
type: Array as unknown as PropType<CodegenColumnVO[]>,
default: () => null
}
})
/** 查询字典下拉列表 */
const dictOptions = ref<DictTypeVO[]>()
const getDictOptions = async () => {
const res = await listSimpleDictTypeApi()
dictOptions.value = res
}
const tableHeight = document.documentElement.scrollHeight - 245 + 'px'
onMounted(async () => {
await getDictOptions()
})
</script>
<template>
<el-table ref="dragTable" :data="currentRow" row-key="columnId" :max-height="tableHeight">
<el-table-column
label="字段列名"
prop="columnName"
min-width="10%"
:show-overflow-tooltip="true"
/>
<el-table-column label="字段描述" min-width="10%">
<template #default="scope">
<el-input v-model="scope.row.columnComment" />
</template>
</el-table-column>
<el-table-column
label="物理类型"
prop="dataType"
min-width="10%"
:show-overflow-tooltip="true"
/>
<el-table-column label="Java类型" min-width="11%">
<template #default="scope">
<el-select v-model="scope.row.javaType">
<el-option label="Long" value="Long" />
<el-option label="String" value="String" />
<el-option label="Integer" value="Integer" />
<el-option label="Double" value="Double" />
<el-option label="BigDecimal" value="BigDecimal" />
<el-option label="Date" value="Date" />
<el-option label="Boolean" value="Boolean" />
</el-select>
</template>
</el-table-column>
<el-table-column label="java属性" min-width="10%">
<template #default="scope">
<el-input v-model="scope.row.javaField" />
</template>
</el-table-column>
<el-table-column label="插入" min-width="4%">
<template #default="scope">
<el-checkbox true-label="true" false-label="false" v-model="scope.row.createOperation" />
</template>
</el-table-column>
<el-table-column label="编辑" min-width="4%">
<template #default="scope">
<el-checkbox true-label="true" false-label="false" v-model="scope.row.updateOperation" />
</template>
</el-table-column>
<el-table-column label="列表" min-width="4%">
<template #default="scope">
<el-checkbox
true-label="true"
false-label="false"
v-model="scope.row.listOperationResult"
/>
</template>
</el-table-column>
<el-table-column label="查询" min-width="4%">
<template #default="scope">
<el-checkbox true-label="true" false-label="false" v-model="scope.row.listOperation" />
</template>
</el-table-column>
<el-table-column label="查询方式" min-width="10%">
<template #default="scope">
<el-select v-model="scope.row.listOperationCondition">
<el-option label="=" value="=" />
<el-option label="!=" value="!=" />
<el-option label=">" value=">" />
<el-option label=">=" value=">=" />
<el-option label="<" value="<>" />
<el-option label="<=" value="<=" />
<el-option label="LIKE" value="LIKE" />
<el-option label="BETWEEN" value="BETWEEN" />
</el-select>
</template>
</el-table-column>
<el-table-column label="允许空" min-width="5%">
<template #default="scope">
<el-checkbox true-label="true" false-label="false" v-model="scope.row.nullable" />
</template>
</el-table-column>
<el-table-column label="显示类型" min-width="12%">
<template #default="scope">
<el-select v-model="scope.row.htmlType">
<el-option label="文本框" value="input" />
<el-option label="文本域" value="textarea" />
<el-option label="下拉框" value="select" />
<el-option label="单选框" value="radio" />
<el-option label="复选框" value="checkbox" />
<el-option label="日期控件" value="datetime" />
<el-option label="图片上传" value="imageUpload" />
<el-option label="文件上传" value="fileUpload" />
<el-option label="富文本控件" value="editor" />
</el-select>
</template>
</el-table-column>
<el-table-column label="字典类型" min-width="12%">
<template #default="scope">
<el-select v-model="scope.row.dictType" clearable filterable placeholder="请选择">
<el-option
v-for="dict in dictOptions"
:key="dict.id"
:label="dict.name"
:value="dict.type"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="示例" min-width="10%">
<template #default="scope">
<el-input v-model="scope.row.example" />
</template>
</el-table-column>
</el-table>
</template>

View File

@@ -0,0 +1,110 @@
<script setup lang="ts">
import { PropType, reactive, watch } from 'vue'
import { required } from '@/utils/formRules'
import { CodegenTableVO } from '@/api/infra/codegen/types'
import { Form } from '@/components/Form'
import { useForm } from '@/hooks/web/useForm'
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
const props = defineProps({
currentRow: {
type: Object as PropType<Nullable<CodegenTableVO>>,
default: () => null
}
})
const rules = reactive({
templateType: [required],
scene: [required],
moduleName: [required],
businessName: [required],
businessPackage: [required],
className: [required],
classComment: [required]
})
const schema = reactive<FormSchema[]>([
{
label: '生成模板',
field: 'templateType',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.INFRA_CODEGEN_TEMPLATE_TYPE)
},
colProps: {
span: 12
}
},
{
label: '生成场景',
field: 'scene',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.INFRA_CODEGEN_SCENE)
},
colProps: {
span: 12
}
},
{
label: '模块名',
field: 'moduleName',
component: 'Input',
colProps: {
span: 12
}
},
{
label: '业务名',
field: 'businessName',
component: 'Input',
colProps: {
span: 12
}
},
{
label: '类名称',
field: 'className',
component: 'Input',
colProps: {
span: 12
}
},
{
label: '类描述',
field: 'classComment',
component: 'Input',
colProps: {
span: 12
}
},
{
label: '上级菜单',
field: 'parentMenuId',
component: 'Input',
colProps: {
span: 12
}
}
])
const { register, methods, elFormRef } = useForm({
schema
})
watch(
() => props.currentRow,
(currentRow) => {
if (!currentRow) return
const { setValues } = methods
setValues(currentRow)
},
{
deep: true,
immediate: true
}
)
defineExpose({
elFormRef,
getFormData: methods.getFormData
})
</script>
<template>
<Form :rules="rules" @register="register" />
</template>

View File

@@ -0,0 +1,128 @@
<script setup lang="ts">
import { reactive, ref } from 'vue'
import { getSchemaTableListApi, createCodegenListApi } from '@/api/infra/codegen'
import {
ElMessage,
ElTable,
ElTableColumn,
ElForm,
ElFormItem,
ElInput,
ElEmpty
} from 'element-plus'
import { useI18n } from '@/hooks/web/useI18n'
import { useEmitt } from '@/hooks/web/useEmitt'
import { getDataSourceConfigListApi } from '@/api/infra/dataSourceConfig'
import type { DataSourceConfigVO } from '@/api/infra/dataSourceConfig/types'
import type { DatabaseTableVO } from '@/api/infra/codegen/types'
const { t } = useI18n() // 国际化
const { emitter } = useEmitt()
// ======== 显示页面 ========
const visible = ref(false)
const queryParams = reactive({
tableName: undefined,
tableComment: undefined,
dataSourceConfigId: 0
})
const dataSourceConfigs = ref<DataSourceConfigVO[]>([])
const show = async () => {
const res = await getDataSourceConfigListApi()
dataSourceConfigs.value = res
queryParams.dataSourceConfigId = dataSourceConfigs.value[0].id
visible.value = true
await getList()
}
/** 查询表数据 */
const dbTableList = ref<DatabaseTableVO[]>([])
/** 查询表数据 */
const getList = async () => {
const res = await getSchemaTableListApi(queryParams)
dbTableList.value = res
}
// 查询操作
const handleQuery = async () => {
await getList()
}
// 重置操作
const resetQuery = async () => {
queryParams.tableName = undefined
queryParams.tableComment = undefined
await getList()
}
/** 多选框选中数据 */
const tables = ref<string[]>([])
const handleSelectionChange = (val: DatabaseTableVO[]) => {
tables.value = val.map((item) => item.name)
}
/** 导入按钮操作 */
const handleImportTable = () => {
if (tables.value.length === 0) {
ElMessage.error('请选择要导入的表')
return
}
createCodegenListApi({
dataSourceConfigId: queryParams.dataSourceConfigId,
tableNames: tables.value
}).then((res) => {
ElMessage.success(res.msg)
visible.value = false
emitter.emit('ok')
})
}
defineExpose({
show
})
</script>
<template>
<!-- 导入表 -->
<Dialog title="导入表" v-model="visible" maxHeight="500px" width="50%">
<el-form :model="queryParams" ref="queryRef" :inline="true">
<!-- <el-form-item label="数据源" prop="dataSourceConfigId">
<el-select v-model="queryParams.dataSourceConfigId" placeholder="请选择数据源" clearable>
<el-option
v-for="config in dataSourceConfigs"
:key="config.id"
:label="config.name"
:value="config.id"
/>
</el-select>
</el-form-item> -->
<el-form-item label="表名称" prop="tableName">
<el-input v-model="queryParams.tableName" placeholder="请输入表名称" clearable />
</el-form-item>
<el-form-item label="表描述" prop="tableComment">
<el-input v-model="queryParams.tableComment" placeholder="请输入表描述" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" />
{{ t('common.query') }}
</el-button>
<el-button @click="resetQuery">
<Icon icon="ep:refresh-right" class="mr-5px" />
{{ t('common.reset') }}
</el-button>
</el-form-item>
</el-form>
<el-table
ref="table"
:data="dbTableList"
@selection-change="handleSelectionChange"
height="400px"
>
<template #empty>
<el-empty description="加载中" />
</template>
<el-table-column type="selection" width="55" />
<el-table-column prop="name" label="表名称" :show-overflow-tooltip="true" />
<el-table-column prop="comment" label="表描述" :show-overflow-tooltip="true" />
</el-table>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="handleImportTable">{{ t('action.import') }}</el-button>
<el-button @click="visible = false">{{ t('dialog.close') }}</el-button>
</div>
</template>
</Dialog>
</template>

View File

@@ -0,0 +1,148 @@
<script setup lang="ts">
import { reactive, ref, unref } from 'vue'
import { handleTree2 } from '@/utils/tree'
import { ElCard, ElTree, ElTabs, ElTabPane, ElMessage } from 'element-plus'
import { previewCodegenApi } from '@/api/infra/codegen'
import { CodegenTableVO, CodegenPreviewVO } from '@/api/infra/codegen/types'
import { useI18n } from '@/hooks/web/useI18n'
import { useClipboard } from '@vueuse/core'
const { t } = useI18n()
// ======== 显示页面 ========
const preview = reactive({
open: false,
titel: '代码预览',
fileTree: [],
activeName: ''
})
const previewCodegen = ref<CodegenPreviewVO[]>()
const show = async (row: CodegenTableVO) => {
const res = await previewCodegenApi(row.id)
let file = handleFiles(res)
previewCodegen.value = res
preview.fileTree = handleTree2(file, 'id', 'parentId', 'children', '/')
preview.activeName = res[0].filePath
preview.open = true
}
const handleNodeClick = async (data, node) => {
if (node && !node.isLeaf) {
return false
}
preview.activeName = data.id
}
/** 生成 files 目录 **/
interface filesType {
id: string
label: string
parentId: string
}
const handleFiles = (datas: CodegenPreviewVO[]) => {
let exists = {} // keyfile 的 idvaluetrue
let files: filesType[] = []
// 遍历每个元素
for (const data of datas) {
let paths = data.filePath.split('/')
let fullPath = '' // 从头开始的路径,用于生成 id
// 特殊处理 java 文件
if (paths[paths.length - 1].indexOf('.java') >= 0) {
let newPaths: string[] = []
for (let i = 0; i < paths.length; i++) {
let path = paths[i]
if (path !== 'java') {
newPaths.push(path)
continue
}
newPaths.push(path)
// 特殊处理中间的 package进行合并
let tmp = ''
while (i < paths.length) {
path = paths[i + 1]
if (
path === 'controller' ||
path === 'convert' ||
path === 'dal' ||
path === 'enums' ||
path === 'service' ||
path === 'vo' || // 下面三个,主要是兜底。可能考虑到有人改了包结构
path === 'mysql' ||
path === 'dataobject'
) {
break
}
tmp = tmp ? tmp + '.' + path : path
i++
}
if (tmp) {
newPaths.push(tmp)
}
}
paths = newPaths
}
// 遍历每个 path 拼接成树
for (let i = 0; i < paths.length; i++) {
// 已经添加到 files 中,则跳过
let oldFullPath = fullPath
// 下面的 replaceAll 的原因,是因为上面包处理了,导致和 tabs 不匹配,所以 replaceAll 下
fullPath = fullPath.length === 0 ? paths[i] : fullPath.replaceAll('.', '/') + '/' + paths[i]
if (exists[fullPath]) {
continue
}
// 添加到 files 中
exists[fullPath] = true
files.push({
id: fullPath,
label: paths[i],
parentId: oldFullPath || '/' // "/" 为根节点
})
}
}
return files
}
/** 复制 **/
const copy = async (text: string) => {
const { copy, copied, isSupported } = useClipboard({ source: text })
if (!isSupported) {
ElMessage.error(t('common.copyError'))
} else {
await copy()
if (unref(copied)) {
ElMessage.success(t('common.copySuccess'))
}
}
}
defineExpose({
show
})
</script>
<template>
<Dialog title="预览" v-model="preview.open" top="5vh" maxHeight="800px" width="90%">
<div class="flex">
<el-card class="w-1/4" :gutter="12" shadow="hover">
<el-tree
ref="treeRef"
node-key="id"
:data="preview.fileTree"
:expand-on-click-node="false"
default-expand-all
highlight-current
@node-click="handleNodeClick"
/>
</el-card>
<el-card class="w-3/4" style="margin-left: 10px" :gutter="12" shadow="hover">
<el-tabs v-model="preview.activeName">
<el-tab-pane
v-for="item in previewCodegen"
:label="item.filePath.substring(item.filePath.lastIndexOf('/') + 1)"
:name="item.filePath"
:key="item.filePath"
>
<el-button text style="float: right" @click="copy(item.code)">
{{ t('common.copy') }}
</el-button>
<pre>{{ item.code }}</pre>
<!-- <pre><code class="language-html" v-html="highlightedCode(item)"></code></pre> -->
</el-tab-pane>
</el-tabs>
</el-card>
</div>
</Dialog>
</template>

View File

@@ -0,0 +1,142 @@
<script setup lang="ts">
import { ref } from 'vue'
import dayjs from 'dayjs'
import * as CodegenApi from '@/api/infra/codegen'
import { useTable } from '@/hooks/web/useTable'
import { CodegenTableVO } from '@/api/infra/codegen/types'
import { allSchemas } from './codegen.data'
import { useI18n } from '@/hooks/web/useI18n'
import ImportTable from './components/ImportTable.vue'
import Preview from './components/Preview.vue'
import download from '@/utils/download'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
const { t } = useI18n() // 国际化
const { push } = useRouter()
// ========== 列表相关 ==========
const { register, tableObject, methods } = useTable<PageResult<CodegenTableVO>, CodegenTableVO>({
getListApi: CodegenApi.getCodegenTablePageApi,
delListApi: CodegenApi.deleteCodegenTableApi
})
const { getList, setSearchParams, delList } = methods
// 导入操作
const importRef = ref()
const openImportTable = () => {
importRef.value.show()
}
// 预览操作
const previewRef = ref()
const handlePreview = (row: CodegenTableVO) => {
previewRef.value.show(row)
}
// 编辑操作
const handleEditTable = (row: CodegenTableVO) => {
push('/codegen/edit?id=' + row.id)
}
// 同步操作
const handleSynchDb = (row: CodegenTableVO) => {
// 基于 DB 同步
const tableName = row.tableName
ElMessageBox.confirm('确认要强制同步' + tableName + '表结构吗?', t('common.reminder'), {
confirmButtonText: t('common.ok'),
cancelButtonText: t('common.cancel'),
type: 'warning'
}).then(async () => {
await CodegenApi.syncCodegenFromDBApi(row.id)
ElMessage.success('同步成功')
})
}
// 生成代码操作
const handleGenTable = (row: CodegenTableVO) => {
const res = CodegenApi.downloadCodegenApi(row.id)
download.zip(res, 'codegen-' + row.className + '.zip')
}
// 删除操作
const handleDelete = (row: CodegenTableVO) => {
delList(row.id, false)
}
// 查询操作
const handleQuery = () => {
getList()
}
// ========== 初始化 ==========
getList()
</script>
<template>
<!-- 搜索工作区 -->
<ContentWrap>
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
</ContentWrap>
<ContentWrap>
<!-- 操作工具栏 -->
<div class="mb-10px">
<el-button type="primary" v-hasPermi="['infra:codegen:create']" @click="openImportTable">
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.import') }}
</el-button>
</div>
<!-- 列表 -->
<Table
:columns="allSchemas.tableColumns"
:selection="false"
:data="tableObject.tableList"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
@register="register"
>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #updateTime="{ row }">
<span>{{ dayjs(row.updateTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #action="{ row }">
<el-button
link
type="primary"
v-hasPermi="['infra:codegen:preview']"
@click="handlePreview(row)"
>
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.preview') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['infra:codegen:update']"
@click="handleEditTable(row)"
>
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['infra:codegen:delete']"
@click="handleDelete(row)"
>
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['infra:codegen:update']"
@click="handleSynchDb(row)"
>
<Icon icon="ep:refresh" class="mr-5px" /> {{ t('action.sync') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['infra:codegen:download']"
@click="handleGenTable(row)"
>
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.generate') }}
</el-button>
</template>
</Table>
</ContentWrap>
<ImportTable ref="importRef" @ok="handleQuery" />
<Preview ref="previewRef" />
</template>

View File

@@ -0,0 +1,108 @@
import { reactive } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { required } from '@/utils/formRules'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
import { DICT_TYPE } from '@/utils/dict'
const { t } = useI18n() // 国际化
// 表单校验
export const rules = reactive({
category: [required],
name: [required],
key: [required],
value: [required]
})
// CrudSchema
const crudSchemas = reactive<CrudSchema[]>([
{
label: t('common.index'),
field: 'id',
type: 'index',
form: {
show: false
},
detail: {
show: false
}
},
{
label: '参数分类',
field: 'category'
},
{
label: '参数名称',
field: 'name',
search: {
show: true
}
},
{
label: '参数键名',
field: 'key',
search: {
show: true
}
},
{
label: '参数键值',
field: 'value'
},
{
label: '系统内置',
field: 'type',
dictType: DICT_TYPE.INFRA_CONFIG_TYPE,
search: {
show: true
}
},
{
label: '是否可见',
field: 'visible',
form: {
component: 'RadioButton',
componentProps: {
options: [
{ label: '是', value: true },
{ label: '否', value: false }
]
}
}
},
{
label: t('form.remark'),
field: 'remark',
form: {
component: 'Input',
componentProps: {
type: 'textarea',
rows: 4
},
colProps: {
span: 24
}
},
table: {
show: false
}
},
{
label: t('common.createTime'),
field: 'createTime',
form: {
show: false
}
},
{
label: t('table.action'),
field: 'action',
width: '240px',
form: {
show: false
},
detail: {
show: false
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@@ -0,0 +1,204 @@
<script setup lang="ts">
import { ref, unref } from 'vue'
import dayjs from 'dayjs'
import { ElMessage } from 'element-plus'
import { DICT_TYPE } from '@/utils/dict'
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import { FormExpose } from '@/components/Form'
import type { ConfigVO } from '@/api/infra/config/types'
import { rules, allSchemas } from './config.data'
import * as ConfigApi from '@/api/infra/config'
const { t } = useI18n() // 国际化
// ========== 列表相关 ==========
const { register, tableObject, methods } = useTable<PageResult<ConfigVO>, ConfigVO>({
getListApi: ConfigApi.getConfigPageApi,
delListApi: ConfigApi.deleteConfigApi,
exportListApi: ConfigApi.exportConfigApi
})
const { getList, setSearchParams, delList, exportList } = methods
// 导出操作
const handleExport = async () => {
await exportList('参数配置.xls')
}
// ========== CRUD 相关 ==========
const actionLoading = ref(false) // 遮罩层
const actionType = ref('') // 操作按钮的类型
const dialogVisible = ref(false) // 是否显示弹出层
const dialogTitle = ref('edit') // 弹出层标题
const formRef = ref<FormExpose>() // 表单 Ref
// 设置标题
const setDialogTile = (type: string) => {
dialogTitle.value = t('action.' + type)
actionType.value = type
dialogVisible.value = true
}
// 新增操作
const handleCreate = () => {
setDialogTile('create')
// 重置表单
unref(formRef)?.getElFormRef()?.resetFields()
}
// 修改操作
const handleUpdate = async (row: ConfigVO) => {
setDialogTile('update')
// 设置数据
const res = await ConfigApi.getConfigApi(row.id)
unref(formRef)?.setValues(res)
}
// 提交按钮
const submitForm = async () => {
actionLoading.value = true
// 提交请求
try {
const data = unref(formRef)?.formModel as ConfigVO
if (actionType.value === 'create') {
await ConfigApi.createConfigApi(data)
ElMessage.success(t('common.createSuccess'))
} else {
await ConfigApi.updateConfigApi(data)
ElMessage.success(t('common.updateSuccess'))
}
// 操作成功,重新加载列表
dialogVisible.value = false
await getList()
} finally {
actionLoading.value = false
}
}
// 删除操作
const handleDelete = (row: ConfigVO) => {
delList(row.id, false)
}
// ========== 详情相关 ==========
const detailRef = ref() // 详情 Ref
// 详情操作
const handleDetail = async (row: ConfigVO) => {
// 设置数据
detailRef.value = row
setDialogTile('detail')
}
// ========== 初始化 ==========
getList()
</script>
<template>
<!-- 搜索工作区 -->
<ContentWrap>
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
</ContentWrap>
<ContentWrap>
<!-- 操作工具栏 -->
<div class="mb-10px">
<el-button type="primary" v-hasPermi="['infra:config:create']" @click="handleCreate">
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
</el-button>
<el-button
type="warning"
v-hasPermi="['infra:config:export']"
:loading="tableObject.exportLoading"
@click="handleExport"
>
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') }}
</el-button>
</div>
<!-- 列表 -->
<Table
:columns="allSchemas.tableColumns"
:selection="false"
:data="tableObject.tableList"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
@register="register"
>
<template #visible="{ row }">
<span>{{ row.visible ? '是' : '否' }} </span>
</template>
<template #type="{ row }">
<DictTag :type="DICT_TYPE.INFRA_CONFIG_TYPE" :value="row.type" />
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #action="{ row }">
<el-button
link
type="primary"
v-hasPermi="['infra:config:update']"
@click="handleUpdate(row)"
>
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['infra:config:update']"
@click="handleDetail(row)"
>
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['infra:config:delete']"
@click="handleDelete(row)"
>
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
</el-button>
</template>
</Table>
</ContentWrap>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<!-- 对话框(添加 / 修改) -->
<Form
v-if="['create', 'update'].includes(actionType)"
:schema="allSchemas.formSchema"
:rules="rules"
ref="formRef"
/>
<!-- 对话框(详情) -->
<Descriptions
v-if="actionType === 'detail'"
:schema="allSchemas.detailSchema"
:data="detailRef"
>
<template #visible="{ row }">
<span>{{ row.visible ? '是' : '否' }} </span>
</template>
<template #type="{ row }">
<DictTag :type="DICT_TYPE.INFRA_CONFIG_TYPE" :value="row.type" />
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
</Descriptions>
<!-- 操作按钮 -->
<template #footer>
<el-button
v-if="['create', 'update'].includes(actionType)"
type="primary"
:loading="actionLoading"
@click="submitForm"
>
{{ t('action.save') }}
</el-button>
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
</template>
</Dialog>
</template>

View File

@@ -0,0 +1,75 @@
import { reactive } from 'vue'
import { required } from '@/utils/formRules'
import { useI18n } from '@/hooks/web/useI18n'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
// 国际化
const { t } = useI18n()
// 表单校验
export const rules = reactive({
name: [required],
url: [required],
username: [required],
password: [required]
})
// 新增 + 修改
const crudSchemas = reactive<CrudSchema[]>([
{
label: t('common.index'),
field: 'id',
type: 'index',
form: {
show: false
},
detail: {
show: false
}
},
{
label: '数据源名称',
field: 'name'
},
{
label: '数据源连接',
field: 'url',
form: {
component: 'Input',
componentProps: {
type: 'textarea',
rows: 4
},
colProps: {
span: 24
}
}
},
{
label: '用户名',
field: 'username'
},
{
label: '密码',
field: 'password',
table: {
show: false
}
},
{
label: t('common.createTime'),
field: 'createTime',
form: {
show: false
}
},
{
field: 'action',
width: '240px',
label: t('table.action'),
form: {
show: false
},
detail: {
show: false
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@@ -0,0 +1,161 @@
<script setup lang="ts">
import { onMounted, ref, unref } from 'vue'
import dayjs from 'dayjs'
import { ElMessage } from 'element-plus'
import { FormExpose } from '@/components/Form'
import { rules, allSchemas } from './dataSourceConfig.data'
import type { DataSourceConfigVO } from '@/api/infra/dataSourceConfig/types'
import * as DataSourceConfiggApi from '@/api/infra/dataSourceConfig'
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n() // 国际化
const tableData = ref()
const getList = async () => {
const res = await DataSourceConfiggApi.getDataSourceConfigListApi()
tableData.value = res
}
// ========== CRUD 相关 ==========
const loading = ref(false) // 遮罩层
const actionType = ref('') // 操作按钮的类型
const dialogVisible = ref(false) // 是否显示弹出层
const dialogTitle = ref('edit') // 弹出层标题
const formRef = ref<FormExpose>() // 表单 Ref
// 设置标题
const setDialogTile = (type: string) => {
dialogTitle.value = t('action.' + type)
actionType.value = type
dialogVisible.value = true
}
// 新增操作
const handleCreate = () => {
setDialogTile('create')
// 重置表单
unref(formRef)?.getElFormRef()?.resetFields()
}
// 修改操作
const handleUpdate = async (row: DataSourceConfigVO) => {
setDialogTile('update')
// 设置数据
const res = await DataSourceConfiggApi.getDataSourceConfigApi(row.id)
unref(formRef)?.setValues(res)
}
// 提交按钮
const submitForm = async () => {
loading.value = true
// 提交请求
try {
const data = unref(formRef)?.formModel as DataSourceConfigVO
if (actionType.value === 'create') {
await DataSourceConfiggApi.createDataSourceConfigApi(data)
ElMessage.success(t('common.createSuccess'))
} else {
await DataSourceConfiggApi.updateDataSourceConfigApi(data)
ElMessage.success(t('common.updateSuccess'))
}
// 操作成功,重新加载列表
dialogVisible.value = false
await getList()
} finally {
loading.value = false
}
}
// 删除操作
const handleDelete = async (row: DataSourceConfigVO) => {
await DataSourceConfiggApi.deleteDataSourceConfigApi(row.id)
ElMessage.success(t('common.delSuccess'))
}
// ========== 详情相关 ==========
const detailRef = ref() // 详情 Ref
// 详情操作
const handleDetail = async (row: DataSourceConfigVO) => {
// 设置数据
detailRef.value = row
setDialogTile('detail')
}
onMounted(async () => {
await getList()
})
</script>
<template>
<ContentWrap>
<!-- 操作工具栏 -->
<div class="mb-10px">
<el-button
v-hasPermi="['infra:data-source-config:create']"
type="primary"
@click="handleCreate"
>
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
</el-button>
</div>
<Table :columns="allSchemas.tableColumns" :data="tableData">
<template #createTime="{ row }">
<span>{{ row.createTime ? dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') : '' }}</span>
</template>
<template #action="{ row }">
<el-button
link
type="primary"
v-hasPermi="['infra:data-source-config:update']"
@click="handleUpdate(row)"
>
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['infra:data-source-config:update']"
@click="handleDetail(row)"
>
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['infra:data-source-config:delete']"
@click="handleDelete(row)"
>
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
</el-button>
</template>
</Table>
</ContentWrap>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<!-- 对话框(添加 / 修改) -->
<Form
v-if="['create', 'update'].includes(actionType)"
:schema="allSchemas.formSchema"
:rules="rules"
ref="formRef"
/>
<!-- 对话框(详情) -->
<Descriptions
v-if="actionType === 'detail'"
:schema="allSchemas.detailSchema"
:data="detailRef"
>
<template #createTime="{ row }">
<span>{{ row.createTime ? dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') : '' }}</span>
</template>
</Descriptions>
<!-- 操作按钮 -->
<template #footer>
<el-button
v-if="['create', 'update'].includes(actionType)"
type="primary"
:loading="loading"
@click="submitForm"
>
{{ t('action.save') }}
</el-button>
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
</template>
</Dialog>
</template>

View File

@@ -0,0 +1,55 @@
<script setup lang="ts">
import { IFrame } from '@/components/IFrame'
import * as DbDocApi from '@/api/infra/dbDoc'
import { onMounted, ref } from 'vue'
import download from '@/utils/download'
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n() // 国际化
const loding = ref(true)
const src = ref('')
/** 页面加载 */
const init = async () => {
const res = await DbDocApi.exportHtmlApi()
let blob = new Blob([res], { type: 'text/html' })
let blobUrl = window.URL.createObjectURL(blob)
src.value = blobUrl
loding.value = false
}
/** 处理导出 HTML */
const handleExportHtml = async () => {
const res = await DbDocApi.exportHtmlApi()
download.html(res, '数据库文档.html')
}
/** 处理导出 Word */
const handleExportWord = async () => {
const res = await DbDocApi.exportHtmlApi()
download.word(res, '数据库文档.doc')
}
/** 处理导出 Markdown */
const handleExportMarkdown = async () => {
const res = await DbDocApi.exportHtmlApi()
download.markdown(res, '数据库文档.md')
}
onMounted(async () => {
await init()
})
</script>
<template>
<ContentWrap title="数据库文档" message="https://doc.iocoder.cn/db-doc/">
<!-- 操作工具栏 -->
<div class="mb-10px">
<el-button type="primary" @click="handleExportHtml">
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') + ' HTML' }}
</el-button>
<el-button type="primary" @click="handleExportWord">
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') + ' Word' }}
</el-button>
<el-button type="primary" @click="handleExportMarkdown">
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') + ' Markdown' }}
</el-button>
</div>
<IFrame v-if="!loding" v-loading="loding" :src="src" />
</ContentWrap>
</template>

View File

@@ -0,0 +1,12 @@
<script setup lang="ts">
import { IFrame } from '@/components/IFrame'
import { ref } from 'vue'
const BASE_URL = import.meta.env.VITE_BASE_URL
const src = ref(BASE_URL + '/druid/index.html')
</script>
<template>
<ContentWrap>
<IFrame :src="src" />
</ContentWrap>
</template>

View File

@@ -0,0 +1,53 @@
import { reactive } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
const { t } = useI18n() // 国际化
// CrudSchema
const crudSchemas = reactive<CrudSchema[]>([
{
label: t('common.index'),
field: 'id',
type: 'index',
form: {
show: false
},
detail: {
show: false
}
},
{
label: '文件名',
field: 'path',
search: {
show: true
}
},
{
label: 'URL',
field: 'url'
},
{
label: '文件类型',
field: 'type'
},
{
label: t('common.createTime'),
field: 'createTime',
form: {
show: false
}
},
{
label: t('table.action'),
field: 'action',
width: '300px',
form: {
show: false
},
detail: {
show: false
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@@ -0,0 +1,192 @@
<script setup lang="ts">
import { ref } from 'vue'
import dayjs from 'dayjs'
import { ElMessage, ElUpload, UploadInstance, UploadRawFile, ElImage } from 'element-plus'
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import type { FileVO } from '@/api/infra/file/types'
import { allSchemas } from './fileList.data'
import * as FileApi from '@/api/infra/file'
import { useCache } from '@/hooks/web/useCache'
const { wsCache } = useCache()
const { t } = useI18n() // 国际化
// ========== 列表相关 ==========
const { register, tableObject, methods } = useTable<PageResult<FileVO>, FileVO>({
getListApi: FileApi.getFilePageApi,
delListApi: FileApi.deleteFileApi
})
const { getList, setSearchParams, delList } = methods
// ========== 上传相关 ==========
const uploadDialogVisible = ref(false)
const uploadDialogTitle = ref('上传')
const updateSupport = ref(0)
const uploadDisabled = ref(false)
const uploadRef = ref<UploadInstance>()
let updateUrl = import.meta.env.VITE_UPLOAD_URL
const uploadHeaders = ref()
// 文件上传之前判断
const beforeUpload = (file: UploadRawFile) => {
const isImg = file.type === 'image/jpeg' || 'image/gif' || 'image/png'
const isLt5M = file.size / 1024 / 1024 < 5
if (!isImg) ElMessage.error('上传文件只能是 xls / xlsx 格式!')
if (!isLt5M) ElMessage.error('上传文件大小不能超过 5MB!')
return isImg && isLt5M
}
// 处理上传的文件发生变化
// const handleFileChange = (uploadFile: UploadFile): void => {
// uploadRef.value.data.path = uploadFile.name
// }
// 文件上传
const submitFileForm = () => {
uploadHeaders.value = {
Authorization: 'Bearer ' + wsCache.get('ACCESS_TOKEN'),
'tenant-id': wsCache.get('tenantId')
}
uploadDisabled.value = true
uploadRef.value!.submit()
}
// 文件上传成功
const handleFileSuccess = (response: any): void => {
if (response.code !== 0) {
ElMessage.error(response.msg)
return
}
ElMessage.success('上传成功')
getList()
uploadDialogVisible.value = false
uploadDisabled.value = false
}
// 文件数超出提示
const handleExceed = (): void => {
ElMessage.error('最多只能上传一个文件!')
}
// 上传错误提示
const excelUploadError = (): void => {
ElMessage.error('导入数据失败,请您重新上传!')
}
// ========== 详情相关 ==========
const detailRef = ref() // 详情 Ref
const dialogVisible = ref(false) // 是否显示弹出层
const dialogTitle = ref('') // 弹出层标题
// 删除操作
const handleDelete = (row: FileVO) => {
delList(row.id, false)
}
// 详情操作
const handleDetail = (row: FileVO) => {
// 设置数据
detailRef.value = row
dialogTitle.value = t('action.detail')
dialogVisible.value = true
}
// ========== 初始化 ==========
getList()
</script>
<template>
<!-- 搜索工作区 -->
<ContentWrap>
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
</ContentWrap>
<ContentWrap>
<el-button type="primary" @click="uploadDialogVisible = true">
<Icon icon="ep:upload" class="mr-5px" /> 上传文件
</el-button>
<!-- 列表 -->
<Table
:columns="allSchemas.tableColumns"
:selection="false"
:data="tableObject.tableList"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
@register="register"
>
<template #url="{ row }">
<el-image
v-if="row.type === 'jpg' || 'png' || 'gif'"
style="width: 80px; height: 50px"
:src="row.url"
:key="row.url"
fit="contain"
lazy
/>
<span v-else>{{ row.url }}</span>
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #action="{ row }">
<el-button link type="primary" @click="handleDetail(row)">
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['infra:file:delete']"
@click="handleDelete(row)"
>
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
</el-button>
</template>
</Table>
</ContentWrap>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<!-- 对话框(详情) -->
<Descriptions :schema="allSchemas.detailSchema" :data="detailRef">
<template #url="{ row }">
<el-image
v-if="row.type === 'jpg' || 'png' || 'gif'"
style="width: 100px; height: 100px"
:src="row.url"
:key="row.url"
lazy
/>
<span>{{ row.url }}</span>
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
</Descriptions>
<!-- 操作按钮 -->
<template #footer>
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
</template>
</Dialog>
<Dialog v-model="uploadDialogVisible" :title="uploadDialogTitle" :destroy-on-close="true">
<el-upload
ref="uploadRef"
:action="updateUrl + '?updateSupport=' + updateSupport"
:headers="uploadHeaders"
:drag="true"
:limit="1"
:multiple="true"
:show-file-list="true"
:disabled="uploadDisabled"
:before-upload="beforeUpload"
:on-exceed="handleExceed"
:on-success="handleFileSuccess"
:on-error="excelUploadError"
:auto-upload="false"
accept=".jpg, .png, .gif"
>
<Icon icon="ep:upload-filled" />
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<template #tip>
<div class="el-upload__tip">请上传 .jpg, .png, .gif 标准格式文件</div>
</template>
</el-upload>
<template #footer>
<el-button type="primary" @click="submitFileForm">
<Icon icon="ep:upload-filled" />
{{ t('action.save') }}
</el-button>
<el-button @click="uploadDialogVisible = false">{{ t('dialog.close') }}</el-button>
</template>
</Dialog>
</template>

View File

@@ -0,0 +1,93 @@
import { reactive } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { required } from '@/utils/formRules'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
import { DICT_TYPE } from '@/utils/dict'
const { t } = useI18n() // 国际化
// 表单校验
export const rules = reactive({
name: [required],
storage: [required],
config: {
basePath: [required],
host: [required],
port: [required],
username: [required],
password: [required],
mode: [required],
endpoint: [required],
bucket: [required],
accessKey: [required],
accessSecret: [required],
domain: [required]
}
})
// CrudSchema
const crudSchemas = reactive<CrudSchema[]>([
{
label: t('common.index'),
field: 'id',
type: 'index',
form: {
show: false
},
detail: {
show: false
}
},
{
label: '配置名',
field: 'name',
search: {
show: true
}
},
{
label: '存储器',
field: 'storage',
dictType: DICT_TYPE.INFRA_FILE_STORAGE,
search: {
show: true
}
},
{
label: '主配置',
field: 'primary',
dictType: DICT_TYPE.INFRA_BOOLEAN_STRING
},
{
label: t('form.remark'),
field: 'remark',
form: {
component: 'Input',
componentProps: {
type: 'textarea',
rows: 4
},
colProps: {
span: 24
}
}
},
{
label: t('common.createTime'),
field: 'createTime',
form: {
show: false
}
},
{
label: t('table.action'),
field: 'action',
width: '400px',
form: {
show: false
},
detail: {
show: false
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@@ -0,0 +1,220 @@
<script setup lang="ts">
import { ref, unref } from 'vue'
import dayjs from 'dayjs'
import { ElMessage, ElMessageBox } from 'element-plus'
import { DICT_TYPE } from '@/utils/dict'
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import { FormExpose } from '@/components/Form'
import type { FileConfigVO } from '@/api/infra/fileConfig/types'
import { rules, allSchemas } from './fileConfig.data'
import * as FileConfigApi from '@/api/infra/fileConfig'
const { t } = useI18n() // 国际化
// ========== 列表相关 ==========
const { register, tableObject, methods } = useTable<PageResult<FileConfigVO>, FileConfigVO>({
getListApi: FileConfigApi.getFileConfigPageApi,
delListApi: FileConfigApi.deleteFileConfigApi
})
const { getList, setSearchParams, delList } = methods
// ========== CRUD 相关 ==========
const actionLoading = ref(false) // 遮罩层
const actionType = ref('') // 操作按钮的类型
const dialogVisible = ref(false) // 是否显示弹出层
const dialogTitle = ref('edit') // 弹出层标题
const formRef = ref<FormExpose>() // 表单 Ref
// 设置标题
const setDialogTile = (type: string) => {
dialogTitle.value = t('action.' + type)
actionType.value = type
dialogVisible.value = true
}
// 新增操作
const handleCreate = () => {
setDialogTile('create')
// 重置表单
unref(formRef)?.getElFormRef()?.resetFields()
}
// 修改操作
const handleUpdate = async (row: FileConfigVO) => {
setDialogTile('update')
// 设置数据
const res = await FileConfigApi.getFileConfigApi(row.id)
unref(formRef)?.setValues(res)
}
// 主配置操作
const handleMaster = (row: FileConfigVO) => {
ElMessageBox.confirm('是否确认修改配置【 ' + row.name + ' 】为主配置?', t('common.reminder'), {
confirmButtonText: t('common.ok'),
cancelButtonText: t('common.cancel'),
type: 'warning'
})
.then(async () => {
await FileConfigApi.updateFileConfigMasterApi(row.id)
await getList()
})
.catch(() => {})
}
// 提交按钮
const submitForm = async () => {
actionLoading.value = true
// 提交请求
try {
const data = unref(formRef)?.formModel as FileConfigVO
if (actionType.value === 'create') {
await FileConfigApi.createFileConfigApi(data)
ElMessage.success(t('common.createSuccess'))
} else {
await FileConfigApi.updateFileConfigApi(data)
ElMessage.success(t('common.updateSuccess'))
}
// 操作成功,重新加载列表
dialogVisible.value = false
await getList()
} finally {
actionLoading.value = false
}
}
// 删除操作
const handleDelete = (row: FileConfigVO) => {
delList(row.id, false)
}
// ========== 详情相关 ==========
const detailRef = ref() // 详情 Ref
// 详情操作
const handleDetail = async (row: FileConfigVO) => {
// 设置数据
detailRef.value = row
setDialogTile('detail')
}
// ========== 初始化 ==========
getList()
</script>
<template>
<!-- 搜索工作区 -->
<ContentWrap>
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
</ContentWrap>
<ContentWrap>
<!-- 操作工具栏 -->
<div class="mb-10px">
<el-button type="primary" v-hasPermi="['infra:file-config:create']" @click="handleCreate">
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
</el-button>
</div>
<!-- 列表 -->
<Table
:columns="allSchemas.tableColumns"
:selection="false"
:data="tableObject.tableList"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
@register="register"
>
<template #storage="{ row }">
<DictTag :type="DICT_TYPE.INFRA_FILE_STORAGE" :value="row.storage" />
</template>
<template #primary="{ row }">
<DictTag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="row.master" />
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #action="{ row }">
<el-button
link
type="primary"
v-hasPermi="['infra:file-config:update']"
@click="handleUpdate(row)"
>
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['infra:file-config:update']"
@click="handleDetail(row)"
>
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['infra:file-config:update']"
@click="handleMaster(row)"
>
<Icon icon="ep:flag" class="mr-5px" /> 主配置
</el-button>
<el-button
link
type="primary"
v-hasPermi="['infra:file-config:update']"
@click="handleUpdate(row)"
>
<Icon icon="ep:share" class="mr-5px" /> {{ t('action.test') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['infra:file-config:delete']"
@click="handleDelete(row)"
>
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
</el-button>
</template>
</Table>
</ContentWrap>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<!-- 对话框(添加 / 修改) -->
<Form
v-if="['create', 'update'].includes(actionType)"
:schema="allSchemas.formSchema"
:rules="rules"
ref="formRef"
/>
<!-- 对话框(详情) -->
<Descriptions
v-if="actionType === 'detail'"
:schema="allSchemas.detailSchema"
:data="detailRef"
>
<template #storage="{ row }">
<DictTag :type="DICT_TYPE.INFRA_FILE_STORAGE" :value="row.storage" />
</template>
<template #primary="{ row }">
<DictTag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="row.master" />
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
</Descriptions>
<!-- 操作按钮 -->
<template #footer>
<el-button
v-if="['create', 'update'].includes(actionType)"
type="primary"
:loading="actionLoading"
@click="submitForm"
>
{{ t('action.save') }}
</el-button>
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
</template>
</Dialog>
</template>

View File

@@ -0,0 +1,122 @@
<script lang="ts" setup>
import { onMounted, ref } from 'vue'
import dayjs from 'dayjs'
import DictTag from '@/components/DictTag/src/DictTag.vue'
import * as JobLogApi from '@/api/infra/jobLog'
import { JobLogVO } from '@/api/infra/jobLog/types'
import Icon from '@/components/Icon/src/Icon.vue'
import { DICT_TYPE } from '@/utils/dict'
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import { useRoute } from 'vue-router'
import { allSchemas } from './jobLog.data'
const { t } = useI18n() // 国际化
const { query } = useRoute()
// ========== 列表相关 ==========
const { register, tableObject, methods } = useTable<PageResult<JobLogVO>, JobLogVO>({
getListApi: JobLogApi.getJobLogPageApi,
exportListApi: JobLogApi.exportJobLogApi
})
const { getList, setSearchParams, exportList } = methods
const getTableList = async () => {
const id = (query.id as unknown as number) && (query.jobId as unknown as number)
tableObject.paramsObj.params = {
jobId: id
}
await getList()
}
// 导出操作
const handleExport = async () => {
await exportList('定时任务日志.xls')
}
// ========== CRUD 相关 ==========
const dialogVisible = ref(false) // 是否显示弹出层
const dialogTitle = ref('') // 弹出层标题
// ========== 详情相关 ==========
const detailRef = ref() // 详情 Ref
// 详情操作
const handleDetail = async (row: JobLogVO) => {
// 设置数据
const res = JobLogApi.getJobLogApi(row.id)
detailRef.value = res
dialogTitle.value = t('action.detail')
dialogVisible.value = true
}
// ========== 初始化 ==========
onMounted(() => {
getTableList()
})
</script>
<template>
<!-- 搜索工作区 -->
<ContentWrap>
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
</ContentWrap>
<ContentWrap>
<!-- 操作工具栏 -->
<div class="mb-10px">
<el-button
type="warning"
v-hasPermi="['infra:job:export']"
:loading="tableObject.exportLoading"
@click="handleExport"
>
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') }}
</el-button>
</div>
<!-- 列表 -->
<Table
:columns="allSchemas.tableColumns"
:selection="false"
:data="tableObject.tableList"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
@register="register"
>
<template #beginTime="{ row }">
<span>{{
dayjs(row.beginTime).format('YYYY-MM-DD HH:mm:ss') +
' ~ ' +
dayjs(row.endTime).format('YYYY-MM-DD HH:mm:ss')
}}</span>
</template>
<template #duration="{ row }">
<span>{{ row.duration + ' 毫秒' }}</span>
</template>
<template #status="{ row }">
<DictTag :type="DICT_TYPE.INFRA_JOB_LOG_STATUS" :value="row.status" />
</template>
<template #action="{ row }">
<el-button link type="primary" v-hasPermi="['infra:job:query']" @click="handleDetail(row)">
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
</el-button>
</template>
</Table>
</ContentWrap>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<!-- 对话框(详情) -->
<Descriptions :schema="allSchemas.detailSchema" :data="detailRef">
<template #status="{ row }">
<DictTag :type="DICT_TYPE.INFRA_JOB_LOG_STATUS" :value="row.status" />
</template>
<template #retryInterval="{ row }">
<span>{{ row.retryInterval + '毫秒' }} </span>
</template>
<template #monitorTimeout="{ row }">
<span>{{ row.monitorTimeout > 0 ? row.monitorTimeout + ' 毫秒' : '未开启' }}</span>
</template>
</Descriptions>
<!-- 操作按钮 -->
<template #footer>
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
</template>
</Dialog>
</template>

View File

@@ -0,0 +1,215 @@
<script lang="ts" setup>
import { ref, unref } from 'vue'
import DictTag from '@/components/DictTag/src/DictTag.vue'
import * as JobApi from '@/api/infra/job'
import { JobVO } from '@/api/infra/job/types'
import Icon from '@/components/Icon/src/Icon.vue'
import { DICT_TYPE } from '@/utils/dict'
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import { FormExpose } from '@/components/Form'
import { rules, allSchemas } from './job.data'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useRouter } from 'vue-router'
const { t } = useI18n() // 国际化
const { push } = useRouter()
// ========== 列表相关 ==========
const { register, tableObject, methods } = useTable<PageResult<JobVO>, JobVO>({
getListApi: JobApi.getJobPageApi,
delListApi: JobApi.deleteJobApi,
exportListApi: JobApi.exportJobApi
})
const { getList, setSearchParams, delList, exportList } = methods
// 导出操作
const handleExport = async () => {
await exportList('定时任务.xls')
}
// ========== CRUD 相关 ==========
const actionLoading = ref(false) // 遮罩层
const actionType = ref('') // 操作按钮的类型
const dialogVisible = ref(false) // 是否显示弹出层
const dialogTitle = ref('edit') // 弹出层标题
const formRef = ref<FormExpose>() // 表单 Ref
// 设置标题
const setDialogTile = (type: string) => {
dialogTitle.value = t('action.' + type)
actionType.value = type
dialogVisible.value = true
}
// 新增操作
const handleCreate = () => {
setDialogTile('create')
// 重置表单
unref(formRef)?.getElFormRef()?.resetFields()
}
// 修改操作
const handleUpdate = async (row: JobVO) => {
setDialogTile('update')
// 设置数据
const res = await JobApi.getJobApi(row.id)
unref(formRef)?.setValues(res)
}
// 执行日志
const handleJobLog = (row: JobVO) => {
if (row.id) {
push('/job/job-log?id=' + row.id)
} else {
push('/job/job-log')
}
}
// 执行一次
const handleRun = (row: JobVO) => {
ElMessageBox.confirm('确认要立即执行一次' + row.name + '?', t('common.reminder'), {
confirmButtonText: t('common.ok'),
cancelButtonText: t('common.cancel'),
type: 'warning'
})
.then(async () => {
JobApi.runJobApi(row.id).then(() => {
ElMessage.success('执行成功')
getList()
})
})
.catch(() => {})
}
// 提交按钮
const submitForm = async () => {
actionLoading.value = true
// 提交请求
try {
const data = unref(formRef)?.formModel as JobVO
if (actionType.value === 'create') {
await JobApi.createJobApi(data)
ElMessage.success(t('common.createSuccess'))
} else {
await JobApi.updateJobApi(data)
ElMessage.success(t('common.updateSuccess'))
}
// 操作成功,重新加载列表
dialogVisible.value = false
await getList()
} finally {
actionLoading.value = false
}
}
// 删除操作
const handleDelete = (row: JobVO) => {
delList(row.id, false)
}
// ========== 详情相关 ==========
const detailRef = ref() // 详情 Ref
// 详情操作
const handleDetail = async (row: JobVO) => {
// 设置数据
const res = JobApi.getJobApi(row.id)
detailRef.value = res
setDialogTile('detail')
}
// ========== 初始化 ==========
getList()
</script>
<template>
<!-- 搜索工作区 -->
<ContentWrap>
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
</ContentWrap>
<ContentWrap>
<!-- 操作工具栏 -->
<div class="mb-10px">
<el-button type="primary" v-hasPermi="['infra:job:create']" @click="handleCreate">
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
</el-button>
<el-button
type="warning"
v-hasPermi="['infra:job:export']"
:loading="tableObject.exportLoading"
@click="handleExport"
>
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') }}
</el-button>
<el-button type="info" v-hasPermi="['infra:job:query']" @click="handleJobLog">
<Icon icon="el:zoom-in" class="mr-5px" /> 执行日志
</el-button>
</div>
<!-- 列表 -->
<Table
:columns="allSchemas.tableColumns"
:selection="false"
:data="tableObject.tableList"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
@register="register"
>
<template #status="{ row }">
<DictTag :type="DICT_TYPE.INFRA_JOB_STATUS" :value="row.status" />
</template>
<template #action="{ row }">
<el-button link type="primary" v-hasPermi="['infra:job:update']" @click="handleUpdate(row)">
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
</el-button>
<el-button link type="primary" v-hasPermi="['infra:job:query']" @click="handleDetail(row)">
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
</el-button>
<el-button link type="primary" v-hasPermi="['infra:job:delete']" @click="handleDelete(row)">
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
</el-button>
<el-button link type="primary" v-hasPermi="['infra:job:trigger']" @click="handleRun(row)">
<Icon icon="ep:view" class="mr-5px" /> 执行一次
</el-button>
<el-button link type="primary" v-hasPermi="['infra:job:query']" @click="handleJobLog(row)">
<Icon icon="ep:view" class="mr-5px" /> 调度日志
</el-button>
</template>
</Table>
</ContentWrap>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<!-- 对话框(添加 / 修改) -->
<Form
v-if="['create', 'update'].includes(actionType)"
:schema="allSchemas.formSchema"
:rules="rules"
ref="formRef"
/>
<!-- 对话框(详情) -->
<Descriptions
v-if="actionType === 'detail'"
:schema="allSchemas.detailSchema"
:data="detailRef"
>
<template #status="{ row }">
<DictTag :type="DICT_TYPE.INFRA_JOB_STATUS" :value="row.status" />
</template>
<template #retryInterval="{ row }">
<span>{{ row.retryInterval + '毫秒' }} </span>
</template>
<template #monitorTimeout="{ row }">
<span>{{ row.monitorTimeout > 0 ? row.monitorTimeout + ' 毫秒' : '未开启' }}</span>
</template>
</Descriptions>
<!-- 操作按钮 -->
<template #footer>
<el-button
v-if="['create', 'update'].includes(actionType)"
type="primary"
:loading="actionLoading"
@click="submitForm"
>
{{ t('action.save') }}
</el-button>
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
</template>
</Dialog>
</template>

View File

@@ -0,0 +1,93 @@
import { reactive } from 'vue'
import { DICT_TYPE } from '@/utils/dict'
import { required } from '@/utils/formRules'
import { useI18n } from '@/hooks/web/useI18n'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
// 国际化
const { t } = useI18n()
// 表单校验
export const rules = reactive({
name: [required],
handlerName: [required],
cronExpression: [required],
retryCount: [required],
retryInterval: [required]
})
// CrudSchema
const crudSchemas = reactive<CrudSchema[]>([
{
label: t('common.index'),
field: 'id',
type: 'index',
form: {
show: false
},
detail: {
show: false
}
},
{
label: '任务名称',
field: 'name',
search: {
show: true
}
},
{
label: t('common.status'),
field: 'status',
dictType: DICT_TYPE.INFRA_JOB_STATUS
},
{
label: '处理器的名字',
field: 'handlerName',
search: {
show: true
}
},
{
label: '处理器的参数',
field: 'handlerParam',
table: {
show: false
}
},
{
label: 'CRON 表达式',
field: 'cronExpression'
},
{
label: '重试次数',
field: 'retryCount',
table: {
show: false
}
},
{
label: '重试间隔',
field: 'retryInterval',
table: {
show: false
}
},
{
label: '监控超时时间',
field: 'monitorTimeout',
table: {
show: false
}
},
{
field: 'action',
width: '400px',
label: t('table.action'),
form: {
show: false
},
detail: {
show: false
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@@ -0,0 +1,94 @@
import { reactive } from 'vue'
import { DICT_TYPE } from '@/utils/dict'
import { useI18n } from '@/hooks/web/useI18n'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
// 国际化
const { t } = useI18n()
// CrudSchema
const crudSchemas = reactive<CrudSchema[]>([
{
label: t('common.index'),
field: 'id',
type: 'index',
form: {
show: false
},
detail: {
show: false
}
},
{
label: '任务编号',
field: 'jobId',
search: {
show: true
}
},
{
label: '处理器的名字',
field: 'handlerName',
search: {
show: true
}
},
{
label: '处理器的参数',
field: 'handlerParam'
},
{
label: '第几次执行',
field: 'executeIndex'
},
{
label: '开始执行时间',
field: 'beginTime',
search: {
show: true,
component: 'DatePicker',
componentProps: {
type: 'date',
valueFormat: 'YYYY-MM-DD HH:mm:ss'
}
}
},
{
label: '结束执行时间',
field: 'endTime',
search: {
show: true,
component: 'DatePicker',
componentProps: {
type: 'date',
valueFormat: 'YYYY-MM-DD HH:mm:ss'
}
},
table: {
show: false
}
},
{
label: '执行时长',
field: 'duration'
},
{
label: t('common.status'),
field: 'status',
dictType: DICT_TYPE.INFRA_JOB_LOG_STATUS,
search: {
show: true
}
},
{
field: 'action',
width: '100px',
label: t('table.action'),
form: {
show: false
},
detail: {
show: false
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@@ -0,0 +1,205 @@
<script lang="ts" setup>
import { onBeforeMount, ref } from 'vue'
import * as RedisApi from '@/api/infra/redis'
import DictTag from '@/components/DictTag/src/DictTag.vue'
import { DICT_TYPE } from '@/utils/dict'
import * as echarts from 'echarts'
import { RedisKeyInfo, RedisMonitorInfoVO } from '@/api/infra/redis/types'
import {
ElRow,
ElCard,
ElCol,
ElTable,
ElTableColumn,
ElScrollbar,
ElDescriptions,
ElDescriptionsItem
} from 'element-plus'
const cache = ref<RedisMonitorInfoVO>()
const keyListLoad = ref(true)
const keyList = ref<RedisKeyInfo[]>([])
// 基本信息
const readRedisInfo = async () => {
const data = await RedisApi.redisMonitorInfo()
cache.value = data
loadEchartOptions(cache.value.commandStats)
const redisKeysInfo = await RedisApi.redisKeysInfo()
keyList.value = redisKeysInfo
keyListLoad.value = false //加载完成
}
// 图表
const commandStatsRef = ref<HTMLElement>()
const usedmemory = ref<HTMLDivElement>()
const loadEchartOptions = (stats) => {
const commandStats = [] as any[]
const nameList = [] as string[]
stats.forEach((row) => {
commandStats.push({
name: row.command,
value: row.calls
})
nameList.push(row.command)
})
const commandStatsInstance = echarts.init(commandStatsRef.value!, 'macarons')
commandStatsInstance.setOption({
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: nameList,
textStyle: {
color: '#a1a1a1'
}
},
series: [
{
name: '命令',
type: 'pie',
radius: [20, 120],
center: ['40%', '60%'],
data: commandStats,
roseType: 'radius',
label: {
show: true
},
emphasis: {
label: {
show: true
},
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
})
const usedMemoryInstance = echarts.init(usedmemory.value!, 'macarons')
usedMemoryInstance.setOption({
title: {
text: '内存使用情况',
left: 'center'
},
tooltip: {
formatter: '{b} <br/>{a} : ' + cache.value!.info.used_memory_human
},
series: [
{
name: '峰值',
type: 'gauge',
min: 0,
max: 100,
progress: {
show: true
},
detail: {
formatter: cache.value!.info.used_memory_human
},
data: [
{
value: parseFloat(cache.value!.info.used_memory_human),
name: '内存消耗'
}
]
}
]
})
}
onBeforeMount(() => {
readRedisInfo()
})
</script>
<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" style="margin-top: 10px">
<el-card :gutter="12" shadow="hover">
<div ref="commandStatsRef" style="height: 350px"></div>
</el-card>
</el-col>
<el-col :span="12" style="margin-top: 10px">
<el-card style="margin-left: 10px" :gutter="12" shadow="hover">
<div ref="usedmemory" style="height: 350px"></div>
</el-card>
</el-col>
</el-row>
<el-row style="margin-top: 10px">
<el-col :span="24" class="card-box" shadow="hover">
<el-card>
<el-table v-loading="keyListLoad" :data="keyList" row-key="id">
<el-table-column prop="keyTemplate" label="Key 模板" width="200" />
<el-table-column prop="keyType" label="Key 类型" width="100" />
<el-table-column prop="valueType" label="Value 类型" />
<el-table-column prop="timeoutType" label="超时时间" width="200">
<template #default="{ row }">
<DictTag :type="DICT_TYPE.INFRA_REDIS_TIMEOUT_TYPE" :value="row?.timeoutType" />
<span v-if="row?.timeout > 0">({{ row?.timeout / 1000 }} )</span>
</template>
</el-table-column>
<el-table-column prop="memo" label="备注" />
</el-table>
</el-card>
</el-col>
</el-row>
</el-scrollbar>
</template>

View File

@@ -0,0 +1,12 @@
<script setup lang="ts">
import { IFrame } from '@/components/IFrame'
import { ref } from 'vue'
const BASE_URL = import.meta.env.VITE_BASE_URL
const src = ref(BASE_URL + '/admin/applications')
</script>
<template>
<ContentWrap>
<IFrame :src="src" />
</ContentWrap>
</template>

View File

@@ -0,0 +1,11 @@
<script setup lang="ts">
import { IFrame } from '@/components/IFrame'
import { ref } from 'vue'
const src = ref('http://skywalking.shop.iocoder.cn')
</script>
<template>
<ContentWrap>
<IFrame :src="src" />
</ContentWrap>
</template>

View File

@@ -0,0 +1,12 @@
<script setup lang="ts">
import { IFrame } from '@/components/IFrame'
import { ref } from 'vue'
const BASE_URL = import.meta.env.VITE_BASE_URL
const src = ref(BASE_URL + '/doc.html')
</script>
<template>
<ContentWrap>
<IFrame :src="src" />
</ContentWrap>
</template>

View File

@@ -0,0 +1,7 @@
<script setup lang="ts"></script>
<template>
<div>index</div>
</template>
<style scoped></style>