Browse Source

Merge branch 'master' of ssh://www.yixzm.cn:22022/Dim5-ideal/bj-maritime-dcds

Luka 5 days ago
parent
commit
37cddc2008
31 changed files with 4292 additions and 532 deletions
  1. 10 0
      frontend/.eslintrc.js
  2. 22 22
      frontend/package.json
  3. 1 4
      frontend/src/api/main.ts
  4. 57 0
      frontend/src/api/monitor/cache.ts
  5. 70 0
      frontend/src/api/monitor/job.ts
  6. 26 0
      frontend/src/api/monitor/jobLog.ts
  7. 34 0
      frontend/src/api/monitor/logininfor.ts
  8. 18 0
      frontend/src/api/monitor/online.ts
  9. 26 0
      frontend/src/api/monitor/operlog.ts
  10. 9 0
      frontend/src/api/monitor/server.ts
  11. 1 4
      frontend/src/api/system/dict/data.ts
  12. 151 203
      frontend/src/components/components/pageContent.vue
  13. 142 159
      frontend/src/components/components/pageSearch.vue
  14. 320 108
      frontend/src/views/login.vue
  15. 187 0
      frontend/src/views/monitor/cache/index.vue
  16. 246 0
      frontend/src/views/monitor/cache/list.vue
  17. 13 0
      frontend/src/views/monitor/druid/index.vue
  18. 561 0
      frontend/src/views/monitor/job/index.vue
  19. 314 0
      frontend/src/views/monitor/job/log.vue
  20. 270 0
      frontend/src/views/monitor/logininfor/index.vue
  21. 124 0
      frontend/src/views/monitor/online/index.vue
  22. 355 0
      frontend/src/views/monitor/operlog/index.vue
  23. 272 0
      frontend/src/views/monitor/server/index.vue
  24. 15 32
      frontend/src/views/system/user/index.vue
  25. 3 0
      frontend/src/views/tool/build/index.vue
  26. 55 0
      frontend/src/views/tool/gen/basicInfoForm.vue
  27. 205 0
      frontend/src/views/tool/gen/editTable.vue
  28. 295 0
      frontend/src/views/tool/gen/genInfoForm.vue
  29. 133 0
      frontend/src/views/tool/gen/importTable.vue
  30. 345 0
      frontend/src/views/tool/gen/index.vue
  31. 12 0
      frontend/src/views/tool/swagger/index.vue

+ 10 - 0
frontend/.eslintrc.js

@@ -45,7 +45,17 @@ module.exports = {
         ],
         'no-eval': 2,
         semi: 1,
+<<<<<<< HEAD
         indent: off,
+=======
+        indent: [
+            2,
+            4,
+            {
+                SwitchCase: 1,
+            },
+        ],
+>>>>>>> e94f5f44564039f665611f42b365b51740d0649c
         camelcase: 2,
         'vue/no-empty-component-block': 2, // 禁止<template> <script> <style>块为空
         'vue/html-self-closing': 0,

+ 22 - 22
frontend/package.json

@@ -15,48 +15,48 @@
         "@vueup/vue-quill": "1.1.0",
         "@vueuse/core": "9.5.0",
         "@wangeditor/editor-for-vue": "^5.1.12",
-        "@zeronejs/utils": "^1.4.2",
+        "@zeronejs/utils": "^1.4.0",
         "axios": "0.27.2",
         "crypto-js": "^4.2.0",
-        "dayjs": "^1.11.18",
+        "dayjs": "^1.11.11",
         "echarts": "5.4.0",
-        "element-china-area-data": "^6.1.0",
+        "element-china-area-data": "^6.0.2",
         "element-plus": "2.2.27",
         "file-saver": "2.0.5",
         "fuse.js": "6.6.2",
-        "jodit": "^4.6.2",
+        "jodit": "^4.0.18",
         "js-cookie": "3.0.1",
         "jsencrypt": "3.3.1",
         "lodash": "^4.17.21",
         "nprogress": "0.2.0",
         "pinia": "2.0.22",
