mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-22 05:02:57 +00:00
feat: 添加任务管理和字典管理功能,优化系统配置和代码结构
本次提交主要包含以下变更: 1. 新增任务管理模块,支持定时任务的创建、更新、删除和状态管理 2. 新增字典管理模块,用于维护系统中常用的固定数据 3. 优化系统配置,移除Celery,改用APScheduler进行任务调度 4. 修复部分前端组件的样式问题,统一输入框宽度 5. 更新文档和相关图片资源,完善系统功能描述 这些变更旨在增强系统的可维护性和功能性,提供更灵活的任务调度机制和更便捷的字典数据管理方式。
This commit is contained in:
@@ -1,38 +0,0 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
export function getTaskList() {
|
||||
return request({
|
||||
url: "/api/v1/monitor/task/list",
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
export function getTaskDetail(taskId) {
|
||||
return request({
|
||||
url: `/api/v1/monitor/task/detail/${taskId}`,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function createTask(body) {
|
||||
return request({
|
||||
url: "/api/v1/monitor/task/create",
|
||||
method: "post",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function cancelTask(taskId) {
|
||||
return request({
|
||||
url: `/api/v1/monitor/task/cancel/${taskId}`,
|
||||
method: "put",
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteTask(taskId) {
|
||||
return request({
|
||||
url: `/api/v1/monitor/task/delete/${taskId}`,
|
||||
method: "delete",
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
export function getDictTypeList(query) {
|
||||
return request({
|
||||
url: "/api/v1/system/dict/type/list",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function getDictTypeOptionselect() {
|
||||
return request({
|
||||
url: "/api/v1/system/dict/type/optionselect",
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
export function getDictTypeDetail(query) {
|
||||
return request({
|
||||
url: "/api/v1/system/dict/type/detail",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function createDictType(body) {
|
||||
return request({
|
||||
url: "/api/v1/system/dict/type/create",
|
||||
method: "post",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function updateDictType(body) {
|
||||
return request({
|
||||
url: "/api/v1/system/dict/type/update",
|
||||
method: "put",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteDictType(query) {
|
||||
return request({
|
||||
url: "/api/v1/system/dict/type/delete",
|
||||
method: "delete",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function exportDictType(body) {
|
||||
return request({
|
||||
url: "/api/v1/system/dict/type/export",
|
||||
method: "post",
|
||||
data: body,
|
||||
responseType: "blob",
|
||||
});
|
||||
}
|
||||
|
||||
export function getDictDataList(query) {
|
||||
return request({
|
||||
url: "/api/v1/system/dict/data/list",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function getDictDataDetail(query) {
|
||||
return request({
|
||||
url: "/api/v1/system/dict/data/detail",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function createDictData(body) {
|
||||
return request({
|
||||
url: "/api/v1/system/dict/data/create",
|
||||
method: "post",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function updateDictData(body) {
|
||||
return request({
|
||||
url: "/api/v1/system/dict/data/update",
|
||||
method: "put",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteDictData(query) {
|
||||
return request({
|
||||
url: "/api/v1/system/dict/data/delete",
|
||||
method: "delete",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function exportDictData(body) {
|
||||
return request({
|
||||
url: "/api/v1/system/dict/data/export",
|
||||
method: "post",
|
||||
data: body,
|
||||
responseType: "blob",
|
||||
});
|
||||
}
|
||||
|
||||
export function getDictTypeOption(query) {
|
||||
return request({
|
||||
url: "/api/v1/system/dict/type/data",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function getDictDataByType(query) {
|
||||
return request({
|
||||
url: "/api/v1/system/dict/data/type",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
export function getJobList(query) {
|
||||
return request({
|
||||
url: "/api/v1/system/job/list",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function getJobDetail(query) {
|
||||
return request({
|
||||
url: "/api/v1/system/job/detail",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function createJob(body) {
|
||||
return request({
|
||||
url: "/api/v1/system/job/create",
|
||||
method: "post",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function updateJob(body) {
|
||||
return request({
|
||||
url: "/api/v1/system/job/update",
|
||||
method: "put",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteJob(query) {
|
||||
return request({
|
||||
url: "/api/v1/system/job/delete",
|
||||
method: "delete",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function exportJob(body) {
|
||||
return request({
|
||||
url: "/api/v1/system/job/export",
|
||||
method: "post",
|
||||
data: body,
|
||||
responseType: "blob",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function clearJob() {
|
||||
return request({
|
||||
url: "/api/v1/system/job/clear",
|
||||
method: "delete",
|
||||
});
|
||||
}
|
||||
|
||||
export function OptionJob(params) {
|
||||
return request({
|
||||
url: "/api/v1/system/job/option",
|
||||
method: "put",
|
||||
params: params
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,251 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 页面头部 -->
|
||||
<page-header />
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<div class="table-wrapper">
|
||||
<a-card title="任务管理列表"
|
||||
:bordered="false"
|
||||
:headStyle="{ borderBottom: 'none', padding: '20px 24px' }"
|
||||
:bodyStyle="{ padding: '0 24px', minHeight: 'calc(100vh - 250px)' }">
|
||||
<template #extra>
|
||||
<a-button type="primary" :icon="h(PlusOutlined)" @click="handleOpenModal('create')" style="margin-right: 10px;">
|
||||
新建
|
||||
</a-button>
|
||||
</template>
|
||||
<a-table :rowKey="record => record.task_id"
|
||||
:columns="columns"
|
||||
:data-source="dataSource"
|
||||
:loading="tableLoading"
|
||||
:scroll="{ x: 400 }"
|
||||
:style="{ minHeight: '420px' }">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'operation'">
|
||||
<a-space size="middle">
|
||||
<a @click="viewTaskDetail(record)">查看</a>
|
||||
<a-popconfirm title="确定取消吗?" ok-text="确定" cancel-text="取消" @confirm="cancelTaskID(record.task_id)">
|
||||
<a style="color: blue;">取消</a>
|
||||
</a-popconfirm>
|
||||
<a-popconfirm title="确定删除吗?" ok-text="确定" cancel-text="取消" @confirm="deleteTaskID(record.task_id)">
|
||||
<a style="color: red;">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<!-- 弹窗区域 -->
|
||||
<a-modal v-model:open="openModal" @ok="handleModalSubmit" :width="800" :destroyOnClose="true"
|
||||
:confirmLoading="modalSubmitLoading" style="top: 30px">
|
||||
<template #title>
|
||||
<span>{{ modalTitle === 'create' ? '新建任务' : '查看任务' }}</span>
|
||||
</template>
|
||||
<div v-if="modalTitle === 'view'">
|
||||
<a-spin :spinning="detailLoading">
|
||||
<a-descriptions :column="{ xxl: 2, xl: 2, lg: 2, md: 2, sm: 1, xs: 1 }"
|
||||
:labelStyle="{ width: '140px' }" bordered>
|
||||
<a-descriptions-item label="任务编号">{{ taskDetail.task_id || '无' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="任务状态">{{ taskDetail.status || '无' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="任务结果">
|
||||
{{ typeof taskDetail.result === 'object' ? JSON.stringify(taskDetail.result, null, 2) : taskDetail.result || '无' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="任务完成日期">
|
||||
{{ taskDetail.date_done ? dayjs(taskDetail.date_done).format('YYYY-MM-DD HH:mm:ss') : '无' }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-spin>
|
||||
</div>
|
||||
<div v-else-if="modalTitle === 'create'">
|
||||
<a-form ref="createForm" :model="createFormState"
|
||||
:labelCol="{ span: 5 }" :wrapperCol="{ span: 15 }">
|
||||
<a-form-item name="res" label="任务参数" :rules="[{ required: true, message: '请输入任务参数' }]">
|
||||
<a-input v-model:value="createFormState.res" placeholder="请输入任务参数" allowClear />
|
||||
</a-form-item>
|
||||
<a-form-item name="is_cron" label="是否开启定时任务">
|
||||
<a-switch v-model:checked="createFormState.is_cron" />
|
||||
</a-form-item>
|
||||
<a-form-item v-if="createFormState.is_cron" name="cron_expression" label="定时策略" :rules="[{ required: true, message: '请输入定时策略' }]">
|
||||
<a-input v-model:value="createFormState.cron_expression" placeholder="请输入 Cron 表达式" allowClear />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, onMounted, h } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import type { TableColumnsType } from 'ant-design-vue';
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import { cloneDeep, isEmpty } from '@/utils/util';
|
||||
import PageHeader from '@/components/PageHeader.vue';
|
||||
import { getTaskList, getTaskDetail, createTask, cancelTask, deleteTask } from '@/api/monitor/task';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
// 类型定义
|
||||
interface Task {
|
||||
task_id?: string;
|
||||
result?: string;
|
||||
status?: string;
|
||||
date_done?: string;
|
||||
res?: string;
|
||||
is_cron?: boolean;
|
||||
cron_expression?: string;
|
||||
}
|
||||
|
||||
// 状态管理
|
||||
const tableLoading = ref(false);
|
||||
const openModal = ref(false);
|
||||
const modalTitle = ref<'create' | 'view'>('create');
|
||||
const modalSubmitLoading = ref(false);
|
||||
const detailLoading = ref(false);
|
||||
const dataSource = ref<Task[]>([]);
|
||||
const taskDetail = ref<Task>({});
|
||||
const createFormState = reactive<Task>({
|
||||
res: '',
|
||||
is_cron: false,
|
||||
cron_expression: '',
|
||||
});
|
||||
|
||||
const createForm = ref(); // 表单引用
|
||||
|
||||
// 表格列配置
|
||||
const columns: TableColumnsType = [
|
||||
{
|
||||
title: '任务编号',
|
||||
dataIndex: 'task_id',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '任务状态',
|
||||
dataIndex: ['result', 'status'],
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '任务结果',
|
||||
dataIndex: ['result', 'result'],
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => {
|
||||
if (text && text.exc_type) {
|
||||
return `异常: ${text.exc_type} - ${text.exc_message.join(', ')}`;
|
||||
}
|
||||
return text || '无';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '完成时间',
|
||||
dataIndex: ['result', 'date_done'],
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => {
|
||||
return text ? dayjs(text).format('YYYY-MM-DD HH:mm:ss') : '无';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
ellipsis: true,
|
||||
width: 180,
|
||||
},
|
||||
];
|
||||
|
||||
// 加载表格数据
|
||||
const loadTableData = async () => {
|
||||
tableLoading.value = true;
|
||||
try {
|
||||
const response = await getTaskList();
|
||||
dataSource.value = response.data.data; // 移除 index 字段
|
||||
} catch (error) {
|
||||
message.error('加载任务列表失败');
|
||||
} finally {
|
||||
tableLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 查看任务详情
|
||||
const viewTaskDetail = async (record: Task) => {
|
||||
detailLoading.value = true;
|
||||
try {
|
||||
const response = await getTaskDetail(record.task_id!);
|
||||
taskDetail.value = response.data.data;
|
||||
modalTitle.value = 'view';
|
||||
openModal.value = true;
|
||||
} catch (error) {
|
||||
message.error('获取任务详情失败');
|
||||
} finally {
|
||||
detailLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 取消任务
|
||||
const cancelTaskID = async (taskId: string) => {
|
||||
try {
|
||||
const response = await cancelTask(taskId);
|
||||
message.success(response.data.msg);
|
||||
loadTableData();
|
||||
} catch (error) {
|
||||
message.error('取消任务失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 删除任务
|
||||
const deleteTaskID = async (taskId: string) => {
|
||||
try {
|
||||
const response = await deleteTask(taskId);
|
||||
message.success(response.data.msg);
|
||||
loadTableData();
|
||||
} catch (error) {
|
||||
message.error('删除任务失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 提交新建任务
|
||||
const handleModalSubmit = async () => {
|
||||
try {
|
||||
await createForm.value.validate(); // 确保表单验证通过
|
||||
const createBody = cloneDeep(createFormState);
|
||||
|
||||
// 如果未开启定时任务,移除 cron_expression 字段
|
||||
if (!createBody.is_cron) {
|
||||
delete createBody.cron_expression;
|
||||
}
|
||||
|
||||
// 提交任务
|
||||
const response = await createTask(createBody);
|
||||
message.success(response.data.msg);
|
||||
openModal.value = false;
|
||||
createForm.value.resetFields(); // 重置表单
|
||||
loadTableData();
|
||||
} catch (error) {
|
||||
if (error.errorFields) {
|
||||
message.error('请填写完整任务参数');
|
||||
} else {
|
||||
message.error('创建任务失败');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 打开弹窗
|
||||
const handleOpenModal = (type: 'create' | 'view') => {
|
||||
modalTitle.value = type;
|
||||
openModal.value = true;
|
||||
if (type === 'create') {
|
||||
createForm.value?.resetFields(); // 重置表单
|
||||
}
|
||||
};
|
||||
|
||||
// 生命周期钩子
|
||||
onMounted(() => loadTableData());
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.table-wrapper {
|
||||
margin-block-end: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -144,7 +144,7 @@
|
||||
label="排序"
|
||||
:rules="[{ required: true, message: '请输入排序' }]"
|
||||
>
|
||||
<a-input-number v-model:value="createState.order" :min="1" />
|
||||
<a-input-number v-model:value="createState.order" :min="1" style="width: 100%"/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item name="parent_id" label="上级部门">
|
||||
@@ -204,7 +204,7 @@
|
||||
label="排序"
|
||||
:rules="[{ required: true, message: '请输入排序' }]"
|
||||
>
|
||||
<a-input-number v-model:value="updateState.order" :min="1" />
|
||||
<a-input-number v-model:value="updateState.order" :min="1" style="width: 100%"/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item name="parent_id" label="上级部门">
|
||||
|
||||
@@ -0,0 +1,631 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 页面头部 -->
|
||||
<page-header />
|
||||
|
||||
<!-- 搜索表单 -->
|
||||
<div class="table-search-wrapper">
|
||||
<a-card :bordered="false">
|
||||
<a-form :model="queryState" @finish="onFinish">
|
||||
<a-row>
|
||||
<a-col flex="0 1 450px">
|
||||
<a-form-item name="dict_type" label="字典" style="max-width: 300px;">
|
||||
<a-select v-model:value="queryState.dict_type" placeholder="请选择字典名称" allowClear @dropdownVisibleChange="loadDictTypes">
|
||||
<a-select-option v-for="item in dictTypeOptions" :key="item.dict_type" :value="item.dict_type">
|
||||
{{ item.dict_name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col flex="0 1 450px">
|
||||
<a-form-item name="dict_name" label="标签" style="max-width: 300px;">
|
||||
<a-input v-model:value="queryState.dict_label" placeholder="请输入字典数据标签"
|
||||
allowClear></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col flex="0 1 450px">
|
||||
<a-form-item name="available" label="状态" style="max-width: 300px;">
|
||||
<a-select v-model:value="queryState.available" placeholder="全部" allowClear>
|
||||
<a-select-option value="1">启用</a-select-option>
|
||||
<a-select-option value="0">停用</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col>
|
||||
<a-button type="primary" html-type="submit" :loading="tableLoading">查询</a-button>
|
||||
<a-button style="margin: 0 8px" @click="resetFields">重置</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<div class="table-wrapper">
|
||||
<a-card title="数据字典列表"
|
||||
:bordered="false"
|
||||
:headStyle="{ borderBottom: 'none', padding: '20px 24px' }"
|
||||
:bodyStyle="{ padding: '0 24px', minHeight: 'calc(100vh - 400px)' }">
|
||||
<template #extra>
|
||||
<a-button type="primary" :icon="h(PlusOutlined)" @click="modalHandle('create')"
|
||||
style="margin-right: 10px;">新建</a-button>
|
||||
<a-button type="primary" :icon="h(DownOutlined)" @click="handleExport"
|
||||
style="margin-right: 10px;">导出
|
||||
</a-button>
|
||||
</template>
|
||||
<a-table :rowKey="record => record.id"
|
||||
:columns="columns"
|
||||
:data-source="dataSource"
|
||||
:row-selection="rowSelection"
|
||||
:loading="tableLoading"
|
||||
@change="handleTableChange"
|
||||
:scroll="{ x: 400 }"
|
||||
:pagination="pagination"
|
||||
:style="{ minHeight: '420px' }"
|
||||
>
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.dataIndex === 'index'">
|
||||
<span>{{ (pagination.current - 1) * pagination.pageSize + index + 1 }}</span>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'is_default'">
|
||||
<span>
|
||||
<a-badge :status="record.is_default ? 'processing': 'error'" :text="record.is_default ? '是' : '否'" />
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'available'">
|
||||
<span>
|
||||
<a-badge :status="record.available ? 'processing': 'error'" :text="record.available ? '启用' : '停用'" />
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'operation'">
|
||||
<a-space size="middle">
|
||||
<a @click="modalHandle('view', index)">查看</a>
|
||||
<a @click="modalHandle('update', index)">修改</a>
|
||||
<a-popconfirm title="确定删除吗?" ok-text="确定" cancel-text="取消" @confirm="deleteRow(record)">
|
||||
<a style="color: red;">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<!-- 弹窗区域 -->
|
||||
<div class="modal-wrapper">
|
||||
<a-modal v-model:open="openModal" @ok="handleModalSumbit" :width="800" :destroyOnClose="true"
|
||||
:confirmLoading="modalSubmitLoading" style="top: 30px">
|
||||
<template #title>
|
||||
<span>{{ modalTitle === 'create' ? '新建数据字典' : (modalTitle === 'view' ? '查看数据字典' : '修改数据字典')
|
||||
}}</span>
|
||||
</template>
|
||||
<div v-if="modalTitle === 'view'">
|
||||
<a-spin :spinning="detailStateLoading">
|
||||
<a-descriptions :column="{ xxl: 2, xl: 2, lg: 2, md: 2, sm: 1, xs: 1 }"
|
||||
:labelStyle="{ width: '140px' }" bordered>
|
||||
<a-descriptions-item label="序号">{{ (pagination.current - 1) * pagination.pageSize +
|
||||
detailState.index + 1 }}</a-descriptions-item>
|
||||
<a-descriptions-item label="字典排序">{{ detailState.dict_sort }}</a-descriptions-item>
|
||||
<a-descriptions-item label="字典标签">{{ detailState.dict_label }}</a-descriptions-item>
|
||||
<a-descriptions-item label="字典键值">{{ detailState.dict_value }}</a-descriptions-item>
|
||||
<a-descriptions-item label="字典类型">{{ detailState.dict_type }}</a-descriptions-item>
|
||||
<a-descriptions-item label="样式属性">{{ detailState.css_class }}</a-descriptions-item>
|
||||
<a-descriptions-item label="表格回显样式">{{ detailState.list_class }}</a-descriptions-item>
|
||||
<a-descriptions-item label="是否默认">
|
||||
<a-badge :status="detailState.is_default ? 'processing': 'error'" :text="detailState.is_default ? '是' : '否'" />
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="状态">
|
||||
<a-badge :status="detailState.available ? 'processing': 'error'" :text="detailState.available ? '启用' : '停用'" />
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="创建人">{{ detailState.creator ? detailState.creator.name : '-'
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">{{ detailState.created_at }}</a-descriptions-item>
|
||||
<a-descriptions-item label="修改时间">{{ detailState.updated_at }}</a-descriptions-item>
|
||||
<a-descriptions-item label="备注" :span="2">{{ detailState.description
|
||||
}}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-spin>
|
||||
</div>
|
||||
<div v-else-if="modalTitle === 'create'">
|
||||
<a-form ref="createForm" :model="createState"
|
||||
v-bind="{ labelCol: { span: 5 }, wrapperCol: { span: 15 } }">
|
||||
<a-form-item name="dict_type" label="字典类型">
|
||||
<a-input v-model:value="createState.dict_type" placeholder="请输入字典类型" :disabled="true"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="dict_sort" label="字典排序" :rules="[{ required: true, message: '请输入字典排序', pattern: /^[0-9]*$/ }]">
|
||||
<a-input-number v-model:value="createState.dict_sort" placeholder="请输入字典排序" :min="1" :precision="0" style="width: 100%"/>
|
||||
</a-form-item>
|
||||
<a-form-item name="dict_label" label="字典标签" :rules="[{ required: true, message: '请输入字典标签' }]">
|
||||
<a-input v-model:value="createState.dict_label" placeholder="请输入字典标签" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="dict_value" label="字典键值" :rules="[{ required: true, message: '请输入字典键值' }]">
|
||||
<a-input v-model:value="createState.dict_value" placeholder="请输入字典键值" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="css_class" label="样式属性" :rules="[{ required: false, message: '请输入样式属性' }]">
|
||||
<a-input v-model:value="createState.css_class" placeholder="请输入样式属性" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="list_class" label="表格回显样式" :rules="[{ required: false, message: '请选择表格回显样式' }]">
|
||||
<a-select v-model:value="createState.list_class" placeholder="请选择表格回显样式" allowClear>
|
||||
<a-select-option v-for="item in listClassOptions" :key="item.value" :value="item.value">
|
||||
{{ item.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item name="is_default" label="是否默认" :rules="[{ required: true, message: '请选择是否默认' }]">
|
||||
<a-radio-group v-model:value="createState.is_default">
|
||||
<a-radio :value="true">是</a-radio>
|
||||
<a-radio :value="false">否</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item name="available" label="状态" :rules="[{ required: true, message: '请选择状态' }]">
|
||||
<a-radio-group v-model:value="createState.available">
|
||||
<a-radio :value="true">启用</a-radio>
|
||||
<a-radio :value="false">停用</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item name="description" label="备注">
|
||||
<a-textarea v-model:value="createState.description" placeholder="请输入备注" :rows="4"
|
||||
allowClear />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
<div v-else>
|
||||
<a-form ref="updateForm" :model="updateState"
|
||||
v-bind="{ labelCol: { span: 5 }, wrapperCol: { span: 15 } }">
|
||||
<a-form-item name="dict_type" label="字典类型">
|
||||
<a-input v-model:value="updateState.dict_type" placeholder="请输入字典类型" :disabled="true"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="dict_sort" label="字典排序" :rules="[{ required: true, message: '请输入字典排序', pattern: /^[0-9]*$/ }]">
|
||||
<a-input-number v-model:value="updateState.dict_sort" placeholder="请输入字典排序" :min="1" :precision="0" style="width: 100%"/>
|
||||
</a-form-item>
|
||||
<a-form-item name="dict_label" label="字典标签" :rules="[{ required: true, message: '请输入字典标签' }]">
|
||||
<a-input v-model:value="updateState.dict_label" placeholder="请输入字典标签" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="dict_value" label="字典键值" :rules="[{ required: true, message: '请输入字典键值' }]">
|
||||
<a-input v-model:value="updateState.dict_value" placeholder="请输入字典键值" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="css_class" label="样式属性" :rules="[{ required: false, message: '请输入样式属性' }]">
|
||||
<a-input v-model:value="updateState.css_class" placeholder="请输入样式属性" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="list_class" label="表格回显样式" :rules="[{ required: false, message: '请选择表格回显样式' }]">
|
||||
<a-select v-model:value="updateState.list_class" placeholder="请选择表格回显样式" allowClear>
|
||||
<a-select-option v-for="item in listClassOptions" :key="item.value" :value="item.value">
|
||||
{{ item.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item name="is_default" label="是否默认" :rules="[{ required: true, message: '请选择是否默认' }]">
|
||||
<a-radio-group v-model:value="updateState.is_default">
|
||||
<a-radio :value="true">是</a-radio>
|
||||
<a-radio :value="false">否</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item name="available" label="状态" :rules="[{ required: true, message: '请选择状态' }]">
|
||||
<a-radio-group v-model:value="updateState.available">
|
||||
<a-radio :value="true">启用</a-radio>
|
||||
<a-radio :value="false">停用</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item name="description" label="备注">
|
||||
<a-textarea v-model:value="updateState.description" placeholder="请输入备注" :rows="4"
|
||||
allowClear />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed, unref, onMounted, h } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { Table, message, Modal } from 'ant-design-vue';
|
||||
import type { TableColumnsType, MenuProps } from 'ant-design-vue';
|
||||
import { PlusOutlined, DownOutlined, CheckOutlined, StopOutlined } from '@ant-design/icons-vue';
|
||||
import { cloneDeep, isEmpty } from '@/utils/util';
|
||||
import PageHeader from '@/components/PageHeader.vue';
|
||||
import { getDictTypeOptionselect, getDictTypeList,getDictTypeDetail,createDictType,updateDictType,deleteDictType,exportDictType,getDictDataList,getDictDataDetail,createDictData,updateDictData,deleteDictData,exportDictData,getDictTypeOption,getDictDataByType } from '@/api/system/dict'
|
||||
import type { searchDictDataType, tableDictDataType } from './types'
|
||||
|
||||
const route = useRoute();
|
||||
const createForm = ref();
|
||||
const updateForm = ref();
|
||||
const tableLoading = ref(false);
|
||||
const openModal = ref(false);
|
||||
const modalTitle = ref('');
|
||||
const modalSubmitLoading = ref(false);
|
||||
const detailStateLoading = ref(false);
|
||||
const dataSource = ref<tableDictDataType[]>([]);
|
||||
const selectedRowKeys = ref<tableDictDataType['id'][]>([]);
|
||||
|
||||
// 数据标签回显样式
|
||||
const listClassOptions = ref([
|
||||
{ value: "default", label: "默认(default)" },
|
||||
{ value: "primary", label: "主要(primary)" },
|
||||
{ value: "success", label: "成功(success)" },
|
||||
{ value: "info", label: "信息(info)" },
|
||||
{ value: "warning", label: "警告(warning)" },
|
||||
{ value: "danger", label: "危险(danger)" }
|
||||
]);
|
||||
const queryState = reactive<searchDictDataType>({
|
||||
dict_label: null,
|
||||
dict_type: null,
|
||||
available: null
|
||||
});
|
||||
|
||||
const dictTypeOptions = ref<any[]>([]);
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
defaultPageSize: 10,
|
||||
showSizeChanger: true,
|
||||
total: dataSource.value.length,
|
||||
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条 / 总共 ${total} 条`
|
||||
})
|
||||
const createState = reactive<tableDictDataType>({
|
||||
dict_sort: null,
|
||||
dict_label: '',
|
||||
dict_value: '',
|
||||
dict_type: '',
|
||||
css_class: '',
|
||||
list_class: null,
|
||||
is_default: false,
|
||||
available: true,
|
||||
description: ''
|
||||
})
|
||||
const updateState = reactive<tableDictDataType>({
|
||||
id: undefined,
|
||||
dict_sort: null,
|
||||
dict_label: '',
|
||||
dict_value: '',
|
||||
dict_type: '',
|
||||
css_class: '',
|
||||
list_class: null,
|
||||
is_default: false,
|
||||
available: true,
|
||||
description: ''
|
||||
})
|
||||
const detailState = ref<tableDictDataType>({})
|
||||
|
||||
const columns: TableColumnsType = [
|
||||
{
|
||||
title: '序号',
|
||||
dataIndex: 'index',
|
||||
align: 'center',
|
||||
ellipsis: true,
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '字典排序',
|
||||
dataIndex: 'dict_sort',
|
||||
ellipsis: true,
|
||||
// align: 'center'
|
||||
},
|
||||
{
|
||||
title: '字典标签',
|
||||
dataIndex: 'dict_label',
|
||||
ellipsis: true,
|
||||
// align: 'center'
|
||||
},
|
||||
{
|
||||
title: '字典键值',
|
||||
dataIndex: 'dict_value',
|
||||
ellipsis: true,
|
||||
// align: 'center'
|
||||
},
|
||||
{
|
||||
title: '字典类型',
|
||||
dataIndex: 'dict_type',
|
||||
ellipsis: true,
|
||||
// align: 'center'
|
||||
},
|
||||
// {
|
||||
// title: '样式属性',
|
||||
// dataIndex: 'css_class',
|
||||
// ellipsis: true,
|
||||
// // align: 'center'
|
||||
// },
|
||||
// {
|
||||
// title: '表格回显样式',
|
||||
// dataIndex: 'list_class',
|
||||
// ellipsis: true,
|
||||
// // align: 'center'
|
||||
// },
|
||||
{
|
||||
title: '是否默认',
|
||||
dataIndex: 'is_default',
|
||||
ellipsis: true,
|
||||
// align: 'center'
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'available',
|
||||
// ellipsis: true,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'description',
|
||||
// align: 'center',
|
||||
ellipsis: true,
|
||||
// width: 500
|
||||
},
|
||||
{
|
||||
title: '创建日期',
|
||||
dataIndex: 'created_at',
|
||||
// align: 'center',
|
||||
ellipsis: true,
|
||||
// width: 120
|
||||
},
|
||||
{
|
||||
title: '更新日期',
|
||||
dataIndex: 'updated_at',
|
||||
// align: 'center',
|
||||
ellipsis: true,
|
||||
// width: 120
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
ellipsis: true,
|
||||
width: 150
|
||||
}
|
||||
];
|
||||
|
||||
// 表格选中配置
|
||||
const rowSelection = computed(() => {
|
||||
return {
|
||||
selectedRowKeys: unref(selectedRowKeys),
|
||||
onChange: (selectingRowKeys: tableDictDataType['id'][]) => {
|
||||
selectedRowKeys.value = selectingRowKeys;
|
||||
},
|
||||
hideDefaultSelections: true,
|
||||
selections: [
|
||||
Table.SELECTION_ALL,
|
||||
Table.SELECTION_INVERT,
|
||||
Table.SELECTION_NONE
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
// 加载字典类型
|
||||
const loadDictTypes = () => {
|
||||
getDictTypeOptionselect().then(response => {
|
||||
const result = response.data;
|
||||
dictTypeOptions.value = result.data;
|
||||
message.success(result.msg);
|
||||
}).catch(error => {
|
||||
console.log(error);
|
||||
message.error('获取字典类型失败');
|
||||
});
|
||||
}
|
||||
|
||||
// 加载表格数据
|
||||
const loadingData = () => {
|
||||
tableLoading.value = true;
|
||||
|
||||
let params = {};
|
||||
if (queryState.dict_label) {
|
||||
params['dict_label'] = queryState.dict_label
|
||||
}
|
||||
if (queryState.dict_type) {
|
||||
params['dict_type'] = queryState.dict_type
|
||||
} else if (route.query.dict_type) {
|
||||
params['dict_type'] = Array.isArray(route.query.dict_type) ? route.query.dict_type[0] : route.query.dict_type
|
||||
queryState.dict_type = Array.isArray(route.query.dict_type) ? route.query.dict_type[0] : route.query.dict_type
|
||||
}
|
||||
if (queryState.available) {
|
||||
params['available'] = queryState.available == true ? true : false;
|
||||
}
|
||||
params['page_no'] = pagination.current
|
||||
params['page_size'] = pagination.pageSize
|
||||
|
||||
getDictDataList(params).then(response => {
|
||||
const result = response.data;
|
||||
dataSource.value = result.data.items;
|
||||
pagination.total = result.data.total;
|
||||
pagination.current = result.data.page_no;
|
||||
pagination.pageSize = result.data.page_size;
|
||||
}).catch(error => {
|
||||
console.log(error);
|
||||
}).finally(() => {
|
||||
tableLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 生命周期钩子
|
||||
onMounted(() => {
|
||||
loadingData();
|
||||
});
|
||||
|
||||
// 查询
|
||||
const onFinish = () => {
|
||||
pagination.current = 1;
|
||||
loadingData();
|
||||
};
|
||||
|
||||
// 重置查询
|
||||
const resetFields = () => {
|
||||
Object.keys(queryState).forEach((key: string) => {
|
||||
delete queryState[key];
|
||||
});
|
||||
pagination.current = 1;
|
||||
queryState.available = null
|
||||
loadingData();
|
||||
}
|
||||
|
||||
// 表格分页处理
|
||||
const handleTableChange = (values: any) => {
|
||||
pagination.current = values.current;
|
||||
pagination.pageSize = values.pageSize;
|
||||
loadingData();
|
||||
}
|
||||
|
||||
// 弹窗关键字处理
|
||||
const modalHandle = (modalType: string, index?: number) => {
|
||||
modalTitle.value = modalType;
|
||||
openModal.value = true;
|
||||
|
||||
// 重新创建 createState 和 updateState 以重置为初始状态
|
||||
Object.assign(createState, {
|
||||
dict_sort: null,
|
||||
dict_label: '',
|
||||
dict_value: '',
|
||||
dict_type: route.query.dict_type || '',
|
||||
css_class: '',
|
||||
list_class: null,
|
||||
is_default: false,
|
||||
available: true,
|
||||
description: ''
|
||||
});
|
||||
|
||||
Object.assign(updateState, {
|
||||
id: undefined,
|
||||
dict_sort: null,
|
||||
dict_label: '',
|
||||
dict_value: '',
|
||||
dict_type: '',
|
||||
css_class: '',
|
||||
list_class: null,
|
||||
is_default: false,
|
||||
available: true,
|
||||
description: ''
|
||||
});
|
||||
|
||||
if (modalType === 'view' && index !== undefined) {
|
||||
detailStateLoading.value = true;
|
||||
|
||||
detailState.value = dataSource.value[index];
|
||||
detailState.value.index = index;
|
||||
|
||||
detailStateLoading.value = false;
|
||||
|
||||
} else if (modalType === 'update' && index !== undefined) {
|
||||
const selected = dataSource.value[index];
|
||||
Object.keys(updateState).forEach(key => {
|
||||
updateState[key] = selected[key];
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 删除
|
||||
const deleteRow = (row: tableDictDataType) => {
|
||||
deleteDictData({ id: row.id }).then(response => {
|
||||
const result = response.data;
|
||||
message.success(result.msg);
|
||||
loadingData();
|
||||
}).catch(error => {
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
|
||||
// 弹窗提交(详情/新建/修改)
|
||||
const handleModalSumbit = () => {
|
||||
modalSubmitLoading.value = true;
|
||||
|
||||
if (modalTitle.value === 'view') {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
|
||||
} else if (modalTitle.value === 'create') {
|
||||
createForm.value.validate().then(() => {
|
||||
const createBody = cloneDeep(createState);
|
||||
Object.keys(createBody).forEach(key => {
|
||||
if (isEmpty(createBody[key])) {
|
||||
delete createBody[key];
|
||||
}
|
||||
})
|
||||
|
||||
createDictData(createBody).then(response => {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
Object.keys(createState).forEach(key => delete createState[key]);
|
||||
const result = response.data;
|
||||
message.success(result.msg);
|
||||
loadingData();
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
} else if (modalTitle.value === 'update') {
|
||||
updateForm.value.validate().then(() => {
|
||||
updateDictData(updateState).then(response => {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
message.success(response.data.msg);
|
||||
loadingData();
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 导出按钮操作
|
||||
const handleExport = () => {
|
||||
Modal.confirm({
|
||||
title: '警告',
|
||||
content: '是否确认导出所有数据字典数据?',
|
||||
onOk() {
|
||||
const body = {
|
||||
...queryState,
|
||||
page_no: 1,
|
||||
page_size: pagination.total
|
||||
};
|
||||
message.loading('正在导出数据,请稍候...', 0);
|
||||
|
||||
return exportDictData(body).then(response => {
|
||||
const blob = new Blob([response.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
|
||||
// 从响应头获取文件名
|
||||
const contentDisposition = response.headers['content-disposition'];
|
||||
let fileName = '数据字典.xlsx';
|
||||
if (contentDisposition) {
|
||||
const fileNameMatch = contentDisposition.match(/filename=(.*?)(;|$)/);
|
||||
if (fileNameMatch) {
|
||||
fileName = decodeURIComponent(fileNameMatch[1]);
|
||||
}
|
||||
}
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = fileName;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
message.destroy();
|
||||
message.success('导出成功');
|
||||
}).catch((error) => {
|
||||
message.destroy();
|
||||
console.error('导出错误:', error);
|
||||
message.error('文件处理失败');
|
||||
});
|
||||
},
|
||||
onCancel() {
|
||||
message.info('已取消导出');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.table-search-wrapper {
|
||||
margin-block-end: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,498 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 页面头部 -->
|
||||
<page-header />
|
||||
|
||||
<!-- 搜索表单 -->
|
||||
<div class="table-search-wrapper">
|
||||
<a-card :bordered="false">
|
||||
<a-form :model="queryState" @finish="onFinish">
|
||||
<a-row>
|
||||
<a-col flex="0 1 450px">
|
||||
<a-form-item name="dict_name" label="名称" style="max-width: 300px;">
|
||||
<a-input v-model:value="queryState.dict_name" placeholder="请输入字典名称"
|
||||
allowClear></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col flex="0 1 450px">
|
||||
<a-form-item name="dict_type" label="类型" style="max-width: 300px;">
|
||||
<a-input v-model:value="queryState.dict_type" placeholder="请输入字典类型"
|
||||
allowClear></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col flex="0 1 450px">
|
||||
<a-form-item name="available" label="状态" style="max-width: 300px;">
|
||||
<a-select v-model:value="queryState.available" placeholder="全部" allowClear>
|
||||
<a-select-option value="1">启用</a-select-option>
|
||||
<a-select-option value="0">停用</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col>
|
||||
<a-button type="primary" html-type="submit" :loading="tableLoading">查询</a-button>
|
||||
<a-button style="margin: 0 8px" @click="resetFields">重置</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<div class="table-wrapper">
|
||||
<a-card title="字典类型列表"
|
||||
:bordered="false"
|
||||
:headStyle="{ borderBottom: 'none', padding: '20px 24px' }"
|
||||
:bodyStyle="{ padding: '0 24px', minHeight: 'calc(100vh - 400px)' }">
|
||||
<template #extra>
|
||||
<a-button type="primary" :icon="h(PlusOutlined)" @click="modalHandle('create')"
|
||||
style="margin-right: 10px;">新建</a-button>
|
||||
<a-button type="primary" :icon="h(DownOutlined)" @click="handleExport"
|
||||
style="margin-right: 10px;">导出
|
||||
</a-button>
|
||||
</template>
|
||||
<a-table :rowKey="record => record.id"
|
||||
:columns="columns"
|
||||
:data-source="dataSource"
|
||||
:row-selection="rowSelection"
|
||||
:loading="tableLoading"
|
||||
@change="handleTableChange"
|
||||
:scroll="{ x: 400, y: 370 }"
|
||||
:pagination="pagination"
|
||||
:style="{ minHeight: '400px' }"
|
||||
>
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.dataIndex === 'index'">
|
||||
<span>{{ (pagination.current - 1) * pagination.pageSize + index + 1 }}</span>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'dict_type'">
|
||||
<router-link :to="'/system/dict_data?dict_type=' + record.dict_type" class="link-type">
|
||||
<span>{{ record.dict_type }}</span>
|
||||
</router-link>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'available'">
|
||||
<span>
|
||||
<a-badge :status="record.available ? 'processing': 'error'" :text="record.available ? '启用' : '停用'" />
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'operation'">
|
||||
<a-space size="middle">
|
||||
<a @click="modalHandle('view', index)">查看</a>
|
||||
<a @click="modalHandle('update', index)">修改</a>
|
||||
<a-popconfirm title="确定删除吗?" ok-text="确定" cancel-text="取消" @confirm="deleteRow(record)">
|
||||
<a style="color: red;">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<!-- 弹窗区域 -->
|
||||
<div class="modal-wrapper">
|
||||
<a-modal v-model:open="openModal" @ok="handleModalSumbit" :width="800" :destroyOnClose="true"
|
||||
:confirmLoading="modalSubmitLoading" style="top: 30px">
|
||||
<template #title>
|
||||
<span>{{ modalTitle === 'create' ? '新建字典类型' : (modalTitle === 'view' ? '查看字典类型' : '修改字典类型')
|
||||
}}</span>
|
||||
</template>
|
||||
<div v-if="modalTitle === 'view'">
|
||||
<a-spin :spinning="detailStateLoading">
|
||||
<a-descriptions :column="{ xxl: 2, xl: 2, lg: 2, md: 2, sm: 1, xs: 1 }"
|
||||
:labelStyle="{ width: '140px' }" bordered>
|
||||
<a-descriptions-item label="序号">{{ (pagination.current - 1) * pagination.pageSize +
|
||||
detailState.index + 1 }}</a-descriptions-item>
|
||||
<a-descriptions-item label="字典名称">{{ detailState.dict_name }}</a-descriptions-item>
|
||||
<a-descriptions-item label="字典类型">{{ detailState.dict_type }}</a-descriptions-item>
|
||||
<a-descriptions-item label="状态">
|
||||
<a-badge :status="detailState.available ? 'processing': 'error'" :text="detailState.available ? '启用' : '停用'" />
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="创建人">{{ detailState.creator ? detailState.creator.name : '-'
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">{{ detailState.created_at }}</a-descriptions-item>
|
||||
<a-descriptions-item label="修改时间">{{ detailState.updated_at }}</a-descriptions-item>
|
||||
<a-descriptions-item label="备注" :span="2">{{ detailState.description
|
||||
}}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-spin>
|
||||
</div>
|
||||
<div v-else-if="modalTitle === 'create'">
|
||||
<a-form ref="createForm" :model="createState"
|
||||
v-bind="{ labelCol: { span: 5 }, wrapperCol: { span: 15 } }">
|
||||
<a-form-item name="dict_name" label="字典名称" :rules="[{ required: true, message: '请输入字典名称' }]">
|
||||
<a-input v-model:value="createState.dict_name" placeholder="请输入字典名称" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="dict_type" label="字典类型" :rules="[{ required: true, message: '请输入字典类型' }]">
|
||||
<a-input v-model:value="createState.dict_type" placeholder="请输入字典类型" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="available" label="状态" :rules="[{ required: true, message: '请选择状态' }]">
|
||||
<a-radio-group v-model:value="createState.available">
|
||||
<a-radio :value="true">启用</a-radio>
|
||||
<a-radio :value="false">停用</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item name="description" label="备注">
|
||||
<a-textarea v-model:value="createState.description" placeholder="请输入备注" :rows="4"
|
||||
allowClear />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
<div v-else>
|
||||
<a-form ref="updateForm" :model="updateState"
|
||||
v-bind="{ labelCol: { span: 5 }, wrapperCol: { span: 15 } }">
|
||||
<a-form-item name="dict_name" label="字典名称" :rules="[{ required: true, message: '请输入字典名称' }]">
|
||||
<a-input v-model:value="updateState.dict_name" placeholder="请输入字典名称" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="dict_type" label="字典类型" :rules="[{ required: true, message: '请输入字典类型' }]">
|
||||
<a-input v-model:value="updateState.dict_type" placeholder="请输入字典类型" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="available" label="状态" :rules="[{ required: true, message: '请选择状态' }]">
|
||||
<a-radio-group v-model:value="updateState.available">
|
||||
<a-radio :value="true">启用</a-radio>
|
||||
<a-radio :value="false">停用</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item name="description" label="备注">
|
||||
<a-textarea v-model:value="updateState.description" placeholder="请输入备注" :rows="4"
|
||||
allowClear />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed, unref, onMounted, h } from 'vue';
|
||||
import { Table, message, Modal } from 'ant-design-vue';
|
||||
import type { TableColumnsType, MenuProps } from 'ant-design-vue';
|
||||
import { PlusOutlined, DownOutlined, CheckOutlined, StopOutlined } from '@ant-design/icons-vue';
|
||||
import { cloneDeep, isEmpty } from '@/utils/util';
|
||||
import PageHeader from '@/components/PageHeader.vue';
|
||||
import { getDictTypeList,getDictTypeDetail,createDictType,updateDictType,deleteDictType,exportDictType,getDictDataList,getDictDataDetail,createDictData,updateDictData,deleteDictData,exportDictData,getDictTypeOption,getDictDataByType } from '@/api/system/dict'
|
||||
import type { searchDataType, tableDictType } from './types'
|
||||
|
||||
const createForm = ref();
|
||||
const updateForm = ref();
|
||||
const tableLoading = ref(false);
|
||||
const openModal = ref(false);
|
||||
const modalTitle = ref('');
|
||||
const modalSubmitLoading = ref(false);
|
||||
const detailStateLoading = ref(false);
|
||||
const dataSource = ref<tableDictType[]>([]);
|
||||
const selectedRowKeys = ref<tableDictType['id'][]>([]);
|
||||
|
||||
const queryState = reactive<searchDataType>({
|
||||
dict_name: null,
|
||||
dict_type: null,
|
||||
available: null
|
||||
});
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
defaultPageSize: 10,
|
||||
showSizeChanger: true,
|
||||
total: dataSource.value.length,
|
||||
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条 / 总共 ${total} 条`
|
||||
})
|
||||
const createState = reactive<tableDictType>({
|
||||
dict_name: '',
|
||||
dict_type: '',
|
||||
available: true,
|
||||
description: ''
|
||||
})
|
||||
const updateState = reactive<tableDictType>({
|
||||
id: undefined,
|
||||
dict_name: '',
|
||||
dict_type: '',
|
||||
available: true,
|
||||
description: ''
|
||||
})
|
||||
const detailState = ref<tableDictType>({})
|
||||
|
||||
const columns: TableColumnsType = [
|
||||
{
|
||||
title: '序号',
|
||||
dataIndex: 'index',
|
||||
align: 'center',
|
||||
ellipsis: true,
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '字典名称',
|
||||
dataIndex: 'dict_name',
|
||||
ellipsis: true,
|
||||
// align: 'center'
|
||||
},
|
||||
{
|
||||
title: '字典类型',
|
||||
dataIndex: 'dict_type',
|
||||
ellipsis: true,
|
||||
// align: 'center'
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'available',
|
||||
// ellipsis: true,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'description',
|
||||
// align: 'center',
|
||||
ellipsis: true,
|
||||
// width: 500
|
||||
},
|
||||
{
|
||||
title: '创建日期',
|
||||
dataIndex: 'created_at',
|
||||
// align: 'center',
|
||||
ellipsis: true,
|
||||
// width: 120
|
||||
},
|
||||
{
|
||||
title: '更新日期',
|
||||
dataIndex: 'updated_at',
|
||||
// align: 'center',
|
||||
ellipsis: true,
|
||||
// width: 120
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
ellipsis: true,
|
||||
width: 150
|
||||
}
|
||||
];
|
||||
|
||||
// 表格选中配置
|
||||
const rowSelection = computed(() => {
|
||||
return {
|
||||
selectedRowKeys: unref(selectedRowKeys),
|
||||
onChange: (selectingRowKeys: tableDictType['id'][]) => {
|
||||
selectedRowKeys.value = selectingRowKeys;
|
||||
},
|
||||
hideDefaultSelections: true,
|
||||
selections: [
|
||||
Table.SELECTION_ALL,
|
||||
Table.SELECTION_INVERT,
|
||||
Table.SELECTION_NONE
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
// 加载表格数据
|
||||
const loadingData = () => {
|
||||
tableLoading.value = true;
|
||||
|
||||
let params = {};
|
||||
if (queryState.dict_name) {
|
||||
params['dict_name'] = queryState.dict_name
|
||||
}
|
||||
if (queryState.dict_type) {
|
||||
params['dict_type'] = queryState.dict_type
|
||||
}
|
||||
if (queryState.available) {
|
||||
params['available'] = queryState.available == true ? true : false;
|
||||
}
|
||||
params['page_no'] = pagination.current
|
||||
params['page_size'] = pagination.pageSize
|
||||
|
||||
getDictTypeList(params).then(response => {
|
||||
const result = response.data;
|
||||
dataSource.value = result.data.items;
|
||||
pagination.total = result.data.total;
|
||||
pagination.current = result.data.page_no;
|
||||
pagination.pageSize = result.data.page_size;
|
||||
}).catch(error => {
|
||||
console.log(error);
|
||||
}).finally(() => {
|
||||
tableLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 生命周期钩子
|
||||
onMounted(() => loadingData());
|
||||
|
||||
// 查询
|
||||
const onFinish = () => {
|
||||
pagination.current = 1;
|
||||
loadingData();
|
||||
};
|
||||
|
||||
// 重置查询
|
||||
const resetFields = () => {
|
||||
Object.keys(queryState).forEach((key: string) => {
|
||||
delete queryState[key];
|
||||
});
|
||||
pagination.current = 1;
|
||||
queryState.available = null
|
||||
loadingData();
|
||||
}
|
||||
|
||||
// 表格分页处理
|
||||
const handleTableChange = (values: any) => {
|
||||
pagination.current = values.current;
|
||||
pagination.pageSize = values.pageSize;
|
||||
loadingData();
|
||||
}
|
||||
|
||||
// 弹窗关键字处理
|
||||
const modalHandle = (modalType: string, index?: number) => {
|
||||
modalTitle.value = modalType;
|
||||
openModal.value = true;
|
||||
|
||||
// 重新创建 createState 和 updateState 以重置为初始状态
|
||||
Object.assign(createState, {
|
||||
dict_name: '',
|
||||
dict_type: '',
|
||||
available: true,
|
||||
description: ''
|
||||
});
|
||||
|
||||
Object.assign(updateState, {
|
||||
id: undefined,
|
||||
dict_name: '',
|
||||
dict_type: '',
|
||||
available: true,
|
||||
description: ''
|
||||
});
|
||||
|
||||
if (modalType === 'view' && index !== undefined) {
|
||||
detailStateLoading.value = true;
|
||||
|
||||
detailState.value = dataSource.value[index];
|
||||
detailState.value.index = index;
|
||||
|
||||
detailStateLoading.value = false;
|
||||
|
||||
} else if (modalType === 'update' && index !== undefined) {
|
||||
const selected = dataSource.value[index];
|
||||
Object.keys(updateState).forEach(key => {
|
||||
updateState[key] = selected[key];
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 删除
|
||||
const deleteRow = (row: tableDictType) => {
|
||||
deleteDictType({ id: row.id }).then(response => {
|
||||
const result = response.data;
|
||||
message.success(result.msg);
|
||||
loadingData();
|
||||
}).catch(error => {
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 弹窗提交(详情/新建/修改)
|
||||
const handleModalSumbit = () => {
|
||||
modalSubmitLoading.value = true;
|
||||
|
||||
if (modalTitle.value === 'view') {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
|
||||
} else if (modalTitle.value === 'create') {
|
||||
createForm.value.validate().then(() => {
|
||||
const createBody = cloneDeep(createState);
|
||||
Object.keys(createBody).forEach(key => {
|
||||
if (isEmpty(createBody[key])) {
|
||||
delete createBody[key];
|
||||
}
|
||||
})
|
||||
|
||||
createDictType(createBody).then(response => {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
Object.keys(createState).forEach(key => delete createState[key]);
|
||||
const result = response.data;
|
||||
message.success(result.msg);
|
||||
loadingData();
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
} else if (modalTitle.value === 'update') {
|
||||
updateForm.value.validate().then(() => {
|
||||
updateDictType(updateState).then(response => {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
message.success(response.data.msg);
|
||||
loadingData();
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 导出按钮操作
|
||||
const handleExport = () => {
|
||||
Modal.confirm({
|
||||
title: '警告',
|
||||
content: '是否确认导出所有字典类型数据?',
|
||||
onOk() {
|
||||
const body = {
|
||||
...queryState,
|
||||
page_no: 1,
|
||||
page_size: pagination.total
|
||||
};
|
||||
message.loading('正在导出数据,请稍候...', 0);
|
||||
|
||||
return exportDictType(body).then(response => {
|
||||
const blob = new Blob([response.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
|
||||
// 从响应头获取文件名
|
||||
const contentDisposition = response.headers['content-disposition'];
|
||||
let fileName = '字典类型.xlsx';
|
||||
if (contentDisposition) {
|
||||
const fileNameMatch = contentDisposition.match(/filename=(.*?)(;|$)/);
|
||||
if (fileNameMatch) {
|
||||
fileName = decodeURIComponent(fileNameMatch[1]);
|
||||
}
|
||||
}
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = fileName;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
message.destroy();
|
||||
message.success('导出成功');
|
||||
}).catch((error) => {
|
||||
message.destroy();
|
||||
console.error('导出错误:', error);
|
||||
message.error('文件处理失败');
|
||||
});
|
||||
},
|
||||
onCancel() {
|
||||
message.info('已取消导出');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.table-search-wrapper {
|
||||
margin-block-end: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,47 @@
|
||||
export interface searchDataType {
|
||||
dict_name?: string
|
||||
dict_type?: string
|
||||
available?: boolean
|
||||
}
|
||||
|
||||
export interface searchDictDataType {
|
||||
dict_label?: string
|
||||
dict_type?: string
|
||||
available?: boolean
|
||||
}
|
||||
|
||||
export interface tableDictType {
|
||||
id?: number;
|
||||
index?: number;
|
||||
dict_name?: string;
|
||||
dict_type?: string;
|
||||
available?: boolean;
|
||||
description?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
creator?: creatorType;
|
||||
}
|
||||
|
||||
export interface tableDictDataType {
|
||||
id?: number;
|
||||
index?: number;
|
||||
dict_sort?: number;
|
||||
dict_label?: string;
|
||||
dict_value?: string;
|
||||
dict_type?: string;
|
||||
css_class?: string;
|
||||
list_class?: string;
|
||||
is_default?: boolean;
|
||||
available?: boolean;
|
||||
description?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
creator?: creatorType;
|
||||
}
|
||||
|
||||
interface creatorType {
|
||||
id?: number;
|
||||
name?: string;
|
||||
username?: string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,629 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 页面头部 -->
|
||||
<page-header />
|
||||
|
||||
<!-- 搜索表单 -->
|
||||
<div class="table-search-wrapper">
|
||||
<a-card :bordered="false">
|
||||
<a-form :model="queryState" @finish="onFinish">
|
||||
<a-row>
|
||||
<a-col flex="0 1 450px">
|
||||
<a-form-item name="name" label="名称" style="max-width: 300px;">
|
||||
<a-input v-model:value="queryState.name" placeholder="请输入任务名称"
|
||||
allowClear></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col flex="0 1 450px">
|
||||
<a-form-item name="status" label="状态" style="max-width: 300px;">
|
||||
<a-select v-model:value="queryState.status" placeholder="全部" allowClear>
|
||||
<a-select-option value="1">启用</a-select-option>
|
||||
<a-select-option value="0">停用</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col flex="0 1 450px">
|
||||
<a-form-item name="date-range-picker" label="创建日期" style="max-width: 350px;">
|
||||
<a-range-picker v-model:value="queryState.date_range" value-format="YYYY-MM-DD" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col>
|
||||
<a-button type="primary" html-type="submit" :loading="tableLoading">查询</a-button>
|
||||
<a-button style="margin: 0 8px" @click="resetFields">重置</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<div class="table-wrapper">
|
||||
<a-card title="定时任务列表"
|
||||
:bordered="false"
|
||||
:headStyle="{ borderBottom: 'none', padding: '20px 24px' }"
|
||||
:bodyStyle="{ padding: '0 24px', minHeight: 'calc(100vh - 400px)' }">
|
||||
<template #extra>
|
||||
<a-button type="primary" :icon="h(PlusOutlined)" @click="modalHandle('create')"
|
||||
style="margin-right: 10px;">新建</a-button>
|
||||
<a-button type="primary" :icon="h(DownOutlined)" @click="handleExport"
|
||||
style="margin-right: 10px;">导出
|
||||
</a-button>
|
||||
<a-button type="primary" danger :icon="h(DeleteOutlined)" @click="handleClear"
|
||||
style="margin-right: 10px;">清除
|
||||
</a-button>
|
||||
</template>
|
||||
<a-table :rowKey="record => record.id"
|
||||
:columns="columns"
|
||||
:data-source="dataSource"
|
||||
:row-selection="rowSelection"
|
||||
:loading="tableLoading"
|
||||
@change="handleTableChange"
|
||||
:scroll="{ x: 400 }"
|
||||
:pagination="pagination"
|
||||
:style="{ minHeight: '420px' }"
|
||||
>
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.dataIndex === 'index'">
|
||||
<span>{{ (pagination.current - 1) * pagination.pageSize + index + 1 }}</span>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'status'">
|
||||
<span>
|
||||
<a-badge :status="record.status ? 'processing': 'error'" :text="record.status ? '启用' : '停用'" />
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'coalesce'">
|
||||
<span>
|
||||
<a-badge :status="record.coalesce ? 'processing': 'error'" :text="record.status ? '是' : '否'" />
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'operation'">
|
||||
<a-space size="middle">
|
||||
<a @click="modalHandle('view', index)">查看</a>
|
||||
<a @click="modalHandle('update', index)">修改</a>
|
||||
<a-popconfirm title="确定删除吗?" ok-text="确定" cancel-text="取消" @confirm="deleteRow(record)">
|
||||
<a style="color: red;">删除</a>
|
||||
</a-popconfirm>
|
||||
<a-dropdown>
|
||||
<a>更多 <DownOutlined /></a>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item @click="handleOption(record, 1)">暂停</a-menu-item>
|
||||
<a-menu-item @click="handleOption(record, 2)">恢复</a-menu-item>
|
||||
<a-menu-item @click="handleOption(record, 3)">重启</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<!-- 弹窗区域 -->
|
||||
<div class="modal-wrapper">
|
||||
<a-modal v-model:open="openModal" @ok="handleModalSumbit" :width="800" :destroyOnClose="true"
|
||||
:confirmLoading="modalSubmitLoading" style="top: 30px">
|
||||
<template #title>
|
||||
<span>
|
||||
{{ modalTitle === 'create' ? '新建定时任务' : (modalTitle === 'view' ? '查看定时任务' : '修改定时任务')}}
|
||||
</span>
|
||||
</template>
|
||||
<div v-if="modalTitle === 'view'">
|
||||
<a-spin :spinning="detailStateLoading">
|
||||
<a-descriptions :column="{ xxl: 2, xl: 2, lg: 2, md: 2, sm: 1, xs: 1 }"
|
||||
:labelStyle="{ width: '140px' }" bordered>
|
||||
<a-descriptions-item label="序号">{{ (pagination.current - 1) * pagination.pageSize + detailState.index + 1 }}</a-descriptions-item>
|
||||
<a-descriptions-item label="任务名称">{{ detailState.name }}</a-descriptions-item>
|
||||
<a-descriptions-item label="任务函数">{{ detailState.func }}</a-descriptions-item>
|
||||
<a-descriptions-item label="触发器">
|
||||
{{ detailState.trigger }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="位置参数">{{ detailState.args }}</a-descriptions-item>
|
||||
<a-descriptions-item label="关键字参数">{{ detailState.kwargs }}</a-descriptions-item>
|
||||
<a-descriptions-item label="是否并行">
|
||||
<a-badge :status="detailState.coalesce ? 'processing': 'error'" :text="detailState.coalesce ? '是' : '否'" />
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="最大实例数">{{ detailState.max_instances }}</a-descriptions-item>
|
||||
<a-descriptions-item label="任务存储">
|
||||
{{ detailState.jobstore }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="任务执行器">
|
||||
{{ detailState.executor }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="触发器参数">{{ detailState.trigger_args }}</a-descriptions-item>
|
||||
<a-descriptions-item label="任务状态">
|
||||
<a-badge :status="detailState.status ? 'processing': 'error'" :text="detailState.status ? '启用' : '停用'" />
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="日志信息">{{ detailState.message }}</a-descriptions-item>
|
||||
<a-descriptions-item label="创建人">{{ detailState.creator ? detailState.creator.name : '-'}}</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">{{ detailState.created_at }}</a-descriptions-item>
|
||||
<a-descriptions-item label="修改时间">{{ detailState.updated_at }}</a-descriptions-item>
|
||||
<a-descriptions-item label="备注" :span="2">{{ detailState.description}}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-spin>
|
||||
</div>
|
||||
<div v-else-if="modalTitle === 'create'">
|
||||
<a-form ref="createForm" :model="createState"
|
||||
v-bind="{ labelCol: { span: 5 }, wrapperCol: { span: 15 } }">
|
||||
<a-form-item name="name" label="任务名称" :rules="[{ required: true, message: '请输入任务名称' }]">
|
||||
<a-input v-model:value="createState.name" placeholder="请输入任务名称" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="jobstore" label="任务存储器" :rules="[{ required: true, message: '请选择任务存储' }]">
|
||||
<a-select v-model:value="createState.jobstore" placeholder="请选择任务存储" allowClear :options="jobGroupOptions" />
|
||||
</a-form-item>
|
||||
<a-form-item name="executor" label="任务执行器" :rules="[{ required: true, message: '请选择任务执行器' }]">
|
||||
<a-select v-model:value="createState.executor" placeholder="请选择任务执行器" allowClear :options="jobExecutorOptions" />
|
||||
</a-form-item>
|
||||
<a-form-item name="func" label="任务函数" :rules="[{ required: true, message: '请输入任务函数 module.function' }]">
|
||||
<a-input v-model:value="createState.func" placeholder="请输入任务函数 module.function" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="args" label="位置参数" :rules="[{ required: false, message: '请输入位置参数' }]">
|
||||
<a-input v-model:value="createState.args" placeholder="请输入位置参数" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="kwargs" label="关键字参数" :rules="[{ required: false, message: '请输入关键字参数' }]">
|
||||
<a-input v-model:value="createState.kwargs" placeholder="请输入关键字参数" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="trigger" label="任务触发器" :rules="[{ required: true, message: '请选择触发器' }]">
|
||||
<a-select v-model:value="createState.trigger" placeholder="请选择触发器" allowClear :options="triggerOptions" />
|
||||
</a-form-item>
|
||||
<a-form-item v-if="createState.trigger === 'date'" name="trigger_args" label="运行日期" :rules="[{ required: true, message: '请选择运行日期' }]">
|
||||
<a-date-picker v-model:value="createState.trigger_args" format="YYYY-MM-DD HH:mm:ss" placeholder="请选择运行日期" :show-time="{ defaultValue: dayjs('00:00:00', 'HH:mm:ss') }" style="width: 100%" :value-format="'YYYY-MM-DD HH:mm:ss'"/>
|
||||
</a-form-item>
|
||||
<a-form-item v-else-if="createState.trigger === 'interval'" name="trigger_args" label="间隔时间" :rules="[{ required: true, message: '请输入间隔时间' }]">
|
||||
<a-input v-model:value="createState.trigger_args" placeholder="请输入 秒-分-时-天-周 (* * * * 1) " allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-else-if="createState.trigger === 'cron'" name="trigger_args" label="Cron表达式" :rules="[{ required: true, message: '请输入Cron表达式' }]">
|
||||
<a-input v-model:value="createState.trigger_args" placeholder="请输入 Cron表达式(*/3 * * * *)" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="coalesce" label="是否并行" :rules="[{ required: true, message: '请选择是否合并运行' }]">
|
||||
<a-radio-group v-model:value="createState.coalesce">
|
||||
<a-radio :value="true">是</a-radio>
|
||||
<a-radio :value="false">否</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item name="max_instances" label="最大实例数" :rules="[{ required: true, message: '请输入最大实例数' }]">
|
||||
<a-input-number v-model:value="createState.max_instances" :min="1" placeholder="请输入最大实例数" style="width: 100%" />
|
||||
</a-form-item>
|
||||
<a-form-item name="description" label="备注">
|
||||
<a-textarea v-model:value="createState.description" placeholder="请输入备注" :rows="4"
|
||||
allowClear />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
<div v-else>
|
||||
<a-form ref="updateForm" :model="updateState"
|
||||
v-bind="{ labelCol: { span: 5 }, wrapperCol: { span: 15 } }">
|
||||
<a-form-item name="name" label="任务名称" :rules="[{ required: true, message: '请输入任务名称' }]">
|
||||
<a-input v-model:value="updateState.name" placeholder="请输入任务名称" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="jobstore" label="任务存储器" :rules="[{ required: true, message: '请选择任务存储' }]">
|
||||
<a-select v-model:value="updateState.jobstore" placeholder="请选择任务存储" allowClear :options="jobGroupOptions" />
|
||||
</a-form-item>
|
||||
<a-form-item name="executor" label="任务执行器" :rules="[{ required: true, message: '请选择任务执行器' }]">
|
||||
<a-select v-model:value="updateState.executor" placeholder="请选择任务执行器" allowClear :options="jobExecutorOptions" />
|
||||
</a-form-item>
|
||||
<a-form-item name="func" label="任务函数" :rules="[{ required: true, message: '请输入任务函数' }]">
|
||||
<a-input v-model:value="updateState.func" placeholder="请输入任务函数" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="args" label="位置参数" :rules="[{ required: false, message: '请输入位置参数' }]">
|
||||
<a-input v-model:value="updateState.args" placeholder="请输入位置参数" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="kwargs" label="关键字参数" :rules="[{ required: false, message: '请输入关键字参数' }]">
|
||||
<a-input v-model:value="updateState.kwargs" placeholder="请输入关键字参数" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="trigger" label="任务触发器" :rules="[{ required: true, message: '请选择触发器' }]">
|
||||
<a-select v-model:value="updateState.trigger" placeholder="请选择触发器" allowClear :options="triggerOptions" />
|
||||
</a-form-item>
|
||||
<a-form-item v-if="updateState.trigger === 'date'" name="trigger_args" label="运行日期" :rules="[{ required: true, message: '请选择运行日期' }]">
|
||||
<a-date-picker v-model:value="updateState.trigger_args" format="YYYY-MM-DD HH:mm:ss" placeholder="请选择运行日期" :show-time="{ defaultValue: dayjs('00:00:00', 'HH:mm:ss') }" style="width: 100%" :value-format="'YYYY-MM-DD HH:mm:ss'"/>
|
||||
</a-form-item>
|
||||
<a-form-item v-else-if="updateState.trigger === 'interval'" name="trigger_args" label="间隔时间" :rules="[{ required: true, message: '请输入间隔时间' }]">
|
||||
<a-input v-model:value="updateState.trigger_args" placeholder="请输入 秒-分-时-天-周 (* * * * 1) " allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-else-if="updateState.trigger === 'cron'" name="trigger_args" label="Cron表达式" :rules="[{ required: true, message: '请输入Cron表达式' }]">
|
||||
<a-input v-model:value="updateState.trigger_args" placeholder="请输入 Cron表达式(*/3 * * * *)" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="coalesce" label="是否并行" :rules="[{ required: true, message: '请选择是否合并运行' }]">
|
||||
<a-radio-group v-model:value="updateState.coalesce">
|
||||
<a-radio :value="true">启用</a-radio>
|
||||
<a-radio :value="false">停用</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item name="max_instances" label="最大实例数" :rules="[{ required: true, message: '请输入最大实例数' }]">
|
||||
<a-input-number v-model:value="updateState.max_instances" :min="1" placeholder="请输入最大实例数" style="width: 100%" />
|
||||
</a-form-item>
|
||||
<a-form-item name="description" label="备注">
|
||||
<a-textarea v-model:value="updateState.description" placeholder="请输入备注" :rows="4"
|
||||
allowClear />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed, unref, onMounted, h, watch } from 'vue';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import { Table, message, Modal } from 'ant-design-vue';
|
||||
import type { TableColumnsType, MenuProps } from 'ant-design-vue';
|
||||
import { PlusOutlined, DownOutlined, DeleteOutlined, CheckOutlined, StopOutlined } from '@ant-design/icons-vue';
|
||||
import { cloneDeep, isEmpty } from '@/utils/util';
|
||||
import PageHeader from '@/components/PageHeader.vue';
|
||||
import { getJobList, getJobDetail, createJob,updateJob,deleteJob,exportJob, clearJob, OptionJob} from '@/api/system/job'
|
||||
import type { searchType, tableJobType } from './types'
|
||||
|
||||
const createForm = ref();
|
||||
const updateForm = ref();
|
||||
const tableLoading = ref(false);
|
||||
const openModal = ref(false);
|
||||
const modalTitle = ref('');
|
||||
const modalSubmitLoading = ref(false);
|
||||
const detailStateLoading = ref(false);
|
||||
const dataSource = ref<tableJobType[]>([]);
|
||||
const selectedRowKeys = ref<tableJobType['id'][]>([]);
|
||||
|
||||
const queryState = reactive<searchType>({});
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
defaultPageSize: 10,
|
||||
showSizeChanger: true,
|
||||
total: dataSource.value.length,
|
||||
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条 / 总共 ${total} 条`
|
||||
})
|
||||
|
||||
const jobGroupOptions = ref([
|
||||
{ value: 'default', label: '默认(Memory)' },
|
||||
{ value: 'redis', label: 'Redis' },
|
||||
])
|
||||
|
||||
const jobExecutorOptions = ref([
|
||||
{ value: 'default', label: '默认执行器' },
|
||||
{ value: 'processpool', label: '进程池' }
|
||||
])
|
||||
|
||||
|
||||
const triggerOptions = ref([
|
||||
{ value: 'date', label: '指定日期(date)' },
|
||||
{ value: 'interval', label: '间隔触发器(interval)' },
|
||||
{ value: 'cron', label: 'cron表达式' },
|
||||
])
|
||||
|
||||
|
||||
const createState = reactive<tableJobType>({
|
||||
name:'',
|
||||
func:'',
|
||||
trigger:null,
|
||||
args:'',
|
||||
kwargs:'',
|
||||
coalesce: false,
|
||||
max_instances:1,
|
||||
jobstore:'default',
|
||||
executor:'default',
|
||||
trigger_args: null,
|
||||
status: null,
|
||||
message:'',
|
||||
description:''
|
||||
})
|
||||
const updateState = reactive<tableJobType>({
|
||||
id: undefined,
|
||||
name:'',
|
||||
func:'',
|
||||
trigger:null,
|
||||
args:'',
|
||||
kwargs:'',
|
||||
coalesce:false,
|
||||
max_instances:1,
|
||||
jobstore:'default',
|
||||
executor:'default',
|
||||
trigger_args: null,
|
||||
status:null,
|
||||
message:'',
|
||||
description:''
|
||||
})
|
||||
const detailState = ref<tableJobType>({})
|
||||
|
||||
const columns: TableColumnsType = [
|
||||
{
|
||||
title: '序号',
|
||||
dataIndex: 'index',
|
||||
align: 'center',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '任务名称',
|
||||
dataIndex: 'name',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '执行函数',
|
||||
dataIndex: 'func',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '触发器',
|
||||
dataIndex: 'trigger',
|
||||
ellipsis: true,
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '存储器',
|
||||
dataIndex: 'jobstore',
|
||||
ellipsis: true,
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '执行器',
|
||||
dataIndex: 'executor',
|
||||
ellipsis: true,
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '并发执行',
|
||||
dataIndex: 'coalesce',
|
||||
align: 'center',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
align: 'center',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'created_at',
|
||||
ellipsis: true,
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
width: 200
|
||||
}
|
||||
];
|
||||
|
||||
// 表格选中配置
|
||||
const rowSelection = computed(() => {
|
||||
return {
|
||||
selectedRowKeys: unref(selectedRowKeys),
|
||||
onChange: (selectingRowKeys: tableJobType['id'][]) => {
|
||||
selectedRowKeys.value = selectingRowKeys;
|
||||
},
|
||||
hideDefaultSelections: true,
|
||||
selections: [
|
||||
Table.SELECTION_ALL,
|
||||
Table.SELECTION_INVERT,
|
||||
Table.SELECTION_NONE
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
// 加载表格数据
|
||||
const loadingData = () => {
|
||||
tableLoading.value = true;
|
||||
|
||||
let params = {};
|
||||
if (queryState.name) {
|
||||
params['name'] = queryState.name
|
||||
}
|
||||
if (queryState.date_range) {
|
||||
params['start_time'] = `${queryState.date_range[0]} 00:00:00`;
|
||||
params['end_time'] = `${queryState.date_range[1]} 23:59:59`;
|
||||
}
|
||||
if (queryState.status) {
|
||||
params['status'] = queryState.status == true ? true : false;
|
||||
}
|
||||
params['page_no'] = pagination.current
|
||||
params['page_size'] = pagination.pageSize
|
||||
|
||||
getJobList(params).then(response => {
|
||||
const result = response.data;
|
||||
dataSource.value = result.data.items;
|
||||
pagination.total = result.data.total;
|
||||
pagination.current = result.data.page_no;
|
||||
pagination.pageSize = result.data.page_size;
|
||||
}).catch(error => {
|
||||
console.log(error);
|
||||
}).finally(() => {
|
||||
tableLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 生命周期钩子
|
||||
onMounted(() => loadingData());
|
||||
|
||||
// 查询
|
||||
const onFinish = () => {
|
||||
pagination.current = 1;
|
||||
loadingData();
|
||||
};
|
||||
|
||||
// 重置查询
|
||||
const resetFields = () => {
|
||||
Object.keys(queryState).forEach((key: string) => {
|
||||
delete queryState[key];
|
||||
});
|
||||
pagination.current = 1;
|
||||
queryState.status = null
|
||||
queryState.date_range = null
|
||||
loadingData();
|
||||
}
|
||||
|
||||
// 表格分页处理
|
||||
const handleTableChange = (values: any) => {
|
||||
pagination.current = values.current;
|
||||
pagination.pageSize = values.pageSize;
|
||||
loadingData();
|
||||
}
|
||||
|
||||
const modalHandle = (modalType: string, index?: number) => {
|
||||
modalTitle.value = modalType;
|
||||
openModal.value = true;
|
||||
|
||||
if (modalType === 'view' && index !== undefined) {
|
||||
detailStateLoading.value = true;
|
||||
|
||||
detailState.value = dataSource.value[index];
|
||||
detailState.value.index = index;
|
||||
|
||||
detailStateLoading.value = false;
|
||||
|
||||
} else if (modalType === 'update' && index !== undefined) {
|
||||
const selected = dataSource.value[index];
|
||||
if (selected.trigger === 'date' && selected.trigger_args) {
|
||||
selected.trigger_args = dayjs(selected.trigger_args).format('YYYY-MM-DD HH:mm:ss');
|
||||
}
|
||||
Object.keys(updateState).forEach(key => {
|
||||
updateState[key] = selected[key];
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 删除
|
||||
const deleteRow = (row: tableJobType) => {
|
||||
deleteJob({ id: row.id }).then(response => {
|
||||
const result = response.data;
|
||||
message.success(result.msg);
|
||||
loadingData();
|
||||
}).catch(error => {
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
|
||||
// 弹窗提交(详情/新建/修改)
|
||||
const handleModalSumbit = () => {
|
||||
modalSubmitLoading.value = true;
|
||||
|
||||
if (modalTitle.value === 'view') {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
|
||||
} else if (modalTitle.value === 'create') {
|
||||
createForm.value.validate().then(() => {
|
||||
const createBody = cloneDeep(createState);
|
||||
Object.keys(createBody).forEach(key => {
|
||||
if (isEmpty(createBody[key])) {
|
||||
delete createBody[key];
|
||||
}
|
||||
})
|
||||
|
||||
createJob(createBody).then(response => {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
Object.keys(createState).forEach(key => delete createState[key]);
|
||||
const result = response.data;
|
||||
message.success(result.msg);
|
||||
loadingData();
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
} else if (modalTitle.value === 'update') {
|
||||
updateForm.value.validate().then(() => {
|
||||
updateJob(updateState).then(response => {
|
||||
modalSubmitLoading.value = false;
|
||||
openModal.value = false;
|
||||
message.success(response.data.msg);
|
||||
loadingData();
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
}).catch(error => {
|
||||
modalSubmitLoading.value = false;
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 导出按钮操作
|
||||
const handleExport = () => {
|
||||
Modal.confirm({
|
||||
title: '警告',
|
||||
content: '是否确认导出所有定时任务数据?',
|
||||
onOk() {
|
||||
const body = {
|
||||
...queryState,
|
||||
page_no: 1,
|
||||
page_size: pagination.total
|
||||
};
|
||||
message.loading('正在导出数据,请稍候...', 0);
|
||||
|
||||
return exportJob(body).then(response => {
|
||||
const blob = new Blob([response.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
|
||||
// 从响应头获取文件名
|
||||
const contentDisposition = response.headers['content-disposition'];
|
||||
let fileName = '定时任务.xlsx';
|
||||
if (contentDisposition) {
|
||||
const fileNameMatch = contentDisposition.match(/filename=(.*?)(;|$)/);
|
||||
if (fileNameMatch) {
|
||||
fileName = decodeURIComponent(fileNameMatch[1]);
|
||||
}
|
||||
}
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = fileName;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
message.destroy();
|
||||
message.success('导出成功');
|
||||
}).catch((error) => {
|
||||
message.destroy();
|
||||
console.error('导出错误:', error);
|
||||
message.error('文件处理失败');
|
||||
});
|
||||
},
|
||||
onCancel() {
|
||||
message.info('已取消导出');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 清空按钮操作
|
||||
const handleClear = () => {
|
||||
Modal.confirm({
|
||||
title: '警告',
|
||||
content: '是否确认清空所有定时任务数据?',
|
||||
onOk() {
|
||||
clearJob().then(response => {
|
||||
const result = response.data;
|
||||
message.success(result.msg);
|
||||
loadingData();
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 操作按钮:操作类型 1: 暂停 2: 恢复 3: 重启
|
||||
const handleOption = (row: tableJobType, option: number) => {
|
||||
OptionJob({id: row.id, option: option}).then(response => {
|
||||
const result = response.data;
|
||||
message.success(result.msg);
|
||||
loadingData();
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.table-search-wrapper {
|
||||
margin-block-end: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,33 @@
|
||||
export interface searchType {
|
||||
name?: string
|
||||
status?: boolean
|
||||
date_range?: [string, string];
|
||||
}
|
||||
|
||||
export interface tableJobType {
|
||||
id?: number;
|
||||
index?: number;
|
||||
name?: string;
|
||||
func?: string;
|
||||
trigger?: string;
|
||||
args?: string;
|
||||
kwargs?: string;
|
||||
coalesce?: boolean;
|
||||
max_instances?: number;
|
||||
jobstore?: string;
|
||||
executor?: string;
|
||||
trigger_args?: string;
|
||||
status?: boolean;
|
||||
message?: string;
|
||||
description?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
creator?: creatorType;
|
||||
}
|
||||
|
||||
interface creatorType {
|
||||
id?: number;
|
||||
name?: string;
|
||||
username?: string;
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@
|
||||
</a-popover>
|
||||
</a-form-item>
|
||||
<a-form-item name="order" label="排序">
|
||||
<a-input-number v-model:value="createState.order" :min="1" />
|
||||
<a-input-number v-model:value="createState.order" :min="1" style="width: 100%"/>
|
||||
</a-form-item>
|
||||
<a-form-item name="available" label="状态" :rules="[{ required: true, message: '请选择状态' }]">
|
||||
<a-radio-group v-model:value="createState.available">
|
||||
@@ -303,7 +303,7 @@
|
||||
</a-popover>
|
||||
</a-form-item>
|
||||
<a-form-item name="order" label="排序">
|
||||
<a-input-number v-model:value="updateState.order" :min="1" />
|
||||
<a-input-number v-model:value="updateState.order" :min="1" style="width: 100%"/>
|
||||
</a-form-item>
|
||||
<a-form-item name="available" label="状态" :rules="[{ required: true, message: '请选择状态' }]">
|
||||
<a-radio-group v-model:value="updateState.available">
|
||||
@@ -325,7 +325,7 @@
|
||||
<a-input v-model:value="updateState.route_path" placeholder="请输入路由路径" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="updateState.type === 1" name="redirect" label="重定向"
|
||||
:rules="[{ required: updateState.type === 1 ? true : false, message: '请输入重定向' }]">
|
||||
:rules="[{ required: false, message: '请输入重定向' }]">
|
||||
<a-input v-model:value="updateState.redirect" placeholder="请输入重定向" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="updateState.type === 2" name="component_path" label="组件地址"
|
||||
@@ -390,7 +390,7 @@ const createState = reactive<tableDataType>({
|
||||
route_name: '',
|
||||
route_path: '',
|
||||
component_path: '',
|
||||
redirect: '',
|
||||
redirect: null,
|
||||
parent_id: undefined,
|
||||
cache: true,
|
||||
hidden: false,
|
||||
@@ -407,7 +407,7 @@ const updateState = reactive<tableDataType>({
|
||||
route_name: '',
|
||||
route_path: '',
|
||||
component_path: '',
|
||||
redirect: '',
|
||||
redirect: null,
|
||||
parent_id: undefined,
|
||||
cache: true,
|
||||
hidden: false,
|
||||
|
||||
@@ -134,7 +134,7 @@
|
||||
<a-input v-model:value="createState.name" placeholder="请输入名称" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="order" label="排序" :rules="[{ required: true, message: '请输入排序' }]">
|
||||
<a-input-number v-model:value="createState.order" :min="1" />
|
||||
<a-input-number v-model:value="createState.order" :min="1" style="width: 100%"/>
|
||||
</a-form-item>
|
||||
<a-form-item name="available" label="状态" :rules="[{ required: true, message: '请选择状态' }]">
|
||||
<a-radio-group v-model:value="createState.available">
|
||||
@@ -153,7 +153,7 @@
|
||||
<a-input v-model:value="updateState.name" placeholder="请输入名称" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="order" label="排序" :rules="[{ required: true, message: '请输入排序' }]">
|
||||
<a-input-number v-model:value="updateState.order" :min="1" />
|
||||
<a-input-number v-model:value="updateState.order" :min="1" style="width: 100%"/>
|
||||
</a-form-item>
|
||||
<a-form-item name="available" label="状态" :rules="[{ required: true, message: '请选择状态' }]">
|
||||
<a-radio-group v-model:value="updateState.available">
|
||||
|
||||
@@ -143,7 +143,7 @@
|
||||
<a-input v-model:value="createState.name" placeholder="请输入名称" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="order" label="排序" :rules="[{ required: true, message: '请输入排序' }]">
|
||||
<a-input-number v-model:value="createState.order" :min="1" />
|
||||
<a-input-number v-model:value="createState.order" :min="1" style="width: 100%"/>
|
||||
</a-form-item>
|
||||
<a-form-item name="available" label="状态" :rules="[{ required: true, message: '请选择状态' }]">
|
||||
<a-radio-group v-model:value="createState.available">
|
||||
@@ -162,7 +162,7 @@
|
||||
<a-input v-model:value="updateState.name" placeholder="请输入名称" allowClear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="order" label="排序" :rules="[{ required: true, message: '请输入排序' }]">
|
||||
<a-input-number v-model:value="updateState.order" :min="1" />
|
||||
<a-input-number v-model:value="updateState.order" :min="1" style="width: 100%"/>
|
||||
</a-form-item>
|
||||
<a-form-item name="available" label="状态" :rules="[{ required: true, message: '请选择状态' }]">
|
||||
<a-radio-group v-model:value="updateState.available">
|
||||
|
||||
Reference in New Issue
Block a user