初始化
This commit is contained in:
805
saiadmin-artd/src/views/tool/code/components/editInfo.vue
Normal file
805
saiadmin-artd/src/views/tool/code/components/editInfo.vue
Normal file
@@ -0,0 +1,805 @@
|
||||
<template>
|
||||
<el-drawer
|
||||
v-model="visible"
|
||||
:title="`编辑生成信息 - ${record?.table_comment}`"
|
||||
size="100%"
|
||||
destroy-on-close
|
||||
@close="handleClose"
|
||||
>
|
||||
<div v-loading="loading" element-loading-text="加载数据中...">
|
||||
<el-form ref="formRef" :model="form">
|
||||
<el-tabs v-model="activeTab">
|
||||
<!-- 配置信息 Tab -->
|
||||
<el-tab-pane label="配置信息" name="base_config">
|
||||
<el-divider content-position="left">基础信息</el-divider>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="表名称" prop="table_name" label-width="100px">
|
||||
<el-input v-model="form.table_name" disabled />
|
||||
<div class="text-xs text-gray-400 mt-1">
|
||||
数据库表的名称,自动读取数据库表名称
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item
|
||||
label="表描述"
|
||||
prop="table_comment"
|
||||
label-width="100px"
|
||||
:rules="[{ required: true, message: '表描述必填' }]"
|
||||
>
|
||||
<el-input v-model="form.table_comment" />
|
||||
<div class="text-xs text-gray-400 mt-1"> 表的描述,自动读取数据库表注释 </div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item
|
||||
label="实体类"
|
||||
prop="class_name"
|
||||
label-width="100px"
|
||||
:rules="[{ required: true, message: '实体类必填' }]"
|
||||
>
|
||||
<el-input v-model="form.class_name" />
|
||||
<div class="text-xs text-gray-400 mt-1"> 生成的实体类名称,可以修改去掉前缀 </div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="8">
|
||||
<el-form-item
|
||||
label="业务名称"
|
||||
prop="business_name"
|
||||
label-width="100px"
|
||||
:rules="[{ required: true, message: '业务名称必填' }]"
|
||||
>
|
||||
<el-input v-model="form.business_name" />
|
||||
<div class="text-xs text-gray-400 mt-1"> 英文、业务名称、同一个分组包下唯一 </div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="数据源" prop="source" label-width="100px">
|
||||
<el-select v-model="form.source" placeholder="请选择数据源" style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in dataSourceList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
<div class="text-xs text-gray-400 mt-1"> 数据库配置文件中配置的数据源 </div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="备注信息" prop="remark" label-width="100px">
|
||||
<el-input v-model="form.remark" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-divider content-position="left">生成信息</el-divider>
|
||||
<el-row :gutter="24">
|
||||
<el-col :xs="24" :md="8" :xl="8">
|
||||
<el-form-item
|
||||
label="应用类型"
|
||||
prop="template"
|
||||
label-width="100px"
|
||||
:rules="[{ required: true, message: '应用类型必选' }]"
|
||||
>
|
||||
<el-select
|
||||
v-model="form.template"
|
||||
placeholder="请选择生成模板"
|
||||
style="width: 100%"
|
||||
clearable
|
||||
>
|
||||
<el-option label="webman应用[app]" value="app" />
|
||||
<el-option label="webman插件[plugin]" value="plugin" />
|
||||
</el-select>
|
||||
<div class="text-xs text-gray-400 mt-1"
|
||||
>默认app模板,生成文件放app目录下,plugin应用需要先手动初始化</div
|
||||
>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :md="8" :xl="8">
|
||||
<el-form-item
|
||||
label="应用名称"
|
||||
prop="namespace"
|
||||
label-width="100px"
|
||||
:rules="[{ required: true, message: '应用名称必填' }]"
|
||||
>
|
||||
<el-input v-model="form.namespace" />
|
||||
<div class="text-xs text-gray-400 mt-1">
|
||||
plugin插件名称, 或者app下应用名称, 禁止使用saiadmin
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :md="8" :xl="8">
|
||||
<el-form-item
|
||||
label="分组包名"
|
||||
prop="package_name"
|
||||
label-width="100px"
|
||||
:rules="[{ required: true, message: '分组包名必填' }]"
|
||||
>
|
||||
<el-input v-model="form.package_name" placeholder="请输入分组包名" clearable />
|
||||
<div class="text-xs text-gray-400 mt-1">
|
||||
生成的文件放在分组包名目录下,功能模块分组
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="24">
|
||||
<el-col :xs="24" :md="8" :xl="8">
|
||||
<el-form-item
|
||||
label="生成类型"
|
||||
prop="tpl_category"
|
||||
label-width="100px"
|
||||
:rules="[{ required: true, message: '生成类型必填' }]"
|
||||
>
|
||||
<el-select
|
||||
v-model="form.tpl_category"
|
||||
placeholder="请选择所属模块"
|
||||
style="width: 100%"
|
||||
clearable
|
||||
>
|
||||
<el-option label="单表CRUD" value="single" />
|
||||
<el-option label="树表CRUD" value="tree" />
|
||||
</el-select>
|
||||
<div class="text-xs text-gray-400 mt-1">
|
||||
单表须有主键,树表须指定id、parent_id、name等字段
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :md="8" :xl="8">
|
||||
<el-form-item
|
||||
label="生成路径"
|
||||
prop="generate_path"
|
||||
label-width="100px"
|
||||
:rules="[{ required: true, message: '生成路径必填' }]"
|
||||
>
|
||||
<el-input v-model="form.generate_path" />
|
||||
<div class="text-xs text-gray-400 mt-1">
|
||||
前端根目录文件夹名称,必须与后端根目录同级
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :md="8" :xl="8">
|
||||
<el-form-item label="模型类型" prop="stub" label-width="100px">
|
||||
<div class="flex-col">
|
||||
<el-radio-group v-model="form.stub">
|
||||
<el-radio value="think">ThinkOrm</el-radio>
|
||||
<el-radio value="eloquent">EloquentORM</el-radio>
|
||||
</el-radio-group>
|
||||
<div class="text-xs text-gray-400 mt-1">生成不同驱动模型的代码</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="24">
|
||||
<el-col :xs="24" :md="8" :xl="8">
|
||||
<el-form-item label="所属菜单" prop="belong_menu_id" label-width="100px">
|
||||
<el-cascader
|
||||
v-model="form.belong_menu_id"
|
||||
:options="menus"
|
||||
:props="{
|
||||
expandTrigger: 'hover',
|
||||
checkStrictly: true,
|
||||
value: 'id',
|
||||
label: 'label'
|
||||
}"
|
||||
style="width: 100%"
|
||||
placeholder="生成功能所属菜单"
|
||||
clearable
|
||||
/>
|
||||
<div class="text-xs text-gray-400 mt-1">
|
||||
默认为工具菜单栏目下的子菜单。不选择则为顶级菜单栏目
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :md="8" :xl="8">
|
||||
<el-form-item
|
||||
label="菜单名称"
|
||||
prop="menu_name"
|
||||
label-width="100px"
|
||||
:rules="[{ required: true, message: '菜单名称必选' }]"
|
||||
>
|
||||
<el-input v-model="form.menu_name" placeholder="请输入菜单名称" clearable />
|
||||
<div class="text-xs text-gray-400 mt-1">
|
||||
显示在菜单栏目上的菜单名称、以及代码中的业务功能名称
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="表单效果" prop="component_type" label-width="100px">
|
||||
<div class="flex-col">
|
||||
<el-radio-group v-model="form.component_type">
|
||||
<el-radio-button :value="1">弹出框</el-radio-button>
|
||||
<el-radio-button :value="2">抽屉</el-radio-button>
|
||||
</el-radio-group>
|
||||
<div class="text-xs text-gray-400 mt-1">表单显示方式</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="表单宽度" prop="form_width" label-width="100px">
|
||||
<div class="flex-col">
|
||||
<el-input-number v-model="form.form_width" :min="200" :max="10000" />
|
||||
<div class="text-xs text-gray-400 mt-1">表单组件的宽度,单位为px</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="表单全屏" prop="is_full" label-width="100px">
|
||||
<div class="flex-col">
|
||||
<el-radio-group v-model="form.is_full">
|
||||
<el-radio :value="1">否</el-radio>
|
||||
<el-radio :value="2">是</el-radio>
|
||||
</el-radio-group>
|
||||
<div class="text-xs text-gray-400 mt-1">编辑表单是否全屏</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 树表配置 -->
|
||||
<template v-if="form.tpl_category === 'tree'">
|
||||
<el-divider content-position="left">树表配置</el-divider>
|
||||
<el-row :gutter="24">
|
||||
<el-col :xs="24" :md="8" :xl="8">
|
||||
<el-form-item label="树主ID" prop="tree_id" label-width="100px">
|
||||
<el-select
|
||||
v-model="formOptions.tree_id"
|
||||
placeholder="请选择树表的主ID"
|
||||
style="width: 100%"
|
||||
clearable
|
||||
filterable
|
||||
>
|
||||
<el-option
|
||||
v-for="(item, index) in form.columns"
|
||||
:key="index"
|
||||
:label="`${item.column_name} - ${item.column_comment}`"
|
||||
:value="item.column_name"
|
||||
/>
|
||||
</el-select>
|
||||
<div class="text-xs text-gray-400 mt-1">指定树表的主要ID,一般为主键</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :md="8" :xl="8">
|
||||
<el-form-item label="树父ID" prop="tree_parent_id" label-width="100px">
|
||||
<el-select
|
||||
v-model="formOptions.tree_parent_id"
|
||||
placeholder="请选择树表的父ID"
|
||||
style="width: 100%"
|
||||
clearable
|
||||
filterable
|
||||
>
|
||||
<el-option
|
||||
v-for="(item, index) in form.columns"
|
||||
:key="index"
|
||||
:label="`${item.column_name} - ${item.column_comment}`"
|
||||
:value="item.column_name"
|
||||
/>
|
||||
</el-select>
|
||||
<div class="text-xs text-gray-400 mt-1">指定树表的父ID,比如:parent_id</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :md="8" :xl="8">
|
||||
<el-form-item label="树名称" prop="tree_name" label-width="100px">
|
||||
<el-select
|
||||
v-model="formOptions.tree_name"
|
||||
placeholder="请选择树表的名称字段"
|
||||
style="width: 100%"
|
||||
clearable
|
||||
filterable
|
||||
>
|
||||
<el-option
|
||||
v-for="(item, index) in form.columns"
|
||||
:key="index"
|
||||
:label="`${item.column_name} - ${item.column_comment}`"
|
||||
:value="item.column_name"
|
||||
/>
|
||||
</el-select>
|
||||
<div class="text-xs text-gray-400 mt-1">指定树显示的名称字段,比如:name</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 字段配置 Tab -->
|
||||
<el-tab-pane label="字段配置" name="field_config">
|
||||
<el-table :data="form.columns" max-height="750">
|
||||
<el-table-column prop="sort" label="排序" width="150">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.sort"
|
||||
style="width: 100px"
|
||||
controls-position="right"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="column_name"
|
||||
label="字段名称"
|
||||
width="160"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="column_comment" label="字段描述" width="160">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.column_comment" clearable />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="column_type" label="物理类型" width="100" />
|
||||
<el-table-column prop="is_required" label="必填" width="80" align="center">
|
||||
<template #header>
|
||||
<div class="flex-c justify-center items-center gap-1">
|
||||
<span>必填</span>
|
||||
<el-checkbox @change="(val) => handlerAll(val, 'required')" />
|
||||
</div>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<el-checkbox v-model="row.is_required" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="is_insert" label="表单" width="80" align="center">
|
||||
<template #header>
|
||||
<div class="flex-c justify-center items-center gap-1">
|
||||
<span>表单</span>
|
||||
<el-checkbox @change="(val) => handlerAll(val, 'insert')" />
|
||||
</div>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<el-checkbox v-model="row.is_insert" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="is_list" label="列表" width="80" align="center">
|
||||
<template #header>
|
||||
<div class="flex-c justify-center items-center gap-1">
|
||||
<span>列表</span>
|
||||
<el-checkbox @change="(val) => handlerAll(val, 'list')" />
|
||||
</div>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<el-checkbox v-model="row.is_list" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="is_query" label="查询" width="80" align="center">
|
||||
<template #header>
|
||||
<div class="flex-c justify-center items-center gap-1">
|
||||
<span>查询</span>
|
||||
<el-checkbox @change="(val) => handlerAll(val, 'query')" />
|
||||
</div>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<el-checkbox v-model="row.is_query" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="is_sort" label="排序" width="80" align="center">
|
||||
<template #header>
|
||||
<div class="flex-c justify-center items-center gap-1">
|
||||
<span>排序</span>
|
||||
<el-checkbox @change="(val) => handlerAll(val, 'sort')" />
|
||||
</div>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<el-checkbox v-model="row.is_sort" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="query_type" label="查询方式" width="150">
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.query_type" clearable>
|
||||
<el-option
|
||||
v-for="item in queryType"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="view_type" label="页面控件">
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center gap-2">
|
||||
<el-select
|
||||
v-model="row.view_type"
|
||||
style="width: 140px"
|
||||
@change="changeViewType(row)"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
v-for="item in viewComponent"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-link
|
||||
v-if="notNeedSettingComponents.includes(row.view_type)"
|
||||
@click="settingComponentRef.open(row)"
|
||||
>
|
||||
设置
|
||||
</el-link>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="dict_type" label="数据字典">
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
v-model="row.dict_type"
|
||||
clearable
|
||||
placeholder="选择数据字典"
|
||||
:disabled="!['saSelect', 'radio', 'checkbox'].includes(row.view_type)"
|
||||
>
|
||||
<el-option
|
||||
v-for="(item, key) in dictStore.dictList"
|
||||
:key="key"
|
||||
:label="key"
|
||||
:value="key"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 关联配置 Tab -->
|
||||
<el-tab-pane label="关联配置" name="relation_config">
|
||||
<el-alert type="info" :closable="false">
|
||||
模型关联支持:一对一、一对多、一对一(反向)、多对多。
|
||||
</el-alert>
|
||||
|
||||
<el-button type="primary" class="mt-4 mb-4" @click="addRelation">
|
||||
<template #icon>
|
||||
<ArtSvgIcon icon="ri:add-line" />
|
||||
</template>
|
||||
新增关联
|
||||
</el-button>
|
||||
|
||||
<div v-for="(item, index) in formOptions.relations" :key="index">
|
||||
<el-divider content-position="left">
|
||||
{{ item.name ? item.name : '定义新关联' }}
|
||||
<el-link type="danger" class="ml-5" @click="delRelation(index)">
|
||||
<ArtSvgIcon icon="ri:delete-bin-line" class="mr-1" />
|
||||
删除定义
|
||||
</el-link>
|
||||
</el-divider>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="关联类型" label-width="100px">
|
||||
<el-select
|
||||
v-model="item.type"
|
||||
placeholder="请选择关联类型"
|
||||
clearable
|
||||
filterable
|
||||
>
|
||||
<el-option
|
||||
v-for="types in relationsType"
|
||||
:key="types.value"
|
||||
:label="types.name"
|
||||
:value="types.value"
|
||||
/>
|
||||
</el-select>
|
||||
<div class="text-xs text-gray-400 mt-1">指定关联类型</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="关联名称" label-width="100px">
|
||||
<el-input v-model="item.name" placeholder="设置关联名称" clearable />
|
||||
<div class="text-xs text-gray-400 mt-1">属性名称,代码中with调用的名称</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="8">
|
||||
<el-form-item label="关联模型" label-width="100px">
|
||||
<el-input v-model="item.model" placeholder="设置关联模型" clearable />
|
||||
<div class="text-xs text-gray-400 mt-1">选择要关联的实体模型</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item
|
||||
:label="
|
||||
item.type === 'belongsTo'
|
||||
? '外键'
|
||||
: item.type === 'belongsToMany'
|
||||
? '外键'
|
||||
: '当前模型主键'
|
||||
"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-input v-model="item.localKey" placeholder="设置键名" clearable />
|
||||
<div class="text-xs text-gray-400 mt-1">
|
||||
{{
|
||||
item.type === 'belongsTo'
|
||||
? '关联模型_id'
|
||||
: item.type === 'belongsToMany'
|
||||
? '关联模型_id'
|
||||
: '当前模型主键'
|
||||
}}
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col v-show="item.type === 'belongsToMany'" :span="8">
|
||||
<el-form-item label="中间模型" label-width="100px">
|
||||
<el-input v-model="item.table" placeholder="请输入中间模型" clearable />
|
||||
<div class="text-xs text-gray-400 mt-1">多对多关联的中间模型</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item
|
||||
:label="item.type === 'belongsTo' ? '关联主键' : '外键'"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-input v-model="item.foreignKey" placeholder="设置键名" clearable />
|
||||
<div class="text-xs text-gray-400 mt-1">
|
||||
{{ item.type === 'belongsTo' ? '关联模型主键' : '当前模型_id' }}
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 设置组件弹窗 -->
|
||||
<SettingComponent ref="settingComponentRef" @confirm="confirmSetting" />
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" :loading="submitLoading" @click="save">保存</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ElMessage } from 'element-plus'
|
||||
import type { CheckboxValueType, FormInstance } from 'element-plus'
|
||||
import { useDictStore } from '@/store/modules/dict'
|
||||
|
||||
// 接口导入
|
||||
import generate from '@/api/tool/generate'
|
||||
import database from '@/api/safeguard/database'
|
||||
import menuApi from '@/api/system/menu'
|
||||
|
||||
import SettingComponent from './settingComponent.vue'
|
||||
|
||||
// 导入变量
|
||||
import { relationsType, queryType, viewComponent } from '../js/vars'
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean
|
||||
data?: Record<string, any>
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:modelValue', value: boolean): void
|
||||
(e: 'success'): void
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: false,
|
||||
data: undefined
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const dictStore = useDictStore()
|
||||
|
||||
const record = ref<any>({})
|
||||
const loading = ref(true)
|
||||
const submitLoading = ref(false)
|
||||
const activeTab = ref('base_config')
|
||||
const formRef = ref<FormInstance>()
|
||||
const settingComponentRef = ref()
|
||||
|
||||
const notNeedSettingComponents = ref([
|
||||
'uploadFile',
|
||||
'uploadImage',
|
||||
'imagePicker',
|
||||
'chunkUpload',
|
||||
'editor',
|
||||
'date',
|
||||
'userSelect'
|
||||
])
|
||||
|
||||
const form = ref<any>({
|
||||
generate_menus: ['index', 'save', 'update', 'read', 'destroy'],
|
||||
columns: []
|
||||
})
|
||||
|
||||
// form扩展组
|
||||
const formOptions = ref<any>({
|
||||
relations: []
|
||||
})
|
||||
// 菜单列表
|
||||
const menus = ref<any[]>([])
|
||||
// 数据源
|
||||
const dataSourceList = ref<{ label: string; value: string }[]>([])
|
||||
|
||||
/**
|
||||
* 弹窗显示状态双向绑定
|
||||
*/
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit('update:modelValue', value)
|
||||
})
|
||||
|
||||
/**
|
||||
* 监听弹窗打开,初始化表单数据
|
||||
*/
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
initPage()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* 初始化页面数据
|
||||
*/
|
||||
const initPage = async () => {
|
||||
loading.value = true
|
||||
// 获取数据源
|
||||
const data = await database.getDataSource()
|
||||
dataSourceList.value = data.map((item: any) => ({
|
||||
label: item,
|
||||
value: item
|
||||
}))
|
||||
const response = await generate.read({ id: props.data?.id })
|
||||
record.value = response
|
||||
initForm()
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置组件确认
|
||||
*/
|
||||
const confirmSetting = (name: string, value: any) => {
|
||||
form.value.columns.find((item: any, idx: number) => {
|
||||
if (item.column_name === name) {
|
||||
form.value.columns[idx].options = value
|
||||
}
|
||||
})
|
||||
ElMessage.success('组件设置成功')
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换页面控件类型
|
||||
*/
|
||||
const changeViewType = (record: any) => {
|
||||
if (
|
||||
record.view_type === 'uploadImage' ||
|
||||
record.view_type === 'imagePicker' ||
|
||||
record.view_type === 'uploadFile' ||
|
||||
record.view_type === 'chunkUpload'
|
||||
) {
|
||||
record.options = { multiple: false, limit: 1 }
|
||||
} else if (record.view_type === 'editor') {
|
||||
record.options = { height: 400 }
|
||||
} else if (record.view_type === 'date') {
|
||||
record.options = { mode: 'date' }
|
||||
} else if (record.view_type === 'userSelect') {
|
||||
record.options = { multiple: false }
|
||||
} else {
|
||||
record.options = {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存
|
||||
*/
|
||||
const save = async () => {
|
||||
if (form.value.namespace === 'saiadmin') {
|
||||
ElMessage.error('应用名称不能为saiadmin')
|
||||
return
|
||||
}
|
||||
|
||||
const validResult = await formRef.value?.validate().catch((err) => err)
|
||||
if (validResult !== true) {
|
||||
return
|
||||
}
|
||||
|
||||
submitLoading.value = true
|
||||
try {
|
||||
form.value.options = formOptions.value
|
||||
await generate.update({ ...form.value })
|
||||
ElMessage.success('更新成功')
|
||||
emit('success')
|
||||
handleClose()
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 全选 / 全不选
|
||||
*/
|
||||
const handlerAll = (value: CheckboxValueType, type: string) => {
|
||||
form.value.columns.forEach((item: any) => {
|
||||
item['is_' + type] = value
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增关联定义
|
||||
*/
|
||||
const addRelation = () => {
|
||||
formOptions.value.relations.push({
|
||||
name: '',
|
||||
type: 'hasOne',
|
||||
model: '',
|
||||
foreignKey: '',
|
||||
localKey: '',
|
||||
table: ''
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除关联定义
|
||||
*/
|
||||
const delRelation = (idx: number | string) => {
|
||||
formOptions.value.relations.splice(idx, 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化数据
|
||||
*/
|
||||
const initForm = () => {
|
||||
// 设置form数据
|
||||
for (const name in record.value) {
|
||||
if (name === 'generate_menus') {
|
||||
form.value[name] = record.value[name] ? record.value[name].split(',') : []
|
||||
} else {
|
||||
form.value[name] = record.value[name]
|
||||
}
|
||||
}
|
||||
|
||||
if (record.value.options && record.value.options.relations) {
|
||||
formOptions.value.relations = record.value.options.relations
|
||||
} else {
|
||||
formOptions.value.relations = []
|
||||
}
|
||||
|
||||
if (record.value.tpl_category === 'tree') {
|
||||
formOptions.value.tree_id = record.value.options.tree_id
|
||||
formOptions.value.tree_name = record.value.options.tree_name
|
||||
formOptions.value.tree_parent_id = record.value.options.tree_parent_id
|
||||
}
|
||||
|
||||
// 请求表字段
|
||||
generate.getTableColumns({ table_id: record.value.id }).then((data: any) => {
|
||||
form.value.columns = []
|
||||
data.forEach((item: any) => {
|
||||
item.is_required = item.is_required === 2 ? true : false
|
||||
item.is_insert = item.is_insert === 2 ? true : false
|
||||
item.is_edit = item.is_edit === 2 ? true : false
|
||||
item.is_list = item.is_list === 2 ? true : false
|
||||
item.is_query = item.is_query === 2 ? true : false
|
||||
item.is_sort = item.is_sort === 2 ? true : false
|
||||
form.value.columns.push(item)
|
||||
})
|
||||
})
|
||||
|
||||
// 请求菜单列表
|
||||
menuApi.list({ tree: true, menu: true }).then((data: any) => {
|
||||
menus.value = data
|
||||
menus.value.unshift({ id: 0, value: 0, label: '顶级菜单' })
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭弹窗
|
||||
*/
|
||||
const handleClose = () => {
|
||||
visible.value = false
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
228
saiadmin-artd/src/views/tool/code/components/loadTable.vue
Normal file
228
saiadmin-artd/src/views/tool/code/components/loadTable.vue
Normal file
@@ -0,0 +1,228 @@
|
||||
<template>
|
||||
<el-drawer
|
||||
v-model="visible"
|
||||
title="装载数据表"
|
||||
size="70%"
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
@close="handleClose"
|
||||
>
|
||||
<div class="art-full-height">
|
||||
<el-alert type="info" :closable="false">
|
||||
<template #title>
|
||||
<div>1、支持配置多数据源;</div>
|
||||
<div>
|
||||
2、载入表[sa_shop_category]会自动处理为[SaShopCategory]类,可以编辑对类名进行修改[ShopCategory]
|
||||
</div>
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
<div class="flex justify-between items-center mt-4">
|
||||
<ElSpace wrap>
|
||||
<el-select v-model="searchForm.source" placeholder="切换数据源" style="width: 200px">
|
||||
<el-option
|
||||
v-for="item in dataSourceList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-input
|
||||
v-model="searchForm.name"
|
||||
placeholder="请输入数据表名称"
|
||||
style="width: 300px"
|
||||
clearable
|
||||
/>
|
||||
</ElSpace>
|
||||
<ElSpace wrap>
|
||||
<ElButton class="reset-button" @click="handleReset" v-ripple>
|
||||
<template #icon>
|
||||
<ArtSvgIcon icon="ri:reset-right-line" />
|
||||
</template>
|
||||
重置
|
||||
</ElButton>
|
||||
<ElButton type="primary" class="search-button" @click="handleSearch" v-ripple>
|
||||
<template #icon>
|
||||
<ArtSvgIcon icon="ri:search-line" />
|
||||
</template>
|
||||
查询
|
||||
</ElButton>
|
||||
</ElSpace>
|
||||
</div>
|
||||
<ElCard class="art-table-card" shadow="never">
|
||||
<div>
|
||||
<ElSpace wrap>
|
||||
<ElButton :disabled="selectedRows.length === 0" @click="handleLoadTable" v-ripple>
|
||||
<template #icon>
|
||||
<ArtSvgIcon icon="ri:check-fill" />
|
||||
</template>
|
||||
确认选择
|
||||
</ElButton>
|
||||
</ElSpace>
|
||||
</div>
|
||||
<!-- 表格 -->
|
||||
<ArtTable
|
||||
ref="tableRef"
|
||||
rowKey="name"
|
||||
:loading="loading"
|
||||
:data="tableData"
|
||||
:columns="columns"
|
||||
:pagination="pagination"
|
||||
@sort-change="handleSortChange"
|
||||
@selection-change="handleSelectionChange"
|
||||
@pagination:size-change="handleSizeChange"
|
||||
@pagination:current-change="handleCurrentChange"
|
||||
/>
|
||||
</ElCard>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ElMessage } from 'element-plus'
|
||||
import api from '@/api/safeguard/database'
|
||||
import { useTable } from '@/hooks/core/useTable'
|
||||
import generate from '@/api/tool/generate'
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean
|
||||
data?: Record<string, any>
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:modelValue', value: boolean): void
|
||||
(e: 'success'): void
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: false,
|
||||
data: undefined
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const selectedRows = ref<Record<string, any>[]>([])
|
||||
const dataSourceList = ref<{ label: string; value: string }[]>([])
|
||||
const searchForm = ref({
|
||||
name: '',
|
||||
source: ''
|
||||
})
|
||||
|
||||
/**
|
||||
* 弹窗显示状态双向绑定
|
||||
*/
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit('update:modelValue', value)
|
||||
})
|
||||
|
||||
/**
|
||||
* 监听弹窗打开,初始化表单数据
|
||||
*/
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
initPage()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* 初始化页面数据
|
||||
*/
|
||||
const initPage = async () => {
|
||||
const response = await api.getDataSource()
|
||||
dataSourceList.value = response.map((item: any) => ({
|
||||
label: item,
|
||||
value: item
|
||||
}))
|
||||
searchForm.value.source = dataSourceList.value[0]?.value || ''
|
||||
refreshData()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表格数据
|
||||
*/
|
||||
const refreshData = () => {
|
||||
Object.assign(searchParams, searchForm.value)
|
||||
getData()
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索
|
||||
*/
|
||||
const handleSearch = () => {
|
||||
refreshData()
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置
|
||||
*/
|
||||
const handleReset = () => {
|
||||
searchForm.value.name = ''
|
||||
refreshData()
|
||||
}
|
||||
|
||||
// 表格行选择变化
|
||||
const handleSelectionChange = (selection: Record<string, any>[]): void => {
|
||||
selectedRows.value = selection
|
||||
}
|
||||
|
||||
// 确认选择装载数据表
|
||||
const handleLoadTable = async () => {
|
||||
if (selectedRows.value.length < 1) {
|
||||
ElMessage.info('至少要选择一条数据')
|
||||
return
|
||||
}
|
||||
const names = selectedRows.value.map((item) => ({
|
||||
name: item.name,
|
||||
comment: item.comment,
|
||||
sourceName: item.name
|
||||
}))
|
||||
|
||||
await generate.loadTable({
|
||||
source: searchForm.value.source,
|
||||
names
|
||||
})
|
||||
ElMessage.success('装载成功')
|
||||
emit('success')
|
||||
handleClose()
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭弹窗
|
||||
*/
|
||||
const handleClose = () => {
|
||||
visible.value = false
|
||||
selectedRows.value = []
|
||||
}
|
||||
|
||||
const {
|
||||
loading,
|
||||
data: tableData,
|
||||
columns,
|
||||
getData,
|
||||
pagination,
|
||||
searchParams,
|
||||
handleSortChange,
|
||||
handleSizeChange,
|
||||
handleCurrentChange
|
||||
} = useTable({
|
||||
core: {
|
||||
apiFn: api.list,
|
||||
immediate: false,
|
||||
apiParams: {
|
||||
...searchForm.value
|
||||
},
|
||||
columnsFactory: () => [
|
||||
{ type: 'selection' },
|
||||
{ prop: 'name', label: '表名称' },
|
||||
{ prop: 'comment', label: '表注释' },
|
||||
{ prop: 'engine', label: '引擎' },
|
||||
{ prop: 'collation', label: '编码' },
|
||||
{ prop: 'create_time', label: '创建时间' }
|
||||
]
|
||||
}
|
||||
})
|
||||
</script>
|
||||
111
saiadmin-artd/src/views/tool/code/components/preview.vue
Normal file
111
saiadmin-artd/src/views/tool/code/components/preview.vue
Normal file
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<el-drawer v-model="visible" title="预览代码" size="100%" destroy-on-close @close="handleClose">
|
||||
<el-tabs v-model="activeTab" type="card">
|
||||
<el-tab-pane
|
||||
v-for="item in previewCode"
|
||||
:key="item.name"
|
||||
:label="item.tab_name"
|
||||
:name="item.name"
|
||||
>
|
||||
<div class="relative">
|
||||
<SaCode :code="item.code" :language="item.lang" />
|
||||
<el-button class="copy-button" type="primary" @click="handleCopy(item.code)">
|
||||
<template #icon>
|
||||
<ArtSvgIcon icon="ri:file-copy-line" />
|
||||
</template>
|
||||
复制
|
||||
</el-button>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import generate from '@/api/tool/generate'
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean
|
||||
data?: Record<string, any>
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:modelValue', value: boolean): void
|
||||
(e: 'success'): void
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: false,
|
||||
data: undefined
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const activeTab = ref('controller')
|
||||
const previewCode = ref<any[]>([])
|
||||
|
||||
/**
|
||||
* 弹窗显示状态双向绑定
|
||||
*/
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit('update:modelValue', value)
|
||||
})
|
||||
|
||||
/**
|
||||
* 监听弹窗打开,初始化表单数据
|
||||
*/
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
initPage()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* 打开弹窗
|
||||
*/
|
||||
const initPage = async () => {
|
||||
try {
|
||||
const response = await generate.preview({ id: props.data?.id })
|
||||
previewCode.value = response
|
||||
activeTab.value = previewCode.value[0]?.name || 'controller'
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
handleClose()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭弹窗
|
||||
*/
|
||||
const handleClose = () => {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制代码到剪贴板
|
||||
*/
|
||||
const { copy } = useClipboard()
|
||||
const handleCopy = async (code: string) => {
|
||||
try {
|
||||
await copy(code)
|
||||
ElMessage.success('代码已复制到剪贴板')
|
||||
} catch {
|
||||
ElMessage.error('复制失败,请手动复制')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.copy-button {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 0px;
|
||||
z-index: 999;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="`设置组件 - ${row?.column_comment}`"
|
||||
width="600px"
|
||||
draggable
|
||||
destroy-on-close
|
||||
@close="handleClose"
|
||||
>
|
||||
<el-form :model="form" label-width="120px">
|
||||
<!-- 编辑器相关 -->
|
||||
<template v-if="row.view_type === 'editor'">
|
||||
<el-form-item label="编辑器高度" prop="height">
|
||||
<el-input-number v-model="form.height" :max="1000" :min="100" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 上传、资源选择器相关 -->
|
||||
<template
|
||||
v-if="['uploadImage', 'imagePicker', 'uploadFile', 'chunkUpload'].includes(row.view_type)"
|
||||
>
|
||||
<el-form-item label="是否多选" prop="multiple">
|
||||
<el-radio-group v-model="form.multiple">
|
||||
<el-radio :value="true">是</el-radio>
|
||||
<el-radio :value="false">否</el-radio>
|
||||
</el-radio-group>
|
||||
<div class="text-xs text-gray-400 ml-2">多个文件必须选是,字段自动处理为数组</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="数量限制" prop="limit">
|
||||
<el-input-number v-model="form.limit" :max="10" :min="1" />
|
||||
<div class="text-xs text-gray-400 ml-2">限制上传数量</div>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 用户选择器 -->
|
||||
<template v-if="row.view_type === 'userSelect'">
|
||||
<el-form-item label="是否多选" prop="multiple">
|
||||
<el-radio-group v-model="form.multiple">
|
||||
<el-radio :value="true">是</el-radio>
|
||||
<el-radio :value="false">否</el-radio>
|
||||
</el-radio-group>
|
||||
<div class="text-xs text-gray-400 ml-2">多个用户,字段自动处理为数组</div>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 日期、时间选择器 -->
|
||||
<template v-if="['date'].includes(row.view_type)">
|
||||
<el-form-item label="选择器类型" prop="mode">
|
||||
<el-select v-model="form.mode" clearable>
|
||||
<el-option label="日期选择器" value="date" />
|
||||
<el-option label="日期时间择器" value="datetime" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" @click="save">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const emit = defineEmits<{
|
||||
(e: 'confirm', name: string, value: any): void
|
||||
}>()
|
||||
|
||||
const visible = ref(false)
|
||||
const row = ref<any>({})
|
||||
const form = ref<any>({})
|
||||
|
||||
/**
|
||||
* 打开弹窗
|
||||
*/
|
||||
const open = (record: any) => {
|
||||
row.value = record
|
||||
if (
|
||||
record.view_type === 'uploadImage' ||
|
||||
record.view_type === 'imagePicker' ||
|
||||
record.view_type === 'uploadFile' ||
|
||||
record.view_type === 'chunkUpload'
|
||||
) {
|
||||
form.value = record.options ? { ...record.options } : { multiple: false }
|
||||
} else if (record.view_type === 'editor') {
|
||||
form.value = record.options ? { ...record.options } : { height: 400 }
|
||||
} else if (record.view_type === 'date' || record.view_type === 'datetime') {
|
||||
form.value = record.options ? { ...record.options } : { mode: record.view_type }
|
||||
} else if (record.view_type === 'userSelect') {
|
||||
form.value = record.options ? { ...record.options } : { multiple: false }
|
||||
} else {
|
||||
form.value = record.options ? { ...record.options } : {}
|
||||
}
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存
|
||||
*/
|
||||
const save = () => {
|
||||
emit('confirm', row.value.column_name, form.value)
|
||||
handleClose()
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭弹窗
|
||||
*/
|
||||
const handleClose = () => {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
272
saiadmin-artd/src/views/tool/code/index.vue
Normal file
272
saiadmin-artd/src/views/tool/code/index.vue
Normal file
@@ -0,0 +1,272 @@
|
||||
<template>
|
||||
<div class="art-full-height">
|
||||
<!-- 搜索面板 -->
|
||||
<TableSearch v-model="searchForm" @search="handleSearch" @reset="resetSearchParams" />
|
||||
|
||||
<ElCard class="art-table-card" shadow="never">
|
||||
<!-- 表格头部 -->
|
||||
<ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
|
||||
<template #left>
|
||||
<ElSpace wrap>
|
||||
<ElButton v-permission="'core:database:index'" @click="showTableDialog('add')" v-ripple>
|
||||
<template #icon>
|
||||
<ArtSvgIcon icon="ri:upload-2-line" />
|
||||
</template>
|
||||
装载
|
||||
</ElButton>
|
||||
<ElButton
|
||||
v-permission="'tool:code:edit'"
|
||||
:disabled="selectedRows.length === 0"
|
||||
@click="batchGenerate"
|
||||
v-ripple
|
||||
>
|
||||
<template #icon>
|
||||
<ArtSvgIcon icon="ri:download-2-line" />
|
||||
</template>
|
||||
生成
|
||||
</ElButton>
|
||||
<ElButton
|
||||
v-permission="'tool:code:edit'"
|
||||
:disabled="selectedRows.length === 0"
|
||||
@click="deleteSelectedRows(api.delete, refreshData)"
|
||||
v-ripple
|
||||
>
|
||||
<template #icon>
|
||||
<ArtSvgIcon icon="ri:delete-bin-5-line" />
|
||||
</template>
|
||||
删除
|
||||
</ElButton>
|
||||
</ElSpace>
|
||||
</template>
|
||||
</ArtTableHeader>
|
||||
|
||||
<!-- 表格 -->
|
||||
<ArtTable
|
||||
rowKey="id"
|
||||
:loading="loading"
|
||||
:data="data"
|
||||
:columns="columns"
|
||||
:pagination="pagination"
|
||||
@sort-change="handleSortChange"
|
||||
@selection-change="handleSelectionChange"
|
||||
@pagination:size-change="handleSizeChange"
|
||||
@pagination:current-change="handleCurrentChange"
|
||||
>
|
||||
<!-- 生成类型列 -->
|
||||
<template #tpl_category="{ row }">
|
||||
<el-tag v-if="row.tpl_category === 'single'" type="success">单表CRUD</el-tag>
|
||||
<el-tag v-else type="danger">树表CRUD</el-tag>
|
||||
</template>
|
||||
|
||||
<!-- 操作列 -->
|
||||
<template #operation="{ row }">
|
||||
<div class="flex gap-2">
|
||||
<SaButton
|
||||
v-permission="'tool:code:edit'"
|
||||
type="secondary"
|
||||
icon="ri:eye-line"
|
||||
@click="showDialog('edit', row)"
|
||||
/>
|
||||
<SaButton
|
||||
v-permission="'tool:code:edit'"
|
||||
type="primary"
|
||||
icon="ri:refresh-line"
|
||||
@click="syncTable(row.id)"
|
||||
/>
|
||||
<SaButton
|
||||
v-permission="'tool:code:edit'"
|
||||
type="secondary"
|
||||
@click="showEditDialog('edit', row)"
|
||||
/>
|
||||
<SaButton
|
||||
v-permission="'tool:code:edit'"
|
||||
type="error"
|
||||
@click="deleteRow(row, api.delete, refreshData)"
|
||||
/>
|
||||
<ElDropdown>
|
||||
<ArtIconButton
|
||||
icon="ri:more-2-fill"
|
||||
class="!size-8 bg-g-200 dark:bg-g-300/45 text-sm"
|
||||
/>
|
||||
<template #dropdown>
|
||||
<ElDropdownMenu>
|
||||
<ElDropdownItem>
|
||||
<div
|
||||
v-permission="'tool:code:edit'"
|
||||
class="flex-c gap-2"
|
||||
@click="generateFile(row.id)"
|
||||
>
|
||||
<ArtSvgIcon icon="ri:folder-add-line" />
|
||||
<span>生成到项目</span>
|
||||
</div>
|
||||
</ElDropdownItem>
|
||||
<ElDropdownItem>
|
||||
<div
|
||||
v-permission="'tool:code:edit'"
|
||||
class="flex-c gap-2"
|
||||
@click="generateCode(row.id)"
|
||||
>
|
||||
<ArtSvgIcon icon="ri:download-line" />
|
||||
<span>代码下载</span>
|
||||
</div>
|
||||
</ElDropdownItem>
|
||||
</ElDropdownMenu>
|
||||
</template>
|
||||
</ElDropdown>
|
||||
</div>
|
||||
</template>
|
||||
</ArtTable>
|
||||
</ElCard>
|
||||
|
||||
<!-- 装载数据表 -->
|
||||
<LoadTable v-model="tableVisible" :dialog-type="dialogType" @success="refreshData" />
|
||||
|
||||
<!-- 预览代码 -->
|
||||
<Preview v-model="dialogVisible" :data="dialogData" />
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<EditInfo v-model="editVisible" :data="editDialogData" @success="refreshData" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useTable } from '@/hooks/core/useTable'
|
||||
import { useSaiAdmin } from '@/composables/useSaiAdmin'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import api from '@/api/tool/generate'
|
||||
import { downloadFile } from '@/utils/tool'
|
||||
|
||||
import TableSearch from './modules/table-search.vue'
|
||||
import LoadTable from './components/loadTable.vue'
|
||||
import Preview from './components/preview.vue'
|
||||
import EditInfo from './components/editInfo.vue'
|
||||
|
||||
// 编辑弹窗
|
||||
const {
|
||||
dialogType,
|
||||
dialogVisible,
|
||||
dialogData,
|
||||
showDialog,
|
||||
handleSelectionChange,
|
||||
deleteRow,
|
||||
deleteSelectedRows,
|
||||
selectedRows
|
||||
} = useSaiAdmin()
|
||||
|
||||
const { dialogVisible: tableVisible, showDialog: showTableDialog } = useSaiAdmin()
|
||||
|
||||
const {
|
||||
dialogVisible: editVisible,
|
||||
dialogData: editDialogData,
|
||||
showDialog: showEditDialog
|
||||
} = useSaiAdmin()
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = ref({
|
||||
table_name: undefined,
|
||||
source: undefined
|
||||
})
|
||||
|
||||
// 搜索处理
|
||||
const handleSearch = (params: Record<string, any>) => {
|
||||
Object.assign(searchParams, params)
|
||||
getData()
|
||||
}
|
||||
|
||||
// 表格配置
|
||||
const {
|
||||
columns,
|
||||
columnChecks,
|
||||
data,
|
||||
loading,
|
||||
getData,
|
||||
pagination,
|
||||
searchParams,
|
||||
resetSearchParams,
|
||||
handleSortChange,
|
||||
handleSizeChange,
|
||||
handleCurrentChange,
|
||||
refreshData
|
||||
} = useTable({
|
||||
core: {
|
||||
apiFn: api.list,
|
||||
apiParams: {
|
||||
...searchForm.value
|
||||
},
|
||||
columnsFactory: () => [
|
||||
{ type: 'selection', width: 50 },
|
||||
{ prop: 'table_name', label: '表名称', minWidth: 180, align: 'left' },
|
||||
{ prop: 'table_comment', label: '表描述', minWidth: 150, align: 'left' },
|
||||
{ prop: 'template', label: '应用类型', minWidth: 120 },
|
||||
{ prop: 'namespace', label: '应用名称', minWidth: 120 },
|
||||
{ prop: 'stub', label: '模板类型', minWidth: 120 },
|
||||
{ prop: 'tpl_category', label: '生成类型', minWidth: 120, useSlot: true },
|
||||
{ prop: 'update_time', label: '更新时间', width: 180, sortable: true },
|
||||
{ prop: 'operation', label: '操作', width: 220, fixed: 'right', useSlot: true }
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 生成代码下载
|
||||
*/
|
||||
const generateCode = async (ids: number | string) => {
|
||||
ElMessage.info('代码生成下载中,请稍后')
|
||||
const response = await api.generateCode({
|
||||
ids: ids.toString().split(',')
|
||||
})
|
||||
if (response) {
|
||||
downloadFile(response, 'code.zip')
|
||||
ElMessage.success('代码生成成功,开始下载')
|
||||
} else {
|
||||
ElMessage.error('文件下载失败')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步表结构
|
||||
*/
|
||||
const syncTable = async (id: number) => {
|
||||
ElMessageBox.confirm('执行同步操作将会覆盖已经设置的表结构,确定要同步吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
api.async({ id }).then(() => {
|
||||
ElMessage.success('同步成功')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成到项目
|
||||
*/
|
||||
const generateFile = async (id: number) => {
|
||||
ElMessageBox.confirm('生成到项目将会覆盖原有文件,确定要生成吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
api.generateFile({ id }).then(() => {
|
||||
ElMessage.success('生成到项目成功')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量生成代码
|
||||
*/
|
||||
const batchGenerate = () => {
|
||||
if (selectedRows.value.length === 0) {
|
||||
ElMessage.error('至少要选择一条数据')
|
||||
return
|
||||
}
|
||||
generateCode(selectedRows.value.map((item: any) => item.id).join(','))
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-drawer__header) {
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
</style>
|
||||
45
saiadmin-artd/src/views/tool/code/js/vars.ts
Normal file
45
saiadmin-artd/src/views/tool/code/js/vars.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
export const relationsType: { name: string; value: string }[] = [
|
||||
{ name: '一对一[hasOne]', value: 'hasOne' },
|
||||
{ name: '一对多[hasMany]', value: 'hasMany' },
|
||||
{ name: '一对一(反向)[belongsTo]', value: 'belongsTo' },
|
||||
{ name: '多对多[belongsToMany]', value: 'belongsToMany' }
|
||||
]
|
||||
|
||||
export const queryType: { label: string; value: string }[] = [
|
||||
{ label: '=', value: 'eq' },
|
||||
{ label: '!=', value: 'neq' },
|
||||
{ label: '>', value: 'gt' },
|
||||
{ label: '>=', value: 'gte' },
|
||||
{ label: '<', value: 'lt' },
|
||||
{ label: '<=', value: 'lte' },
|
||||
{ label: 'LIKE', value: 'like' },
|
||||
{ label: 'IN', value: 'in' },
|
||||
{ label: 'NOT IN', value: 'notin' },
|
||||
{ label: 'BETWEEN', value: 'between' }
|
||||
]
|
||||
|
||||
// 页面控件
|
||||
export const viewComponent: { label: string; value: string }[] = [
|
||||
{ label: '输入框', value: 'input' },
|
||||
{ label: '密码框', value: 'password' },
|
||||
{ label: '文本域', value: 'textarea' },
|
||||
{ label: '数字输入框', value: 'inputNumber' },
|
||||
{ label: '标签输入框', value: 'inputTag' },
|
||||
{ label: '开关', value: 'switch' },
|
||||
{ label: '滑块', value: 'slider' },
|
||||
{ label: '数据下拉框', value: 'select' },
|
||||
{ label: '字典下拉框', value: 'saSelect' },
|
||||
{ label: '树形下拉框', value: 'treeSelect' },
|
||||
{ label: '字典单选框', value: 'radio' },
|
||||
{ label: '字典复选框', value: 'checkbox' },
|
||||
{ label: '日期选择器', value: 'date' },
|
||||
{ label: '时间选择器', value: 'time' },
|
||||
{ label: '评分器', value: 'rate' },
|
||||
{ label: '级联选择器', value: 'cascader' },
|
||||
{ label: '用户选择器', value: 'userSelect' },
|
||||
{ label: '图片上传', value: 'uploadImage' },
|
||||
{ label: '图片选择', value: 'imagePicker' },
|
||||
{ label: '文件上传', value: 'uploadFile' },
|
||||
{ label: '大文件切片', value: 'chunkUpload' },
|
||||
{ label: '富文本编辑器', value: 'editor' }
|
||||
]
|
||||
66
saiadmin-artd/src/views/tool/code/modules/table-search.vue
Normal file
66
saiadmin-artd/src/views/tool/code/modules/table-search.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<sa-search-bar
|
||||
ref="searchBarRef"
|
||||
v-model="formData"
|
||||
label-width="100px"
|
||||
:showExpand="false"
|
||||
@reset="handleReset"
|
||||
@search="handleSearch"
|
||||
>
|
||||
<el-col v-bind="setSpan(6)">
|
||||
<el-form-item label="表名称" prop="table_name">
|
||||
<el-input v-model="formData.table_name" placeholder="请输入数据表名称" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col v-bind="setSpan(6)">
|
||||
<el-form-item label="数据源" prop="source">
|
||||
<el-input v-model="formData.source" placeholder="请输入数据源名称" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</sa-search-bar>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
modelValue: Record<string, any>
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:modelValue', value: Record<string, any>): void
|
||||
(e: 'search', params: Record<string, any>): void
|
||||
(e: 'reset'): void
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
// 表单数据双向绑定
|
||||
const searchBarRef = ref()
|
||||
const formData = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val)
|
||||
})
|
||||
|
||||
// 重置
|
||||
function handleReset() {
|
||||
searchBarRef.value?.ref.resetFields()
|
||||
emit('reset')
|
||||
}
|
||||
|
||||
// 搜索
|
||||
async function handleSearch() {
|
||||
emit('search', formData.value)
|
||||
}
|
||||
|
||||
// 栅格占据的列数
|
||||
const setSpan = (span: number) => {
|
||||
return {
|
||||
span: span,
|
||||
xs: 24, // 手机:满宽显示
|
||||
sm: span >= 12 ? span : 12, // 平板:大于等于12保持,否则用半宽
|
||||
md: span >= 8 ? span : 8, // 中等屏幕:大于等于8保持,否则用三分之一宽
|
||||
lg: span,
|
||||
xl: span
|
||||
}
|
||||
}
|
||||
</script>
|
||||
160
saiadmin-artd/src/views/tool/crontab/index.vue
Normal file
160
saiadmin-artd/src/views/tool/crontab/index.vue
Normal file
@@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<div class="art-full-height">
|
||||
<!-- 搜索面板 -->
|
||||
<TableSearch v-model="searchForm" @search="handleSearch" @reset="resetSearchParams" />
|
||||
|
||||
<ElCard class="art-table-card" shadow="never">
|
||||
<!-- 表格头部 -->
|
||||
<ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
|
||||
<template #left>
|
||||
<ElSpace wrap>
|
||||
<ElButton v-permission="'tool:crontab:edit'" @click="showDialog('add')" v-ripple>
|
||||
<template #icon>
|
||||
<ArtSvgIcon icon="ri:add-fill" />
|
||||
</template>
|
||||
新增
|
||||
</ElButton>
|
||||
</ElSpace>
|
||||
</template>
|
||||
</ArtTableHeader>
|
||||
|
||||
<!-- 表格 -->
|
||||
<ArtTable
|
||||
ref="tableRef"
|
||||
rowKey="id"
|
||||
:loading="loading"
|
||||
:data="data"
|
||||
:columns="columns"
|
||||
:pagination="pagination"
|
||||
@sort-change="handleSortChange"
|
||||
@selection-change="handleSelectionChange"
|
||||
@pagination:size-change="handleSizeChange"
|
||||
@pagination:current-change="handleCurrentChange"
|
||||
>
|
||||
<!-- 操作列 -->
|
||||
<template #operation="{ row }">
|
||||
<div class="flex gap-2">
|
||||
<SaButton
|
||||
v-permission="'tool:crontab:run'"
|
||||
type="primary"
|
||||
icon="ri:play-fill"
|
||||
toolTip="运行任务"
|
||||
@click="handleRun(row)"
|
||||
/>
|
||||
<SaButton
|
||||
type="primary"
|
||||
icon="ri:history-line"
|
||||
toolTip="运行日志"
|
||||
@click="showTableDialog('edit', row)"
|
||||
/>
|
||||
<SaButton
|
||||
v-permission="'tool:crontab:edit'"
|
||||
type="secondary"
|
||||
@click="showDialog('edit', row)"
|
||||
/>
|
||||
<SaButton
|
||||
v-permission="'tool:crontab:edit'"
|
||||
type="error"
|
||||
@click="deleteRow(row, api.delete, refreshData)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</ArtTable>
|
||||
</ElCard>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<EditDialog
|
||||
v-model="dialogVisible"
|
||||
:dialog-type="dialogType"
|
||||
:data="dialogData"
|
||||
@success="refreshData"
|
||||
/>
|
||||
|
||||
<!-- 日志弹窗 -->
|
||||
<LogListDialog v-model="tableVisible" :dialog-type="tableDialogType" :data="tableData" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useTable } from '@/hooks/core/useTable'
|
||||
import { useSaiAdmin } from '@/composables/useSaiAdmin'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import api from '@/api/tool/crontab'
|
||||
import TableSearch from './modules/table-search.vue'
|
||||
import EditDialog from './modules/edit-dialog.vue'
|
||||
import LogListDialog from './modules/log-list.vue'
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = ref({
|
||||
name: undefined,
|
||||
type: undefined,
|
||||
status: undefined
|
||||
})
|
||||
|
||||
// 搜索处理
|
||||
const handleSearch = (params: Record<string, any>) => {
|
||||
Object.assign(searchParams, params)
|
||||
getData()
|
||||
}
|
||||
|
||||
// 表格配置
|
||||
const {
|
||||
columns,
|
||||
columnChecks,
|
||||
data,
|
||||
loading,
|
||||
getData,
|
||||
searchParams,
|
||||
pagination,
|
||||
resetSearchParams,
|
||||
handleSortChange,
|
||||
handleSizeChange,
|
||||
handleCurrentChange,
|
||||
refreshData
|
||||
} = useTable({
|
||||
core: {
|
||||
apiFn: api.list,
|
||||
columnsFactory: () => [
|
||||
{ prop: 'id', label: '编号', width: 100, align: 'center' },
|
||||
{ prop: 'name', label: '任务名称', minWidth: 120 },
|
||||
{
|
||||
prop: 'type',
|
||||
label: '任务类型',
|
||||
saiType: 'dict',
|
||||
saiDict: 'crontab_task_type',
|
||||
minWidth: 120
|
||||
},
|
||||
{ prop: 'rule', label: '定时规则', minWidth: 140 },
|
||||
{ prop: 'target', label: '调用目标', minWidth: 200, showOverflowTooltip: true },
|
||||
{ prop: 'status', label: '状态', saiType: 'dict', saiDict: 'data_status', width: 100 },
|
||||
{ prop: 'update_time', label: '更新日期', width: 180, sortable: true },
|
||||
{ prop: 'operation', label: '操作', width: 180, fixed: 'right', useSlot: true }
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
// 编辑配置
|
||||
const { dialogType, dialogVisible, dialogData, showDialog, deleteRow, handleSelectionChange } =
|
||||
useSaiAdmin()
|
||||
|
||||
const {
|
||||
dialogVisible: tableVisible,
|
||||
dialogType: tableDialogType,
|
||||
dialogData: tableData,
|
||||
showDialog: showTableDialog
|
||||
} = useSaiAdmin()
|
||||
|
||||
// 运行任务
|
||||
const handleRun = (row: any) => {
|
||||
ElMessageBox.confirm(`确定要运行任务【${row.name}】吗?`, '运行任务', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
api.run({ id: row.id }).then(() => {
|
||||
ElMessage.success('任务运行成功')
|
||||
refreshData()
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
||||
278
saiadmin-artd/src/views/tool/crontab/modules/edit-dialog.vue
Normal file
278
saiadmin-artd/src/views/tool/crontab/modules/edit-dialog.vue
Normal file
@@ -0,0 +1,278 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="dialogType === 'add' ? '新增定时任务' : '编辑定时任务'"
|
||||
width="800px"
|
||||
align-center
|
||||
:close-on-click-modal="false"
|
||||
@close="handleClose"
|
||||
>
|
||||
<el-form ref="formRef" :model="formData" :rules="rules" label-width="120px">
|
||||
<el-form-item label="任务名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入任务名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="任务类型" prop="type">
|
||||
<sa-select v-model="formData.type" dict="crontab_task_type" />
|
||||
</el-form-item>
|
||||
<el-form-item label="定时规则" prop="task_style">
|
||||
<el-space>
|
||||
<el-select v-model="formData.task_style" :style="{ width: '100px' }">
|
||||
<el-option :value="1" label="每天" />
|
||||
<el-option :value="2" label="每小时" />
|
||||
<el-option :value="3" label="N小时" />
|
||||
<el-option :value="4" label="N分钟" />
|
||||
<el-option :value="5" label="N秒" />
|
||||
<el-option :value="6" label="每周" />
|
||||
<el-option :value="7" label="每月" />
|
||||
<el-option :value="8" label="每年" />
|
||||
</el-select>
|
||||
<template v-if="formData.task_style == 8">
|
||||
<el-input-number
|
||||
v-model="formData.month"
|
||||
:precision="0"
|
||||
:min="1"
|
||||
:max="12"
|
||||
controls-position="right"
|
||||
:style="{ width: '100px' }"
|
||||
/>
|
||||
<span>月</span>
|
||||
</template>
|
||||
<template v-if="formData.task_style > 6">
|
||||
<el-input-number
|
||||
v-model="formData.day"
|
||||
:precision="0"
|
||||
:min="1"
|
||||
:max="31"
|
||||
controls-position="right"
|
||||
:style="{ width: '100px' }"
|
||||
/>
|
||||
<span>日</span>
|
||||
</template>
|
||||
<el-select
|
||||
v-if="formData.task_style == 6"
|
||||
v-model="formData.week"
|
||||
:style="{ width: '100px' }"
|
||||
>
|
||||
<el-option :value="1" label="周一" />
|
||||
<el-option :value="2" label="周二" />
|
||||
<el-option :value="3" label="周三" />
|
||||
<el-option :value="4" label="周四" />
|
||||
<el-option :value="5" label="周五" />
|
||||
<el-option :value="6" label="周六" />
|
||||
<el-option :value="0" label="周日" />
|
||||
</el-select>
|
||||
<template v-if="[1, 3, 6, 7, 8].includes(formData.task_style)">
|
||||
<el-input-number
|
||||
v-model="formData.hour"
|
||||
:precision="0"
|
||||
:min="0"
|
||||
:max="23"
|
||||
controls-position="right"
|
||||
:style="{ width: '100px' }"
|
||||
/>
|
||||
<span>时</span>
|
||||
</template>
|
||||
<template v-if="formData.task_style != 5">
|
||||
<el-input-number
|
||||
v-model="formData.minute"
|
||||
:precision="0"
|
||||
:min="0"
|
||||
:max="59"
|
||||
controls-position="right"
|
||||
:style="{ width: '100px' }"
|
||||
/>
|
||||
<span>分</span>
|
||||
</template>
|
||||
<template v-if="formData.task_style == 5">
|
||||
<el-input-number
|
||||
v-model="formData.second"
|
||||
:precision="0"
|
||||
:min="0"
|
||||
:max="59"
|
||||
controls-position="right"
|
||||
:style="{ width: '100px' }"
|
||||
/>
|
||||
<span>秒</span>
|
||||
</template>
|
||||
</el-space>
|
||||
</el-form-item>
|
||||
<el-form-item label="调用目标" prop="target">
|
||||
<el-input
|
||||
v-model="formData.target"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入调用目标"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="任务参数" prop="params">
|
||||
<el-input
|
||||
v-model="formData.parameter"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入任务参数"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<sa-radio v-model="formData.status" dict="data_status" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="formData.remark" type="textarea" :rows="2" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit">提交</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import api from '@/api/tool/crontab'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean
|
||||
dialogType: string
|
||||
data?: Record<string, any>
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:modelValue', value: boolean): void
|
||||
(e: 'success'): void
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: false,
|
||||
dialogType: 'add',
|
||||
data: undefined
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
/**
|
||||
* 弹窗显示状态双向绑定
|
||||
*/
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit('update:modelValue', value)
|
||||
})
|
||||
|
||||
/**
|
||||
* 表单验证规则
|
||||
*/
|
||||
const rules = reactive<FormRules>({
|
||||
name: [{ required: true, message: '任务名称不能为空', trigger: 'blur' }],
|
||||
type: [{ required: true, message: '任务类型不能为空', trigger: 'blur' }],
|
||||
task_style: [{ required: true, message: '定时规则不能为空', trigger: 'blur' }],
|
||||
target: [{ required: true, message: '调用目标不能为空', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
/**
|
||||
* 初始数据
|
||||
*/
|
||||
const initialFormData = {
|
||||
id: null,
|
||||
name: '',
|
||||
type: '',
|
||||
rule: '',
|
||||
task_style: 1,
|
||||
month: 1,
|
||||
day: 1,
|
||||
week: 1,
|
||||
hour: 1,
|
||||
minute: 1,
|
||||
second: 1,
|
||||
target: '',
|
||||
parameter: '',
|
||||
status: 1,
|
||||
remark: ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 表单数据
|
||||
*/
|
||||
const formData = reactive({ ...initialFormData })
|
||||
|
||||
/**
|
||||
* 监听弹窗打开,初始化表单数据
|
||||
*/
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
initPage()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* 初始化页面数据
|
||||
*/
|
||||
const initPage = async () => {
|
||||
// 先重置为初始值
|
||||
Object.assign(formData, initialFormData)
|
||||
// 如果有数据,则填充数据
|
||||
if (props.data) {
|
||||
await nextTick()
|
||||
initForm()
|
||||
}
|
||||
}
|
||||
|
||||
// 提取数字
|
||||
const extractNumber = (str: string) => {
|
||||
const match = str.match(/\d+/)
|
||||
return match ? Number.parseInt(match[0]) : 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化表单数据
|
||||
*/
|
||||
const initForm = () => {
|
||||
if (props.data) {
|
||||
for (const key in formData) {
|
||||
if (props.data[key] != null && props.data[key] != undefined) {
|
||||
;(formData as any)[key] = props.data[key]
|
||||
}
|
||||
}
|
||||
const words = formData['rule'].split(' ')
|
||||
formData['second'] = extractNumber(words[0])
|
||||
formData['minute'] = extractNumber(words[1])
|
||||
formData['hour'] = extractNumber(words[2])
|
||||
formData['day'] = extractNumber(words[3])
|
||||
formData['month'] = extractNumber(words[4])
|
||||
formData['week'] = extractNumber(words[5])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭弹窗并重置表单
|
||||
*/
|
||||
const handleClose = () => {
|
||||
visible.value = false
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交表单
|
||||
*/
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
if (props.dialogType === 'add') {
|
||||
await api.save(formData)
|
||||
ElMessage.success('新增成功')
|
||||
} else {
|
||||
await api.update(formData)
|
||||
ElMessage.success('修改成功')
|
||||
}
|
||||
emit('success')
|
||||
handleClose()
|
||||
} catch (error) {
|
||||
console.log('表单验证失败:', error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
222
saiadmin-artd/src/views/tool/crontab/modules/log-list.vue
Normal file
222
saiadmin-artd/src/views/tool/crontab/modules/log-list.vue
Normal file
@@ -0,0 +1,222 @@
|
||||
<template>
|
||||
<el-drawer
|
||||
v-model="visible"
|
||||
title="任务执行日志"
|
||||
size="70%"
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
@close="handleClose"
|
||||
>
|
||||
<div class="art-full-height">
|
||||
<div class="flex justify-between items-center">
|
||||
<ElSpace wrap>
|
||||
<el-date-picker
|
||||
v-model="searchForm.create_time"
|
||||
type="datetimerange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
clearable
|
||||
/>
|
||||
</ElSpace>
|
||||
<ElSpace wrap>
|
||||
<ElButton class="reset-button" @click="handleReset" v-ripple>
|
||||
<template #icon>
|
||||
<ArtSvgIcon icon="ri:reset-right-line" />
|
||||
</template>
|
||||
重置
|
||||
</ElButton>
|
||||
<ElButton type="primary" class="search-button" @click="handleSearch" v-ripple>
|
||||
<template #icon>
|
||||
<ArtSvgIcon icon="ri:search-line" />
|
||||
</template>
|
||||
查询
|
||||
</ElButton>
|
||||
</ElSpace>
|
||||
</div>
|
||||
<ElCard class="art-table-card" shadow="never">
|
||||
<div>
|
||||
<ElSpace wrap>
|
||||
<ElButton
|
||||
v-permission="'tool:crontab:edit'"
|
||||
:disabled="selectedRows.length === 0"
|
||||
@click="handleLoadTable"
|
||||
v-ripple
|
||||
>
|
||||
<template #icon>
|
||||
<ArtSvgIcon icon="ri:delete-bin-5-line" />
|
||||
</template>
|
||||
删除
|
||||
</ElButton>
|
||||
</ElSpace>
|
||||
</div>
|
||||
<!-- 表格 -->
|
||||
<ArtTable
|
||||
ref="tableRef"
|
||||
rowKey="id"
|
||||
:loading="loading"
|
||||
:data="tableData"
|
||||
:columns="columns"
|
||||
:pagination="pagination"
|
||||
@sort-change="handleSortChange"
|
||||
@selection-change="handleSelectionChange"
|
||||
@pagination:size-change="handleSizeChange"
|
||||
@pagination:current-change="handleCurrentChange"
|
||||
>
|
||||
<template #status="{ row }">
|
||||
<ElTag v-if="row.status == 1" type="success">成功</ElTag>
|
||||
<ElTag v-else type="danger">失败</ElTag>
|
||||
</template>
|
||||
</ArtTable>
|
||||
</ElCard>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import api from '@/api/tool/crontab'
|
||||
import { useTable } from '@/hooks/core/useTable'
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean
|
||||
data?: Record<string, any>
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:modelValue', value: boolean): void
|
||||
(e: 'success'): void
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: false,
|
||||
data: undefined
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const selectedRows = ref<Record<string, any>[]>([])
|
||||
const searchForm = ref({
|
||||
crontab_id: '',
|
||||
orderType: 'desc',
|
||||
create_time: []
|
||||
})
|
||||
|
||||
/**
|
||||
* 弹窗显示状态双向绑定
|
||||
*/
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit('update:modelValue', value)
|
||||
})
|
||||
|
||||
/**
|
||||
* 监听弹窗打开,初始化表单数据
|
||||
*/
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
initPage()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* 初始化页面数据
|
||||
*/
|
||||
const initPage = async () => {
|
||||
if (!props.data?.id) {
|
||||
ElMessage.error('请先选择一个任务')
|
||||
return
|
||||
}
|
||||
searchForm.value.crontab_id = props.data.id
|
||||
refreshData()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表格数据
|
||||
*/
|
||||
const refreshData = () => {
|
||||
Object.assign(searchParams, searchForm.value)
|
||||
getData()
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索
|
||||
*/
|
||||
const handleSearch = () => {
|
||||
refreshData()
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置
|
||||
*/
|
||||
const handleReset = () => {
|
||||
searchForm.value.create_time = []
|
||||
refreshData()
|
||||
}
|
||||
|
||||
// 表格行选择变化
|
||||
const handleSelectionChange = (selection: Record<string, any>[]): void => {
|
||||
selectedRows.value = selection
|
||||
}
|
||||
|
||||
// 确认选择装载数据表
|
||||
const handleLoadTable = async () => {
|
||||
if (selectedRows.value.length < 1) {
|
||||
ElMessage.info('至少要选择一条数据')
|
||||
return
|
||||
}
|
||||
ElMessageBox.confirm(
|
||||
`确定要删除选中的 ${selectedRows.value.length} 条数据吗?`,
|
||||
'删除选中数据',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'error'
|
||||
}
|
||||
).then(() => {
|
||||
api.deleteCrontabLog({ ids: selectedRows.value.map((row) => row.id) }).then(() => {
|
||||
ElMessage.success('删除成功')
|
||||
refreshData()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭弹窗
|
||||
*/
|
||||
const handleClose = () => {
|
||||
visible.value = false
|
||||
selectedRows.value = []
|
||||
}
|
||||
|
||||
const {
|
||||
loading,
|
||||
data: tableData,
|
||||
columns,
|
||||
getData,
|
||||
pagination,
|
||||
searchParams,
|
||||
handleSortChange,
|
||||
handleSizeChange,
|
||||
handleCurrentChange
|
||||
} = useTable({
|
||||
core: {
|
||||
apiFn: api.logPageList,
|
||||
immediate: false,
|
||||
apiParams: {
|
||||
...searchForm.value
|
||||
},
|
||||
columnsFactory: () => [
|
||||
{ type: 'selection' },
|
||||
{ prop: 'create_time', label: '执行时间', sortable: true },
|
||||
{ prop: 'target', label: '调用目标' },
|
||||
{ prop: 'parameter', label: '任务参数' },
|
||||
{ prop: 'status', label: '执行状态', useSlot: true, width: 100 }
|
||||
]
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<sa-search-bar
|
||||
ref="searchBarRef"
|
||||
v-model="formData"
|
||||
label-width="100px"
|
||||
:showExpand="false"
|
||||
@reset="handleReset"
|
||||
@search="handleSearch"
|
||||
@expand="handleExpand"
|
||||
>
|
||||
<el-col v-bind="setSpan(6)">
|
||||
<el-form-item label="任务名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入任务名称" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col v-bind="setSpan(6)">
|
||||
<el-form-item label="任务类型" prop="type">
|
||||
<sa-select v-model="formData.type" dict="crontab_task_type" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col v-bind="setSpan(6)">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<sa-select v-model="formData.status" dict="data_status" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</sa-search-bar>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
modelValue: Record<string, any>
|
||||
}
|
||||
interface Emits {
|
||||
(e: 'update:modelValue', value: Record<string, any>): void
|
||||
(e: 'search', params: Record<string, any>): void
|
||||
(e: 'reset'): void
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits<Emits>()
|
||||
// 展开/收起
|
||||
const isExpanded = ref<boolean>(false)
|
||||
|
||||
// 表单数据双向绑定
|
||||
const searchBarRef = ref()
|
||||
const formData = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val)
|
||||
})
|
||||
|
||||
// 重置
|
||||
function handleReset() {
|
||||
searchBarRef.value?.ref.resetFields()
|
||||
emit('reset')
|
||||
}
|
||||
|
||||
// 搜索
|
||||
async function handleSearch() {
|
||||
emit('search', formData.value)
|
||||
}
|
||||
|
||||
// 展开/收起
|
||||
function handleExpand(expanded: boolean) {
|
||||
isExpanded.value = expanded
|
||||
}
|
||||
|
||||
// 栅格占据的列数
|
||||
const setSpan = (span: number) => {
|
||||
return {
|
||||
span: span,
|
||||
xs: 24, // 手机:满宽显示
|
||||
sm: span >= 12 ? span : 12, // 平板:大于等于12保持,否则用半宽
|
||||
md: span >= 8 ? span : 8, // 中等屏幕:大于等于8保持,否则用三分之一宽
|
||||
lg: span,
|
||||
xl: span
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user