-        "terser": "^5.43.1",
-        "tinymce": "^6.8.6",
+        "terser": "^5.30.0",
+        "tinymce": "^6.8.3",
         "vue": "3.2.45",
         "vue-cropper": "1.0.3",
         "vue-router": "4.1.4"
     },
     "devDependencies": {
         "@types/crypto-js": "^4.2.2",
-        "@types/file-saver": "^2.0.7",
-        "@types/js-cookie": "^3.0.6",
-        "@types/node": "^18.19.123",
-        "@types/nprogress": "^0.2.3",
-        "@typescript-eslint/eslint-plugin": "^5.62.0",
-        "@typescript-eslint/parser": "^5.62.0",
+        "@types/file-saver": "^2.0.5",
+        "@types/js-cookie": "^3.0.2",
+        "@types/node": "^18.7.15",
+        "@types/nprogress": "^0.2.0",
+        "@typescript-eslint/eslint-plugin": "^5.36.2",
+        "@typescript-eslint/parser": "^5.36.2",
         "@vitejs/plugin-vue": "3.1.0",
-        "autoprefixer": "^10.4.21",
-        "eslint": "^8.57.1",
-        "eslint-config-prettier": "^8.10.2",
-        "eslint-plugin-vue": "^9.33.0",
-        "postcss": "^8.5.6",
+        "autoprefixer": "^10.4.8",
+        "eslint": "^8.23.0",
+        "eslint-config-prettier": "^8.5.0",
+        "eslint-plugin-vue": "^9.4.0",
+        "postcss": "^8.4.16",
         "sass": "1.56.1",
-        "tailwindcss": "^3.4.17",
-        "ts-node": "^10.9.2",
-        "tsconfig-paths": "^4.2.0",
-        "typescript": "^4.9.5",
+        "tailwindcss": "^3.1.8",
+        "ts-node": "^10.9.1",
+        "tsconfig-paths": "^4.1.0",
+        "typescript": "^4.8.2",
         "unplugin-auto-import": "0.11.4",
-        "unplugin-vue-components": "^0.22.12",
+        "unplugin-vue-components": "^0.22.4",
         "vite": "3.2.3",
         "vite-plugin-compression": "^0.5.1",
         "vite-plugin-svg-icons": "2.0.1",

+ 1 - 4
frontend/src/api/main.ts

@@ -47,11 +47,8 @@ export function getPageDetail(pageName: string, urlId: any) {
 // 公共参数 agreeType 协议类型 assetType 资产类型 systemType 操作系统类型 titleType标签类型 expertLevel专家级别
 export function getCommon(value: string) {
     return request({
-        url: '/system/dict/data/type',
+        url: `/system/dict/data/type/${value}`,
         method: 'get',
-        params: {
-            dictType: value,
-        },
     });
 }
 

+ 57 - 0
frontend/src/api/monitor/cache.ts

@@ -0,0 +1,57 @@
+import request from '@/utils/request';
+
+// 查询缓存详细
+export function getCache() {
+    return request({
+        url: '/monitor/cache',
+        method: 'get',
+    });
+}
+
+// 查询缓存名称列表
+export function listCacheName() {
+    return request({
+        url: '/monitor/cache/getNames',
+        method: 'get',
+    });
+}
+
+// 查询缓存键名列表
+export function listCacheKey(cacheName: any) {
+    return request({
+        url: '/monitor/cache/getKeys/' + cacheName,
+        method: 'get',
+    });
+}
+
+// 查询缓存内容
+export function getCacheValue(cacheName: any, cacheKey: any) {
+    return request({
+        url: '/monitor/cache/getValue/' + cacheName + '/' + cacheKey,
+        method: 'get',
+    });
+}
+
+// 清理指定名称缓存
+export function clearCacheName(cacheName: any) {
+    return request({
+        url: '/monitor/cache/clearCacheName/' + cacheName,
+        method: 'delete',
+    });
+}
+
+// 清理指定键名缓存
+export function clearCacheKey(cacheKey: any) {
+    return request({
+        url: '/monitor/cache/clearCacheKey/' + cacheKey,
+        method: 'delete',
+    });
+}
+
+// 清理全部缓存
+export function clearCacheAll() {
+    return request({
+        url: '/monitor/cache/clearCacheAll',
+        method: 'delete',
+    });
+}

+ 70 - 0
frontend/src/api/monitor/job.ts

@@ -0,0 +1,70 @@
+import request from '@/utils/request';
+
+// 查询定时任务调度列表
+export function listJob(query: any) {
+    return request({
+        url: '/monitor/job/list',
+        method: 'get',
+        params: query,
+    });
+}
+
+// 查询定时任务调度详细
+export function getJob(jobId: any) {
+    return request({
+        url: '/monitor/job/' + jobId,
+        method: 'get',
+    });
+}
+
+// 新增定时任务调度
+export function addJob(data: any) {
+    return request({
+        url: '/monitor/job',
+        method: 'post',
+        data: data,
+    });
+}
+
+// 修改定时任务调度
+export function updateJob(data: any) {
+    return request({
+        url: '/monitor/job',
+        method: 'put',
+        data: data,
+    });
+}
+
+// 删除定时任务调度
+export function delJob(jobId: any) {
+    return request({
+        url: '/monitor/job/' + jobId,
+        method: 'delete',
+    });
+}
+
+// 任务状态修改
+export function changeJobStatus(jobId: any, status: any) {
+    const data = {
+        jobId,
+        status,
+    };
+    return request({
+        url: '/monitor/job/changeStatus',
+        method: 'put',
+        data: data,
+    });
+}
+
+// 定时任务立即执行一次
+export function runJob(jobId: any, jobGroup: any) {
+    const data = {
+        jobId,
+        jobGroup,
+    };
+    return request({
+        url: '/monitor/job/run',
+        method: 'put',
+        data: data,
+    });
+}

+ 26 - 0
frontend/src/api/monitor/jobLog.ts

@@ -0,0 +1,26 @@
+import request from '@/utils/request';
+
+// 查询调度日志列表
+export function listJobLog(query: any) {
+    return request({
+        url: '/monitor/jobLog/list',
+        method: 'get',
+        params: query,
+    });
+}
+
+// 删除调度日志
+export function delJobLog(jobLogId: any) {
+    return request({
+        url: '/monitor/jobLog/' + jobLogId,
+        method: 'delete',
+    });
+}
+
+// 清空调度日志
+export function cleanJobLog() {
+    return request({
+        url: '/monitor/jobLog/clean',
+        method: 'delete',
+    });
+}

+ 34 - 0
frontend/src/api/monitor/logininfor.ts

@@ -0,0 +1,34 @@
+import request from '@/utils/request';
+
+// 查询登录日志列表
+export function list(query: any) {
+    return request({
+        url: '/monitor/logininfor/list',
+        method: 'get',
+        params: query,
+    });
+}
+
+// 删除登录日志
+export function delLogininfor(infoId: any) {
+    return request({
+        url: '/monitor/logininfor/' + infoId,
+        method: 'delete',
+    });
+}
+
+// 解锁用户登录状态
+export function unlockLogininfor(userName: any) {
+    return request({
+        url: '/monitor/logininfor/unlock/' + userName,
+        method: 'get',
+    });
+}
+
+// 清空登录日志
+export function cleanLogininfor() {
+    return request({
+        url: '/monitor/logininfor/clean',
+        method: 'delete',
+    });
+}

+ 18 - 0
frontend/src/api/monitor/online.ts

@@ -0,0 +1,18 @@
+import request from '@/utils/request';
+
+// 查询在线用户列表
+export function list(query: any) {
+    return request({
+        url: '/monitor/online/list',
+        method: 'get',
+        params: query,
+    });
+}
+
+// 强退用户
+export function forceLogout(tokenId: any) {
+    return request({
+        url: '/monitor/online/' + tokenId,
+        method: 'delete',
+    });
+}

+ 26 - 0
frontend/src/api/monitor/operlog.ts

@@ -0,0 +1,26 @@
+import request from '@/utils/request';
+
+// 查询操作日志列表
+export function list(query: any) {
+    return request({
+        url: '/monitor/operlog/list',
+        method: 'get',
+        params: query,
+    });
+}
+
+// 删除操作日志
+export function delOperlog(operId: any) {
+    return request({
+        url: '/monitor/operlog/' + operId,
+        method: 'delete',
+    });
+}
+
+// 清空操作日志
+export function cleanOperlog() {
+    return request({
+        url: '/monitor/operlog/clean',
+        method: 'delete',
+    });
+}

+ 9 - 0
frontend/src/api/monitor/server.ts

@@ -0,0 +1,9 @@
+import request from '@/utils/request';
+
+// 获取服务信息
+export function getServer() {
+    return request({
+        url: '/monitor/server',
+        method: 'get',
+    });
+}

+ 1 - 4
frontend/src/api/system/dict/data.ts

@@ -20,11 +20,8 @@ export function getData(dictCode: any) {
 // 根据字典类型查询字典数据信息
 export function getDicts(dictType: any) {
     return request({
-        url: '/system/dict/data/type/',
+        url: `/system/dict/data/type/${dictType}`,
         method: 'get',
-        params: {
-            dictType,
-        },
     });
 }
 

+ 151 - 203
frontend/src/components/components/pageContent.vue

@@ -1,106 +1,54 @@
 <template>
-	<div class="contenBox">
-		<div class="table-action flex-b">
-			<div class="customList">{{ contentConfig.header.title ? contentConfig.header.title : '' }}</div>
-			<div class="btns">
-				<slot name="button"></slot>
-			</div>
-		</div>
-		<el-table
-			:data="pageList"
-			style="width: 100%; max-height: 55vh; overflow: auto"
-			@selection-change="handleSelectionChange"
-			ref="multipleTableRef"
-			v-bind="props.contentConfig?.tableProps"
-			:row-key="getRowKeys"
-			v-loading="loading"
-		>
-			<template v-for="item in contentConfig.propsList">
-				<!-- 序号 -->
-				<el-table-column
-					:fixed="item.fixed"
-					v-if="item.type === 'index'"
-					type="index"
-					:key="item.prop"
-					center
-				>
-				</el-table-column>
-				<!-- 选择 -->
-				<el-table-column
-					:fixed="item.fixed"
-					type="selection"
-					width="55"
-					align="center"
-					:key="item.prop"
-					v-if="item.type === 'selection'"
-					:reserve-selection="true"
-				/>
-				<!-- 操作类 -->
-				<el-table-column
-					:key="item"
-					:fixed="item.fixed"
-					align="center"
-					v-bind="item"
-					v-else-if="item.type === 'handler'"
-				>
-					<template #default="scope">
-						<slot :name="item.slotName" v-bind="scope"></slot>
-					</template>
-				</el-table-column>
-
-				<!-- 自定义类 -->
-				<el-table-column
-					show-overflow-tooltip
-					:fixed="item.fixed"
-					:key="item.label"
-					v-else-if="item.type === 'custom'"
-					:label="item.label"
-					:width="item.width"
-					:prop="item.prop"
-					:sortable="item.sortable || false"
-					:column-key="item.prop"
-				>
-					<template #default="scope">
-						<slot :name="item.slotName" v-bind="scope"></slot>
-					</template>
-				</el-table-column>
-				<!-- 时间类 -->
-				<el-table-column
-					show-overflow-tooltip
-					:fixed="item.fixed"
-					:key="item.label"
-					v-else-if="item.type === 'time'"
-					align="center"
-					:label="item.label"
-					:width="item.width"
-				>
-					<template #default="scope">
-						{{ parseTime(scope.row[item.prop]) }}
-					</template>
-				</el-table-column>
-				<!-- 普通渲染类 -->
-				<el-table-column
-					:fixed="item.fixed"
-					show-overflow-tooltip
-					v-else-if="item.type === 'normal'"
-					:key="item.label"
-					v-bind="item"
-					:prop="item.prop"
-					:sortable="item.sortable || false"
-					:column-key="item.prop"
-				/>
-				<!-- 倒序类 -->
-				<el-table-column
-					:fixed="item.fixed"
-					show-overflow-tooltip
-					v-else-if="item.type === 'sortable'"
-					:key="item.label"
-					sortable
-					v-bind="item"
-				/>
-			</template>
-		</el-table>
-	</div>
+    <div class="contenBox">
+        <div class="table-action flex-b">
+            <div class="customList">{{ contentConfig.header.title ? contentConfig.header.title : '' }}</div>
+            <div class="btns">
+                <slot name="button"></slot>
+            </div>
+        </div>
+        <el-table :data="pageList" style="width: 100%; max-height: 55vh; overflow: auto"
+            @selection-change="handleSelectionChange" ref="multipleTableRef" v-bind="props.contentConfig?.tableProps"
+            :row-key="getRowKeys" v-loading="loading">
+            <template v-for="item in contentConfig.propsList">
+                <!-- 序号 -->
+                <el-table-column :fixed="item.fixed" v-if="item.type === 'index'" type="index" :key="item.prop" center>
+                </el-table-column>
+                <!-- 选择 -->
+                <el-table-column :fixed="item.fixed" type="selection" width="55" align="center" :key="item.prop"
+                    v-if="item.type === 'selection'" :reserve-selection="true" />
+                <!-- 操作类 -->
+                <el-table-column :key="item" :fixed="item.fixed" align="center" v-bind="item"
+                    v-else-if="item.type === 'handler'">
+                    <template #default="scope">
+                        <slot :name="item.slotName" v-bind="scope"></slot>
+                    </template>
+                </el-table-column>
+
+                <!-- 自定义类 -->
+                <el-table-column show-overflow-tooltip :fixed="item.fixed" :key="item.label"
+                    v-else-if="item.type === 'custom'" :label="item.label" :width="item.width" :prop="item.prop"
+                    :sortable="item.sortable || false" :column-key="item.prop">
+                    <template #default="scope">
+                        <slot :name="item.slotName" v-bind="scope"></slot>
+                    </template>
+                </el-table-column>
+                <!-- 时间类 -->
+                <el-table-column show-overflow-tooltip :fixed="item.fixed" :key="item.label"
+                    v-else-if="item.type === 'time'" align="center" :label="item.label" :width="item.width">
+                    <template #default="scope">
+                        {{ parseTime(scope.row[item.prop]) }}
+                    </template>
+                </el-table-column>
+                <!-- 普通渲染类 -->
+                <el-table-column :fixed="item.fixed" show-overflow-tooltip v-else-if="item.type === 'normal'"
+                    :key="item.label" v-bind="item" :prop="item.prop" :sortable="item.sortable || false"
+                    :column-key="item.prop" />
+                <!-- 倒序类 -->
+                <el-table-column :fixed="item.fixed" show-overflow-tooltip v-else-if="item.type === 'sortable'"
+                    :key="item.label" sortable v-bind="item" />
+            </template>
+        </el-table>
+    </div>
 </template>
 
 <script setup lang="ts" name="content">
@@ -110,34 +58,34 @@ import useUserStore from '@/store/modules/user';
 const userStore = useUserStore().userInfo.userName === 'admin' ? '' : useUserStore().userInfo.userId;
 import { ref, watch } from 'vue';
 const props = withDefaults(
-	defineProps<{
-		contentConfig: {
-			pageName: string;
-			status: string;
-			createUser: string;
-			oneself: number;
-			header: {
-				title: string;
-			};
-			tableProps: any;
-			propsList: any[];
-			pageListParams?: {};
-			moneyTotal?: boolean;
-			energyConsumption?: boolean;
-			energyConsumptionType?: boolean;
-			energyMonery?: boolean;
-			moneyTotalType?: boolean;
-			customSubtotal?: boolean;
-		};
-		sizes?: number[];
-		showSummary?: boolean;
-		summaryMethod?: () => any;
-		subtotalNum?: number;
-	}>(),
-	{
-		sizes: [10, 50, 100, 300, 500],
-		customSubtotal: false,
-	}
+    defineProps<{
+        contentConfig: {
+            pageName: string;
+            status: string;
+            createUser: string;
+            oneself: number;
+            header: {
+                title: string;
+            };
+            tableProps: any;
+            propsList: any[];
+            pageListParams?: {};
+            moneyTotal?: boolean;
+            energyConsumption?: boolean;
+            energyConsumptionType?: boolean;
+            energyMonery?: boolean;
+            moneyTotalType?: boolean;
+            customSubtotal?: boolean;
+        };
+        sizes?: number[];
+        showSummary?: boolean;
+        summaryMethod?: () => any;
+        subtotalNum?: number;
+    }>(),
+    {
+        sizes: [10, 50, 100, 300, 500],
+        customSubtotal: false,
+    }
 );
 const emit = defineEmits(['handleSelect', 'pageChanged']);
 const loading = ref(true);
@@ -147,29 +95,29 @@ const systemStore = useSystemStore();
 
 // 获取列表
 async function fetchPageListData(queryInfo: any = {}) {
-	loading.value = true;
-	await systemStore
-		.getPageListDataAction(props.contentConfig.pageName, {
-			pageNum: systemStore.pageInfo.pageNum,
-			pageSize: systemStore.pageInfo.pageSize,
-			userId: userStore,
-			status: props.contentConfig.status,
-			createUser: props.contentConfig.createUser,
-			oneself: props.contentConfig.oneself,
-			...queryInfo,
-			...props.contentConfig.pageListParams,
-		})
-		.then(res => {
-			loading.value = false;
-		});
+    loading.value = true;
+    await systemStore
+        .getPageListDataAction(props.contentConfig.pageName, {
+            pageNum: systemStore.pageInfo.pageNum,
+            pageSize: systemStore.pageInfo.pageSize,
+            userId: userStore,
+            status: props.contentConfig.status,
+            createUser: props.contentConfig.createUser,
+            oneself: props.contentConfig.oneself,
+            ...queryInfo,
+            ...props.contentConfig.pageListParams,
+        })
+        .then(res => {
+            loading.value = false;
+        });
 }
 
 fetchPageListData();
 systemStore.$onAction(arg => {
-	if (arg.name === 'newPageDataAction' || arg.name === 'deletePageDataAction') {
-		systemStore.pageInfo.pageNum = 1;
-		systemStore.pageInfo.pageSize = 50;
-	}
+    if (arg.name === 'newPageDataAction' || arg.name === 'deletePageDataAction') {
+        systemStore.pageInfo.pageNum = 1;
+        systemStore.pageInfo.pageSize = 50;
+    }
 });
 
 const { pageList, pageTotalCount, resDate } = storeToRefs(systemStore);
@@ -185,94 +133,94 @@ const electricityTotalPrice = ref();
 const gasTotalPrice = ref();
 const waterTotalPrice = ref();
 watch(
-	() => resDate,
-	(newValue: any) => {
-		if (props.contentConfig?.energyConsumption === true) {
-			if (props.contentConfig?.energyConsumptionType === false) {
-				rowDate.value = newValue._object.resDate.otherEnergyList;
-			} else {
-				electricityDosage.value = newValue._object.resDate.electricityDosage;
-				gasDosage.value = newValue._object.resDate.gasDosage;
-				waterDosage.value = newValue._object.resDate.waterDosage;
-			}
-		}
-		if (props.contentConfig?.energyMonery === true) {
-			electricityTotalPrice.value = newValue._object.resDate.electricityDosage;
-			gasTotalPrice.value = newValue._object.resDate.gasDosage;
-			waterTotalPrice.value = newValue._object.resDate.waterDosage;
-		}
-	},
-	{ deep: true }
+    () => resDate,
+    (newValue: any) => {
+        if (props.contentConfig?.energyConsumption === true) {
+            if (props.contentConfig?.energyConsumptionType === false) {
+                rowDate.value = newValue._object.resDate.otherEnergyList
+            } else {
+                electricityDosage.value = newValue._object.resDate.electricityDosage
+                gasDosage.value = newValue._object.resDate.gasDosage
+                waterDosage.value = newValue._object.resDate.waterDosage
+            }
+        } if (props.contentConfig?.energyMonery === true) {
+            electricityTotalPrice.value = newValue._object.resDate.electricityDosage
+            gasTotalPrice.value = newValue._object.resDate.gasDosage
+            waterTotalPrice.value = newValue._object.resDate.waterDosage
+        }
+
+    },
+    { deep: true }
 );
 watch(
-	() => pageList,
-	(newValue: any) => {
-		if (clearFlag.value) {
-			multipleTableRef.value!.clearSelection();
-			subtotal.value = 0;
-			subNumber.value = 0;
-		}
-		clearFlag.value = true;
-	},
-	{ deep: true }
+    () => pageList,
+    (newValue: any) => {
+        if (clearFlag.value) {
+            multipleTableRef.value!.clearSelection();
+            subtotal.value = 0;
+            subNumber.value = 0;
+        }
+        clearFlag.value = true;
+    },
+    { deep: true }
 );
 // 处理选中状态
 const selectedItems = ref([] as any);
 
 const getRowKeys = row => {
-	return row.checkId;
+    return row.checkId;
 };
 
 // 清空多选
 function clearSelection() {
-	if (pageList) {
-		multipleTableRef.value!.clearSelection();
-	}
+    if (pageList) {
+        multipleTableRef.value!.clearSelection();
+    }
 }
 // 多选框切换
 function handleSelectionChange(e: any) {
-	emit('handleSelect', e);
+    emit('handleSelect', e);
 }
 
 // 暴露函数
 defineExpose({
-	clearSelection,
-	fetchPageListData,
+    clearSelection,
+    fetchPageListData,
 });
 </script>
 
 <style scoped lang="scss">
 .contenBox {
-	padding: 15px 20px 20px 20px;
-	margin-top: 20px;
-	background-color: #fff;
+    padding: 15px 20px 20px 20px;
+    margin-top: 20px;
+    background-color: #fff;
 
-	.table-action {
-		margin-bottom: 15px;
+    .table-action {
+        margin-bottom: 15px;
 
-		button {
-			margin-right: 10px;
-		}
-	}
+        button {
+            margin-right: 10px;
+        }
+    }
 
-	.ivu-table-header thead tr th {
-		background-color: #f0f9fe;
-	}
+    .ivu-table-header thead tr th {
+        background-color: #f0f9fe;
+    }
 }
 
 .customList {
-	color: #606266;
-	font-weight: 700;
+    color: #606266;
+    font-weight: 700;
 }
 
 .itemName {
-	margin: 0 10px 5px 10px;
+    margin: 0 10px 5px 10px;
 }
 
 .energyClass {
-	display: flex;
-	align-items: center;
-	flex-wrap: wrap;
-	flex: 1;
+    display: flex;
+    align-items: center;
+    flex-wrap: wrap;
+    flex: 1;
 }
 </style>

+ 142 - 159
frontend/src/components/components/pageSearch.vue

@@ -1,66 +1,47 @@
 <template>
-	<div class="search">
-		<!-- 1.1.表单输入 -->
-		<el-form
-			:model="searchForm"
-			ref="formRef"
-			:label-width="searchConfig.labelWidth ? searchConfig.labelWidth : 100"
-			size="large"
-		>
-			<el-row :gutter="16">
-				<template v-for="item in searchConfig.formItems" :key="item.prop">
-					<el-col :span="8">
-						<el-form-item :label="item.label" :prop="item.prop">
-							<template v-if="item.type === 'input'">
-								<el-input v-model="searchForm[item.prop]" :placeholder="item.placeholder" />
-							</template>
-							<template v-if="item.type === 'select'">
-								<el-select
-									v-model="searchForm[item.prop]"
-									:placeholder="item.placeholder"
-									:disabled="item.disabled"
-								>
-									<template v-for="i in item.options" :key="i.label">
-										<el-option :label="i.label" :value="i.value" />
-									</template>
-								</el-select>
-							</template>
-							<template v-if="item.type === 'date-picker'">
-								<el-date-picker
-									type="daterange"
-									range-separator="-"
-									start-placeholder="开始时间"
-									end-placeholder="结束时间"
-									v-model="searchForm[item.prop]"
-									@change="onDatePickerChange"
-								/>
-							</template>
-							<template v-if="item.type === 'date-month'">
-								<el-date-picker
-									v-model="searchForm[item.prop]"
-									type="monthrange"
-									range-separator="-"
-									:disabled-date="disabledDate"
-									placeholder="请选择费用周期"
-									start-placeholder="开始时间"
-									end-placeholder="结束时间"
-									@change="getMonthDate($event, item.prop)"
-								/>
-							</template>
-						</el-form-item>
-					</el-col>
-				</template>
-				<!-- 1.2.搜索按钮  -->
-				<el-col :span="spanLength" class="flex-b">
-					<div></div>
-					<div class="btns">
-						<el-button size="default" @click="handleResetClick">重置</el-button>
-						<el-button size="default" type="primary" @click="handleQueryClick">搜索</el-button>
-					</div>
-				</el-col>
-			</el-row>
-		</el-form>
-	</div>
+    <div class="search">
+        <!-- 1.1.表单输入 -->
+        <el-form :model="searchForm" ref="formRef"
+            :label-width="searchConfig.labelWidth ? searchConfig.labelWidth : 100" size="large">
+            <el-row :gutter="16">
+                <template v-for="item in searchConfig.formItems" :key="item.prop">
+                    <el-col :span="8">
+                        <el-form-item :label="item.label" :prop="item.prop">
+                            <template v-if="item.type === 'input'">
+                                <el-input v-model="searchForm[item.prop]" :placeholder="item.placeholder" />
+                            </template>
+                            <template v-if="item.type === 'select'">
+                                <el-select v-model="searchForm[item.prop]" :placeholder="item.placeholder"
+                                    :disabled="item.disabled">
+                                    <template v-for="i in item.options" :key="i.label">
+                                        <el-option :label="i.label" :value="i.value" />
+                                    </template>
+                                </el-select>
+                            </template>
+                            <template v-if="item.type === 'date-picker'">
+                                <el-date-picker type="daterange" range-separator="-" start-placeholder="开始时间"
+                                    end-placeholder="结束时间" v-model="searchForm[item.prop]"
+                                    @change="onDatePickerChange" />
+                            </template>
+                            <template v-if="item.type === 'date-month'">
+                                <el-date-picker v-model="searchForm[item.prop]" type="monthrange" range-separator="-"
+                                    :disabled-date="disabledDate" placeholder="请选择费用周期" start-placeholder="开始时间"
+                                    end-placeholder="结束时间" @change="getMonthDate($event, item.prop)" />
+                            </template>
+                        </el-form-item>
+                    </el-col>
+                </template>
+                <!-- 1.2.搜索按钮  -->
+                <el-col :span="spanLength" class="flex-b">
+                    <div></div>
+                    <div class="btns">
+                        <el-button size="default" @click="handleResetClick">重置</el-button>
+                        <el-button size="default" type="primary" @click="handleQueryClick">搜索</el-button>
+                    </div>
+                </el-col>
+            </el-row>
+        </el-form>
+    </div>
 </template>
 
 <script setup lang="ts" name="page-search">
@@ -69,21 +50,21 @@ import { reactive, ref } from 'vue';
 import useSystemStore from '@/store/main';
 import useUserStore from '@/store/modules/user';
 import { parseTime } from '@/utils/ruoyi';
-import dayjs from 'dayjs';
+import dayjs from "dayjs";
 import { useSharedValueStore } from './sharedValueStore';
 
 const userStore = useUserStore().userInfo.userName === 'admin' ? '' : useUserStore().userInfo.userId;
 
 interface IProps {
-	searchConfig: {
-		pageName: string;
-		status: string;
-		createUser: string;
-		oneself: number;
-		formItems: any[];
-		labelWidth?: number;
-		pageListParams?: {};
-	};
+    searchConfig: {
+        pageName: string;
+        status: string;
+        createUser: string;
+        oneself: number;
+        formItems: any[];
+        labelWidth?: number;
+        pageListParams?: {};
+    };
 }
 const props = defineProps<IProps>();
 const emit = defineEmits(['datePickerChange', 'handleSure']);
@@ -92,7 +73,7 @@ const systemStore = useSystemStore();
 const sharedValueStore = useSharedValueStore();
 const { searchObj } = storeToRefs(systemStore);
 function updateValue(newVal: any) {
-	sharedValueStore.updateSharedValue(newVal);
+    sharedValueStore.updateSharedValue(newVal);
 }
 const pageNumValue = computed(() => sharedValueStore.pageNum);
 const pageSizeValue = computed(() => sharedValueStore.pageSize);
@@ -100,129 +81,131 @@ const pageNum = systemStore.pageInfo.pageNum;
 const pageSize = systemStore.pageInfo.pageSize;
 const energyDates = ref([]);
 // 禁用早于当前月份的选项
-const disabledDate = date => {
-	// 获取当前时间的年月
-	const current = new Date();
-	const currentYear = current.getFullYear();
-	const currentMonth = current.getMonth(); // 月份范围 0-11
-
-	// 传入的日期(月份选择器会传入该月的第一天,如 2023-10-01)
-	const year = date.getFullYear();
-	const month = date.getMonth();
-
-	// 判断逻辑:年份小于当前年,或同年且月份小于当前月
-	return year > currentYear || (year === currentYear && month >= currentMonth);
+const disabledDate = (date) => {
+    // 获取当前时间的年月
+    const current = new Date();
+    const currentYear = current.getFullYear();
+    const currentMonth = current.getMonth(); // 月份范围 0-11
+
+    // 传入的日期(月份选择器会传入该月的第一天,如 2023-10-01)
+    const year = date.getFullYear();
+    const month = date.getMonth();
+
+    // 判断逻辑:年份小于当前年,或同年且月份小于当前月
+    return year > currentYear || (year === currentYear && month >= currentMonth);
 };
 // 获取列表
 function fetchPageListData(queryInfo: any = {}) {
-	systemStore.pageInfo.pageNum = 1;
-	// 2.发生网络请求
-	systemStore.getPageListDataAction(props.searchConfig.pageName, {
-		pageNum: 1,
-		pageSize: pageSize,
-		userId: userStore,
-		status: props.searchConfig.status,
-		createUser: props.searchConfig.createUser,
-		oneself: props.searchConfig.oneself,
-		...queryInfo,
-		...props.searchConfig.pageListParams,
-	});
+
+    systemStore.pageInfo.pageNum = 1;
+    // 2.发生网络请求
+    systemStore.getPageListDataAction(props.searchConfig.pageName, {
+        pageNum: 1,
+        pageSize: pageSize,
+        userId: userStore,
+        status: props.searchConfig.status,
+        createUser: props.searchConfig.createUser,
+        oneself: props.searchConfig.oneself,
+        ...queryInfo,
+        ...props.searchConfig.pageListParams,
+    });
 }
 
 // 1.创建表单的数据
 const initialForm: any = {};
 for (const item of props.searchConfig.formItems) {
-	initialForm[item['prop']] = item['initialValue'] ?? '';
+    initialForm[item['prop']] = item['initialValue'] ?? '';
 }
 let searchForm = reactive(initialForm);
 
 if (props.searchConfig.status !== '') {
-	for (const item of props.searchConfig.formItems) {
-		if (item['prop'] === 'status') {
-			searchForm[item['prop']] = props.searchConfig.status;
-		}
-	}
+    for (const item of props.searchConfig.formItems) {
+        if (item['prop'] === 'status') {
+            searchForm[item['prop']] = props.searchConfig.status;
+        }
+    }
 }
 
 // 2.监听按钮的点击
 const formRef = ref<InstanceType<typeof ElForm>>();
 function handleResetClick() {
-	formRef.value?.resetFields();
-	fetchPageListData();
-	sharedValueStore.upDatePageNum(2);
+    formRef.value?.resetFields();
+    fetchPageListData();
+    sharedValueStore.upDatePageNum(2);
 }
 
 function parseTimeWithCheck(value) {
-	// 如果已经是期望的格式(8 位数字),则直接返回
-	if (/^\d{8}$/.test(value)) {
-		return value;
-	}
-	return parseTime(value)?.slice(0, 10).replaceAll('-', '');
+    // 如果已经是期望的格式(8 位数字),则直接返回
+    if (/^\d{8}$/.test(value)) {
+        return value;
+    }
+    return parseTime(value)?.slice(0, 10).replaceAll('-', '');
 }
 async function handleQueryClick() {
-	updateValue(searchForm);
-	props.searchConfig.formItems.forEach(item => {
-		if (item.type === 'date-picker' && searchForm[item.prop]) {
-			searchForm[item.prop][0] = parseTimeWithCheck(searchForm[item.prop][0]);
-			searchForm[item.prop][1] = parseTimeWithCheck(searchForm[item.prop][1]);
-		}
-	});
-	props.searchConfig.formItems.forEach(item => {
-		if (item.type === 'date-month' && searchForm[item.prop]) {
-			searchForm[item.prop][0] = dayjs(searchForm[item.prop][0]).format('YYYY-MM');
-			searchForm[item.prop][1] = dayjs(searchForm[item.prop][1]).format('YYYY-MM');
-		}
-	});
-	await fetchPageListData(searchForm);
-	searchObj.value = searchForm;
-	sharedValueStore.upDatePageNum(2);
-	emit('handleSure');
+    updateValue(searchForm);
+    props.searchConfig.formItems.forEach(item => {
+        if (item.type === 'date-picker' && searchForm[item.prop]) {
+            searchForm[item.prop][0] = parseTimeWithCheck(searchForm[item.prop][0]);
+            searchForm[item.prop][1] = parseTimeWithCheck(searchForm[item.prop][1]);
+        }
+    });
+    props.searchConfig.formItems.forEach(item => {
+        if (item.type === 'date-month' && searchForm[item.prop]) {
+            searchForm[item.prop][0] = dayjs(searchForm[item.prop][0]).format('YYYY-MM');
+            searchForm[item.prop][1] = dayjs(searchForm[item.prop][1]).format('YYYY-MM');
+        }
+    });
+    await fetchPageListData(searchForm);
+    searchObj.value = searchForm;
+    sharedValueStore.upDatePageNum(2);
+    emit('handleSure');
 }
 
 const spanLength = computed(() => {
-	const length = ref(0);
-	if (props.searchConfig.formItems.length % 3 === 0) {
-		length.value = 24;
-	} else if (props.searchConfig.formItems.length % 3 === 1) {
-		length.value = 16;
-	} else if (props.searchConfig.formItems.length % 3 === 2) {
-		length.value = 8;
-	}
-	return length.value;
+    const length = ref(0);
+    if (props.searchConfig.formItems.length % 3 === 0) {
+        length.value = 24;
+    } else if (props.searchConfig.formItems.length % 3 === 1) {
+        length.value = 16;
+    } else if (props.searchConfig.formItems.length % 3 === 2) {
+        length.value = 8;
+    }
+    return length.value;
 });
 
 const onDatePickerChange = (date: any) => {
-	emit('datePickerChange', date);
+    emit('datePickerChange', date);
+};
+const getMonthDate = (ent: any, prop: any) => {
 };
-const getMonthDate = (ent: any, prop: any) => {};
 defineExpose({
-	searchForm,
-	fetchPageListData,
-	handleResetClick,
-	handleQueryClick,
+    searchForm,
+    fetchPageListData,
+    handleResetClick,
+    handleQueryClick,
 });
 </script>
 
 <style scoped lang="scss">
 .search {
-	background-color: #fff;
-	// margin-top: 20px;
-	padding-bottom: 20px;
-	border-radius: 5px;
-
-	.el-form-item {
-		padding: 15px 20px;
-		margin-bottom: 0;
-	}
-
-	.el-select--large {
-		width: 22vw;
-	}
+    background-color: #fff;
+    // margin-top: 20px;
+    padding-bottom: 20px;
+    border-radius: 5px;
+
+    .el-form-item {
+        padding: 15px 20px;
+        margin-bottom: 0;
+    }
+
+    .el-select--large {
+        width: 22vw;
+    }
 }
 
 .btns {
-	margin-top: 19px;
-	text-align: right;
-	padding-right: 20px !important;
+    margin-top: 19px;
+    text-align: right;
+    padding-right: 20px !important;
 }
 </style>

+ 320 - 108
frontend/src/views/login.vue

@@ -1,114 +1,75 @@
 <template>
-	<div class="login">
-		<!--  -->
-		<el-form v-if="isFlag" ref="loginFirstRef" :model="loginForm" :rules="loginRules" class="login-form">
-			<h3 class="title">东航费用台账</h3>
-			<!-- 步骤一 -->
-			<el-form-item prop="username">
-				<el-input
-					v-model="loginForm.username"
-					type="text"
-					size="large"
-					auto-complete="off"
-					placeholder="账号"
-				>
-					<template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
-				</el-input>
-			</el-form-item>
-			<el-form-item prop="password">
-				<el-input
-					v-model="loginForm.password"
-					type="password"
-					size="large"
-					auto-complete="off"
-					placeholder="密码"
-					@keyup.enter="handleAllLogin"
-				>
-					<template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
-				</el-input>
-			</el-form-item>
-			<el-form-item prop="code" v-if="captchaEnabled">
-				<el-input
-					v-model="loginForm.code"
-					size="large"
-					auto-complete="off"
-					placeholder="验证码"
-					style="width: 63%"
-					@keyup.enter="handleAllLogin"
-				>
-					<template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
-				</el-input>
-				<div class="login-code">
-					<img :src="codeUrl" @click="getCode" class="login-code-img" />
-				</div>
-			</el-form-item>
-			<el-checkbox v-model="loginForm.rememberMe" style="margin: 0px 0px 25px 0px">记住密码</el-checkbox>
-			<el-form-item style="width: 100%">
-				<el-button
-					:loading="loading"
-					size="large"
-					type="primary"
-					style="width: 100%"
-					@click.prevent="handleAllLogin"
-				>
-					登 录
-				</el-button>
-				<div style="float: right" v-if="register">
-					<router-link class="link-type" :to="'/register'">立即注册</router-link>
-				</div>
-			</el-form-item>
-		</el-form>
-		<!-- 步骤二 -->
-		<el-form v-else ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
-			<h3 class="title">东航费用台账</h3>
-			<el-form-item prop="phone">
-				<el-input
-					v-model="loginForm.phone"
-					type="text"
-					size="large"
-					auto-complete="off"
-					placeholder="手机号"
-					:disabled="true"
-				>
-					<template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
-				</el-input>
-			</el-form-item>
-			<el-form-item style="width: 100%">
-				<div class="flex">
-					<el-input
-						v-model="loginForm.code"
-						class="mr10"
-						type="text"
-						size="large"
-						auto-complete="off"
-						placeholder="请输入验证码"
-					>
-						<template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
-					</el-input>
-					<el-button size="large" type="primary" style="width: 30%" @click.prevent="getVerificationCode">
-						{{ countdownText }}
-					</el-button>
-				</div>
-			</el-form-item>
-			<el-form-item style="width: 100%">
-				<el-button
-					:loading="loading"
-					size="large"
-					type="primary"
-					style="width: 100%"
-					@click.prevent="handleEndLogin"
-				>
-					<span v-if="!loading">登 录</span>
-					<span v-else>登 录 中...</span>
-				</el-button>
-			</el-form-item>
-		</el-form>
-
-		<!--  底部  -->
-		<!-- <div class="el-login-footer">
+    <div class="login">
+        <!--  -->
+        <el-form v-if="isFlag" ref="loginFirstRef" :model="loginForm" :rules="loginRules" class="login-form">
+            <h3 class="title">东航费用台账</h3>
+            <!-- 步骤一 -->
+            <el-form-item prop="username">
+                <el-input v-model="loginForm.username" type="text" size="large" auto-complete="off" placeholder="账号">
+                    <template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
+                </el-input>
+            </el-form-item>
+            <el-form-item prop="password">
+                <el-input v-model="loginForm.password" type="password" size="large" auto-complete="off" placeholder="密码"
+                    @keyup.enter="handleAllLogin">
+                    <template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
+                </el-input>
+            </el-form-item>
+            <el-form-item prop="code" v-if="captchaEnabled">
+                <el-input v-model="loginForm.code" size="large" auto-complete="off" placeholder="验证码" style="width: 63%"
+                    @keyup.enter="handleAllLogin">
+                    <template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
+                </el-input>
+                <div class="login-code">
+                    <img :src="codeUrl" @click="getCode" class="login-code-img" />
+                </div>
+            </el-form-item>
+            <el-checkbox v-model="loginForm.rememberMe" style="margin: 0px 0px 25px 0px">记住密码</el-checkbox>
+            <el-form-item style="width: 100%">
+                <el-button :loading="loading" size="large" type="primary" style="width: 100%"
+                    @click.prevent="handleAllLogin">
+                    登 录
+                </el-button>
+                <div style="float: right" v-if="register">
+                    <router-link class="link-type" :to="'/register'">立即注册</router-link>
+                </div>
+            </el-form-item>
+        </el-form>
+        <!-- 步骤二 -->
+        <el-form v-else ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
+            <h3 class="title">东航费用台账</h3>
+            <el-form-item prop="phone">
+                <el-input v-model="loginForm.phone" type="text" size="large" auto-complete="off" placeholder="手机号"
+                    :disabled="true">
+                    <template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
+                </el-input>
+            </el-form-item>
+            <el-form-item style="width: 100%">
+                <div class="flex">
+                    <el-input v-model="loginForm.code" class="mr10" type="text" size="large" auto-complete="off"
+                        placeholder="请输入验证码">
+                        <template #prefix><svg-icon icon-class="validCode"
+                                class="el-input__icon input-icon" /></template>
+                    </el-input>
+                    <el-button size="large" type="primary" style="width: 30%" @click.prevent="getVerificationCode">
+                        {{ countdownText }}
+                    </el-button>
+                </div>
+            </el-form-item>
+            <el-form-item style="width: 100%">
+                <el-button :loading="loading" size="large" type="primary" style="width: 100%"
+                    @click.prevent="handleEndLogin">
+                    <span v-if="!loading">登 录</span>
+                    <span v-else>登 录 中...</span>
+                </el-button>
+            </el-form-item>
+        </el-form>
+
+        <!--  底部  -->
+        <!-- <div class="el-login-footer">
             <span>Copyright © 2018-2023 ruoyi.vip All Rights Reserved.</span>
         </div> -->
-	</div>
+    </div>
 </template>
 
 <script setup lang="ts">
@@ -123,6 +84,7 @@ import { ref } from 'vue';
 const userStore = useUserStore();
 const router = useRouter();
 const loginForm = ref<any>({
+<<<<<<< HEAD
 	username: 'admin',
 	password: 'admin123',
 	rememberMe: false,
@@ -135,6 +97,20 @@ const loginRules = {
 	username: [{ required: true, trigger: 'blur', message: '请输入您的账号' }],
 	password: [{ required: true, trigger: 'blur', message: '请输入您的密码' }],
 	code: [{ required: true, trigger: 'change', message: '请输入验证码' }],
+=======
+    username: 'admin',
+    password: 'admin123',
+    rememberMe: false,
+    code: '',
+    uuid: '',
+    phone: '',
+});
+
+const loginRules = {
+    username: [{ required: true, trigger: 'blur', message: '请输入您的账号' }],
+    password: [{ required: true, trigger: 'blur', message: '请输入您的密码' }],
+    code: [{ required: true, trigger: 'change', message: '请输入验证码' }],
+>>>>>>> e94f5f44564039f665611f42b365b51740d0649c
 };
 
 const codeUrl = ref('');
@@ -153,6 +129,7 @@ const isFlag = ref(true);
 let countdown = ref(60);
 const isCountingDown = ref(false);
 const countdownText = computed(() => {
+<<<<<<< HEAD
 	return isCountingDown.value ? `${countdown.value}s 后重新获取` : '获取验证码';
 });
 const getVerificationCode = async () => {
@@ -233,10 +210,93 @@ const handleNext = () => {
 				});
 		}
 	});
+=======
+    return isCountingDown.value ? `${countdown.value}s 后重新获取` : '获取验证码';
+});
+const getVerificationCode = async () => {
+    if (!isCountingDown.value) {
+        await getPhoneCode({ uuid: loginForm.value.uuid }).then((res: any) => {
+            console.log(res);
+            if (res.code === 200) {
+                ElMessage.success('验证码发送成功');
+            } else {
+                ElMessage.error('验证码发送失败,请稍后重试');
+            }
+        });
+        isCountingDown.value = true;
+        countdown.value = 60;
+        const intervalId = setInterval(() => {
+            countdown.value--;
+            if (countdown.value <= 0) {
+                isCountingDown.value = false;
+                clearInterval(intervalId);
+            }
+        }, 1000);
+    }
+};
+
+const publicKey =
+    'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\n' +
+    'nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==';
+const privateKey =
+    'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY\n' +
+    '7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\n' +
+    'PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA\n' +
+    'kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow\n' +
+    'cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv\n' +
+    'DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\n' +
+    'YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\n' +
+    'UP8iWi1Qw0Y=';
+// -------------- 获取验证码 倒计时60秒 end ----------------
+// 有下一步的登陆
+const handleNext = () => {
+    loginFirstRef.value?.validate(valid => {
+        if (valid) {
+            loading.value = true;
+            // 勾选了需要记住密码设置在 cookie 中设置记住用户名和密码
+            if (loginForm.value.rememberMe) {
+                Cookies.set('username', loginForm.value.username, { expires: 30 });
+                const enPwd = encrypt(loginForm.value.password, publicKey);
+                // const enPwd = loginForm.value.password;
+                if (enPwd) {
+                    Cookies.set('password', enPwd, { expires: 30 });
+                }
+                if (loginForm.value.rememberMe) {
+                    Cookies.set('rememberMe', String(loginForm.value.rememberMe), { expires: 30 });
+                }
+            } else {
+                // 否则移除
+                Cookies.remove('username');
+                Cookies.remove('password');
+                Cookies.remove('rememberMe');
+            }
+            // 调用action的登录方法
+            userStore
+                .phoneLogin(loginForm.value)
+                .then((res: any) => {
+                    console.log(res);
+                    // router.push({ path: redirect.value || '/' });
+                    isFlag.value = false;
+                    loading.value = false;
+                    loginForm.value.code = '';
+                    loginForm.value.uuid = res.uuid;
+                    loginForm.value.phone = res.phone;
+                })
+                .catch(() => {
+                    loading.value = false;
+                    // 重新获取验证码
+                    if (captchaEnabled.value) {
+                        getCode();
+                    }
+                });
+        }
+    });
+>>>>>>> e94f5f44564039f665611f42b365b51740d0649c
 };
 
 // 有下一步的最后一步
 const handleEndLogin = () => {
+<<<<<<< HEAD
 	userStore
 		.phoneLogin(loginForm.value)
 		.then((res: any) => {
@@ -251,10 +311,27 @@ const handleEndLogin = () => {
 		.catch(() => {
 			loading.value = false;
 		});
+=======
+    userStore
+        .phoneLogin(loginForm.value)
+        .then((res: any) => {
+            if (res.token) {
+                ElMessage.success('登陆成功');
+                router.push({ path: redirect.value || '/' });
+            } else {
+                ElMessage.error('登陆失败');
+            }
+            loading.value = false;
+        })
+        .catch(() => {
+            loading.value = false;
+        });
+>>>>>>> e94f5f44564039f665611f42b365b51740d0649c
 };
 
 // 无下一步的登陆
 function handleLogin() {
+<<<<<<< HEAD
 	loginFirstRef.value?.validate(valid => {
 		if (valid) {
 			loading.value = true;
@@ -273,10 +350,31 @@ function handleLogin() {
 				});
 		}
 	});
+=======
+    loginFirstRef.value?.validate(valid => {
+        if (valid) {
+            loading.value = true;
+            // 调用action的登录方法
+            userStore
+                .login(loginForm.value)
+                .then(() => {
+                    router.push({ path: redirect.value || '/' });
+                })
+                .catch(() => {
+                    loading.value = false;
+                    // 重新获取验证码
+                    if (captchaEnabled.value) {
+                        getCode();
+                    }
+                });
+        }
+    });
+>>>>>>> e94f5f44564039f665611f42b365b51740d0649c
 }
 
 // 登陆按钮
 const handleAllLogin = () => {
+<<<<<<< HEAD
 	// 本地
 	// if (env === 'development') {
 	//     handleLogin();
@@ -289,11 +387,26 @@ const handleAllLogin = () => {
 	} else {
 		handleLogin();
 	}
+=======
+    // 本地
+    // if (env === 'development') {
+    //     handleLogin();
+    // } else {
+    //     handleNext();
+    // }
+    // handleNext();
+    if (phoneCaptchaEnabled.value) {
+        handleNext();
+    } else {
+        handleLogin();
+    }
+>>>>>>> e94f5f44564039f665611f42b365b51740d0649c
 };
 
 // 是否有手机号
 const phoneCaptchaEnabled = ref(false);
 function getCode() {
+<<<<<<< HEAD
 	getCodeImg().then((res: any) => {
 		captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled;
 		phoneCaptchaEnabled.value = res.phoneCaptchaEnabled;
@@ -320,6 +433,34 @@ function getCookie() {
 		// password: password === undefined ? loginForm.value.password : password || '',
 		rememberMe: rememberMe === undefined ? false : Boolean(rememberMe),
 	};
+=======
+    getCodeImg().then((res: any) => {
+        captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled;
+        phoneCaptchaEnabled.value = res.phoneCaptchaEnabled;
+        if (captchaEnabled.value) {
+            codeUrl.value = 'data:image/gif;base64,' + res.img;
+            loginForm.value.uuid = res.uuid;
+        }
+    });
+}
+
+function getCookie() {
+    const username = Cookies.get('username');
+    const password = Cookies.get('password');
+    const rememberMe = Cookies.get('rememberMe');
+    // let publicKey = '';
+
+    // getPublicKey().then((res: any) => {
+    //     publicKey = res.publicKey;
+    // });
+
+    loginForm.value = {
+        username: username === undefined ? loginForm.value.username : username,
+        password: password === undefined ? loginForm.value.password : decrypt(password, privateKey) || '',
+        // password: password === undefined ? loginForm.value.password : password || '',
+        rememberMe: rememberMe === undefined ? false : Boolean(rememberMe),
+    };
+>>>>>>> e94f5f44564039f665611f42b365b51740d0649c
 }
 
 getCode();
@@ -328,6 +469,7 @@ getCookie();
 
 <style lang="scss" scoped>
 .login {
+<<<<<<< HEAD
 	display: flex;
 	justify-content: center;
 	align-items: center;
@@ -396,5 +538,75 @@ getCookie();
 .login-code-img {
 	height: 40px;
 	padding-left: 12px;
+=======
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    height: 100%;
+    background-image: url('../assets/images/login-background.jpg');
+    background-size: cover;
+}
+
+.title {
+    margin: 0px auto 30px auto;
+    text-align: center;
+    color: #707070;
+}
+
+.login-form {
+    border-radius: 6px;
+    background: #ffffff;
+    width: 400px;
+    padding: 25px 25px 5px 25px;
+
+    .el-input {
+        height: 40px;
+
+        input {
+            height: 40px;
+        }
+    }
+
+    .input-icon {
+        height: 39px;
+        width: 14px;
+        margin-left: 0px;
+    }
+}
+
+.login-tip {
+    font-size: 13px;
+    text-align: center;
+    color: #bfbfbf;
+}
+
+.login-code {
+    width: 33%;
+    height: 40px;
+    float: right;
+
+    img {
+        cursor: pointer;
+        vertical-align: middle;
+    }
+}
+
+.el-login-footer {
+    height: 40px;
+    line-height: 40px;
+    position: fixed;
+    bottom: 0;
+    width: 100%;
+    text-align: center;
+    color: #fff;
+    font-family: Arial;
+    font-size: 12px;
+    letter-spacing: 1px;
+}
+
+.login-code-img {
+    height: 40px;
+    padding-left: 12px;
+>>>>>>> e94f5f44564039f665611f42b365b51740d0649c
 }
 </style>

+ 187 - 0
frontend/src/views/monitor/cache/index.vue

@@ -0,0 +1,187 @@
+<template>
+    <div class="app-container">
+        <el-row>
+            <el-col :span="24" class="card-box">
+                <el-card>
+                    <template #header
+                        ><Monitor style="width: 1em; height: 1em; vertical-align: middle" />
+                        <span style="vertical-align: middle">基本信息</span></template
+                    >
+                    <div class="el-table el-table--enable-row-hover el-table--medium">
+                        <table cellspacing="0" style="width: 100%">
+                            <tbody>
+                                <tr>
+                                    <td class="el-table__cell is-leaf"><div class="cell">Redis版本</div></td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="cache.info" class="cell">
+                                            {{ cache.info.redis_version }}
+                                        </div>
+                                    </td>
+                                    <td class="el-table__cell is-leaf"><div class="cell">运行模式</div></td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="cache.info" class="cell">
+                                            {{ cache.info.redis_mode == 'standalone' ? '单机' : '集群' }}
+                                        </div>
+                                    </td>
+                                    <td class="el-table__cell is-leaf"><div class="cell">端口</div></td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="cache.info" class="cell">{{ cache.info.tcp_port }}</div>
+                                    </td>
+                                    <td class="el-table__cell is-leaf"><div class="cell">客户端数</div></td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="cache.info" class="cell">
+                                            {{ cache.info.connected_clients }}
+                                        </div>
+                                    </td>
+                                </tr>
+                                <tr>
+                                    <td class="el-table__cell is-leaf">
+                                        <div class="cell">运行时间(天)</div>
+                                    </td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="cache.info" class="cell">
+                                            {{ cache.info.uptime_in_days }}
+                                        </div>
+                                    </td>
+                                    <td class="el-table__cell is-leaf"><div class="cell">使用内存</div></td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="cache.info" class="cell">
+                                            {{ cache.info.used_memory_human }}
+                                        </div>
+                                    </td>
+                                    <td class="el-table__cell is-leaf"><div class="cell">使用CPU</div></td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="cache.info" class="cell">
+                                            {{ parseFloat(cache.info.used_cpu_user_children).toFixed(2) }}
+                                        </div>
+                                    </td>
+                                    <td class="el-table__cell is-leaf"><div class="cell">内存配置</div></td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="cache.info" class="cell">
+                                            {{ cache.info.maxmemory_human }}
+                                        </div>
+                                    </td>
+                                </tr>
+                                <tr>
+                                    <td class="el-table__cell is-leaf">
+                                        <div class="cell">AOF是否开启</div>
+                                    </td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="cache.info" class="cell">
+                                            {{ cache.info.aof_enabled == '0' ? '否' : '是' }}
+                                        </div>
+                                    </td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div class="cell">RDB是否成功</div>
+                                    </td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="cache.info" class="cell">
+                                            {{ cache.info.rdb_last_bgsave_status }}
+                                        </div>
+                                    </td>
+                                    <td class="el-table__cell is-leaf"><div class="cell">Key数量</div></td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="cache.dbSize" class="cell">{{ cache.dbSize }}</div>
+                                    </td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div class="cell">网络入口/出口</div>
+                                    </td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="cache.info" class="cell">
+                                            {{ cache.info.instantaneous_input_kbps }}kps/{{
+                                                cache.info.instantaneous_output_kbps
+                                            }}kps
+                                        </div>
+                                    </td>
+                                </tr>
+                            </tbody>
+                        </table>
+                    </div>
+                </el-card>
+            </el-col>
+
+            <el-col :span="12" class="card-box">
+                <el-card>
+                    <template #header><PieChart style="width: 1em; height: 1em; vertical-align: middle;" /> <span style="vertical-align: middle;">命令统计</span></template>
+                    <div class="el-table el-table--enable-row-hover el-table--medium">
+                        <div ref="commandstats" style="height: 420px" />
+                    </div>
+                </el-card>
+            </el-col>
+
+            <el-col :span="12" class="card-box">
+                <el-card>
+                    <template #header><Odometer style="width: 1em; height: 1em; vertical-align: middle;" /> <span style="vertical-align: middle;">内存信息</span></template>
+                    <div class="el-table el-table--enable-row-hover el-table--medium">
+                        <div ref="usedmemory" style="height: 420px" />
+                    </div>
+                </el-card>
+            </el-col>
+        </el-row>
+    </div>
+</template>
+
+<script setup name="Cache" lang="ts">
+import { getCache } from '@/api/monitor/cache';
+import * as echarts from 'echarts';
+import { ref, getCurrentInstance, ComponentInternalInstance } from 'vue';
+
+const cache = ref<any>([]);
+const commandstats = ref(null);
+const usedmemory = ref(null);
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+function getList() {
+    proxy!.$modal.loading('正在加载缓存监控数据,请稍候!');
+    getCache().then(response => {
+        proxy!.$modal.closeLoading();
+        cache.value = response.data;
+
+        const commandstatsIntance = echarts.init(commandstats.value!, 'macarons');
+        commandstatsIntance.setOption({
+            tooltip: {
+                trigger: 'item',
+                formatter: '{a} <br/>{b} : {c} ({d}%)',
+            },
+            series: [
+                {
+                    name: '命令',
+                    type: 'pie',
+                    roseType: 'radius',
+                    radius: [15, 95],
+                    center: ['50%', '38%'],
+                    data: response.data.commandStats,
+                    animationEasing: 'cubicInOut',
+                    animationDuration: 1000,
+                },
+            ],
+        });
+
+        const usedmemoryInstance = echarts.init(usedmemory.value!, 'macarons');
+        usedmemoryInstance.setOption({
+            tooltip: {
+                formatter: '{b} <br/>{a} : ' + cache.value.info.used_memory_human,
+            },
+            series: [
+                {
+                    name: '峰值',
+                    type: 'gauge',
+                    min: 0,
+                    max: 1000,
+                    detail: {
+                        formatter: cache.value.info.used_memory_human,
+                    },
+                    data: [
+                        {
+                            value: parseFloat(cache.value.info.used_memory_human),
+                            name: '内存消耗',
+                        },
+                    ],
+                },
+            ],
+        });
+    });
+}
+
+getList();
+</script>

+ 246 - 0
frontend/src/views/monitor/cache/list.vue

@@ -0,0 +1,246 @@
+<template>
+    <div class="app-container">
+        <el-row :gutter="10">
+            <el-col :span="8">
+                <el-card style="height: calc(100vh - 125px)">
+                    <template #header>
+                        <Collection style="width: 1em; height: 1em; vertical-align: middle;" /> <span style="vertical-align: middle;">缓存列表</span>
+                        <el-button
+                            style="float: right; padding: 3px 0"
+                            link
+                            type="primary"
+                            icon="Refresh"
+                            @click="refreshCacheNames()"
+                        ></el-button>
+                    </template>
+                    <el-table
+                        v-loading="loading"
+                        :data="cacheNames"
+                        :height="tableHeight"
+                        highlight-current-row
+                        style="width: 100%"
+                        @rowClick="getCacheKeys"
+                    >
+                        <el-table-column label="序号" width="60" type="index"></el-table-column>
+
+                        <el-table-column
+                            label="缓存名称"
+                            align="center"
+                            prop="cacheName"
+                            :show-overflow-tooltip="true"
+                            :formatter="nameFormatter"
+                        ></el-table-column>
+
+                        <el-table-column
+                            label="备注"
+                            align="center"
+                            prop="remark"
+                            :show-overflow-tooltip="true"
+                        />
+                        <el-table-column
+                            label="操作"
+                            width="60"
+                            align="center"
+                            class-name="small-padding fixed-width"
+                        >
+                            <template #default="scope">
+                                <el-button
+                                    link
+                                    type="primary"
+                                    icon="Delete"
+                                    @click="handleClearCacheName(scope.row)"
+                                ></el-button>
+                            </template>
+                        </el-table-column>
+                    </el-table>
+                </el-card>
+            </el-col>
+
+            <el-col :span="8">
+                <el-card style="height: calc(100vh - 125px)">
+                    <template #header>
+                        <Key style="width: 1em; height: 1em; vertical-align: middle;" /> <span style="vertical-align: middle;">键名列表</span>
+                        <el-button
+                            style="float: right; padding: 3px 0"
+                            link
+                            type="primary"
+                            icon="Refresh"
+                            @click="refreshCacheKeys()"
+                        ></el-button>
+                    </template>
+                    <el-table
+                        v-loading="subLoading"
+                        :data="cacheKeys"
+                        :height="tableHeight"
+                        highlight-current-row
+                        style="width: 100%"
+                        @rowClick="handleCacheValue"
+                    >
+                        <el-table-column label="序号" width="60" type="index"></el-table-column>
+                        <el-table-column
+                            label="缓存键名"
+                            align="center"
+                            :show-overflow-tooltip="true"
+                            :formatter="keyFormatter"
+                        >
+                        </el-table-column>
+                        <el-table-column
+                            label="操作"
+                            width="60"
+                            align="center"
+                            class-name="small-padding fixed-width"
+                        >
+                            <template #default="scope">
+                                <el-button
+                                    link
+                                    type="primary"
+                                    icon="Delete"
+                                    @click="handleClearCacheKey(scope.row)"
+                                ></el-button>
+                            </template>
+                        </el-table-column>
+                    </el-table>
+                </el-card>
+            </el-col>
+
+            <el-col :span="8">
+                <el-card :bordered="false" style="height: calc(100vh - 125px)">
+                    <template #header>
+                        <Document style="width: 1em; height: 1em; vertical-align: middle;" /> <span style="vertical-align: middle;">缓存内容</span>
+                        <el-button
+                            style="float: right; padding: 3px 0"
+                            link
+                            type="primary"
+                            icon="Refresh"
+                            @click="handleClearCacheAll()"
+                            >清理全部</el-button
+                        >
+                    </template>
+                    <el-form :model="cacheForm">
+                        <el-row :gutter="32">
+                            <el-col :offset="1" :span="22">
+                                <el-form-item label="缓存名称:" prop="cacheName">
+                                    <el-input v-model="cacheForm.cacheName" :readOnly="true" />
+                                </el-form-item>
+                            </el-col>
+                            <el-col :offset="1" :span="22">
+                                <el-form-item label="缓存键名:" prop="cacheKey">
+                                    <el-input v-model="cacheForm.cacheKey" :readOnly="true" />
+                                </el-form-item>
+                            </el-col>
+                            <el-col :offset="1" :span="22">
+                                <el-form-item label="缓存内容:" prop="cacheValue">
+                                    <el-input
+                                        v-model="cacheForm.cacheValue"
+                                        type="textarea"
+                                        :rows="8"
+                                        :readOnly="true"
+                                    />
+                                </el-form-item>
+                            </el-col>
+                        </el-row>
+                    </el-form>
+                </el-card>
+            </el-col>
+        </el-row>
+    </div>
+</template>
+
+<script setup name="CacheList" lang="ts">
+import {
+    listCacheName,
+    listCacheKey,
+    getCacheValue,
+    clearCacheName,
+    clearCacheKey,
+    clearCacheAll,
+} from '@/api/monitor/cache';
+import { getCurrentInstance, ComponentInternalInstance, ref } from 'vue';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const cacheNames = ref<any[]>([]);
+const cacheKeys = ref<any[]>([]);
+const cacheForm = ref<any>({});
+const loading = ref(true);
+const subLoading = ref(false);
+const nowCacheName = ref('');
+const tableHeight = ref(window.innerHeight - 200);
+
+/** 查询缓存名称列表 */
+function getCacheNames() {
+    loading.value = true;
+    listCacheName().then(response => {
+        cacheNames.value = response.data;
+        loading.value = false;
+    });
+}
+
+/** 刷新缓存名称列表 */
+function refreshCacheNames() {
+    getCacheNames();
+    proxy!.$modal.msgSuccess('刷新缓存列表成功');
+}
+
+/** 清理指定名称缓存 */
+function handleClearCacheName(row: any) {
+    clearCacheName(row.cacheName).then(response => {
+        proxy!.$modal.msgSuccess('清理缓存名称[' + nowCacheName.value + ']成功');
+        getCacheKeys();
+    });
+}
+
+/** 查询缓存键名列表 */
+function getCacheKeys(row?: any) {
+    const cacheName = row !== undefined ? row.cacheName : nowCacheName.value;
+    if (cacheName === '') {
+        return;
+    }
+    subLoading.value = true;
+    listCacheKey(cacheName).then(response => {
+        cacheKeys.value = response.data;
+        subLoading.value = false;
+        nowCacheName.value = cacheName;
+    });
+}
+
+/** 刷新缓存键名列表 */
+function refreshCacheKeys() {
+    getCacheKeys();
+    proxy!.$modal.msgSuccess('刷新键名列表成功');
+}
+
+/** 清理指定键名缓存 */
+function handleClearCacheKey(cacheKey: any) {
+    clearCacheKey(cacheKey).then(response => {
+        proxy!.$modal.msgSuccess('清理缓存键名[' + cacheKey + ']成功');
+        getCacheKeys();
+    });
+}
+
+/** 列表前缀去除 */
+function nameFormatter(row: any) {
+    return row.cacheName.replace(':', '');
+}
+
+/** 键名前缀去除 */
+function keyFormatter(cacheKey: any) {
+    return cacheKey.replace(nowCacheName.value, '');
+}
+
+/** 查询缓存内容详细 */
+function handleCacheValue(cacheKey: any) {
+    getCacheValue(nowCacheName.value, cacheKey).then(response => {
+        cacheForm.value = response.data;
+    });
+}
+
+/** 清理全部缓存 */
+function handleClearCacheAll() {
+    clearCacheAll().then(response => {
+        proxy!.$modal.msgSuccess('清理全部缓存成功');
+    });
+}
+
+getCacheNames();
+</script>

+ 13 - 0
frontend/src/views/monitor/druid/index.vue

@@ -0,0 +1,13 @@
+<template>
+    <div class="app-container">
+        <i-frame v-model:src="url"></i-frame>
+    </div>
+</template>
+
+<script setup lang="ts">
+import iFrame from '@/components/iFrame/index.vue';
+
+import { ref } from 'vue';
+
+const url = ref(import.meta.env.VITE_APP_BASE_API + '/druid/login.html');
+</script>

+ 561 - 0
frontend/src/views/monitor/job/index.vue

@@ -0,0 +1,561 @@
+<template>
+    <div class="app-container">
+        <el-form v-show="showSearch" ref="queryRef" :model="queryParams" :inline="true" >
+            <el-form-item label="任务名称" prop="jobName">
+                <el-input
+                    v-model="queryParams.jobName"
+                    placeholder="请输入任务名称"
+                    clearable
+                    style="width: 200px"
+                    @keyup.enter="handleQuery"
+                />
+            </el-form-item>
+            <el-form-item label="任务组名" prop="jobGroup">
+                <el-select v-model="queryParams.jobGroup" placeholder="请选择任务组名" clearable style="width: 200px">
+                    <el-option
+                        v-for="dict in sys_job_group"
+                        :key="dict.value"
+                        :label="dict.label"
+                        :value="dict.value"
+                    />
+                </el-select>
+            </el-form-item>
+            <el-form-item label="任务状态" prop="status">
+                <el-select v-model="queryParams.status" placeholder="请选择任务状态" clearable style="width: 200px">
+                    <el-option
+                        v-for="dict in sys_job_status"
+                        :key="dict.value"
+                        :label="dict.label"
+                        :value="dict.value"
+                    />
+                </el-select>
+            </el-form-item>
+            <el-form-item>
+                <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+                <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+        </el-form>
+
+        <el-row :gutter="10" class="mb8">
+            <el-col :span="1.5">
+                <el-button
+                    v-hasPermi="['monitor:job:add']"
+                    type="primary"
+                    plain
+                    icon="Plus"
+                    @click="handleAdd"
+                    >新增</el-button
+                >
+            </el-col>
+            <el-col :span="1.5">
+                <el-button
+                    v-hasPermi="['monitor:job:edit']"
+                    type="success"
+                    plain
+                    icon="Edit"
+                    :disabled="single"
+                    @click="handleUpdate"
+                    >修改</el-button
+                >
+            </el-col>
+            <el-col :span="1.5">
+                <el-button
+                    v-hasPermi="['monitor:job:remove']"
+                    type="danger"
+                    plain
+                    icon="Delete"
+                    :disabled="multiple"
+                    @click="handleDelete"
+                    >删除</el-button
+                >
+            </el-col>
+            <el-col :span="1.5">
+                <el-button
+                    v-hasPermi="['monitor:job:export']"
+                    type="warning"
+                    plain
+                    icon="Download"
+                    @click="handleExport"
+                    >导出</el-button
+                >
+            </el-col>
+            <el-col :span="1.5">
+                <el-button
+                    v-hasPermi="['monitor:job:query']"
+                    type="info"
+                    plain
+                    icon="Operation"
+                    @click="handleJobLog"
+                    >日志</el-button
+                >
+            </el-col>
+            <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+        </el-row>
+
+        <el-table v-loading="loading" :data="jobList" @selectionChange="handleSelectionChange">
+            <el-table-column type="selection" width="55" align="center" />
+            <el-table-column label="任务编号" width="100" align="center" prop="jobId" />
+            <el-table-column label="任务名称" align="center" prop="jobName" :show-overflow-tooltip="true" />
+            <el-table-column label="任务组名" align="center" prop="jobGroup">
+                <template #default="scope">
+                    <dict-tag :options="sys_job_group" :value="scope.row.jobGroup" />
+                </template>
+            </el-table-column>
+            <el-table-column
+                label="调用目标字符串"
+                align="center"
+                prop="invokeTarget"
+                :show-overflow-tooltip="true"
+            />
+            <el-table-column
+                label="cron执行表达式"
+                align="center"
+                prop="cronExpression"
+                :show-overflow-tooltip="true"
+            />
+            <el-table-column label="状态" align="center">
+                <template #default="scope">
+                    <el-switch
+                        v-model="scope.row.status"
+                        active-value="0"
+                        inactive-value="1"
+                        @change="handleStatusChange(scope.row)"
+                    ></el-switch>
+                </template>
+            </el-table-column>
+            <el-table-column label="操作" align="center" width="200" class-name="small-padding fixed-width">
+                <template #default="scope">
+                    <el-tooltip content="修改" placement="top">
+                        <el-button
+                            v-hasPermi="['monitor:job:edit']"
+                            link
+                            type="primary"
+                            icon="Edit"
+                            @click="handleUpdate(scope.row)"
+                        ></el-button>
+                    </el-tooltip>
+                    <el-tooltip content="删除" placement="top">
+                        <el-button
+                            v-hasPermi="['monitor:job:remove']"
+                            link
+                            type="primary"
+                            icon="Delete"
+                            @click="handleDelete(scope.row)"
+                        ></el-button>
+                    </el-tooltip>
+                    <el-tooltip content="执行一次" placement="top">
+                        <el-button
+                            v-hasPermi="['monitor:job:changeStatus']"
+                            link
+                            type="primary"
+                            icon="CaretRight"
+                            @click="handleRun(scope.row)"
+                        ></el-button>
+                    </el-tooltip>
+                    <el-tooltip content="任务详细" placement="top">
+                        <el-button
+                            v-hasPermi="['monitor:job:query']"
+                            link
+                            type="primary"
+                            icon="View"
+                            @click="handleView(scope.row)"
+                        ></el-button>
+                    </el-tooltip>
+                    <el-tooltip content="调度日志" placement="top">
+                        <el-button
+                            v-hasPermi="['monitor:job:query']"
+                            link
+                            type="primary"
+                            icon="Operation"
+                            @click="handleJobLog(scope.row)"
+                        ></el-button>
+                    </el-tooltip>
+                </template>
+            </el-table-column>
+        </el-table>
+
+        <pagination
+            v-show="total > 0"
+            v-model:page="queryParams.pageNum"
+            v-model:limit="queryParams.pageSize"
+            :total="total"
+            @pagination="getList"
+        />
+
+        <!-- 添加或修改定时任务对话框 -->
+        <el-dialog :close-on-click-modal="false" v-model="open" :title="title" width="800px" append-to-body>
+            <el-form ref="jobRef" :model="form" :rules="rules" label-width="120px">
+                <el-row>
+                    <el-col :span="12">
+                        <el-form-item label="任务名称" prop="jobName">
+                            <el-input v-model="form.jobName" placeholder="请输入任务名称" />
+                        </el-form-item>
+                    </el-col>
+                    <el-col :span="12">
+                        <el-form-item label="任务分组" prop="jobGroup">
+                            <el-select v-model="form.jobGroup" placeholder="请选择">
+                                <el-option
+                                    v-for="dict in sys_job_group"
+                                    :key="dict.value"
+                                    :label="dict.label"
+                                    :value="dict.value"
+                                ></el-option>
+                            </el-select>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :span="24">
+                        <el-form-item prop="invokeTarget">
+                            <template #label>
+                                <span>
+                                    调用方法
+                                    <el-tooltip placement="top">
+                                        <template #content>
+                                            <div>
+                                                Bean调用示例:ryTask.ryParams('ry')
+                                                <br />Class类调用示例:com.ruoyi.quartz.task.RyTask.ryParams('ry')
+                                                <br />参数说明:支持字符串,布尔类型,长整型,浮点型,整型
+                                            </div>
+                                        </template>
+                                        <el-icon><question-filled /></el-icon>
+                                    </el-tooltip>
+                                </span>
+                            </template>
+                            <el-input v-model="form.invokeTarget" placeholder="请输入调用目标字符串" />
+                        </el-form-item>
+                    </el-col>
+                    <el-col :span="24">
+                        <el-form-item label="cron表达式" prop="cronExpression">
+                            <el-input
+                                v-model="form.cronExpression"
+                                placeholder="请输入cron执行表达式"
+                            >
+                                <template #append>
+                                    <el-button type="primary" @click="handleShowCron">
+                                        生成表达式
+                                        <i class="el-icon-time el-icon--right"></i>
+                                    </el-button>
+                                </template>
+                            </el-input>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :span="24">
+                        <el-form-item label="执行策略" prop="misfirePolicy">
+                            <el-radio-group v-model="form.misfirePolicy">
+                                <el-radio-button label="1">立即执行</el-radio-button>
+                                <el-radio-button label="2">执行一次</el-radio-button>
+                                <el-radio-button label="3">放弃执行</el-radio-button>
+                            </el-radio-group>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :span="12">
+                        <el-form-item label="是否并发" prop="concurrent">
+                            <el-radio-group v-model="form.concurrent">
+                                <el-radio-button label="0">允许</el-radio-button>
+                                <el-radio-button label="1">禁止</el-radio-button>
+                            </el-radio-group>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :span="12">
+                        <el-form-item label="状态">
+                            <el-radio-group v-model="form.status">
+                                <el-radio
+                                    v-for="dict in sys_job_status"
+                                    :key="dict.value"
+                                    :label="dict.value"
+                                    >{{ dict.label }}</el-radio
+                                >
+                            </el-radio-group>
+                        </el-form-item>
+                    </el-col>
+                </el-row>
+            </el-form>
+            <template #footer>
+                <div class="dialog-footer">
+                    <el-button type="primary" @click="submitForm">确 定</el-button>
+                    <el-button @click="cancel">取 消</el-button>
+                </div>
+            </template>
+        </el-dialog>
+        <el-dialog :close-on-click-modal="false" v-model="openCron" title="Cron表达式生成器" append-to-body destroy-on-close>
+            <crontab
+                ref="crontabRef"
+                :expression="expression"
+                @hide="openCron = false"
+                @fill="crontabFill"
+            ></crontab>
+        </el-dialog>
+        <!-- 任务日志详细 -->
+        <el-dialog :close-on-click-modal="false" v-model="openView" title="任务详细" width="700px" append-to-body>
+            <el-form :model="form" label-width="120px">
+                <el-row>
+                    <el-col :span="12">
+                        <el-form-item label="任务编号:">{{ form.jobId }}</el-form-item>
+                        <el-form-item label="任务名称:">{{ form.jobName }}</el-form-item>
+                    </el-col>
+                    <el-col :span="12">
+                        <el-form-item label="任务分组:">{{ jobGroupFormat(form) }}</el-form-item>
+                        <el-form-item label="创建时间:">{{ form.createTime }}</el-form-item>
+                    </el-col>
+                    <el-col :span="12">
+                        <el-form-item label="cron表达式:">{{ form.cronExpression }}</el-form-item>
+                    </el-col>
+                    <el-col :span="12">
+                        <el-form-item label="下次执行时间:">{{
+                            parseTime(form.nextValidTime)
+                        }}</el-form-item>
+                    </el-col>
+                    <el-col :span="24">
+                        <el-form-item label="调用目标方法:">{{ form.invokeTarget }}</el-form-item>
+                    </el-col>
+                    <el-col :span="12">
+                        <el-form-item label="任务状态:">
+                            <div v-if="form.status == 0">正常</div>
+                            <div v-else-if="form.status == 1">失败</div>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :span="12">
+                        <el-form-item label="是否并发:">
+                            <div v-if="form.concurrent == 0">允许</div>
+                            <div v-else-if="form.concurrent == 1">禁止</div>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :span="12">
+                        <el-form-item label="执行策略:">
+                            <div v-if="form.misfirePolicy == 0">默认策略</div>
+                            <div v-else-if="form.misfirePolicy == 1">立即执行</div>
+                            <div v-else-if="form.misfirePolicy == 2">执行一次</div>
+                            <div v-else-if="form.misfirePolicy == 3">放弃执行</div>
+                        </el-form-item>
+                    </el-col>
+                </el-row>
+            </el-form>
+            <template #footer>
+                <div class="dialog-footer">
+                    <el-button @click="openView = false">关 闭</el-button>
+                </div>
+            </template>
+        </el-dialog>
+    </div>
+</template>
+
+<script setup name="Job" lang="ts">
+/* eslint-disable camelcase */
+import { listJob, getJob, delJob, addJob, updateJob, runJob, changeJobStatus } from '@/api/monitor/job';
+import { parseTime } from '@/utils/ruoyi';
+import { getCurrentInstance, ComponentInternalInstance, ref, reactive, toRefs } from 'vue';
+import { useRouter } from 'vue-router';
+import Crontab from '@/components/Crontab/index.vue';
+const router = useRouter();
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { sys_job_group, sys_job_status } = proxy!.useDict('sys_job_group', 'sys_job_status');
+
+const jobList = ref<any[]>([]);
+const open = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<number[]>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const title = ref('');
+const openView = ref(false);
+const openCron = ref(false);
+const expression = ref('');
+
+const data = reactive<{
+    form: any;
+    queryParams: any;
+    rules: any;
+}>({
+    form: {},
+    queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        jobName: undefined,
+        jobGroup: undefined,
+        status: undefined,
+    },
+    rules: {
+        jobName: [{ required: true, message: '任务名称不能为空', trigger: 'blur' }],
+        invokeTarget: [{ required: true, message: '调用目标字符串不能为空', trigger: 'blur' }],
+        cronExpression: [{ required: true, message: 'cron执行表达式不能为空', trigger: 'change' }],
+    },
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询定时任务列表 */
+function getList() {
+    loading.value = true;
+    listJob(queryParams.value).then((response: any) => {
+        jobList.value = response.rows;
+        total.value = response.total;
+        loading.value = false;
+    });
+}
+/** 任务组名字典翻译 */
+function jobGroupFormat(row: any, column?: any) {
+    return proxy!.selectDictLabel(sys_job_group.value, row.jobGroup);
+}
+/** 取消按钮 */
+function cancel() {
+    open.value = false;
+    reset();
+}
+/** 表单重置 */
+function reset() {
+    form.value = {
+        jobId: undefined,
+        jobName: undefined,
+        jobGroup: undefined,
+        invokeTarget: undefined,
+        cronExpression: undefined,
+        misfirePolicy: 1,
+        concurrent: 1,
+        status: '0',
+    };
+    proxy!.resetForm('jobRef');
+}
+/** 搜索按钮操作 */
+function handleQuery() {
+    queryParams.value.pageNum = 1;
+    getList();
+}
+/** 重置按钮操作 */
+function resetQuery() {
+    proxy!.resetForm('queryRef');
+    handleQuery();
+}
+// 多选框选中数据
+function handleSelectionChange(selection: any[]) {
+    ids.value = selection.map(item => item.jobId);
+    single.value = selection.length !== 1;
+    multiple.value = !selection.length;
+}
+// 更多操作触发
+function handleCommand(command: any, row: any) {
+    switch (command) {
+        case 'handleRun':
+            handleRun(row);
+            break;
+        case 'handleView':
+            handleView(row);
+            break;
+        case 'handleJobLog':
+            handleJobLog(row);
+            break;
+        default:
+            break;
+    }
+}
+// 任务状态修改
+function handleStatusChange(row: any) {
+    let text = row.status === '0' ? '启用' : '停用';
+    proxy!.$modal
+        .confirm('确认要"' + text + '""' + row.jobName + '"任务吗?')
+        .then(function () {
+            return changeJobStatus(row.jobId, row.status);
+        })
+        .then(() => {
+            proxy!.$modal.msgSuccess(text + '成功');
+        })
+        .catch(function () {
+            row.status = row.status === '0' ? '1' : '0';
+        });
+}
+/* 立即执行一次 */
+function handleRun(row: any) {
+    proxy!.$modal
+        .confirm('确认要立即执行一次"' + row.jobName + '"任务吗?')
+        .then(function () {
+            return runJob(row.jobId, row.jobGroup);
+        })
+        .then(() => {
+            proxy!.$modal.msgSuccess('执行成功');
+        });
+    //   .catch(() => {});
+}
+/** 任务详细信息 */
+function handleView(row: any) {
+    getJob(row.jobId).then(response => {
+        form.value = response.data;
+        openView.value = true;
+    });
+}
+/** cron表达式按钮操作 */
+function handleShowCron() {
+    expression.value = form.value.cronExpression;
+    openCron.value = true;
+}
+/** 确定后回传值 */
+function crontabFill(value: any) {
+    form.value.cronExpression = value;
+}
+/** 任务日志列表查询 */
+function handleJobLog(row: any) {
+    const jobId = row.jobId || 0;
+    router.push('/monitor/job-log/index/' + jobId)
+}
+/** 新增按钮操作 */
+function handleAdd() {
+    reset();
+    open.value = true;
+    title.value = '添加任务';
+}
+/** 修改按钮操作 */
+function handleUpdate(row: any) {
+    reset();
+    const jobId = row.jobId || ids.value;
+    getJob(jobId).then(response => {
+        form.value = response.data;
+        open.value = true;
+        title.value = '修改任务';
+    });
+}
+/** 提交按钮 */
+function submitForm() {
+    (proxy!.$refs['jobRef'] as any).validate((valid: any) => {
+        if (valid) {
+            if (form.value.jobId !== undefined) {
+                updateJob(form.value).then(response => {
+                    proxy!.$modal.msgSuccess('修改成功');
+                    open.value = false;
+                    getList();
+                });
+            } else {
+                addJob(form.value).then(response => {
+                    proxy!.$modal.msgSuccess('新增成功');
+                    open.value = false;
+                    getList();
+                });
+            }
+        }
+    });
+}
+/** 删除按钮操作 */
+function handleDelete(row: any) {
+    const jobIds = row.jobId || ids.value;
+    proxy!.$modal
+        .confirm('是否确认删除定时任务编号为"' + jobIds + '"的数据项?')
+        .then(function () {
+            return delJob(jobIds);
+        })
+        .then(() => {
+            getList();
+            proxy!.$modal.msgSuccess('删除成功');
+        });
+    //   .catch(() => {});
+}
+/** 导出按钮操作 */
+function handleExport() {
+    proxy!.download(
+        'monitor/job/export',
+        {
+            ...queryParams.value,
+        },
+        `job_${new Date().getTime()}.xlsx`
+    );
+}
+
+getList();
+</script>

+ 314 - 0
frontend/src/views/monitor/job/log.vue

@@ -0,0 +1,314 @@
+<template>
+    <div class="app-container">
+        <el-form v-show="showSearch" ref="queryRef" :model="queryParams" :inline="true" label-width="68px">
+            <el-form-item label="任务名称" prop="jobName">
+                <el-input
+                    v-model="queryParams.jobName"
+                    placeholder="请输入任务名称"
+                    clearable
+                    style="width: 240px"
+                    @keyup.enter="handleQuery"
+                />
+            </el-form-item>
+            <el-form-item label="任务组名" prop="jobGroup">
+                <el-select
+                    v-model="queryParams.jobGroup"
+                    placeholder="请选择任务组名"
+                    clearable
+                    style="width: 240px"
+                >
+                    <el-option
+                        v-for="dict in sys_job_group"
+                        :key="dict.value"
+                        :label="dict.label"
+                        :value="dict.value"
+                    />
+                </el-select>
+            </el-form-item>
+            <el-form-item label="执行状态" prop="status">
+                <el-select
+                    v-model="queryParams.status"
+                    placeholder="请选择执行状态"
+                    clearable
+                    style="width: 240px"
+                >
+                    <el-option
+                        v-for="dict in sys_common_status"
+                        :key="dict.value"
+                        :label="dict.label"
+                        :value="dict.value"
+                    />
+                </el-select>
+            </el-form-item>
+            <el-form-item label="执行时间" style="width: 308px">
+                <el-date-picker
+                    v-model="dateRange"
+                    value-format="YYYY-MM-DD"
+                    type="daterange"
+                    range-separator="-"
+                    start-placeholder="开始日期"
+                    end-placeholder="结束日期"
+                ></el-date-picker>
+            </el-form-item>
+            <el-form-item>
+                <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+                <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+        </el-form>
+
+        <el-row :gutter="10" class="mb8">
+            <el-col :span="1.5">
+                <el-button
+                    v-hasPermi="['monitor:job:remove']"
+                    type="danger"
+                    plain
+                    icon="Delete"
+                    :disabled="multiple"
+                    @click="handleDelete"
+                    >删除</el-button
+                >
+            </el-col>
+            <el-col :span="1.5">
+                <el-button
+                    v-hasPermi="['monitor:job:remove']"
+                    type="danger"
+                    plain
+                    icon="Delete"
+                    @click="handleClean"
+                    >清空</el-button
+                >
+            </el-col>
+            <el-col :span="1.5">
+                <el-button
+                    v-hasPermi="['monitor:job:export']"
+                    type="warning"
+                    plain
+                    icon="Download"
+                    @click="handleExport"
+                    >导出</el-button
+                >
+            </el-col>
+            <el-col :span="1.5">
+                <el-button type="warning" plain icon="Close" @click="handleClose">关闭</el-button>
+            </el-col>
+            <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+        </el-row>
+
+        <el-table v-loading="loading" :data="jobLogList" @selectionChange="handleSelectionChange">
+            <el-table-column type="selection" width="55" align="center" />
+            <el-table-column label="日志编号" width="80" align="center" prop="jobLogId" />
+            <el-table-column label="任务名称" align="center" prop="jobName" :show-overflow-tooltip="true" />
+            <el-table-column label="任务组名" align="center" prop="jobGroup" :show-overflow-tooltip="true">
+                <template #default="scope">
+                    <dict-tag :options="sys_job_group" :value="scope.row.jobGroup" />
+                </template>
+            </el-table-column>
+            <el-table-column
+                label="调用目标字符串"
+                align="center"
+                prop="invokeTarget"
+                :show-overflow-tooltip="true"
+            />
+            <el-table-column
+                label="日志信息"
+                align="center"
+                prop="jobMessage"
+                :show-overflow-tooltip="true"
+            />
+            <el-table-column label="执行状态" align="center" prop="status">
+                <template #default="scope">
+                    <dict-tag :options="sys_common_status" :value="scope.row.status" />
+                </template>
+            </el-table-column>
+            <el-table-column label="执行时间" align="center" prop="createTime" width="180">
+                <template #default="scope">
+                    <span>{{ parseTime(scope.row.createTime) }}</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+                <template #default="scope">
+                    <el-button
+                        v-hasPermi="['monitor:job:query']"
+                        link
+                        type="primary"
+                        icon="View"
+                        @click="handleView(scope.row)"
+                        >详细</el-button
+                    >
+                </template>
+            </el-table-column>
+        </el-table>
+
+        <pagination
+            v-show="total > 0"
+            v-model:page="queryParams.pageNum"
+            v-model:limit="queryParams.pageSize"
+            :total="total"
+            @pagination="getList"
+        />
+
+        <!-- 调度日志详细 -->
+        <el-dialog :close-on-click-modal="false" v-model="open" title="调度日志详细" width="700px" append-to-body>
+            <el-form :model="form" label-width="100px">
+                <el-row>
+                    <el-col :span="12">
+                        <el-form-item label="日志序号:">{{ form.jobLogId }}</el-form-item>
+                        <el-form-item label="任务名称:">{{ form.jobName }}</el-form-item>
+                    </el-col>
+                    <el-col :span="12">
+                        <el-form-item label="任务分组:">{{ form.jobGroup }}</el-form-item>
+                        <el-form-item label="执行时间:">{{ form.createTime }}</el-form-item>
+                    </el-col>
+                    <el-col :span="24">
+                        <el-form-item label="调用方法:">{{ form.invokeTarget }}</el-form-item>
+                    </el-col>
+                    <el-col :span="24">
+                        <el-form-item label="日志信息:">{{ form.jobMessage }}</el-form-item>
+                    </el-col>
+                    <el-col :span="24">
+                        <el-form-item label="执行状态:">
+                            <div v-if="form.status == 0">正常</div>
+                            <div v-else-if="form.status == 1">失败</div>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :span="24">
+                        <el-form-item v-if="form.status == 1" label="异常信息:">{{
+                            form.exceptionInfo
+                        }}</el-form-item>
+                    </el-col>
+                </el-row>
+            </el-form>
+            <template #footer>
+                <div class="dialog-footer">
+                    <el-button @click="open = false">关 闭</el-button>
+                </div>
+            </template>
+        </el-dialog>
+    </div>
+</template>
+
+<script setup name="JobLog" lang="ts">
+/* eslint-disable camelcase */
+import { getJob } from '@/api/monitor/job';
+import { listJobLog, delJobLog, cleanJobLog } from '@/api/monitor/jobLog';
+import { parseTime } from '@/utils/ruoyi';
+import { oneOf } from '@zeronejs/utils';
+import { getCurrentInstance, ComponentInternalInstance, ref, reactive, toRefs } from 'vue';
+import { useRoute } from 'vue-router';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { sys_common_status, sys_job_group } = proxy!.useDict('sys_common_status', 'sys_job_group');
+
+const jobLogList = ref<any[]>([]);
+const open = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<number[]>([]);
+const multiple = ref(true);
+const total = ref(0);
+const dateRange = ref<any>([]);
+const route = useRoute();
+
+const data = reactive<{
+    form: any;
+    queryParams: any;
+}>({
+    form: {},
+    queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        dictName: undefined,
+        dictType: undefined,
+        status: undefined,
+    },
+});
+
+const { queryParams, form } = toRefs(data);
+
+/** 查询调度日志列表 */
+function getList() {
+    loading.value = true;
+    listJobLog(proxy!.addDateRange(queryParams.value, dateRange.value)).then((response: any) => {
+        jobLogList.value = response.rows;
+        total.value = response.total;
+        loading.value = false;
+    });
+}
+// 返回按钮
+function handleClose() {
+    const obj = { path: '/monitor/job' };
+    proxy!.$tab.closeOpenPage(obj);
+}
+/** 搜索按钮操作 */
+function handleQuery() {
+    queryParams.value.pageNum = 1;
+    getList();
+}
+/** 重置按钮操作 */
+function resetQuery() {
+    dateRange.value = [];
+    proxy!.resetForm('queryRef');
+    handleQuery();
+}
+// 多选框选中数据
+function handleSelectionChange(selection: any[]) {
+    ids.value = selection.map(item => item.jobLogId);
+    multiple.value = !selection.length;
+}
+/** 详细按钮操作 */
+function handleView(row: any) {
+    open.value = true;
+    form.value = row;
+}
+/** 删除按钮操作 */
+function handleDelete(row: any) {
+    proxy!.$modal
+        .confirm('是否确认删除调度日志编号为"' + ids.value + '"的数据项?')
+        .then(function () {
+            return delJobLog(ids.value);
+        })
+        .then(() => {
+            getList();
+            proxy!.$modal.msgSuccess('删除成功');
+        });
+    //   .catch(() => {});
+}
+/** 清空按钮操作 */
+function handleClean() {
+    proxy!.$modal
+        .confirm('是否确认清空所有调度日志数据项?')
+        .then(function () {
+            return cleanJobLog();
+        })
+        .then(() => {
+            getList();
+            proxy!.$modal.msgSuccess('清空成功');
+        });
+    //   .catch(() => {});
+}
+/** 导出按钮操作 */
+function handleExport() {
+    proxy!.download(
+        'monitor/jobLog/export',
+        {
+            ...queryParams.value,
+        },
+        `job_log_${new Date().getTime()}.xlsx`
+    );
+}
+
+(() => {
+    const jobId = oneOf(route.params.jobId);
+    if (jobId !== undefined && jobId !== '0') {
+        getJob(jobId).then(response => {
+            queryParams.value.jobName = response.data.jobName;
+            queryParams.value.jobGroup = response.data.jobGroup;
+            getList();
+        });
+    } else {
+        getList();
+    }
+})();
+
+getList();
+</script>

+ 270 - 0
frontend/src/views/monitor/logininfor/index.vue

@@ -0,0 +1,270 @@
+<template>
+    <div class="app-container">
+        <el-form v-show="showSearch" ref="queryRef" :model="queryParams" :inline="true" label-width="68px">
+            <el-form-item label="登录地址" prop="ipaddr">
+                <el-input
+                    v-model="queryParams.ipaddr"
+                    placeholder="请输入登录地址"
+                    clearable
+                    style="width: 240px"
+                    @keyup.enter="handleQuery"
+                />
+            </el-form-item>
+            <el-form-item label="用户名称" prop="userName">
+                <el-input
+                    v-model="queryParams.userName"
+                    placeholder="请输入用户名称"
+                    clearable
+                    style="width: 240px"
+                    @keyup.enter="handleQuery"
+                />
+            </el-form-item>
+            <el-form-item label="状态" prop="status">
+                <el-select v-model="queryParams.status" placeholder="登录状态" clearable style="width: 240px">
+                    <el-option
+                        v-for="dict in sys_common_status"
+                        :key="dict.value"
+                        :label="dict.label"
+                        :value="dict.value"
+                    />
+                </el-select>
+            </el-form-item>
+            <el-form-item label="登录时间" style="width: 308px">
+                <el-date-picker
+                    v-model="dateRange"
+                    value-format="YYYY-MM-DD HH:mm:ss"
+                    type="daterange"
+                    range-separator="-"
+                    start-placeholder="开始日期"
+                    end-placeholder="结束日期"
+                    :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
+                ></el-date-picker>
+            </el-form-item>
+            <el-form-item>
+                <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+                <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+        </el-form>
+
+        <el-row :gutter="10" class="mb8">
+            <el-col :span="1.5">
+                <el-button
+                    v-hasPermi="['monitor:logininfor:remove']"
+                    type="danger"
+                    plain
+                    icon="Delete"
+                    :disabled="multiple"
+                    @click="handleDelete"
+                    >删除</el-button
+                >
+            </el-col>
+            <el-col :span="1.5">
+                <el-button
+                    v-hasPermi="['monitor:logininfor:remove']"
+                    type="danger"
+                    plain
+                    icon="Delete"
+                    @click="handleClean"
+                    >清空</el-button
+                >
+            </el-col>
+            <el-col :span="1.5">
+                <el-button
+                    v-hasPermi="['monitor:logininfor:unlock']"
+                    type="primary"
+                    plain
+                    icon="Unlock"
+                    :disabled="single"
+                    @click="handleUnlock"
+                    >解锁</el-button
+                >
+            </el-col>
+            <el-col :span="1.5">
+                <el-button
+                    v-hasPermi="['monitor:logininfor:export']"
+                    type="warning"
+                    plain
+                    icon="Download"
+                    @click="handleExport"
+                    >导出</el-button
+                >
+            </el-col>
+            <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+        </el-row>
+
+        <el-table
+            ref="logininforRef"
+            v-loading="loading"
+            :data="logininforList"
+            :default-sort="defaultSort"
+            @selectionChange="handleSelectionChange"
+            @sortChange="handleSortChange"
+        >
+            <el-table-column type="selection" width="55" align="center" />
+            <el-table-column label="访问编号" align="center" prop="infoId" />
+            <el-table-column
+                label="用户名称"
+                align="center"
+                prop="userName"
+                :show-overflow-tooltip="true"
+                sortable="custom"
+                :sort-orders="['descending', 'ascending']"
+            />
+            <el-table-column label="地址" align="center" prop="ipaddr" :show-overflow-tooltip="true" />
+            <el-table-column
+                label="登录地点"
+                align="center"
+                prop="loginLocation"
+                :show-overflow-tooltip="true"
+            />
+            <el-table-column label="操作系统" align="center" prop="os" :show-overflow-tooltip="true" />
+            <el-table-column label="浏览器" align="center" prop="browser" :show-overflow-tooltip="true" />
+            <el-table-column label="登录状态" align="center" prop="status">
+                <template #default="scope">
+                    <dict-tag :options="sys_common_status" :value="scope.row.status" />
+                </template>
+            </el-table-column>
+            <el-table-column label="描述" align="center" prop="msg" :show-overflow-tooltip="true" />
+            <el-table-column
+                label="访问时间"
+                align="center"
+                prop="loginTime"
+                sortable="custom"
+                :sort-orders="['descending', 'ascending']"
+                width="180"
+            >
+                <template #default="scope">
+                    <span>{{ parseTime(scope.row.loginTime) }}</span>
+                </template>
+            </el-table-column>
+        </el-table>
+
+        <pagination
+            v-show="total > 0"
+            v-model:page="queryParams.pageNum"
+            v-model:limit="queryParams.pageSize"
+            :total="total"
+            @pagination="getList"
+        />
+    </div>
+</template>
+
+<script setup name="Logininfor" lang="ts">
+/* eslint-disable camelcase */
+import { list, delLogininfor, cleanLogininfor, unlockLogininfor } from '@/api/monitor/logininfor';
+import { parseTime } from '@/utils/ruoyi';
+import { Sort } from 'element-plus';
+import { getCurrentInstance, ComponentInternalInstance, ref } from 'vue';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { sys_common_status } = proxy!.useDict('sys_common_status');
+
+const logininforList = ref<any[]>([]);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<number[]>([]);
+const single = ref(true);
+const multiple = ref(true);
+const selectName = ref<any>('');
+const total = ref(0);
+const dateRange = ref<any>([]);
+const defaultSort = ref<Sort>({ prop: 'loginTime', order: 'descending' });
+
+// 查询参数
+const queryParams = ref({
+    pageNum: 1,
+    pageSize: 10,
+    ipaddr: undefined,
+    userName: undefined,
+    status: undefined,
+    orderByColumn: undefined,
+    isAsc: undefined,
+});
+
+/** 查询登录日志列表 */
+function getList() {
+    loading.value = true;
+    list(proxy!.addDateRange(queryParams.value, dateRange.value)).then((response: any) => {
+        logininforList.value = response.rows;
+        total.value = response.total;
+        loading.value = false;
+    });
+}
+/** 搜索按钮操作 */
+function handleQuery() {
+    queryParams.value.pageNum = 1;
+    getList();
+}
+/** 重置按钮操作 */
+function resetQuery() {
+    dateRange.value = [];
+    proxy!.resetForm('queryRef');
+    queryParams.value.pageNum = 1;
+    (proxy!.$refs['logininforRef'] as any).sort(defaultSort.value.prop, defaultSort.value.order);
+}
+/** 多选框选中数据 */
+function handleSelectionChange(selection: any[]) {
+    ids.value = selection.map(item => item.infoId);
+    multiple.value = !selection.length;
+    single.value = selection.length !== 1;
+    selectName.value = selection.map(item => item.userName);
+}
+/** 排序触发事件 */
+function handleSortChange(column: any, prop: any, order: any) {
+    queryParams.value.orderByColumn = column.prop;
+    queryParams.value.isAsc = column.order;
+    getList();
+}
+/** 删除按钮操作 */
+function handleDelete(row: any) {
+    const infoIds = row.infoId || ids.value;
+    proxy!.$modal
+        .confirm('是否确认删除访问编号为"' + infoIds + '"的数据项?')
+        .then(function () {
+            return delLogininfor(infoIds);
+        })
+        .then(() => {
+            getList();
+            proxy!.$modal.msgSuccess('删除成功');
+        });
+    //   .catch(() => {});
+}
+/** 清空按钮操作 */
+function handleClean() {
+    proxy!.$modal
+        .confirm('是否确认清空所有登录日志数据项?')
+        .then(function () {
+            return cleanLogininfor();
+        })
+        .then(() => {
+            getList();
+            proxy!.$modal.msgSuccess('清空成功');
+        });
+    //   .catch(() => {});
+}
+/** 解锁按钮操作 */
+function handleUnlock() {
+    const username = selectName.value;
+    proxy!.$modal
+        .confirm('是否确认解锁用户"' + username + '"数据项?')
+        .then(function () {
+            return unlockLogininfor(username);
+        })
+        .then(() => {
+            proxy!.$modal.msgSuccess('用户' + username + '解锁成功');
+        });
+    //   .catch(() => {});
+}
+/** 导出按钮操作 */
+function handleExport() {
+    proxy!.download(
+        'monitor/logininfor/export',
+        {
+            ...queryParams.value,
+        },
+        `config_${new Date().getTime()}.xlsx`
+    );
+}
+
+getList();
+</script>

+ 124 - 0
frontend/src/views/monitor/online/index.vue

@@ -0,0 +1,124 @@
+<template>
+    <div class="app-container">
+        <el-form ref="queryRef" :model="queryParams" :inline="true">
+            <el-form-item label="登录地址" prop="ipaddr">
+                <el-input
+                    v-model="queryParams.ipaddr"
+                    placeholder="请输入登录地址"
+                    clearable
+                    style="width: 200px"
+                    @keyup.enter="handleQuery"
+                />
+            </el-form-item>
+            <el-form-item label="用户名称" prop="userName">
+                <el-input
+                    v-model="queryParams.userName"
+                    placeholder="请输入用户名称"
+                    clearable
+                    style="width: 200px"
+                    @keyup.enter="handleQuery"
+                />
+            </el-form-item>
+            <el-form-item>
+                <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+                <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+        </el-form>
+        <el-table
+            v-loading="loading"
+            :data="onlineList.slice((pageNum - 1) * pageSize, pageNum * pageSize)"
+            style="width: 100%"
+        >
+            <el-table-column label="序号" width="50" type="index" align="center">
+                <template #default="scope">
+                    <span>{{ (pageNum - 1) * pageSize + scope.$index + 1 }}</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="会话编号" align="center" prop="tokenId" :show-overflow-tooltip="true" />
+            <el-table-column label="登录名称" align="center" prop="userName" :show-overflow-tooltip="true" />
+            <el-table-column label="所属部门" align="center" prop="deptName" :show-overflow-tooltip="true" />
+            <el-table-column label="主机" align="center" prop="ipaddr" :show-overflow-tooltip="true" />
+            <el-table-column
+                label="登录地点"
+                align="center"
+                prop="loginLocation"
+                :show-overflow-tooltip="true"
+            />
+            <el-table-column label="操作系统" align="center" prop="os" :show-overflow-tooltip="true" />
+            <el-table-column label="浏览器" align="center" prop="browser" :show-overflow-tooltip="true" />
+            <el-table-column label="登录时间" align="center" prop="loginTime" width="180">
+                <template #default="scope">
+                    <span>{{ parseTime(scope.row.loginTime) }}</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+                <template #default="scope">
+                    <el-button
+                        v-hasPermi="['monitor:online:forceLogout']"
+                        link
+                        type="primary"
+                        icon="Delete"
+                        @click="handleForceLogout(scope.row)"
+                        >强退</el-button
+                    >
+                </template>
+            </el-table-column>
+        </el-table>
+
+        <pagination v-show="total > 0" v-model:page="pageNum" v-model:limit="pageSize" :total="total" />
+    </div>
+</template>
+
+<script setup name="Online" lang="ts">
+import { forceLogout, list as initData } from '@/api/monitor/online';
+import { parseTime } from '@/utils/ruoyi';
+import { getCurrentInstance, ComponentInternalInstance, ref } from 'vue';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const onlineList = ref<any[]>([]);
+const loading = ref(true);
+const total = ref(0);
+const pageNum = ref(1);
+const pageSize = ref(10);
+
+const queryParams = ref({
+    ipaddr: undefined,
+    userName: undefined,
+});
+
+/** 查询登录日志列表 */
+function getList() {
+    loading.value = true;
+    initData(queryParams.value).then((response: any) => {
+        onlineList.value = response.rows;
+        total.value = response.total;
+        loading.value = false;
+    });
+}
+/** 搜索按钮操作 */
+function handleQuery() {
+    pageNum.value = 1;
+    getList();
+}
+/** 重置按钮操作 */
+function resetQuery() {
+    proxy!.resetForm('queryRef');
+    handleQuery();
+}
+/** 强退按钮操作 */
+function handleForceLogout(row: any) {
+    proxy!.$modal
+        .confirm('是否确认强退名称为"' + row.userName + '"的用户?')
+        .then(function () {
+            return forceLogout(row.tokenId);
+        })
+        .then(() => {
+            getList();
+            proxy!.$modal.msgSuccess('删除成功');
+        });
+    //   .catch(() => {});
+}
+
+getList();
+</script>

+ 355 - 0
frontend/src/views/monitor/operlog/index.vue

@@ -0,0 +1,355 @@
+<template>
+    <div class="app-container">
+        <el-form v-show="showSearch" ref="queryRef" :model="queryParams" :inline="true" label-width="68px">
+            <el-form-item label="系统模块" prop="title">
+                <el-input
+                    v-model="queryParams.title"
+                    placeholder="请输入系统模块"
+                    clearable
+                    style="width: 240px"
+                    @keyup.enter="handleQuery"
+                />
+            </el-form-item>
+            <el-form-item label="操作人员" prop="operName">
+                <el-input
+                    v-model="queryParams.operName"
+                    placeholder="请输入操作人员"
+                    clearable
+                    style="width: 240px"
+                    @keyup.enter="handleQuery"
+                />
+            </el-form-item>
+            <el-form-item label="类型" prop="businessType">
+                <el-select
+                    v-model="queryParams.businessType"
+                    placeholder="操作类型"
+                    clearable
+                    style="width: 240px"
+                >
+                    <el-option
+                        v-for="dict in sys_oper_type"
+                        :key="dict.value"
+                        :label="dict.label"
+                        :value="dict.value"
+                    />
+                </el-select>
+            </el-form-item>
+            <el-form-item label="状态" prop="status">
+                <el-select v-model="queryParams.status" placeholder="操作状态" clearable style="width: 240px">
+                    <el-option
+                        v-for="dict in sys_common_status"
+                        :key="dict.value"
+                        :label="dict.label"
+                        :value="dict.value"
+                    />
+                </el-select>
+            </el-form-item>
+            <el-form-item label="操作时间" style="width: 308px">
+                <el-date-picker
+                    v-model="dateRange"
+                    value-format="YYYY-MM-DD HH:mm:ss"
+                    type="daterange"
+                    range-separator="-"
+                    start-placeholder="开始日期"
+                    end-placeholder="结束日期"
+                    :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
+                ></el-date-picker>
+            </el-form-item>
+            <el-form-item>
+                <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+                <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+        </el-form>
+
+        <el-row :gutter="10" class="mb8">
+            <el-col :span="1.5">
+                <el-button
+                    v-hasPermi="['monitor:operlog:remove']"
+                    type="danger"
+                    plain
+                    icon="Delete"
+                    :disabled="multiple"
+                    @click="handleDelete"
+                    >删除</el-button
+                >
+            </el-col>
+            <el-col :span="1.5">
+                <el-button
+                    v-hasPermi="['monitor:operlog:remove']"
+                    type="danger"
+                    plain
+                    icon="Delete"
+                    @click="handleClean"
+                    >清空</el-button
+                >
+            </el-col>
+            <el-col :span="1.5">
+                <el-button
+                    v-hasPermi="['monitor:operlog:export']"
+                    type="warning"
+                    plain
+                    icon="Download"
+                    @click="handleExport"
+                    >导出</el-button
+                >
+            </el-col>
+            <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+        </el-row>
+
+        <el-table
+            ref="operlogRef"
+            v-loading="loading"
+            :data="operlogList"
+            :default-sort="defaultSort"
+            @selectionChange="handleSelectionChange"
+            @sortChange="handleSortChange"
+        >
+            <el-table-column type="selection" width="50" align="center" />
+            <el-table-column label="日志编号" align="center" prop="operId" />
+            <el-table-column label="系统模块" align="center" prop="title" :show-overflow-tooltip="true" />
+            <el-table-column label="操作类型" align="center" prop="businessType">
+                <template #default="scope">
+                    <dict-tag :options="sys_oper_type" :value="scope.row.businessType" />
+                </template>
+            </el-table-column>
+            <el-table-column
+                label="操作人员"
+                align="center"
+                width="110"
+                prop="operName"
+                :show-overflow-tooltip="true"
+                sortable="custom"
+                :sort-orders="['descending', 'ascending']"
+            />
+            <el-table-column
+                label="主机"
+                align="center"
+                prop="operIp"
+                width="130"
+                :show-overflow-tooltip="true"
+            />
+            <el-table-column label="操作状态" align="center" prop="status">
+                <template #default="scope">
+                    <dict-tag :options="sys_common_status" :value="scope.row.status" />
+                </template>
+            </el-table-column>
+            <el-table-column
+                label="操作日期"
+                align="center"
+                prop="operTime"
+                sortable="custom"
+                :sort-orders="['descending', 'ascending']"
+                width="180"
+            >
+                <template #default="scope">
+                    <span>{{ parseTime(scope.row.operTime) }}</span>
+                </template>
+            </el-table-column>
+            <el-table-column
+                label="消耗时间"
+                align="center"
+                prop="costTime"
+                width="110"
+                :show-overflow-tooltip="true"
+                sortable="custom"
+                :sort-orders="['descending', 'ascending']"
+            >
+                <template #default="scope">
+                    <span>{{ scope.row.costTime }}毫秒</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+                <template #default="scope">
+                    <el-button
+                        v-hasPermi="['monitor:operlog:query']"
+                        link
+                        type="primary"
+                        icon="View"
+                        @click="handleView(scope.row, scope.index)"
+                        >详细</el-button
+                    >
+                </template>
+            </el-table-column>
+        </el-table>
+
+        <pagination
+            v-show="total > 0"
+            v-model:page="queryParams.pageNum"
+            v-model:limit="queryParams.pageSize"
+            :total="total"
+            @pagination="getList"
+        />
+
+        <!-- 操作日志详细 -->
+        <el-dialog :close-on-click-modal="false" v-model="open" title="操作日志详细" width="700px" append-to-body>
+            <el-form :model="form" label-width="100px">
+                <el-row>
+                    <el-col :span="12">
+                        <el-form-item label="操作模块:"
+                            >{{ form.title }} / {{ typeFormat(form) }}</el-form-item
+                        >
+                        <el-form-item label="登录信息:"
+                            >{{ form.operName }} / {{ form.operIp }} / {{ form.operLocation }}</el-form-item
+                        >
+                    </el-col>
+                    <el-col :span="12">
+                        <el-form-item label="请求地址:">{{ form.operUrl }}</el-form-item>
+                        <el-form-item label="请求方式:">{{ form.requestMethod }}</el-form-item>
+                    </el-col>
+                    <el-col :span="24">
+                        <el-form-item label="操作方法:">{{ form.method }}</el-form-item>
+                    </el-col>
+                    <el-col :span="24">
+                        <el-form-item label="请求参数:">{{ form.operParam }}</el-form-item>
+                    </el-col>
+                    <el-col :span="24">
+                        <el-form-item label="返回参数:">{{ form.jsonResult }}</el-form-item>
+                    </el-col>
+                    <el-col :span="6">
+                        <el-form-item label="操作状态:">
+                            <div v-if="form.status === 0">正常</div>
+                            <div v-else-if="form.status === 1">失败</div>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :span="8">
+                        <el-form-item label="消耗时间:">{{ form.costTime }}毫秒</el-form-item>
+                    </el-col>
+                    <el-col :span="10">
+                        <el-form-item label="操作时间:">{{ parseTime(form.operTime) }}</el-form-item>
+                    </el-col>
+                    <el-col :span="24">
+                        <el-form-item v-if="form.status === 1" label="异常信息:">{{
+                            form.errorMsg
+                        }}</el-form-item>
+                    </el-col>
+                </el-row>
+            </el-form>
+            <template #footer>
+                <div class="dialog-footer">
+                    <el-button @click="open = false">关 闭</el-button>
+                </div>
+            </template>
+        </el-dialog>
+    </div>
+</template>
+
+<script setup name="Operlog" lang="ts">
+/* eslint-disable camelcase */
+import { list, delOperlog, cleanOperlog } from '@/api/monitor/operlog';
+import { parseTime } from '@/utils/ruoyi';
+import { Sort } from 'element-plus';
+import { getCurrentInstance, ComponentInternalInstance, ref, reactive, toRefs } from 'vue';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { sys_oper_type, sys_common_status } = proxy!.useDict('sys_oper_type', 'sys_common_status');
+
+const operlogList = ref<any[]>([]);
+const open = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<number[]>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const title = ref('');
+const dateRange = ref<any>([]);
+const defaultSort = ref<Sort>({ prop: 'operTime', order: 'descending' });
+
+const data = reactive<{
+    form: any;
+    queryParams: any;
+}>({
+    form: {},
+    queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        title: undefined,
+        operName: undefined,
+        businessType: undefined,
+        status: undefined,
+    },
+});
+
+const { queryParams, form } = toRefs(data);
+
+/** 查询登录日志 */
+function getList() {
+    loading.value = true;
+    list(proxy!.addDateRange(queryParams.value, dateRange.value)).then((response: any) => {
+        operlogList.value = response.rows;
+        total.value = response.total;
+        loading.value = false;
+    });
+}
+/** 操作日志类型字典翻译 */
+function typeFormat(row: any, column?: any) {
+    return proxy!.selectDictLabel(sys_oper_type.value, row.businessType);
+}
+/** 搜索按钮操作 */
+function handleQuery() {
+    queryParams.value.pageNum = 1;
+    getList();
+}
+/** 重置按钮操作 */
+function resetQuery() {
+    dateRange.value = [];
+    proxy!.resetForm('queryRef');
+    queryParams.value.pageNum = 1;
+    (proxy!.$refs['operlogRef'] as any).sort(defaultSort.value.prop, defaultSort.value.order);
+}
+/** 多选框选中数据 */
+function handleSelectionChange(selection: any[]) {
+    ids.value = selection.map(item => item.operId);
+    multiple.value = !selection.length;
+}
+/** 排序触发事件 */
+function handleSortChange(column: any, prop?: any, order?: any) {
+    queryParams.value.orderByColumn = column.prop;
+    queryParams.value.isAsc = column.order;
+    getList();
+}
+/** 详细按钮操作 */
+function handleView(row: any, index?: any) {
+    open.value = true;
+    form.value = row;
+}
+/** 删除按钮操作 */
+function handleDelete(row: any) {
+    const operIds = row.operId || ids.value;
+    proxy!.$modal
+        .confirm('是否确认删除日志编号为"' + operIds + '"的数据项?')
+        .then(function () {
+            return delOperlog(operIds);
+        })
+        .then(() => {
+            getList();
+            proxy!.$modal.msgSuccess('删除成功');
+        });
+    //   .catch(() => {});
+}
+/** 清空按钮操作 */
+function handleClean() {
+    proxy!.$modal
+        .confirm('是否确认清空所有操作日志数据项?')
+        .then(function () {
+            return cleanOperlog();
+        })
+        .then(() => {
+            getList();
+            proxy!.$modal.msgSuccess('清空成功');
+        });
+    //   .catch(() => {});
+}
+/** 导出按钮操作 */
+function handleExport() {
+    proxy!.download(
+        'monitor/operlog/export',
+        {
+            ...queryParams.value,
+        },
+        `config_${new Date().getTime()}.xlsx`
+    );
+}
+
+getList();
+</script>

+ 272 - 0
frontend/src/views/monitor/server/index.vue

@@ -0,0 +1,272 @@
+<template>
+    <div class="app-container">
+        <el-row>
+            <el-col :span="12" class="card-box">
+                <el-card>
+                    <template #header><Cpu style="width: 1em; height: 1em; vertical-align: middle;" /> <span style="vertical-align: middle;">CPU</span></template>
+                    <div class="el-table el-table--enable-row-hover el-table--medium">
+                        <table cellspacing="0" style="width: 100%">
+                            <thead>
+                                <tr>
+                                    <th class="el-table__cell is-leaf"><div class="cell">属性</div></th>
+                                    <th class="el-table__cell is-leaf"><div class="cell">值</div></th>
+                                </tr>
+                            </thead>
+                            <tbody>
+                                <tr>
+                                    <td class="el-table__cell is-leaf"><div class="cell">核心数</div></td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="server.cpu" class="cell">{{ server.cpu.cpuNum }}</div>
+                                    </td>
+                                </tr>
+                                <tr>
+                                    <td class="el-table__cell is-leaf"><div class="cell">用户使用率</div></td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="server.cpu" class="cell">{{ server.cpu.used }}%</div>
+                                    </td>
+                                </tr>
+                                <tr>
+                                    <td class="el-table__cell is-leaf"><div class="cell">系统使用率</div></td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="server.cpu" class="cell">{{ server.cpu.sys }}%</div>
+                                    </td>
+                                </tr>
+                                <tr>
+                                    <td class="el-table__cell is-leaf"><div class="cell">当前空闲率</div></td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="server.cpu" class="cell">{{ server.cpu.free }}%</div>
+                                    </td>
+                                </tr>
+                            </tbody>
+                        </table>
+                    </div>
+                </el-card>
+            </el-col>
+
+            <el-col :span="12" class="card-box">
+                <el-card>
+                    <template #header><Tickets style="width: 1em; height: 1em; vertical-align: middle;" /> <span style="vertical-align: middle;">内存</span></template>
+                    <div class="el-table el-table--enable-row-hover el-table--medium">
+                        <table cellspacing="0" style="width: 100%">
+                            <thead>
+                                <tr>
+                                    <th class="el-table__cell is-leaf"><div class="cell">属性</div></th>
+                                    <th class="el-table__cell is-leaf"><div class="cell">内存</div></th>
+                                    <th class="el-table__cell is-leaf"><div class="cell">JVM</div></th>
+                                </tr>
+                            </thead>
+                            <tbody>
+                                <tr>
+                                    <td class="el-table__cell is-leaf"><div class="cell">总内存</div></td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="server.mem" class="cell">{{ server.mem.total }}G</div>
+                                    </td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="server.jvm" class="cell">{{ server.jvm.total }}M</div>
+                                    </td>
+                                </tr>
+                                <tr>
+                                    <td class="el-table__cell is-leaf"><div class="cell">已用内存</div></td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="server.mem" class="cell">{{ server.mem.used }}G</div>
+                                    </td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="server.jvm" class="cell">{{ server.jvm.used }}M</div>
+                                    </td>
+                                </tr>
+                                <tr>
+                                    <td class="el-table__cell is-leaf"><div class="cell">剩余内存</div></td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="server.mem" class="cell">{{ server.mem.free }}G</div>
+                                    </td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="server.jvm" class="cell">{{ server.jvm.free }}M</div>
+                                    </td>
+                                </tr>
+                                <tr>
+                                    <td class="el-table__cell is-leaf"><div class="cell">使用率</div></td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div
+                                            v-if="server.mem"
+                                            class="cell"
+                                            :class="{ 'text-danger': server.mem.usage > 80 }"
+                                        >
+                                            {{ server.mem.usage }}%
+                                        </div>
+                                    </td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div
+                                            v-if="server.jvm"
+                                            class="cell"
+                                            :class="{ 'text-danger': server.jvm.usage > 80 }"
+                                        >
+                                            {{ server.jvm.usage }}%
+                                        </div>
+                                    </td>
+                                </tr>
+                            </tbody>
+                        </table>
+                    </div>
+                </el-card>
+            </el-col>
+
+            <el-col :span="24" class="card-box">
+                <el-card>
+                    <template #header><Monitor style="width: 1em; height: 1em; vertical-align: middle;" /> <span style="vertical-align: middle;">服务器信息</span></template>
+                    <div class="el-table el-table--enable-row-hover el-table--medium">
+                        <table cellspacing="0" style="width: 100%">
+                            <tbody>
+                                <tr>
+                                    <td class="el-table__cell is-leaf"><div class="cell">服务器名称</div></td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="server.sys" class="cell">
+                                            {{ server.sys.computerName }}
+                                        </div>
+                                    </td>
+                                    <td class="el-table__cell is-leaf"><div class="cell">操作系统</div></td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="server.sys" class="cell">{{ server.sys.osName }}</div>
+                                    </td>
+                                </tr>
+                                <tr>
+                                    <td class="el-table__cell is-leaf"><div class="cell">服务器IP</div></td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="server.sys" class="cell">{{ server.sys.computerIp }}</div>
+                                    </td>
+                                    <td class="el-table__cell is-leaf"><div class="cell">系统架构</div></td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="server.sys" class="cell">{{ server.sys.osArch }}</div>
+                                    </td>
+                                </tr>
+                            </tbody>
+                        </table>
+                    </div>
+                </el-card>
+            </el-col>
+
+            <el-col :span="24" class="card-box">
+                <el-card>
+                    <template #header><CoffeeCup style="width: 1em; height: 1em; vertical-align: middle;" /> <span style="vertical-align: middle;">Java虚拟机信息</span></template>
+                    <div class="el-table el-table--enable-row-hover el-table--medium">
+                        <table cellspacing="0" style="width: 100%; table-layout: fixed">
+                            <tbody>
+                                <tr>
+                                    <td class="el-table__cell is-leaf"><div class="cell">Java名称</div></td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="server.jvm" class="cell">{{ server.jvm.name }}</div>
+                                    </td>
+                                    <td class="el-table__cell is-leaf"><div class="cell">Java版本</div></td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="server.jvm" class="cell">{{ server.jvm.version }}</div>
+                                    </td>
+                                </tr>
+                                <tr>
+                                    <td class="el-table__cell is-leaf"><div class="cell">启动时间</div></td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="server.jvm" class="cell">{{ server.jvm.startTime }}</div>
+                                    </td>
+                                    <td class="el-table__cell is-leaf"><div class="cell">运行时长</div></td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div v-if="server.jvm" class="cell">{{ server.jvm.runTime }}</div>
+                                    </td>
+                                </tr>
+                                <tr>
+                                    <td colspan="1" class="el-table__cell is-leaf">
+                                        <div class="cell">安装路径</div>
+                                    </td>
+                                    <td colspan="3" class="el-table__cell is-leaf">
+                                        <div v-if="server.jvm" class="cell">{{ server.jvm.home }}</div>
+                                    </td>
+                                </tr>
+                                <tr>
+                                    <td colspan="1" class="el-table__cell is-leaf">
+                                        <div class="cell">项目路径</div>
+                                    </td>
+                                    <td colspan="3" class="el-table__cell is-leaf">
+                                        <div v-if="server.sys" class="cell">{{ server.sys.userDir }}</div>
+                                    </td>
+                                </tr>
+                                <tr>
+                                    <td colspan="1" class="el-table__cell is-leaf">
+                                        <div class="cell">运行参数</div>
+                                    </td>
+                                    <td colspan="3" class="el-table__cell is-leaf">
+                                        <div v-if="server.jvm" class="cell">{{ server.jvm.inputArgs }}</div>
+                                    </td>
+                                </tr>
+                            </tbody>
+                        </table>
+                    </div>
+                </el-card>
+            </el-col>
+
+            <el-col :span="24" class="card-box">
+                <el-card>
+                    <template #header><MessageBox style="width: 1em; height: 1em; vertical-align: middle;" /> <span style="vertical-align: middle;">磁盘状态</span></template>
+                    <div class="el-table el-table--enable-row-hover el-table--medium">
+                        <table cellspacing="0" style="width: 100%">
+                            <thead>
+                                <tr>
+                                    <th class="el-table__cell el-table__cell is-leaf">
+                                        <div class="cell">盘符路径</div>
+                                    </th>
+                                    <th class="el-table__cell is-leaf"><div class="cell">文件系统</div></th>
+                                    <th class="el-table__cell is-leaf"><div class="cell">盘符类型</div></th>
+                                    <th class="el-table__cell is-leaf"><div class="cell">总大小</div></th>
+                                    <th class="el-table__cell is-leaf"><div class="cell">可用大小</div></th>
+                                    <th class="el-table__cell is-leaf"><div class="cell">已用大小</div></th>
+                                    <th class="el-table__cell is-leaf"><div class="cell">已用百分比</div></th>
+                                </tr>
+                            </thead>
+                            <tbody v-if="server.sysFiles">
+                                <tr v-for="(sysFile, index) in server.sysFiles" :key="index">
+                                    <td class="el-table__cell is-leaf">
+                                        <div class="cell">{{ sysFile.dirName }}</div>
+                                    </td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div class="cell">{{ sysFile.sysTypeName }}</div>
+                                    </td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div class="cell">{{ sysFile.typeName }}</div>
+                                    </td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div class="cell">{{ sysFile.total }}</div>
+                                    </td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div class="cell">{{ sysFile.free }}</div>
+                                    </td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div class="cell">{{ sysFile.used }}</div>
+                                    </td>
+                                    <td class="el-table__cell is-leaf">
+                                        <div class="cell" :class="{ 'text-danger': sysFile.usage > 80 }">
+                                            {{ sysFile.usage }}%
+                                        </div>
+                                    </td>
+                                </tr>
+                            </tbody>
+                        </table>
+                    </div>
+                </el-card>
+            </el-col>
+        </el-row>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { getServer } from '@/api/monitor/server';
+import { ref, getCurrentInstance, ComponentInternalInstance } from 'vue';
+
+const server = ref<any>([]);
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+function getList() {
+    proxy!.$modal.loading('正在加载服务监控数据,请稍候!');
+    getServer().then(response => {
+        server.value = response.data;
+        proxy!.$modal.closeLoading();
+    });
+}
+
+getList();
+</script>

+ 15 - 32
frontend/src/views/system/user/index.vue

@@ -80,38 +80,6 @@
                         prop="dept.deptName" :show-overflow-tooltip="true" />
                     <el-table-column v-if="columns[4].visible" key="phonenumber" label="手机号码" align="center"
                         prop="phonenumber" width="120" />
-                    <el-table-column v-if="columns[8].visible" key="dhPlateNoVos" label="车牌号" align="center"
-                        width="100">
-                        <template #default="scope">
-                            <el-popover placement="right" trigger="hover">
-                                <template #reference>
-                                    <div style="color: #409eff">
-                                        {{ scope.row.dhPlateNoVos[0]?.plateNo || '' }}
-                                    </div>
-                                </template>
-                                <div v-for="item in scope.row.dhPlateNoVos">
-                                    {{ item.plateNo }}
-                                </div>
-                            </el-popover>
-                        </template>
-                    </el-table-column>
-                    <el-table-column v-if="columns[8].visible" key="landlineNumbers" label="座机号" align="center"
-                        width="100">
-                        <template #default="scope">
-                            <el-popover placement="right" trigger="hover">
-                                <template #reference>
-                                    <div style="color: #409eff">
-                                        {{ scope.row.landlineNumbers[0]?.landlineNumber || '' }}
-                                    </div>
-                                </template>
-                                <div v-for="item in scope.row.landlineNumbers">
-                                    {{ item.landlineNumber }}
-                                </div>
-                            </el-popover>
-                        </template>
-                    </el-table-column>
-                    <el-table-column v-if="columns[7].visible" key="jobNumber" label="工号" align="center"
-                        prop="jobNumber" width="100" />
                     <el-table-column v-if="columns[5].visible" key="status" label="状态" align="center">
                         <template #default="scope">
                             <el-switch v-model="scope.row.status" active-value="0" inactive-value="1"
@@ -142,6 +110,7 @@
                                 <el-button v-hasPermi="['system:user:edit']" link type="primary" icon="CircleCheck"
                                     @click="handleAuthRole(scope.row)"></el-button>
                             </el-tooltip>
+<<<<<<< HEAD
                             <el-tooltip v-if="scope.row.userId !== 1" content="车牌号录入" placement="top">
                                 <el-button v-hasPermi="['system:user:edit']" link type="primary" icon="List"
                                     @click="handleAddCar(scope.row)"></el-button>
@@ -150,6 +119,8 @@
                                 <el-button v-hasPermi="['system:user:edit']" link type="primary" icon="PhoneFilled"
                                     @click="handleAddLandline(scope.row)"></el-button>
                             </el-tooltip>
+=======
+>>>>>>> e94f5f44564039f665611f42b365b51740d0649c
                         </template>
                     </el-table-column>
                 </el-table>
@@ -220,6 +191,7 @@
                     </el-col>
                 </el-row>
                 <el-row>
+<<<<<<< HEAD
                     <!-- <el-col :span="12">
                         <el-form-item label="岗位">
                             <el-select v-model="form.postIds" multiple placeholder="请选择">
@@ -233,6 +205,8 @@
                             </el-select>
                         </el-form-item>
                     </el-col> -->
+=======
+>>>>>>> e94f5f44564039f665611f42b365b51740d0649c
                     <el-col :span="12">
                         <el-form-item label="角色" prop="roleIds">
                             <el-select @change="getLabel" v-model="form.roleIds" multiple placeholder="请选择">
@@ -289,6 +263,7 @@
                 </div>
             </template>
         </el-dialog>
+<<<<<<< HEAD
 
         <!-- 录入车牌号对话框 -->
         <el-dialog :close-on-click-modal="false" v-model="car.open" :title="car.title" width="400px" append-to-body>
@@ -325,6 +300,8 @@
                 </div>
             </template>
         </el-dialog>
+=======
+>>>>>>> e94f5f44564039f665611f42b365b51740d0649c
     </div>
 </template>
 
@@ -388,6 +365,7 @@ const upload = reactive({
     // 上传的地址
     url: import.meta.env.VITE_APP_BASE_API + '/system/user/importData',
 });
+<<<<<<< HEAD
 /*** 座机录入参数 */
 const landline = reactive({
     // 是否显示弹出层
@@ -504,6 +482,8 @@ function handleAddLandline(row: any) {
     }
     proxy!.resetForm('carRef');
 }
+=======
+>>>>>>> e94f5f44564039f665611f42b365b51740d0649c
 // 列显隐信息
 const columns = ref([
     { key: 0, label: `用户编号`, visible: true },
@@ -511,8 +491,11 @@ const columns = ref([
     { key: 2, label: `用户昵称`, visible: true },
     { key: 3, label: `部门`, visible: true },
     { key: 4, label: `手机号码`, visible: true },
+<<<<<<< HEAD
     { key: 7, label: `工号`, visible: true },
     { key: 8, label: `车牌号`, visible: true },
+=======
+>>>>>>> e94f5f44564039f665611f42b365b51740d0649c
     { key: 5, label: `状态`, visible: true },
     { key: 6, label: `创建时间`, visible: true },
 ]);

+ 3 - 0
frontend/src/views/tool/build/index.vue

@@ -0,0 +1,3 @@
+<template>
+   <div> 表单构建 <svg-icon icon-class="build" /> </div>
+</template>

+ 55 - 0
frontend/src/views/tool/gen/basicInfoForm.vue

@@ -0,0 +1,55 @@
+<!-- eslint-disable vue/no-mutating-props -->
+<template>
+    <el-form ref="basicInfoForm" :model="info" :rules="rules" label-width="150px">
+        <el-row>
+            <el-col :span="12">
+                <el-form-item label="表名称" prop="tableName">
+                    <el-input v-model="info.tableName" placeholder="请输入仓库名称" />
+                </el-form-item>
+            </el-col>
+            <el-col :span="12">
+                <el-form-item label="表描述" prop="tableComment">
+                    <el-input v-model="info.tableComment" placeholder="请输入" />
+                </el-form-item>
+            </el-col>
+            <el-col :span="12">
+                <el-form-item label="实体类名称" prop="className">
+                    <el-input v-model="info.className" placeholder="请输入" />
+                </el-form-item>
+            </el-col>
+            <el-col :span="12">
+                <el-form-item label="作者" prop="functionAuthor">
+                    <el-input v-model="info.functionAuthor" placeholder="请输入" />
+                </el-form-item>
+            </el-col>
+            <el-col :span="24">
+                <el-form-item label="备注" prop="remark">
+                    <el-input v-model="info.remark" type="textarea" :rows="3"></el-input>
+                </el-form-item>
+            </el-col>
+        </el-row>
+    </el-form>
+</template>
+
+<script setup lang="ts">
+import { FormInstance } from 'element-plus';
+import { ref } from 'vue';
+defineProps({
+    info: {
+        type: Object,
+        default: null,
+    },
+});
+const basicInfoForm = ref<FormInstance>();
+defineExpose({
+    basicInfoForm
+});
+
+// 表单校验
+const rules = ref({
+    tableName: [{ required: true, message: '请输入表名称', trigger: 'blur' }],
+    tableComment: [{ required: true, message: '请输入表描述', trigger: 'blur' }],
+    className: [{ required: true, message: '请输入实体类名称', trigger: 'blur' }],
+    functionAuthor: [{ required: true, message: '请输入作者', trigger: 'blur' }],
+});
+</script>

+ 205 - 0
frontend/src/views/tool/gen/editTable.vue

@@ -0,0 +1,205 @@
+<template>
+    <el-card>
+        <el-tabs v-model="activeName">
+            <el-tab-pane label="基本信息" name="basic">
+                <basic-info-form ref="basicInfo" :info="info" />
+            </el-tab-pane>
+            <el-tab-pane label="字段信息" name="columnInfo">
+                <el-table ref="dragTable" :data="columns" row-key="columnId" :max-height="tableHeight">
+                    <el-table-column label="序号" type="index" min-width="5%" />
+                    <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"></el-input>
+                        </template>
+                    </el-table-column>
+                    <el-table-column
+                        label="物理类型"
+                        prop="columnType"
+                        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"></el-input>
+                        </template>
+                    </el-table-column>
+
+                    <el-table-column label="插入" min-width="5%">
+                        <template #default="scope">
+                            <el-checkbox true-label="1" v-model="scope.row.isInsert"></el-checkbox>
+                        </template>
+                    </el-table-column>
+                    <el-table-column label="编辑" min-width="5%">
+                        <template #default="scope">
+                            <el-checkbox true-label="1" v-model="scope.row.isEdit"></el-checkbox>
+                        </template>
+                    </el-table-column>
+                    <el-table-column label="列表" min-width="5%">
+                        <template #default="scope">
+                            <el-checkbox true-label="1" v-model="scope.row.isList"></el-checkbox>
+                        </template>
+                    </el-table-column>
+                    <el-table-column label="查询" min-width="5%">
+                        <template #default="scope">
+                            <el-checkbox true-label="1" v-model="scope.row.isQuery"></el-checkbox>
+                        </template>
+                    </el-table-column>
+                    <el-table-column label="查询方式" min-width="10%">
+                        <template #default="scope">
+                            <el-select v-model="scope.row.queryType">
+                                <el-option label="=" value="EQ" />
+                                <el-option label="!=" value="NE" />
+                                <el-option label=">" value="GT" />
+                                <el-option label=">=" value="GTE" />
+                                <el-option label="<" value="LT" />
+                                <el-option label="<=" value="LTE" />
+                                <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="1" v-model="scope.row.isRequired"></el-checkbox>
+                        </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.dictType"
+                                    :label="dict.dictName"
+                                    :value="dict.dictType"
+                                >
+                                    <span style="float: left">{{ dict.dictName }}</span>
+                                    <span style="float: right; color: #8492a6; font-size: 13px">{{
+                                        dict.dictType
+                                    }}</span>
+                                </el-option>
+                            </el-select>
+                        </template>
+                    </el-table-column>
+                </el-table>
+            </el-tab-pane>
+            <el-tab-pane label="生成信息" name="genInfo">
+                <gen-info-form ref="genInfo" :info="info" :tables="tables" />
+            </el-tab-pane>
+        </el-tabs>
+        <el-form label-width="100px">
+            <div style="text-align: center; margin-left: -100px; margin-top: 10px">
+                <el-button type="primary" @click="submitForm()">提交</el-button>
+                <el-button @click="close()">返回</el-button>
+            </div>
+        </el-form>
+    </el-card>
+</template>
+
+<script setup name="GenEdit" lang="ts">
+import { getGenTable, updateGenTable } from '@/api/tool/gen';
+import { optionselect as getDictOptionselect } from '@/api/system/dict/type';
+import basicInfoForm from './basicInfoForm.vue';
+import genInfoForm from './genInfoForm.vue';
+import { ComponentInternalInstance, getCurrentInstance, ref } from 'vue';
+import { useRoute } from 'vue-router';
+
+const route = useRoute();
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const activeName = ref('columnInfo');
+const tableHeight = ref(document.documentElement.scrollHeight - 245 + 'px');
+const tables = ref<any[]>([]);
+const columns = ref<any[]>([]);
+const dictOptions = ref<any[]>([]);
+const info = ref<any>({});
+const basicInfo = ref<InstanceType<typeof basicInfoForm>>();
+const genInfo = ref<InstanceType<typeof genInfoForm>>();
+
+/** 提交按钮 */
+function submitForm() {
+    // const basicForm = proxy!.$refs.basicInfo.$refs.basicInfoForm;
+    // const genForm = proxy!.$refs.genInfo.$refs.genInfoForm;
+    Promise.all([basicInfo.value?.basicInfoForm, genInfo.value?.genInfoForm].map(getFormPromise)).then(res => {
+        const validateResult = res.every(item => !!item);
+        if (validateResult) {
+            const genTable = Object.assign({}, info.value);
+            genTable.columns = columns.value;
+            genTable.params = {
+                treeCode: info.value.treeCode,
+                treeName: info.value.treeName,
+                treeParentCode: info.value.treeParentCode,
+                parentMenuId: info.value.parentMenuId,
+            };
+            updateGenTable(genTable).then((res: any) => {
+                proxy?.$modal.msgSuccess(res.msg);
+                if (res.code === 200) {
+                    close();
+                }
+            });
+        } else {
+            proxy?.$modal.msgError('表单校验未通过,请重新检查提交内容');
+        }
+    });
+}
+function getFormPromise(form: any) {
+    return new Promise(resolve => {
+        form.validate((res: any) => {
+            resolve(res);
+        });
+    });
+}
+function close() {
+    const obj = { path: '/tool/gen', query: { t: Date.now(), pageNum: route.query.pageNum } };
+    proxy?.$tab.closeOpenPage(obj);
+}
+
+(() => {
+    const tableId = route.params && route.params.tableId;
+    if (tableId) {
+        // 获取表详细信息
+        getGenTable(tableId).then(res => {
+            columns.value = res.data.rows;
+            info.value = res.data.info;
+            tables.value = res.data.tables;
+        });
+        /** 查询字典下拉列表 */
+        getDictOptionselect().then(response => {
+            dictOptions.value = response.data;
+        });
+    }
+})();
+</script>

+ 295 - 0
frontend/src/views/tool/gen/genInfoForm.vue

@@ -0,0 +1,295 @@
+<!-- eslint-disable vue/no-mutating-props -->
+<template>
+    <el-form ref="genInfoForm" :model="info" :rules="rules" label-width="150px">
+        <el-row>
+            <el-col :span="12">
+                <el-form-item prop="tplCategory">
+                    <template #label>生成模板</template>
+                    <el-select v-model="info.tplCategory" @change="tplSelectChange">
+                        <el-option label="单表(增删改查)" value="crud" />
+                        <el-option label="树表(增删改查)" value="tree" />
+                        <el-option label="主子表(增删改查)" value="sub" />
+                    </el-select>
+                </el-form-item>
+            </el-col>
+
+            <el-col :span="12">
+                <el-form-item prop="packageName">
+                    <template #label>
+                        生成包路径
+                        <el-tooltip content="生成在哪个java包下,例如 com.ruoyi.system" placement="top">
+                            <el-icon><question-filled /></el-icon>
+                        </el-tooltip>
+                    </template>
+                    <el-input v-model="info.packageName" />
+                </el-form-item>
+            </el-col>
+
+            <el-col :span="12">
+                <el-form-item prop="moduleName">
+                    <template #label>
+                        生成模块名
+                        <el-tooltip content="可理解为子系统名,例如 system" placement="top">
+                            <el-icon><question-filled /></el-icon>
+                        </el-tooltip>
+                    </template>
+                    <el-input v-model="info.moduleName" />
+                </el-form-item>
+            </el-col>
+
+            <el-col :span="12">
+                <el-form-item prop="businessName">
+                    <template #label>
+                        生成业务名
+                        <el-tooltip content="可理解为功能英文名,例如 user" placement="top">
+                            <el-icon><question-filled /></el-icon>
+                        </el-tooltip>
+                    </template>
+                    <el-input v-model="info.businessName" />
+                </el-form-item>
+            </el-col>
+
+            <el-col :span="12">
+                <el-form-item prop="functionName">
+                    <template #label>
+                        生成功能名
+                        <el-tooltip content="用作类描述,例如 用户" placement="top">
+                            <el-icon><question-filled /></el-icon>
+                        </el-tooltip>
+                    </template>
+                    <el-input v-model="info.functionName" />
+                </el-form-item>
+            </el-col>
+
+            <el-col :span="12">
+                <el-form-item>
+                    <template #label>
+                        上级菜单
+                        <el-tooltip content="分配到指定菜单下,例如 系统管理" placement="top">
+                            <el-icon><question-filled /></el-icon>
+                        </el-tooltip>
+                    </template>
+                    <tree-select
+                        v-model:value="info.parentMenuId"
+                        :options="menuOptions"
+                        :objMap="{ value: 'menuId', label: 'menuName', children: 'children' }"
+                        placeholder="请选择系统菜单"
+                    />
+                </el-form-item>
+            </el-col>
+
+            <el-col :span="12">
+                <el-form-item prop="genType">
+                    <template #label>
+                        生成代码方式
+                        <el-tooltip content="默认为zip压缩包下载,也可以自定义生成路径" placement="top">
+                            <el-icon><question-filled /></el-icon>
+                        </el-tooltip>
+                    </template>
+                    <el-radio v-model="info.genType" label="0">zip压缩包</el-radio>
+                    <el-radio v-model="info.genType" label="1">自定义路径</el-radio>
+                </el-form-item>
+            </el-col>
+
+            <el-col :span="24" v-if="info.genType == '1'">
+                <el-form-item prop="genPath">
+                    <template #label>
+                        自定义路径
+                        <el-tooltip
+                            content="填写磁盘绝对路径,若不填写,则生成到当前Web项目下"
+                            placement="top"
+                        >
+                            <el-icon><question-filled /></el-icon>
+                        </el-tooltip>
+                    </template>
+                    <el-input v-model="info.genPath">
+                        <template #append>
+                            <el-dropdown>
+                                <el-button type="primary">
+                                    最近路径快速选择
+                                    <i class="el-icon-arrow-down el-icon--right"></i>
+                                </el-button>
+                                <template #dropdown>
+                                    <el-dropdown-menu>
+                                        <el-dropdown-item @click="info.genPath = '/'"
+                                            >恢复默认的生成基础路径</el-dropdown-item
+                                        >
+                                    </el-dropdown-menu>
+                                </template>
+                            </el-dropdown>
+                        </template>
+                    </el-input>
+                </el-form-item>
+            </el-col>
+        </el-row>
+
+        <template v-if="info.tplCategory == 'tree'">
+            <h4 class="form-header">其他信息</h4>
+            <el-row v-show="info.tplCategory == 'tree'">
+                <el-col :span="12">
+                    <el-form-item>
+                        <template #label>
+                            树编码字段
+                            <el-tooltip content="树显示的编码字段名, 如:dept_id" placement="top">
+                                <el-icon><question-filled /></el-icon>
+                            </el-tooltip>
+                        </template>
+                        <el-select v-model="info.treeCode" placeholder="请选择">
+                            <el-option
+                                v-for="(column, index) in info.columns"
+                                :key="index"
+                                :label="column.columnName + ':' + column.columnComment"
+                                :value="column.columnName"
+                            ></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                    <el-form-item>
+                        <template #label>
+                            树父编码字段
+                            <el-tooltip content="树显示的父编码字段名, 如:parent_Id" placement="top">
+                                <el-icon><question-filled /></el-icon>
+                            </el-tooltip>
+                        </template>
+                        <el-select v-model="info.treeParentCode" placeholder="请选择">
+                            <el-option
+                                v-for="(column, index) in info.columns"
+                                :key="index"
+                                :label="column.columnName + ':' + column.columnComment"
+                                :value="column.columnName"
+                            ></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                    <el-form-item>
+                        <template #label>
+                            树名称字段
+                            <el-tooltip content="树节点的显示名称字段名, 如:dept_name" placement="top">
+                                <el-icon><question-filled /></el-icon>
+                            </el-tooltip>
+                        </template>
+                        <el-select v-model="info.treeName" placeholder="请选择">
+                            <el-option
+                                v-for="(column, index) in info.columns"
+                                :key="index"
+                                :label="column.columnName + ':' + column.columnComment"
+                                :value="column.columnName"
+                            ></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+            </el-row>
+        </template>
+
+        <template v-if="info.tplCategory == 'sub'">
+            <h4 class="form-header">关联信息</h4>
+            <el-row>
+                <el-col :span="12">
+                    <el-form-item>
+                        <template #label>
+                            关联子表的表名
+                            <el-tooltip content="关联子表的表名, 如:sys_user" placement="top">
+                                <el-icon><question-filled /></el-icon>
+                            </el-tooltip>
+                        </template>
+                        <el-select v-model="info.subTableName" placeholder="请选择" @change="subSelectChange">
+                            <el-option
+                                v-for="(table, index) in tables"
+                                :key="index"
+                                :label="table.tableName + ':' + table.tableComment"
+                                :value="table.tableName"
+                            ></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                    <el-form-item>
+                        <template #label>
+                            子表关联的外键名
+                            <el-tooltip content="子表关联的外键名, 如:user_id" placement="top">
+                                <el-icon><question-filled /></el-icon>
+                            </el-tooltip>
+                        </template>
+                        <el-select v-model="info.subTableFkName" placeholder="请选择">
+                            <el-option
+                                v-for="(column, index) in subColumns"
+                                :key="index"
+                                :label="column.columnName + ':' + column.columnComment"
+                                :value="column.columnName"
+                            ></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+            </el-row>
+        </template>
+    </el-form>
+</template>
+
+<!-- eslint-disable vue/no-mutating-props -->
+<script setup lang="ts">
+import { listMenu } from '@/api/system/menu';
+import { FormInstance } from 'element-plus';
+import { ref, getCurrentInstance, ComponentInternalInstance, watch } from 'vue';
+
+const subColumns = ref<any[]>([]);
+const menuOptions = ref<any[]>([]);
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const genInfoForm = ref<FormInstance>();
+defineExpose({
+    genInfoForm,
+});
+const props = defineProps({
+    info: {
+        type: Object as () => any,
+        default: null,
+    },
+    tables: {
+        type: Array as () => Array<any>,
+        default: null,
+    },
+});
+
+// 表单校验
+const rules = ref({
+    tplCategory: [{ required: true, message: '请选择生成模板', trigger: 'blur' }],
+    packageName: [{ required: true, message: '请输入生成包路径', trigger: 'blur' }],
+    moduleName: [{ required: true, message: '请输入生成模块名', trigger: 'blur' }],
+    businessName: [{ required: true, message: '请输入生成业务名', trigger: 'blur' }],
+    functionName: [{ required: true, message: '请输入生成功能名', trigger: 'blur' }],
+});
+function subSelectChange(value: any) {
+    props.info.subTableFkName = '';
+}
+function tplSelectChange(value: any) {
+    if (value !== 'sub') {
+        props.info.subTableName = '';
+        props.info.subTableFkName = '';
+    }
+}
+function setSubTableColumns(value: any) {
+    for (let item in props.tables) {
+        const name = props.tables[item].tableName;
+        if (value === name) {
+            subColumns.value = props.tables[item].columns;
+            break;
+        }
+    }
+}
+/** 查询菜单下拉树结构 */
+function getMenuTreeselect() {
+    listMenu().then(response => {
+        menuOptions.value = proxy!.handleTree(response.data, 'menuId');
+    });
+}
+
+watch(
+    () => props.info.subTableName,
+    val => {
+        setSubTableColumns(val);
+    }
+);
+
+getMenuTreeselect();
+</script>

+ 133 - 0
frontend/src/views/tool/gen/importTable.vue

@@ -0,0 +1,133 @@
+<template>
+    <!-- 导入表 -->
+    <el-dialog :close-on-click-modal="false" v-model="visible" title="导入表" width="800px" top="5vh" append-to-body>
+        <el-form ref="queryRef" :model="queryParams" :inline="true">
+            <el-form-item label="表名称" prop="tableName">
+                <el-input
+                    v-model="queryParams.tableName"
+                    placeholder="请输入表名称"
+                    clearable
+                    @keyup.enter="handleQuery"
+                />
+            </el-form-item>
+            <el-form-item label="表描述" prop="tableComment">
+                <el-input
+                    v-model="queryParams.tableComment"
+                    placeholder="请输入表描述"
+                    clearable
+                    @keyup.enter="handleQuery"
+                />
+            </el-form-item>
+            <el-form-item>
+                <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+                <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+        </el-form>
+        <el-row>
+            <el-table
+                ref="table"
+                :data="dbTableList"
+                height="260px"
+                @rowClick="clickRow"
+                @selectionChange="handleSelectionChange"
+            >
+                <el-table-column type="selection" width="55"></el-table-column>
+                <el-table-column
+                    prop="tableName"
+                    label="表名称"
+                    :show-overflow-tooltip="true"
+                ></el-table-column>
+                <el-table-column
+                    prop="tableComment"
+                    label="表描述"
+                    :show-overflow-tooltip="true"
+                ></el-table-column>
+                <el-table-column prop="createTime" label="创建时间"></el-table-column>
+                <el-table-column prop="updateTime" label="更新时间"></el-table-column>
+            </el-table>
+            <pagination
+                v-show="total > 0"
+                v-model:page="queryParams.pageNum"
+                v-model:limit="queryParams.pageSize"
+                :total="total"
+                @pagination="getList"
+            />
+        </el-row>
+        <template #footer>
+            <div class="dialog-footer">
+                <el-button type="primary" @click="handleImportTable">确 定</el-button>
+                <el-button @click="visible = false">取 消</el-button>
+            </div>
+        </template>
+    </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { listDbTable, importTable } from '@/api/tool/gen';
+import { ref, getCurrentInstance, ComponentInternalInstance, reactive } from 'vue';
+
+const total = ref(0);
+const visible = ref(false);
+const tables = ref<any[]>([]);
+const dbTableList = ref<any[]>([]);
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const queryParams = reactive({
+    pageNum: 1,
+    pageSize: 10,
+    tableName: undefined,
+    tableComment: undefined,
+});
+
+const emit = defineEmits(['ok']);
+
+/** 查询参数列表 */
+function show() {
+    getList();
+    visible.value = true;
+}
+/** 单击选择行 */
+function clickRow(row: any) {
+    (proxy?.$refs.table as any).toggleRowSelection(row);
+}
+/** 多选框选中数据 */
+function handleSelectionChange(selection: any[]) {
+    tables.value = selection.map(item => item.tableName);
+}
+/** 查询表数据 */
+function getList() {
+    listDbTable(queryParams).then((res: any) => {
+        dbTableList.value = res.rows;
+        total.value = res.total;
+    });
+}
+/** 搜索按钮操作 */
+function handleQuery() {
+    queryParams.pageNum = 1;
+    getList();
+}
+/** 重置按钮操作 */
+function resetQuery() {
+    proxy?.resetForm('queryRef');
+    handleQuery();
+}
+/** 导入按钮操作 */
+function handleImportTable() {
+    const tableNames = tables.value.join(',');
+    if (tableNames === '') {
+        proxy?.$modal.msgError('请选择要导入的表');
+        return;
+    }
+    importTable({ tables: tableNames }).then((res: any) => {
+        proxy?.$modal.msgSuccess(res.msg);
+        if (res.code === 200) {
+            visible.value = false;
+            emit('ok');
+        }
+    });
+}
+
+defineExpose({
+    show,
+});
+</script>

+ 345 - 0
frontend/src/views/tool/gen/index.vue

@@ -0,0 +1,345 @@
+<template>
+    <div class="app-container">
+        <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
+            <el-form-item label="表名称" prop="tableName">
+                <el-input
+                    v-model="queryParams.tableName"
+                    placeholder="请输入表名称"
+                    clearable
+                    style="width: 200px"
+                    @keyup.enter="handleQuery"
+                />
+            </el-form-item>
+            <el-form-item label="表描述" prop="tableComment">
+                <el-input
+                    v-model="queryParams.tableComment"
+                    placeholder="请输入表描述"
+                    clearable
+                    style="width: 200px"
+                    @keyup.enter="handleQuery"
+                />
+            </el-form-item>
+            <el-form-item label="创建时间" style="width: 308px">
+                <el-date-picker
+                    v-model="dateRange"
+                    value-format="YYYY-MM-DD"
+                    type="daterange"
+                    range-separator="-"
+                    start-placeholder="开始日期"
+                    end-placeholder="结束日期"
+                ></el-date-picker>
+            </el-form-item>
+            <el-form-item>
+                <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+                <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+        </el-form>
+
+        <el-row :gutter="10" class="mb8">
+            <el-col :span="1.5">
+                <el-button
+                    type="primary"
+                    plain
+                    icon="Download"
+                    @click="handleGenTable"
+                    v-hasPermi="['tool:gen:code']"
+                    >生成</el-button
+                >
+            </el-col>
+            <el-col :span="1.5">
+                <el-button
+                    type="info"
+                    plain
+                    icon="Upload"
+                    @click="openImportTable"
+                    v-hasPermi="['tool:gen:import']"
+                    >导入</el-button
+                >
+            </el-col>
+            <el-col :span="1.5">
+                <el-button
+                    type="success"
+                    plain
+                    icon="Edit"
+                    :disabled="single"
+                    @click="handleEditTable"
+                    v-hasPermi="['tool:gen:edit']"
+                    >修改</el-button
+                >
+            </el-col>
+            <el-col :span="1.5">
+                <el-button
+                    type="danger"
+                    plain
+                    icon="Delete"
+                    :disabled="multiple"
+                    @click="handleDelete"
+                    v-hasPermi="['tool:gen:remove']"
+                    >删除</el-button
+                >
+            </el-col>
+            <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+        </el-row>
+
+        <el-table v-loading="loading" :data="tableList" @selectionChange="handleSelectionChange">
+            <el-table-column type="selection" align="center" width="55"></el-table-column>
+            <el-table-column label="序号" type="index" width="50" align="center">
+                <template #default="scope">
+                    <span>{{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="表名称" align="center" prop="tableName" :show-overflow-tooltip="true" />
+            <el-table-column
+                label="表描述"
+                align="center"
+                prop="tableComment"
+                :show-overflow-tooltip="true"
+            />
+            <el-table-column label="实体" align="center" prop="className" :show-overflow-tooltip="true" />
+            <el-table-column label="创建时间" align="center" prop="createTime" width="160" />
+            <el-table-column label="更新时间" align="center" prop="updateTime" width="160" />
+            <el-table-column label="操作" align="center" width="330" class-name="small-padding fixed-width">
+                <template #default="scope">
+                    <el-tooltip content="预览" placement="top">
+                        <el-button
+                            link
+                            type="primary"
+                            icon="View"
+                            @click="handlePreview(scope.row)"
+                            v-hasPermi="['tool:gen:preview']"
+                        ></el-button>
+                    </el-tooltip>
+                    <el-tooltip content="编辑" placement="top">
+                        <el-button
+                            link
+                            type="primary"
+                            icon="Edit"
+                            @click="handleEditTable(scope.row)"
+                            v-hasPermi="['tool:gen:edit']"
+                        ></el-button>
+                    </el-tooltip>
+                    <el-tooltip content="删除" placement="top">
+                        <el-button
+                            link
+                            type="primary"
+                            icon="Delete"
+                            @click="handleDelete(scope.row)"
+                            v-hasPermi="['tool:gen:remove']"
+                        ></el-button>
+                    </el-tooltip>
+                    <el-tooltip content="同步" placement="top">
+                        <el-button
+                            link
+                            type="primary"
+                            icon="Refresh"
+                            @click="handleSynchDb(scope.row)"
+                            v-hasPermi="['tool:gen:edit']"
+                        ></el-button>
+                    </el-tooltip>
+                    <el-tooltip content="生成代码" placement="top">
+                        <el-button
+                            link
+                            type="primary"
+                            icon="Download"
+                            @click="handleGenTable(scope.row)"
+                            v-hasPermi="['tool:gen:code']"
+                        ></el-button>
+                    </el-tooltip>
+                </template>
+            </el-table-column>
+        </el-table>
+        <pagination
+            v-show="total > 0"
+            :total="total"
+            v-model:page="queryParams.pageNum"
+            v-model:limit="queryParams.pageSize"
+            @pagination="getList"
+        />
+        <!-- 预览界面 -->
+        <el-dialog
+            :title="preview.title"
+            v-model="preview.open"
+            width="80%"
+            top="5vh"
+            append-to-body
+            custom-class="scrollbar"
+            :close-on-click-modal="false"
+        >
+            <el-tabs v-model="preview.activeName">
+                <el-tab-pane
+                    v-for="(value, key) in preview.data"
+                    :label="key.substring(key.lastIndexOf('/') + 1, key.indexOf('.vm'))"
+                    :name="key.substring(key.lastIndexOf('/') + 1, key.indexOf('.vm'))"
+                    :key="key"
+                >
+                    <el-link
+                        :underline="false"
+                        icon="DocumentCopy"
+                        v-copyText="value"
+                        v-copyText:callback="copyTextSuccess"
+                        style="float: right"
+                        >&nbsp;复制</el-link
+                    >
+                    <pre>{{ value }}</pre>
+                </el-tab-pane>
+            </el-tabs>
+        </el-dialog>
+        <import-table ref="importRef" @ok="handleQuery" />
+    </div>
+</template>
+
+<script setup name="Gen" lang="ts">
+import { listTable, previewTable, delTable, genCode, synchDb } from '@/api/tool/gen';
+import router from '@/router';
+import { oneOf } from '@zeronejs/utils';
+import { getCurrentInstance, ComponentInternalInstance, ref, reactive, toRefs, onActivated } from 'vue';
+import { useRoute } from 'vue-router';
+import importTable from './importTable.vue';
+
+const route = useRoute();
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const tableList = ref<any[]>([]);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<number[]>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const tableNames = ref<any[]>([]);
+const dateRange = ref<any>([]);
+const uniqueId = ref('');
+
+const data = reactive<{
+    queryParams: any;
+    preview: {
+        open: boolean;
+        title: string;
+        data: Record<string, any>;
+        activeName: string;
+    };
+}>({
+    queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        tableName: undefined,
+        tableComment: undefined,
+    },
+    preview: {
+        open: false,
+        title: '代码预览',
+        data: {},
+        activeName: 'domain.java',
+    },
+});
+
+const { queryParams, preview } = toRefs(data);
+
+onActivated(() => {
+    const time = oneOf(route.query.t);
+    if (time && time !== uniqueId.value) {
+        uniqueId.value = time;
+        queryParams.value.pageNum = Number(route.query.pageNum);
+        dateRange.value = [];
+        proxy?.resetForm('queryForm');
+        getList();
+    }
+});
+
+/** 查询表集合 */
+function getList() {
+    loading.value = true;
+    listTable(proxy?.addDateRange(queryParams.value, dateRange.value)).then((response: any) => {
+        tableList.value = response.rows;
+        total.value = response.total;
+        loading.value = false;
+    });
+}
+/** 搜索按钮操作 */
+function handleQuery() {
+    queryParams.value.pageNum = 1;
+    getList();
+}
+/** 生成代码操作 */
+function handleGenTable(row: any) {
+    const tbNames = row.tableName || tableNames.value;
+    if (tbNames === '') {
+        proxy?.$modal.msgError('请选择要生成的数据');
+        return;
+    }
+    if (row.genType === '1') {
+        genCode(row.tableName).then(response => {
+            proxy?.$modal.msgSuccess('成功生成到自定义路径:' + row.genPath);
+        });
+    } else {
+        proxy?.$download.zip('/tool/gen/batchGenCode?tables=' + tbNames, 'ruoyi.zip');
+    }
+}
+/** 同步数据库操作 */
+function handleSynchDb(row: any) {
+    const tableName = row.tableName;
+    proxy?.$modal
+        .confirm('确认要强制同步"' + tableName + '"表结构吗?')
+        .then(function () {
+            return synchDb(tableName);
+        })
+        .then(() => {
+            proxy!.$modal.msgSuccess('同步成功');
+        })
+        .catch((e: any) => {
+            console.log(e);
+        });
+}
+/** 打开导入表弹窗 */
+function openImportTable() {
+    (proxy?.$refs['importRef'] as any).show();
+}
+/** 重置按钮操作 */
+function resetQuery() {
+    dateRange.value = [];
+    proxy?.resetForm('queryRef');
+    handleQuery();
+}
+/** 预览按钮 */
+function handlePreview(row: any) {
+    previewTable(row.tableId).then(response => {
+        preview.value.data = response.data;
+        preview.value.open = true;
+        preview.value.activeName = 'domain.java';
+    });
+}
+/** 复制代码成功 */
+function copyTextSuccess() {
+    proxy?.$modal.msgSuccess('复制成功');
+}
+// 多选框选中数据
+function handleSelectionChange(selection: any[]) {
+    ids.value = selection.map(item => item.tableId);
+    tableNames.value = selection.map(item => item.tableName);
+    single.value = selection.length !== 1;
+    multiple.value = !selection.length;
+}
+/** 修改按钮操作 */
+function handleEditTable(row: any) {
+    const tableId = row.tableId || ids.value[0];
+    router.push({ path: '/tool/gen-edit/index/' + tableId, query: { pageNum: queryParams.value.pageNum } });
+}
+/** 删除按钮操作 */
+function handleDelete(row: any) {
+    const tableIds = row.tableId || ids.value;
+    proxy?.$modal
+        .confirm('是否确认删除表编号为"' + tableIds + '"的数据项?')
+        .then(function () {
+            return delTable(tableIds);
+        })
+        .then(() => {
+            getList();
+            proxy!.$modal.msgSuccess('删除成功');
+        })
+        .catch((e: any) => {
+            console.log(e);
+        });
+}
+
+getList();
+</script>

+ 12 - 0
frontend/src/views/tool/swagger/index.vue

@@ -0,0 +1,12 @@
+<template>
+    <div>
+        <i-frame v-model:src="url"></i-frame>
+    </div>
+</template>
+
+<script setup lang="ts">
+import iFrame from '@/components/iFrame/index.vue';
+import { ref } from 'vue';
+
+const url = ref(import.meta.env.VITE_APP_BASE_API + '/swagger-ui/index.html');
+</script>