第一次提交

This commit is contained in:
gxwebsoft
2023-08-04 13:32:43 +08:00
commit c02e8be49b
1151 changed files with 200453 additions and 0 deletions

View File

@@ -0,0 +1,186 @@
<!-- 编辑弹窗 -->
<template>
<ele-modal
:width="540"
:visible="visible"
:confirm-loading="loading"
:title="`菜单克隆`"
:body-style="{ paddingBottom: '8px' }"
@update:visible="updateVisible"
@ok="save"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="styleResponsive ? { md: 5, sm: 4, xs: 24 } : { flex: '90px' }"
:wrapper-col="
styleResponsive ? { md: 18, sm: 20, xs: 24 } : { flex: '1' }
"
>
<a-form-item label="选择模板" name="tenantId">
<a-input
placeholder="请输入要克隆的租户ID"
v-model:value="form.tenantId"
@pressEnter="save"
/>
</a-form-item>
</a-form>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { message } from 'ant-design-vue/es';
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
import { isExternalLink } from 'ele-admin-pro/es';
import { storeToRefs } from 'pinia';
import { useThemeStore } from '@/store/modules/theme';
import useFormData from '@/utils/use-form-data';
import { clone } from '@/api/system/menu';
import type { Menu } from '@/api/system/menu/model';
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: Menu | null;
// 上级菜单id
parentId?: number;
// 全部菜单数据
menuList: Menu[];
}>();
//
const formRef = ref<FormInstance | null>(null);
// 是否是修改
const isUpdate = ref(false);
// 提交状态
const loading = ref(false);
// 表单数据
const { form, resetFields, assignFields } = useFormData<Menu>({
title: '',
icon: '',
path: '',
component: '',
tenantId: undefined
});
// 表单验证规则
const rules = reactive<Record<string, Rule[]>>({
tenantId: [
{
required: true,
message: '克隆后原有的菜单将会抹除,请慎用!',
type: 'string',
trigger: 'blur'
}
]
});
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const menuForm = {
...form
};
clone(menuForm)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
/* 判断是否是目录 */
const isDirectory = (d: Menu) => {
return !!d.children?.length && !d.component;
};
// 查询租户列表
// const tenantList = ref<Tenant[]>([]);
// const reload = (tenantName?: any) => {
// listTenant({ tenantName }).then((result) => {
// tenantList.value = result;
// });
// };
//
// reload();
watch(
() => props.visible,
(visible) => {
if (visible) {
if (props.data) {
const isExternal = isExternalLink(props.data.path);
const isInner = isExternalLink(props.data.component);
// menuType 对应的值与后端不一致在前端处理
const menuType =
props.data.menuType === 1 ? 2 : isDirectory(props.data) ? 0 : 1;
assignFields({
...props.data,
menuType,
openType: isExternal ? 2 : isInner ? 1 : 0,
parentId:
props.data.parentId === 0 ? undefined : props.data.parentId
});
isUpdate.value = true;
} else {
form.parentId = props.parentId;
isUpdate.value = false;
}
} else {
resetFields();
formRef.value?.clearValidate();
}
}
);
</script>
<script lang="ts">
import * as icons from '@/layout/menu-icons';
export default {
components: icons,
data() {
return {
iconData: [
{
title: '已引入的图标',
icons: Object.keys(icons)
}
]
};
}
};
</script>

View File

