add. command dev -addd

This commit is contained in:
扶桑花间 2025-08-31 23:09:23 +08:00
parent b80e66d941
commit e7bfa0817d
13 changed files with 950 additions and 3 deletions

View File

@ -0,0 +1,14 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { Command, CommandParam } from './model';
export async function listCommand(params: CommandParam) {
const res = await request.get<ApiResult<PageResult<Command>>>(
'/system/command',
{ params }
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@ -0,0 +1,5 @@
import type { PageParam } from '@/api';
export interface Command {}
export interface CommandParam extends PageParam {}

View File

@ -0,0 +1,147 @@
<!-- 角色编辑弹窗 -->
<template>
<ele-modal
form
destroy-on-close
:width="460"
v-model="visible"
:title="isUpdate ? '修改角色' : '添加角色'"
>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="80px"
@submit.prevent=""
>
<el-form-item label="角色名称" prop="roleName">
<el-input
clearable
:maxlength="20"
v-model="form.roleName"
placeholder="请输入角色名称"
/>
</el-form-item>
<el-form-item label="角色标识" prop="roleCode">
<el-input
clearable
:maxlength="20"
v-model="form.roleCode"
placeholder="请输入角色标识"
/>
</el-form-item>
<el-form-item label="备注">
<el-input
:rows="4"
type="textarea"
v-model="form.comments"
placeholder="请输入备注"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleCancel">取消</el-button>
<el-button type="primary" :loading="loading" @click="save">
保存
</el-button>
</template>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import type { FormInstance, FormRules } from 'element-plus';
import { EleMessage } from 'ele-admin-plus';
import { useFormData } from '@/utils/use-form-data';
import { addRole, updateRole } from '@/api/system/role';
import type { Role } from '@/api/system/role/model';
const props = defineProps<{
/** 修改回显的数据 */
data?: Role | null;
}>();
const emit = defineEmits<{
(e: 'done'): void;
}>();
/** 弹窗是否打开 */
const visible = defineModel({ type: Boolean });
/** 是否是修改 */
const isUpdate = ref(false);
/** 提交状态 */
const loading = ref(false);
/** 表单实例 */
const formRef = ref<FormInstance | null>(null);
/** 表单数据 */
const [form, resetFields, assignFields] = useFormData<Role>({
roleId: void 0,
roleName: '',
roleCode: '',
comments: ''
});
/** 表单验证规则 */
const rules = reactive<FormRules>({
roleName: [
{
required: true,
message: '请输入角色名称',
type: 'string',
trigger: 'blur'
}
],
roleCode: [
{
required: true,
message: '请输入角色标识',
type: 'string',
trigger: 'blur'
}
]
});
/** 关闭弹窗 */
const handleCancel = () => {
visible.value = false;
};
/** 保存编辑 */
const save = () => {
formRef.value?.validate?.((valid) => {
if (!valid) {
return;
}
loading.value = true;
const saveOrUpdate = isUpdate.value ? updateRole : addRole;
saveOrUpdate(form)
.then((msg) => {
loading.value = false;
EleMessage.success({ message: msg, plain: true });
handleCancel();
emit('done');
})
.catch((e) => {
loading.value = false;
EleMessage.error({ message: e.message, plain: true });
});
});
};
/** 监听弹窗打开 */
watch(visible, () => {
if (visible.value) {
if (props.data) {
assignFields(props.data);
isUpdate.value = true;
} else {
resetFields();
isUpdate.value = false;
}
}
});
</script>

View File

@ -0,0 +1,64 @@
<!-- 搜索表单 -->
<template>
<ele-card :body-style="{ paddingBottom: '2px' }">
<el-form
label-width="72px"
@keyup.enter.prevent="search"
@submit.prevent=""
>
<el-row :gutter="8">
<el-col :lg="6" :md="8" :sm="12" :xs="24">
<el-form-item label="角色名称">
<el-input
clearable
v-model.trim="form.roleName"
placeholder="请输入"
/>
</el-form-item>
</el-col>
<el-col :lg="6" :md="8" :sm="12" :xs="24">
<el-form-item label="角色标识">
<el-input
clearable
v-model.trim="form.roleCode"
placeholder="请输入"
/>
</el-form-item>
</el-col>
<el-col :lg="12" :md="8" :sm="24" :xs="24">
<el-form-item label-width="16px">
<el-button type="primary" @click="search">查询</el-button>
<el-button @click="reset">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</ele-card>
</template>
<script lang="ts" setup>
import { useFormData } from '@/utils/use-form-data';
import type { RoleParam } from '@/api/system/role/model';
const emit = defineEmits<{
(e: 'search', where?: RoleParam): void;
}>();
/** 表单数据 */
const [form, resetFields] = useFormData<RoleParam>({
roleName: '',
roleCode: '',
comments: ''
});
/** 搜索 */
const search = () => {
emit('search', { ...form });
};
/** 重置 */
const reset = () => {
resetFields();
search();
};
</script>

View File

@ -0,0 +1,156 @@
<template>
<ele-page>
<!-- 搜索表单 -->
<command-search @search="reload" />
<ele-card :body-style="{ paddingTop: '8px' }">
<!-- 表格 -->
<ele-pro-table
ref="tableRef"
row-key="roleId"
:columns="columns"
:datasource="datasource"
:show-overflow-tooltip="true"
:highlight-current-row="true"
cache-key="systemDevCommandListTable"
:pagination="false"
>
<template #action="{ row }">
<el-link
v-if="disableRunNames.indexOf(row.name) == -1"
type="primary"
underline="never"
@click="openEdit(row)"
>
执行
</el-link>
</template>
<template #arguments="{ row }">
<el-tag
v-for="(_, name) in row.arguments"
:key="name"
style="margin-right: 5px"
>{{ name }}</el-tag
>
</template>
<template #options="{ row }">
<el-tag
v-for="(_, name) in row.options"
:key="name"
style="margin-right: 5px"
>{{ name }}</el-tag
>
</template>
</ele-pro-table>
</ele-card>
<!-- 编辑弹窗 -->
<role-edit v-model="showEdit" :data="current" @done="reload" />
</ele-page>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import type { EleProTable } from 'ele-admin-plus';
import type {
DatasourceFunction,
Columns
} from 'ele-admin-plus/es/ele-pro-table/types';
import CommandSearch from './components/command-search.vue';
import CommandEdit from './components/command-edit.vue';
import type { Role, RoleParam } from '@/api/system/role/model';
import { listCommand } from '@/api/system/dev-command';
defineOptions({ name: 'SystemDevCommandList' });
/** 表格实例 */
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
/** 部分脚本不允许前端执行 */
const disableRunNames = [
'admin:worker',
'admin:gateway',
'vendor:publish',
'run',
'vendor:publish'
];
/** 表格列配置 */
const columns = ref<Columns>([
{
type: 'index',
columnKey: 'index',
width: 50,
align: 'center'
},
{
prop: 'name',
label: '名称',
sortable: 'custom',
width: 260
},
{
prop: 'type',
label: '类型',
sortable: 'custom',
width: 120
},
{
prop: 'description',
label: '简介',
sortable: 'custom',
minWidth: 240
},
{
prop: 'class',
label: '脚本类',
minWidth: 180,
align: 'left'
},
{
prop: 'arguments',
label: '参数',
minWidth: 180,
slot: 'arguments',
align: 'left'
},
{
prop: 'options',
label: '配置',
minWidth: 230,
slot: 'options',
align: 'left'
},
{
columnKey: 'action',
label: '操作',
width: 100,
align: 'center' /* ,
fixed: 'right' */,
slot: 'action',
hideInPrint: true,
hideInExport: true
}
]);
/** 当前编辑数据 */
const current = ref<Role | null>(null);
/** 是否显示编辑弹窗 */
const showEdit = ref(false);
/** 表格数据源 */
const datasource: DatasourceFunction = ({ pages, where, orders }) => {
return listCommand({ ...where, ...orders, ...pages });
};
/** 搜索 */
const reload = (where?: RoleParam) => {
tableRef.value?.reload?.({ page: 1, where });
};
/** 打开编辑弹窗 */
const openEdit = (row?: Role) => {
current.value = row ?? null;
showEdit.value = true;
};
</script>

View File

@ -64,12 +64,12 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { EleProTable, LayoutTool } from 'ele-admin-plus';
import { EleProTable } from 'ele-admin-plus';
import type {
DatasourceFunction,
Columns
} from 'ele-admin-plus/es/ele-pro-table/types';
import { composerInfo } from '@/api/system/composer';
import { composerInfo } from '@/api/system/dev-composer';
import PackageDetails from './components/package-details.vue';
defineOptions({ name: 'SystemComposerPackage' });
@ -140,7 +140,7 @@
};
/** 当前编辑数据 */
const current = ref<Role | null>(null);
const current = ref<any | null>(null);
/** 是否显示编辑弹窗 */
const showDetails = ref(false);

View File

@ -0,0 +1,148 @@
<!-- 角色权限分配弹窗 -->
<template>
<ele-modal
:width="460"
title="分配权限"
position="center"
v-model="visible"
:body-style="{ padding: '12px 0 12px 22px' }"
@open="handleOpen"
>
<ele-loading
:loading="authLoading"
:spinner-style="{ background: 'transparent' }"
:style="{
paddingRight: '20px',
height: 'calc(100vh - 192px)',
maxHeight: 'calc(100dvh - 192px)',
minHeight: '100px',
overflow: 'auto'
}"
>
<el-tree
ref="treeRef"
show-checkbox
:data="authData"
node-key="menuId"
:default-expand-all="true"
:props="{ label: 'title' }"
:default-checked-keys="checkedKeys"
:style="{ '--ele-tree-item-height': '28px' }"
>
<template #default="scope">
<div>
<el-icon
v-if="scope.data.icon"
:size="16"
style="margin-right: 6px; vertical-align: -5px"
>
<component :is="scope.data.icon" />
</el-icon>
<span style="vertical-align: -2px">{{ scope.data.title }}</span>
</div>
</template>
</el-tree>
</ele-loading>
<template #footer>
<el-button @click="handleCancel">取消</el-button>
<el-button type="primary" :loading="loading" @click="save">
保存
</el-button>
</template>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, nextTick } from 'vue';
import type { ElTree } from 'element-plus';
import { EleMessage, toTree, eachTree } from 'ele-admin-plus';
import { listRoleMenus, updateRoleMenus } from '@/api/system/role';
import type { Role } from '@/api/system/role/model';
import type { Menu } from '@/api/system/menu/model';
const props = defineProps<{
/** 当前角色数据 */
data?: Role | null;
}>();
/** 弹窗是否打开 */
const visible = defineModel({ type: Boolean });
/** 树组件实例 */
const treeRef = ref<InstanceType<typeof ElTree> | null>(null);
/** 权限数据 */
const authData = ref<Menu[]>([]);
/** 权限数据请求状态 */
const authLoading = ref(false);
/** 提交状态 */
const loading = ref(false);
/** 角色权限选中的keys */
const checkedKeys = ref<number[]>([]);
/** 查询权限数据 */
const query = () => {
authData.value = [];
checkedKeys.value = [];
if (!props.data) {
return;
}
authLoading.value = true;
listRoleMenus(props.data.roleId)
.then((data) => {
authLoading.value = false;
//
authData.value = toTree({
data: data,
idField: 'menuId',
parentIdField: 'parentId'
});
//
nextTick(() => {
const cks: number[] = [];
eachTree(authData.value, (d) => {
if (d.menuId && d.checked && !d.children?.length) {
cks.push(d.menuId);
}
});
checkedKeys.value = cks;
});
})
.catch((e) => {
authLoading.value = false;
EleMessage.error({ message: e.message, plain: true });
});
};
/** 关闭弹窗 */
const handleCancel = () => {
visible.value = false;
};
/** 保存权限分配 */
const save = () => {
loading.value = true;
const ids =
(treeRef.value?.getCheckedKeys?.() ?? []).concat(
treeRef.value?.getHalfCheckedKeys?.() ?? []
) ?? [];
updateRoleMenus(props.data?.roleId, ids as unknown as number[])
.then((msg) => {
loading.value = false;
EleMessage.success({ message: msg, plain: true });
handleCancel();
})
.catch((e) => {
loading.value = false;
EleMessage.error({ message: e.message, plain: true });
});
};
/** 弹窗打开事件 */
const handleOpen = () => {
query();
};
</script>