@@ -0,0 +1,416 @@
<!-- 编辑弹窗 -->
<template>
<ele-modal
:width="740"
:visible="visible"
:confirm-loading="loading"
:title="isUpdate ? '修改菜单' : '新建菜单'"
:body-style="{ paddingBottom: '8px' }"
@update:visible="updateVisible"
@ok="save"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="styleResponsive ? { md: 6, sm: 4, xs: 24 } : { flex: '90px' }"
:wrapper-col="
styleResponsive ? { md: 18, sm: 20, xs: 24 } : { flex: '1' }
"
>
<a-row :gutter="16">
<a-col
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
>
<a-form-item label="上级菜单" name="parentId">
<a-tree-select
allow-clear
:tree-data="menuList"
tree-default-expand-all
placeholder="请选择上级菜单"
:value="form.parentId || undefined"
:dropdown-style="{ maxHeight: '360px', overflow: 'auto' }"
@update:value="(value?: number) => (form.parentId = value)"
/>
</a-form-item>
<a-form-item label="菜单名称" name="title">
<a-input
allow-clear
placeholder="请输入菜单名称"
v-model:value="form.title"
@pressEnter="save"
/>
</a-form-item>
</a-col>
<a-col
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
>
<a-form-item label="菜单类型" name="menuType">
<a-radio-group
v-model:value="form.menuType"
@change="onMenuTypeChange"
>
<a-radio :value="0">目录</a-radio>
<a-radio :value="1">菜单</a-radio>
<a-radio :value="2">按钮</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="打开方式">
<a-radio-group
v-model:value="form.openType"
:disabled="form.menuType === 0 || form.menuType === 2"
@change="onOpenTypeChange"
>
<a-radio :value="0">组件</a-radio>
<a-radio :value="1">内链</a-radio>
<a-radio :value="2">外链</a-radio>
</a-radio-group>
</a-form-item>
</a-col>
</a-row>
<div style="margin-bottom: 22px">
<a-divider />
</div>
<a-row :gutter="16">
<a-col
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
>
<a-form-item label="菜单图标" name="icon">
<ele-icon-picker
:data="iconData"
:allow-search="false"
v-model:value="form.icon"
placeholder="请选择菜单图标"
:disabled="form.menuType === 2"
>
<template #icon="{ icon }">
<component :is="icon" />
</template>
</ele-icon-picker>
</a-form-item>
<a-form-item name="path">
<template #label>
<a-tooltip
v-if="form.openType === 2"
title="需要以`http://`、`https://`、`//`开头"
>
<question-circle-outlined
style="vertical-align: -2px; margin-right: 4px"
/>
</a-tooltip>
<span>{{ form.openType === 2 ? '外链地址' : '路由地址' }}</span>
</template>
<a-input
allow-clear
v-model:value="form.path"
:disabled="form.menuType === 2"
:placeholder="
form.openType === 2 ? '请输入外链地址' : '请输入路由地址'
"
/>
</a-form-item>
<a-form-item name="component">
<template #label>
<a-tooltip
v-if="form.openType === 1"
title="需要以`http://`、`https://`、`//`开头"
>
<question-circle-outlined
style="vertical-align: -2px; margin-right: 4px"
/>
</a-tooltip>
<span>{{ form.openType === 1 ? '内链地址' : '组件路径' }}</span>
</template>
<a-input
allow-clear
v-model:value="form.component"
:disabled="
form.menuType === 0 ||
form.menuType === 2 ||
form.openType === 2
"
:placeholder="
form.openType === 1 ? '请输入内链地址' : '请输入组件路径'
"
/>
</a-form-item>
</a-col>
<a-col
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
>
<a-form-item label="权限标识" name="authority">
<a-input
allow-clear
placeholder="请输入权限标识"
v-model:value="form.authority"
:disabled="
form.menuType === 0 ||
(form.menuType === 1 && form.openType === 2)
"
/>
</a-form-item>
<a-form-item label="排序号" name="sortNumber">
<a-input-number
:min="0"
:max="99999"
class="ele-fluid"
placeholder="请输入排序号"
v-model:value="form.sortNumber"
@pressEnter="save"
/>
</a-form-item>
<a-form-item label="是否展示">
<a-switch
checked-children=""
un-checked-children=""
:checked="form.hide === 0"
:disabled="form.menuType === 2"
@update:checked="updateHideValue"
/>
<a-tooltip
title="选择不展示只注册路由不展示在侧边栏, 比如添加页面应该选择不展示"
>
<question-circle-outlined
style="vertical-align: -4px; margin-left: 16px"
/>
</a-tooltip>
</a-form-item>
</a-col>
</a-row>
<a-form-item
label="路由元数据"
name="meta"
:label-col="
styleResponsive ? { md: 3, sm: 4, xs: 24 } : { flex: '90px' }
"
:wrapper-col="
styleResponsive ? { md: 21, sm: 20, xs: 24 } : { flex: '1' }
"
>
<a-textarea
:rows="4"
:maxlength="200"
placeholder="请输入JSON格式的路由元数据"
v-model:value="form.meta"
/>
</a-form-item>
</a-form>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { message } from 'ant-design-vue/es';
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
import { QuestionCircleOutlined } from '@ant-design/icons-vue';
import { isExternalLink } from 'ele-admin-pro/es';
import { storeToRefs } from 'pinia';
import { useThemeStore } from '@/store/modules/theme';
import useFormData from '@/utils/use-form-data';
import { addMenu, updateMenu } from '@/api/system/menu';
import type { Menu } from '@/api/system/menu/model';
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: Menu | null;
// 上级菜单id
parentId?: number;
// 全部菜单数据
menuList: Menu[];
}>();
//
const formRef = ref<FormInstance | null>(null);
// 是否是修改
const isUpdate = ref(false);
// 提交状态
const loading = ref(false);
// 表单数据
const { form, resetFields, assignFields } = useFormData<Menu>({
menuId: undefined,
parentId: undefined,
title: '',
menuType: 0,
openType: 0,
icon: '',
path: '',
component: '',
authority: '',
sortNumber: undefined,
hide: 0,
meta: ''
});
// 表单验证规则
const rules = reactive<Record<string, Rule[]>>({
title: [
{
required: true,
message: '请输入菜单名称',
type: 'string',
trigger: 'blur'
}
],
sortNumber: [
{
required: true,
message: '请输入排序号',
type: 'number',
trigger: 'blur'
}
],
meta: [
{
type: 'string',
validator: async (_rule: Rule, value: string) => {
if (value) {
const msg = '请输入正确的JSON格式';
try {
const obj = JSON.parse(value);
if (typeof obj !== 'object' || obj === null) {
return Promise.reject(msg);
}
} catch (_e) {
return Promise.reject(msg);
}
}
return Promise.resolve();
},
trigger: 'blur'
}
]
});
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const menuForm = {
...form,
// menuType 对应的值与后端不一致在前端处理
menuType: form.menuType === 2 ? 1 : 0,
parentId: form.parentId || 0
};
const saveOrUpdate = isUpdate.value ? updateMenu : addMenu;
saveOrUpdate(menuForm)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
/* menuType选择改变 */
const onMenuTypeChange = () => {
if (form.menuType === 0) {
form.authority = '';
form.openType = 0;
form.component = '';
} else if (form.menuType === 1) {
if (form.openType === 2) {
form.authority = '';
}
} else {
form.openType = 0;
form.icon = '';
form.path = '';
form.component = '';
form.hide = 0;
}
};
/* openType选择改变 */
const onOpenTypeChange = () => {
if (form.openType === 2) {
form.component = '';
form.authority = '';
}
};
const updateHideValue = (value: boolean) => {
form.hide = value ? 0 : 1;
};
/* 判断是否是目录 */
const isDirectory = (d: Menu) => {
return !!d.children?.length && !d.component;
};
watch(
() => props.visible,
(visible) => {
if (visible) {
if (props.data) {
const isExternal = isExternalLink(props.data.path);
const isInner = isExternalLink(props.data.component);
// menuType 对应的值与后端不一致在前端处理
const menuType =
props.data.menuType === 1 ? 2 : isDirectory(props.data) ? 0 : 1;
assignFields({
...props.data,
menuType,
openType: isExternal ? 2 : isInner ? 1 : 0,
parentId:
props.data.parentId === 0 ? undefined : props.data.parentId
});
isUpdate.value = true;
} else {
form.parentId = props.parentId;
isUpdate.value = false;
}
} else {
resetFields();
formRef.value?.clearValidate();
}
}
);
</script>
<script lang="ts">
import * as icons from '@/layout/menu-icons';
export default {
components: icons,
data() {
return {
iconData: [
{
title: '已引入的图标',
icons: Object.keys(icons)
}
]
};
}
};
</script>

View File

@@ -0,0 +1,106 @@
<!-- 搜索表单 -->
<template>
<a-form
:label-col="
styleResponsive ? { xl: 7, lg: 5, md: 7, sm: 4 } : { flex: '90px' }
"
:wrapper-col="
styleResponsive ? { xl: 17, lg: 19, md: 17, sm: 20 } : { flex: '1' }
"
>
<a-row :gutter="8">
<a-col
v-bind="
styleResponsive
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
: { span: 6 }
"
>
<a-form-item label="菜单名称">
<a-input
v-model:value.trim="form.title"
placeholder="请输入"
allow-clear
/>
</a-form-item>
</a-col>
<a-col
v-bind="
styleResponsive
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
: { span: 6 }
"
>
<a-form-item label="菜单地址">
<a-input
v-model:value.trim="form.path"
placeholder="请输入"
allow-clear
/>
</a-form-item>
</a-col>
<a-col
v-bind="
styleResponsive
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
: { span: 6 }
"
>
<a-form-item label="权限标识">
<a-input
v-model:value.trim="form.authority"
placeholder="请输入"
allow-clear
/>
</a-form-item>
</a-col>
<a-col
v-bind="
styleResponsive
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
: { span: 6 }
"
>
<a-form-item class="ele-text-right" :wrapper-col="{ span: 24 }">
<a-space>
<a-button type="primary" @click="search">查询</a-button>
<a-button @click="reset">重置</a-button>
</a-space>
</a-form-item>
</a-col>
</a-row>
</a-form>
</template>
<script lang="ts" setup>
import { storeToRefs } from 'pinia';
import { useThemeStore } from '@/store/modules/theme';
import useFormData from '@/utils/use-form-data';
import type { MenuParam } from '@/api/system/menu/model';
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const emit = defineEmits<{
(e: 'search', where?: MenuParam): void;
}>();
// 表单数据
const { form, resetFields } = useFormData<MenuParam>({
title: '',
path: '',
authority: ''
});
/* 搜索 */
const search = () => {
emit('search', form);
};
/* 重置 */
const reset = () => {
resetFields();
search();
};
</script>

View File