View File

@ -0,0 +1,147 @@
<!-- 角色编辑弹窗 -->
<template>
<ele-modal
form
destroy-on-close
:width="460"
v-model="visible"
:title="isUpdate ? '修改角色' : '添加角色'"
>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="80px"
@submit.prevent=""
>
<el-form-item label="角色名称" prop="roleName">
<el-input
clearable
:maxlength="20"
v-model="form.roleName"
placeholder="请输入角色名称"
/>
</el-form-item>
<el-form-item label="角色标识" prop="roleCode">
<el-input
clearable
:maxlength="20"
v-model="form.roleCode"
placeholder="请输入角色标识"
/>
</el-form-item>
<el-form-item label="备注">
<el-input
:rows="4"
type="textarea"
v-model="form.comments"
placeholder="请输入备注"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleCancel">取消</el-button>
<el-button type="primary" :loading="loading" @click="save">
保存
</el-button>
</template>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import type { FormInstance, FormRules } from 'element-plus';
import { EleMessage } from 'ele-admin-plus';
import { useFormData } from '@/utils/use-form-data';
import { addRole, updateRole } from '@/api/system/role';
import type { Role } from '@/api/system/role/model';
const props = defineProps<{
/** 修改回显的数据 */
data?: Role | null;
}>();
const emit = defineEmits<{
(e: 'done'): void;
}>();
/** 弹窗是否打开 */
const visible = defineModel({ type: Boolean });
/** 是否是修改 */
const isUpdate = ref(false);
/** 提交状态 */
const loading = ref(false);
/** 表单实例 */
const formRef = ref<FormInstance | null>(null);
/** 表单数据 */
const [form, resetFields, assignFields] = useFormData<Role>({
roleId: void 0,
roleName: '',
roleCode: '',
comments: ''
});
/** 表单验证规则 */
const rules = reactive<FormRules>({
roleName: [
{
required: true,
message: '请输入角色名称',
type: 'string',
trigger: 'blur'
}
],
roleCode: [
{
required: true,
message: '请输入角色标识',
type: 'string',
trigger: 'blur'
}
]
});
/** 关闭弹窗 */
const handleCancel = () => {
visible.value = false;
};
/** 保存编辑 */
const save = () => {
formRef.value?.validate?.((valid) => {
if (!valid) {
return;
}
loading.value = true;
const saveOrUpdate = isUpdate.value ? updateRole : addRole;
saveOrUpdate(form)
.then((msg) => {
loading.value = false;
EleMessage.success({ message: msg, plain: true });
handleCancel();
emit('done');
})
.catch((e) => {
loading.value = false;
EleMessage.error({ message: e.message, plain: true });
});
});
};
/** 监听弹窗打开 */
watch(visible, () => {
if (visible.value) {
if (props.data) {
assignFields(props.data);
isUpdate.value = true;
} else {
resetFields();
isUpdate.value = false;
}
}
});
</script>