@@ -0,0 +1,253 @@
<!-- 编辑弹窗 -->
<template>
<ele-modal
:width="780"
:visible="visible"
:confirm-loading="loading"
title="安装插件"
:body-style="{ paddingBottom: '8px' }"
@update:visible="updateVisible"
okText="安装插件"
:footer="footer"
@ok="save"
>
<template v-if="!isSuccess">
<a-alert
:description="`安装成功后,插件出现在左侧菜单中,请在 【系统设置->菜单管理】修改插件的名称及顺序`"
type="success"
closable
show-icon
style="margin-bottom: 20px"
>
<template #icon><SmileOutlined /></template>
</a-alert>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="
styleResponsive ? { md: 6, sm: 4, xs: 24 } : { flex: '90px' }
"
:wrapper-col="
styleResponsive ? { md: 18, sm: 20, xs: 24 } : { flex: '1' }
"
>
<a-row :gutter="16">
<a-col
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
>
<a-form-item label="插件名称" name="parentId">
<span class="ele-text-heading">{{ data.title }}</span>
</a-form-item>
<a-form-item label="价格" name="comments">
{{ data.price }}
</a-form-item>
</a-col>
<a-col
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
>
<a-form-item label="插件ID" name="menuId">
<span class="ele-text-secondary">{{ data.menuId }}</span>
</a-form-item>
<a-form-item label="开发商" name="sortName">
<span class="ele-text-secondary">
{{ data.tenantId === 5 ? '官方' : data.shortName }}
</span>
</a-form-item>
</a-col>
<a-col
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 24 }"
>
<a-form-item label="插件介绍" name="comments">
<div class="ele-text-secondary" style="padding: 5px">
<byte-md-viewer :value="data.comments" :plugins="plugins" />
</div>
</a-form-item>
</a-col>
</a-row>
</a-form>
</template>
<a-result
status="success"
title="安装成功"
v-if="isSuccess"
sub-title="请在 系统设置->菜单管理修改插件的名称及顺序"
>
<template #extra>
<a-button key="console" type="primary" @click="reset(data.path)"
>立即前往</a-button
>
</template>
</a-result>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import { message } from 'ant-design-vue/es';
import type { FormInstance } from 'ant-design-vue/es/form';
import { isExternalLink } from 'ele-admin-pro/es';
import { storeToRefs } from 'pinia';
import { useThemeStore } from '@/store/modules/theme';
import useFormData from '@/utils/use-form-data';
import type { Menu } from '@/api/system/menu/model';
import { Plug } from '@/api/system/plug/model';
import { SmileOutlined } from '@ant-design/icons-vue';
import gfm from '@bytemd/plugin-gfm';
import highlight from '@bytemd/plugin-highlight-ssr';
// // 插件的中文语言文件
import zh_HansGfm from '@bytemd/plugin-gfm/locales/zh_Hans.json';
import { installPlug } from '@/api/system/menu';
import { reloadPageTab } from '@/utils/page-tab-util';
import { useRouter } from 'vue-router';
const { push } = useRouter();
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: Plug | null;
// 上级插件id
parentId?: number;
// 全部插件数据
menuList: Plug[];
}>();
// 是否是修改
const isUpdate = ref(false);
// 提交状态
const loading = ref(false);
const appid = ref(undefined);
const formRef = ref<FormInstance | null>(null);
const isSuccess = ref(false);
const footer = ref();
// 表单数据
const { form, resetFields, assignFields } = useFormData<Plug>({
plugId: undefined,
menuId: undefined,
parentId: undefined,
title: '',
menuType: 0,
openType: 0,
icon: '',
path: '',
component: '',
authority: '',
comments: '',
sortNumber: undefined,
hide: 0,
meta: '',
status: 10
});
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const menuForm = {
...form,
// menuType 对应的值与后端不一致在前端处理
menuType: form.menuType === 2 ? 1 : 0,
parentId: form.parentId || 0
};
console.log(menuForm);
installPlug(form.menuId)
.then((msg) => {
loading.value = false;
isSuccess.value = true;
footer.value = null;
// message.success(msg);
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 插件
const plugins = ref([
gfm({
locale: zh_HansGfm
}),
highlight()
]);
const reset = (url) => {
console.log(url);
push(url);
};
/* 判断是否是目录 */
const isDirectory = (d: Menu) => {
return !!d.children?.length && !d.component;
};
watch(
() => props.visible,
(visible) => {
if (visible) {
if (props.data) {
const isExternal = isExternalLink(props.data.path);
const isInner = isExternalLink(props.data.component);
// menuType 对应的值与后端不一致在前端处理
const menuType =
props.data.menuType === 1 ? 2 : isDirectory(props.data) ? 0 : 1;
assignFields({
...props.data,
menuType,
openType: isExternal ? 2 : isInner ? 1 : 0,
parentId:
props.data.parentId === 0 ? undefined : props.data.parentId
});
isUpdate.value = true;
} else {
form.parentId = props.parentId;
isUpdate.value = false;
}
} else {
resetFields();
formRef.value?.clearValidate();
}
}
);
</script>
<script lang="ts">
import * as icons from '@/layout/menu-icons';
export default {
components: icons,
data() {
return {
iconData: [
{
title: '已引入的图标',
icons: Object.keys(icons)
}
]
};
}
};
</script>

View File

@@ -0,0 +1,67 @@
<!-- 搜索表单 -->
<template>
<a-space :size="10" style="flex-wrap: wrap">
<!-- <a-radio-group :defaultValue="plugType" @change="handleTabs">-->
<!-- <a-radio-button :value="0">插件市场</a-radio-button>-->
<!-- <a-radio-button :value="10">我的插件</a-radio-button>-->
<!-- </a-radio-group>-->
<a-input-search
allow-clear
placeholder="请输入搜索关键词"
v-model:value="searchText"
@pressEnter="search"
@search="search"
/>
</a-space>
</template>
<script lang="ts" setup>
import useSearch from '@/utils/use-search';
import type { CustomerParam } from '@/api/oa/customer/model';
import { ref, watch } from 'vue';
import { AppParam } from '@/api/app/model';
const props = withDefaults(
defineProps<{
// 选中的角色
selection?: [];
}>(),
{}
);
const emit = defineEmits<{
(e: 'search', where?: CustomerParam): void;
(e: 'add'): void;
(e: 'remove'): void;
}>();
// 表单数据
const { where, resetFields } = useSearch<AppParam>({
appId: undefined,
userId: undefined,
keywords: undefined,
status: 0
});
// const plugType = ref<number>(0);
// 搜索内容
const searchText = ref(null);
/* 搜索 */
const search = () => {
resetFields();
if (searchText.value) {
where.keywords = searchText.value;
}
emit('search', where);
};
// const reload = () => {
// // 刷新当前路由
// emit('search', where);
// };
watch(
() => props.selection,
() => {}
);
</script>

View File

@@ -0,0 +1,186 @@
<!-- 编辑弹窗 -->
<template>
<ele-modal
:width="540"
:visible="visible"
:confirm-loading="loading"
:title="`菜单克隆`"
:body-style="{ paddingBottom: '8px' }"
@update:visible="updateVisible"
@ok="save"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="styleResponsive ? { md: 5, sm: 4, xs: 24 } : { flex: '90px' }"
:wrapper-col="
styleResponsive ? { md: 18, sm: 20, xs: 24 } : { flex: '1' }
"
>
<a-form-item label="选择模板" name="tenantId">
<a-input
placeholder="请输入要克隆的租户ID"
v-model:value="form.tenantId"
@pressEnter="save"
/>
</a-form-item>
</a-form>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { message } from 'ant-design-vue/es';
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
import { isExternalLink } from 'ele-admin-pro/es';
import { storeToRefs } from 'pinia';
import { useThemeStore } from '@/store/modules/theme';
import useFormData from '@/utils/use-form-data';
import { clone } from '@/api/system/menu';
import type { Menu } from '@/api/system/menu/model';
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: Menu | null;
// 上级菜单id
parentId?: number;
// 全部菜单数据
menuList: Menu[];
}>();
//
const formRef = ref<FormInstance | null>(null);
// 是否是修改
const isUpdate = ref(false);
// 提交状态
const loading = ref(false);
// 表单数据
const { form, resetFields, assignFields } = useFormData<Menu>({
title: '',
icon: '',
path: '',
component: '',
tenantId: undefined
});
// 表单验证规则
const rules = reactive<Record<string, Rule[]>>({
tenantId: [
{
required: true,
message: '克隆后原有的菜单将会抹除,请慎用!',
type: 'string',
trigger: 'blur'
}
]
});
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const menuForm = {
...form
};
clone(menuForm)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
/* 判断是否是目录 */
const isDirectory = (d: Menu) => {
return !!d.children?.length && !d.component;
};
// 查询租户列表
// const tenantList = ref<Tenant[]>([]);
// const reload = (tenantName?: any) => {
// listTenant({ tenantName }).then((result) => {
// tenantList.value = result;
// });
// };
//
// reload();
watch(
() => props.visible,
(visible) => {
if (visible) {
if (props.data) {
const isExternal = isExternalLink(props.data.path);
const isInner = isExternalLink(props.data.component);
// menuType 对应的值与后端不一致在前端处理
const menuType =
props.data.menuType === 1 ? 2 : isDirectory(props.data) ? 0 : 1;
assignFields({
...props.data,
menuType,
openType: isExternal ? 2 : isInner ? 1 : 0,
parentId:
props.data.parentId === 0 ? undefined : props.data.parentId
});
isUpdate.value = true;
} else {
form.parentId = props.parentId;
isUpdate.value = false;
}
} else {
resetFields();
formRef.value?.clearValidate();
}
}
);
</script>
<script lang="ts">
import * as icons from '@/layout/menu-icons';
export default {
components: icons,
data() {
return {
iconData: [
{
title: '已引入的图标',
icons: Object.keys(icons)
}
]
};
}
};
</script>

View File

@@ -0,0 +1,262 @@
<!-- 编辑弹窗 -->
<template>
<ele-modal
:width="740"
:visible="visible"
:confirm-loading="loading"
:title="isUpdate ? '插件管理' : '发布插件'"
:body-style="{ paddingBottom: '8px' }"
@update:visible="updateVisible"
@ok="save"
>
<a-alert
:description="`审核通过后,插件将展示在插件市场,可供其他用户安装和使用后,获取销售分成。`"
type="success"
closable
show-icon
style="margin-bottom: 20px"
>
<template #icon><SmileOutlined /></template>
</a-alert>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="styleResponsive ? { md: 6, sm: 4, xs: 24 } : { flex: '90px' }"
:wrapper-col="
styleResponsive ? { md: 18, sm: 20, xs: 24 } : { flex: '1' }
"
>
<a-row :gutter="16">
<a-col
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
>
<a-form-item label="选择模块" name="parentId">
<a-tree-select
allow-clear
:tree-data="menuList"
tree-default-expand-all
placeholder="请选择上级插件"
:value="form.parentId || undefined"
:dropdown-style="{ maxHeight: '360px', overflow: 'auto' }"
@update:value="(value?: number) => (form.parentId = value)"
/>
</a-form-item>
<a-form-item label="插件名称" name="title">
<a-input
allow-clear
placeholder="请输入插件名称"
v-model:value="form.title"
/>
</a-form-item>
<!-- <a-form-item label="关联应用" name="appId">-->
<!-- <SelectApp v-model:value="appId" @done="onApp" />-->
<!-- </a-form-item>-->
</a-col>
<a-col
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
>
<a-form-item label="插件价格" name="price">
<a-input-number
allow-clear
:min="0"
:precision="2"
style="width: 200px"
placeholder="请输入插件价格"
v-model:value="form.price"
/>
<span class="ml-10"></span>
</a-form-item>
</a-col>
</a-row>
<div style="margin-bottom: 22px">
<a-divider />
</div>
<a-form-item
label="插件简介"
name="comments"
:label-col="
styleResponsive ? { md: 3, sm: 4, xs: 24 } : { flex: '90px' }
"
:wrapper-col="
styleResponsive ? { md: 21, sm: 20, xs: 24 } : { flex: '1' }
"
>
<a-textarea
:rows="4"
:maxlength="200"
placeholder="请输入插件简介"
v-model:value="form.comments"
/>
</a-form-item>
</a-form>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { message } from 'ant-design-vue/es';
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
import { isExternalLink } from 'ele-admin-pro/es';
import { storeToRefs } from 'pinia';
import { useThemeStore } from '@/store/modules/theme';
import useFormData from '@/utils/use-form-data';
import type { Menu } from '@/api/system/menu/model';
import { createPlug, updatePlug } from '@/api/system/plug';
import { Plug } from '@/api/system/plug/model';
import { SmileOutlined } from '@ant-design/icons-vue';
import { addDocs, updateDocs } from '@/api/cms/docs';
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: Menu | null;
// 上级插件id
parentId?: number;
// 全部插件数据
menuList: Menu[];
}>();
// 是否是修改
const isUpdate = ref(false);
// 提交状态
const loading = ref(false);
const formRef = ref<FormInstance | null>(null);
// 表单数据
const { form, resetFields, assignFields } = useFormData<Plug>({
plugId: undefined,
menuId: undefined,
parentId: undefined,
title: '',
price: undefined,
comments: '',
status: undefined
});
// 表单验证规则
const rules = reactive<Record<string, Rule[]>>({
parentId: [
{
required: true,
message: '请选择模块',
type: 'number',
trigger: 'blur'
}
],
title: [
{
required: true,
message: '请输入插件名称',
type: 'string',
trigger: 'blur'
}
]
});
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const formData = {
...form,
status: 10
// content: content.value
};
const saveOrUpdate = isUpdate.value ? updatePlug : createPlug;
saveOrUpdate(formData)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
/* 判断是否是目录 */
const isDirectory = (d: Menu) => {
return !!d.children?.length && !d.component;
};
watch(
() => props.visible,
(visible) => {
if (visible) {
if (props.data) {
const isExternal = isExternalLink(props.data.path);
const isInner = isExternalLink(props.data.component);
// menuType 对应的值与后端不一致在前端处理
const menuType =
props.data.menuType === 1 ? 2 : isDirectory(props.data) ? 0 : 1;
assignFields({
...props.data,
menuType,
openType: isExternal ? 2 : isInner ? 1 : 0,
parentId:
props.data.parentId === 0 ? undefined : props.data.parentId
});
form.parentId = props.parentId;
isUpdate.value = true;
} else {
form.parentId = props.parentId;
isUpdate.value = false;
}
} else {
resetFields();
formRef.value?.clearValidate();
}
}
);
</script>
<script lang="ts">
import * as icons from '@/layout/menu-icons';
export default {
components: icons,
data() {
return {
iconData: [
{
title: '已引入的图标',
icons: Object.keys(icons)
}
]
};
}
};
</script>
<style lang="less">
.tab-pane {
min-height: 300px;
}
.ml-10 {
margin-left: 5px;
}
</style>

View File

@@ -0,0 +1,98 @@
<!-- 搜索表单 -->
<template>
<a-space :size="10" style="flex-wrap: wrap">
<a-button type="primary" class="ele-btn-icon" @click="add">
<template #icon>
<PlusOutlined />
</template>
<span>发布插件</span>
</a-button>
<!-- <a-input-search-->
<!-- allow-clear-->
<!-- placeholder="请输入搜索关键词"-->
<!-- v-model:value="searchText"-->
<!-- @pressEnter="search"-->
<!-- @search="search"-->
<!-- />-->
</a-space>
</template>
<script lang="ts" setup>
import useSearch from '@/utils/use-search';
import type { CustomerParam } from '@/api/oa/customer/model';
import { computed, ref, watch } from 'vue';
import { AppParam } from '@/api/app/model';
import { useUserStore } from '@/store/modules/user';
const props = withDefaults(
defineProps<{
// 选中的角色
selection?: [];
}>(),
{}
);
const emit = defineEmits<{
(e: 'search', where?: CustomerParam): void;
(e: 'add'): void;
(e: 'remove'): void;
}>();
// 表单数据
const { where, resetFields } = useSearch<AppParam>({
appId: undefined,
userId: undefined,
keywords: undefined,
status: 0
});
const plugType = ref<number>(0);
// 搜索内容
const searchText = ref(null);
const userId = ref(0);
/* 搜索 */
const search = () => {
resetFields();
if (searchText.value) {
where.keywords = searchText.value;
}
emit('search', where);
};
// 新增
const add = () => {
emit('add');
};
// 批量删除
const removeBatch = () => {
emit('remove');
};
const handleTabs = (e) => {
const index = Number(e.target.value);
const userStore = useUserStore();
const loginUser = computed(() => userStore.info ?? {});
plugType.value = index;
if (index > 0) {
userId.value = Number(loginUser.value.userId);
where.status = undefined;
where.userId = Number(loginUser.value.userId);
}
if (index == 0) {
where.userId = undefined;
where.status = 20;
}
search();
};
const reload = () => {
// 刷新当前路由
emit('search', where);
};
watch(
() => props.selection,
() => {}
);
</script>