View File

@ -0,0 +1,64 @@
<!-- 搜索表单 -->
<template>
<ele-card :body-style="{ paddingBottom: '2px' }">
<el-form
label-width="72px"
@keyup.enter.prevent="search"
@submit.prevent=""
>
<el-row :gutter="8">
<el-col :lg="6" :md="8" :sm="12" :xs="24">
<el-form-item label="角色名称">
<el-input
clearable
v-model.trim="form.roleName"
placeholder="请输入"
/>
</el-form-item>
</el-col>
<el-col :lg="6" :md="8" :sm="12" :xs="24">
<el-form-item label="角色标识">
<el-input
clearable
v-model.trim="form.roleCode"
placeholder="请输入"
/>
</el-form-item>
</el-col>
<el-col :lg="12" :md="8" :sm="24" :xs="24">
<el-form-item label-width="16px">
<el-button type="primary" @click="search">查询</el-button>
<el-button @click="reset">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</ele-card>
</template>
<script lang="ts" setup>
import { useFormData } from '@/utils/use-form-data';
import type { RoleParam } from '@/api/system/role/model';
const emit = defineEmits<{
(e: 'search', where?: RoleParam): void;
}>();
/** 表单数据 */
const [form, resetFields] = useFormData<RoleParam>({
roleName: '',
roleCode: '',
comments: ''
});
/** 搜索 */
const search = () => {
emit('search', { ...form });
};
/** 重置 */
const reset = () => {
resetFields();
search();
};
</script>