View File

@@ -0,0 +1,296 @@
<template>
<div class="ele-body">
<a-card title="插件管理" :bordered="false">
<!-- 表格 -->
<ele-pro-table
ref="tableRef"
row-key="plugId"
:columns="columns"
:datasource="datasource"
:expand-icon-column-index="1"
:expanded-row-keys="expandedRowKeys"
cache-key="proSystemPlugTable"
@done="onDone"
@expand="onExpand"
>
<template #toolbar>
<PlugSearch
@search="reload"
:selection="selection"
@add="openEdit"
@remove="removeBatch"
/>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'title'">
<div class="app-box">
<a-image
:height="50"
:width="50"
:preview="false"
:src="record.icon"
fallback="https://file.wsdns.cn/20230218/550e610d43334dd2a7f66d5b20bd58eb.svg"
/>
<!-- <component v-if="record.icon" :is="record.icon" />-->
<div class="app-info">
<a class="ele-text-heading" @click="openEdit(record)">
{{ record.title }}
</a>
<div class="ele-text-placeholder comments">
{{ record.comments }}
</div>
<a-space size="large" class="ele-text-placeholder">
<a
class="ele-text-placeholder"
:href="`${record.domain}`"
target="_blank"
>
{{ record.companyName }}
</a>
<span>下载: {{ record.clicks ? record.clicks : 0 }}</span>
<span>收藏: {{ record.installs ? record.installs : 0 }}</span>
</a-space>
</div>
</div>
</template>
<template v-else-if="column.key === 'comments'">
<span class="ele-text-secondary">
{{ record.comments }}
</span>
</template>
<template v-if="column.key === 'appType'">
<span class="ele-text-placeholder" v-if="record.appType === 'web'">
网站应用
</span>
<span
class="ele-text-placeholder"
v-if="record.appType === 'mp-weixin'"
>
小程序
</span>
<span
class="ele-text-placeholder"
v-if="record.appType === 'h5-weixin'"
>
公众号
</span>
<span
class="ele-text-placeholder"
v-if="record.appType === 'app-plus'"
>
移动应用
</span>
<span class="ele-text-placeholder" v-if="record.appType === 'plug'">
插件
</span>
</template>
<template v-if="column.key === 'price'">
<a class="ele-text-warning">{{ record.price }}</a>
</template>
<template v-if="column.key === 'shortName'">
<span class="ele-text-success">
{{ record.sortName }}
</span>
</template>
<template v-if="column.key === 'score'">
<a>{{ record.score.toFixed(1) }}</a>
</template>
<template v-if="column.key === 'status'">
<a-tag v-if="record.status === 0" color="green">正常</a-tag>
<a-tag v-if="record.status === 10" color="orange">待审核</a-tag>
<a-tag v-if="record.status === 20" color="green">已通过</a-tag>
<a-tag v-if="record.status === 30" color="red">已驳回</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a @click="openEdit(record)">管理</a>
</a-space>
</template>
</template>
</ele-pro-table>
</a-card>
<!-- 编辑弹窗 -->
<PlugEdit
v-model:visible="showEdit"
:data="current"
:parent-id="parentId"
:menu-list="menuData"
@done="reload"
/>
<clone v-model:visible="showClone" @done="reload" />
</div>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue';
const { push } = useRouter();
import type {
DatasourceFunction,
ColumnItem
// EleProTableDone
} from 'ele-admin-pro/es/ele-pro-table/types';
import PlugSearch from './components/plug-search.vue';
import { toTreeData, toDateString } from 'ele-admin-pro/es';
import type { EleProTable } from 'ele-admin-pro/es';
import PlugEdit from './components/plug-edit.vue';
import Clone from './components/clone.vue';
import { pagePlug } from '@/api/system/plug';
import type { Plug, PlugParam } from '@/api/system/plug/model';
import { Menu } from '@/api/system/menu/model';
import { listMenus } from '@/api/system/menu';
import { useRouter } from 'vue-router';
import { useUserStore } from '@/store/modules/user';
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格列配置
const columns = ref<ColumnItem[]>([
{
title: '插件ID',
dataIndex: 'menuId',
align: 'center'
},
{
title: '插件名称',
dataIndex: 'title',
key: 'title'
},
{
title: '价格',
dataIndex: 'price',
key: 'price',
align: 'center',
customRender: ({ text }) => '¥' + text
},
{
title: '评分',
dataIndex: 'score',
align: 'center',
key: 'score'
},
{
title: '审核状态',
dataIndex: 'status',
align: 'center',
key: 'status'
},
{
title: '操作',
key: 'action',
width: 200,
align: 'center'
}
]);
// 当前编辑数据
const current = ref<Plug | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
const showClone = ref(false);
// 上级菜单id
const parentId = ref<number>();
// 菜单数据
const menuData = ref<Menu[]>([]);
const userStore = useUserStore();
const loginUser = computed(() => userStore.info ?? {});
// 表格展开的行
const expandedRowKeys = ref<number[]>([]);
// 表格数据源
const datasource: DatasourceFunction = ({ where }) => {
where.userId = loginUser.value.userId;
return pagePlug({ ...where });
};
/* 表格渲染完成回调 */
// const onDone: EleProTableDone<Plug> = ({ data }) => {
// menuData.value = data;
// };
/* 刷新表格 */
const reload = (where?: PlugParam) => {
tableRef?.value?.reload({ where });
};
/* 打开编辑弹窗 */
const openEdit = (row?: Plug | null, id?: number) => {
console.log(row);
current.value = row ?? null;
parentId.value = row?.menuId;
showEdit.value = true;
};
/* 一键克隆 */
const clonePlug = (row?: Plug | null, id?: number) => {
current.value = row ?? null;
parentId.value = id;
showClone.value = true;
};
const query = () => {
listMenus({}).then((res) => {
if (res) {
menuData.value = parseData(res);
}
});
};
/* 数据转为树形结构 */
const parseData = (data: Menu[]) => {
return toTreeData({
data: data
.filter((d) => d.menuType == 0)
.map((d) => {
if (d.parentId != 0) {
// d.disabled = true;
}
return { ...d, key: d.menuId, value: d.menuId };
}),
idField: 'menuId',
parentIdField: 'parentId'
});
};
query();
</script>
<script lang="ts">
import * as PlugIcons from '@/layout/menu-icons';
export default {
name: 'SystemPlug',
components: PlugIcons
};
</script>
<style lang="less" scoped>
.app-box {
display: flex;
.app-info {
display: flex;
margin-left: 6px;
margin-right: 6px;
flex-direction: column;
justify-content: space-between;
}
}
.cursor-pointer {
cursor: pointer;
}
.sys-org-table :deep(.ant-table-body) {
overflow: auto !important;
overflow: overlay !important;
}
.sys-org-table :deep(.ant-table-pagination.ant-pagination) {
padding: 0 4px;
margin-bottom: 0;
}
.ele-text-heading {
}
.comments {
width: 420px;
padding: 5px 0;
margin-bottom: 10px;
}
</style>

View File

@@ -0,0 +1,276 @@
<template>
<div class="ele-body">
<a-card title="扩展插件" :bordered="false">
<template #extra>
<a-button class="ele-btn-icon" @click="push('/system/plug/create')">
<span>发布插件</span>
</a-button>
</template>
<!-- 表格 -->
<ele-pro-table
ref="tableRef"
row-key="plugId"
:columns="columns"
:datasource="datasource"
:expand-icon-column-index="1"
:expanded-row-keys="expandedRowKeys"
cache-key="proSystemPlugTable"
@done="onDone"
@expand="onExpand"
>
<template #toolbar>
<PlugSearch @search="reload" :selection="selection" @add="openEdit" />
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'title'">
<div class="app-box">
<a-image
:height="50"
:width="50"
:preview="false"
:src="record.icon"
fallback="https://file.wsdns.cn/20230218/550e610d43334dd2a7f66d5b20bd58eb.svg"
/>
<!-- <component v-if="record.icon" :is="record.icon" />-->
<div class="app-info">
<a class="ele-text-heading" @click="openEdit(record)">
{{ record.title }}
</a>
<div class="ele-text-placeholder comments">
{{ record.comments }}
</div>
<a-space :size="50" class="ele-text-placeholder">
<span>
{{ record.companyName }}
</span>
<span>赞赏: {{ record.clicks ? record.clicks : 0 }}</span>
<span>安装: {{ record.installs ? record.installs : 0 }}</span>
</a-space>
</div>
</div>
</template>
<template v-else-if="column.key === 'comments'">
<span class="ele-text-secondary">
{{ record.comments }}
</span>
</template>
<template v-if="column.key === 'appType'">
<span class="ele-text-placeholder" v-if="record.appType === 'web'">
网站应用
</span>
<span
class="ele-text-placeholder"
v-if="record.appType === 'mp-weixin'"
>
小程序
</span>
<span
class="ele-text-placeholder"
v-if="record.appType === 'h5-weixin'"
>
公众号
</span>
<span
class="ele-text-placeholder"
v-if="record.appType === 'app-plus'"
>
移动应用
</span>
<span class="ele-text-placeholder" v-if="record.appType === 'plug'">
插件
</span>
</template>
<template v-if="column.key === 'shortName'">
<span class="ele-text-placeholder">
{{ record.shortName }}
</span>
</template>
<template v-if="column.key === 'price'">
<span class="ele-text-success" v-if="record.price === 0">免费</span>
<span class="ele-text-warning" v-else>{{ record.price }}</span>
</template>
<template v-if="column.key === 'score'">
<a>{{ record.score.toFixed(1) }}</a>
</template>
<template v-if="column.key === 'status'">
<a-tag v-if="record.status === 0" color="green">正常</a-tag>
<a-tag v-if="record.status === 10" color="orange">待审核</a-tag>
<a-tag v-if="record.status === 20" color="green">已通过</a-tag>
<a-tag v-if="record.status === 30" color="red">已驳回</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a @click="openEdit(record)">安装</a>
</a-space>
</template>
</template>
</ele-pro-table>
</a-card>
<!-- 编辑弹窗 -->
<PlugEdit
v-model:visible="showEdit"
:data="current"
:parent-id="parentId"
:menu-list="menuData"
@done="reload"
/>
<clone v-model:visible="showClone" @done="reload" />
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const { push } = useRouter();
import type {
DatasourceFunction,
ColumnItem
} from 'ele-admin-pro/es/ele-pro-table/types';
import PlugSearch from './components/plug-search.vue';
import { toTreeData } from 'ele-admin-pro/es';
import type { EleProTable } from 'ele-admin-pro/es';
import PlugEdit from './components/plug-edit.vue';
import Clone from './components/clone.vue';
import { pagePlug } from '@/api/system/plug';
import type { Plug, PlugParam } from '@/api/system/plug/model';
import { Menu } from '@/api/system/menu/model';
import { listMenus } from '@/api/system/menu';
import { useRouter } from 'vue-router';
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格列配置
const columns = ref<ColumnItem[]>([
{
title: '插件ID',
dataIndex: 'menuId',
align: 'center'
},
{
title: '插件名称',
dataIndex: 'title',
key: 'title'
},
{
title: '价格',
dataIndex: 'price',
key: 'price',
align: 'center',
customRender: ({ text }) => '¥' + text
},
{
title: '评分',
dataIndex: 'score',
align: 'center',
key: 'score'
},
{
title: '操作',
key: 'action',
width: 200,
align: 'center'
}
]);
// 当前编辑数据
const current = ref<Plug | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
const showClone = ref(false);
// 上级菜单id
const parentId = ref<number>();
// 菜单数据
const menuData = ref<Menu[]>([]);
// 表格展开的行
const expandedRowKeys = ref<number[]>([]);
// 表格数据源
const datasource: DatasourceFunction = ({ where }) => {
where.status = 20;
return pagePlug({ ...where });
};
/* 表格渲染完成回调 */
// const onDone: EleProTableDone<Plug> = ({ data }) => {
// menuData.value = data;
// };
/* 刷新表格 */
const reload = (where?: PlugParam) => {
tableRef?.value?.reload({ where });
};
/* 打开编辑弹窗 */
const openEdit = (row?: Plug | null, id?: number) => {
current.value = row ?? null;
parentId.value = id;
showEdit.value = true;
};
const query = () => {
listMenus({}).then((res) => {
if (res) {
menuData.value = parseData(res);
}
});
};
/* 数据转为树形结构 */
const parseData = (data: Menu[]) => {
return toTreeData({
data: data
.filter((d) => d.menuType == 0)
.map((d) => {
return { ...d, key: d.menuId, value: d.menuId };
}),
idField: 'menuId',
parentIdField: 'parentId'
});
};
query();
</script>
<script lang="ts">
import * as PlugIcons from '@/layout/menu-icons';
export default {
name: 'SystemPlug',
components: PlugIcons
};
</script>
<style lang="less" scoped>
.app-box {
display: flex;
.app-info {
display: flex;
margin-left: 6px;
margin-right: 6px;
flex-direction: column;
justify-content: space-between;
}
}
.cursor-pointer {
cursor: pointer;
}
.sys-org-table :deep(.ant-table-body) {
overflow: auto !important;
overflow: overlay !important;
}
.sys-org-table :deep(.ant-table-pagination.ant-pagination) {
padding: 0 4px;
margin-bottom: 0;
}
.ele-text-heading {
}
.comments {
width: 420px;
padding: 5px 0;
margin-bottom: 10px;
}
</style>