View File

@ -0,0 +1,202 @@
<template>
<ele-page>
<!-- 搜索表单 -->
<role-search @search="reload" />
<ele-card :body-style="{ paddingTop: '8px' }">
<!-- 表格 -->
<ele-pro-table
ref="tableRef"
row-key="roleId"
:columns="columns"
:datasource="datasource"
:show-overflow-tooltip="true"
v-model:selections="selections"
:highlight-current-row="true"
:export-config="{ fileName: '角色数据', datasource: exportSource }"
:print-config="{ datasource: exportSource }"
cache-key="systemRoleTable"
>
<template #toolbar>
<el-button
type="primary"
class="ele-btn-icon"
:icon="PlusOutlined"
@click="openEdit()"
>
添加
</el-button>
<el-button
type="danger"
class="ele-btn-icon"
:icon="DeleteOutlined"
@click="remove()"
>
删除
</el-button>
</template>
<template #action="{ row }">
<el-link type="primary" underline="never" @click="openEdit(row)">
修改
</el-link>
<el-divider direction="vertical" />
<el-link type="primary" underline="never" @click="openAuth(row)">
分配权限
</el-link>
<el-divider direction="vertical" />
<el-link type="danger" underline="never" @click="remove(row)">
删除
</el-link>
</template>
</ele-pro-table>
</ele-card>
<!-- 编辑弹窗 -->
<role-edit v-model="showEdit" :data="current" @done="reload" />
<!-- 权限分配弹窗 -->
<role-auth v-model="showAuth" :data="current" />
</ele-page>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { ElMessageBox } from 'element-plus';
import { EleMessage } from 'ele-admin-plus';
import type { EleProTable } from 'ele-admin-plus';
import type {
DatasourceFunction,
Columns
} from 'ele-admin-plus/es/ele-pro-table/types';
import { PlusOutlined, DeleteOutlined } from '@/components/icons';
import RoleSearch from './components/role-search.vue';
import RoleEdit from './components/role-edit.vue';
import RoleAuth from './components/role-auth.vue';
import { pageRoles, removeRoles, listRoles } from '@/api/system/role';
import type { Role, RoleParam } from '@/api/system/role/model';
defineOptions({ name: 'SystemRole' });
/** 表格实例 */
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
/** 表格列配置 */
const columns = ref<Columns>([
{
type: 'selection',
columnKey: 'selection',
width: 50,
align: 'center' /* ,
fixed: 'left' */
},
{
type: 'index',
columnKey: 'index',
width: 50,
align: 'center'
},
{
prop: 'roleName',
label: '角色名称',
sortable: 'custom',
minWidth: 120
},
{
prop: 'roleCode',
label: '角色标识',
sortable: 'custom',
minWidth: 120
},
{
prop: 'comments',
label: '备注',
sortable: 'custom',
minWidth: 140
},
{
prop: 'createTime',
label: '创建时间',
sortable: 'custom',
width: 180,
align: 'center'
},
{
columnKey: 'action',
label: '操作',
width: 200,
align: 'center' /* ,
fixed: 'right' */,
slot: 'action',
hideInPrint: true,
hideInExport: true
}
]);
/** 表格选中数据 */
const selections = ref<Role[]>([]);
/** 当前编辑数据 */
const current = ref<Role | null>(null);
/** 是否显示编辑弹窗 */
const showEdit = ref(false);
/** 是否显示权限分配弹窗 */
const showAuth = ref(false);
/** 表格数据源 */
const datasource: DatasourceFunction = ({ pages, where, orders }) => {
return pageRoles({ ...where, ...orders, ...pages });
};
/** 搜索 */
const reload = (where?: RoleParam) => {
selections.value = [];
tableRef.value?.reload?.({ page: 1, where });
};
/** 打开编辑弹窗 */
const openEdit = (row?: Role) => {
current.value = row ?? null;
showEdit.value = true;
};
/** 打开权限分配弹窗 */
const openAuth = (row?: Role) => {
current.value = row ?? null;
showAuth.value = true;
};
/** 删除单个 */
const remove = (row?: Role) => {
const rows = row == null ? selections.value : [row];
if (!rows.length) {
EleMessage.error({ message: '请至少选择一条数据', plain: true });
return;
}
ElMessageBox.confirm(
'确定要删除“' + rows.map((d) => d.roleName).join(', ') + '”吗?',
'系统提示',
{ type: 'warning', draggable: true }
)
.then(() => {
const loading = EleMessage.loading({
message: '请求中..',
plain: true
});
removeRoles(rows.map((d) => d.roleId))
.then((msg) => {
loading.close();
EleMessage.success({ message: msg, plain: true });
reload();
})
.catch((e) => {
loading.close();
EleMessage.error({ message: e.message, plain: true });
});
})
.catch(() => {});
};
/** 导出和打印全部数据的数据源 */
const exportSource: DatasourceFunction = ({ where, orders }) => {
return listRoles({ ...where, ...orders });
};
</script>