updated plugin framework: allow adding routes and tabs

This commit is contained in:
Marvin Zhang
2021-09-01 16:02:09 +08:00
parent ae0b722cb6
commit 78cb4ba616
39 changed files with 690 additions and 32 deletions

View File

@@ -1,3 +1,21 @@
<template>
<router-view/>
<router-view />
</template>
<script lang="ts">
import {defineComponent, onBeforeMount} from 'vue';
import {initPlugins} from '@/utils/plugin';
import {useStore} from 'vuex';
export default defineComponent({
name: 'App',
setup() {
onBeforeMount(() => {
const store = useStore();
initPlugins(store);
});
return {};
},
});
</script>

View File

@@ -0,0 +1,44 @@
<template>
<CreateEditDialog
:action-functions="actionFunctions"
:confirm-disabled="confirmDisabled"
:confirm-loading="confirmLoading"
:tab-name="createEditDialogTabName"
:type="activeDialogKey"
:visible="createEditDialogVisible"
:form-rules="formRules"
no-batch
>
<template #default>
<PluginForm/>
</template>
</CreateEditDialog>
</template>
<script lang="ts">
import {defineComponent} from 'vue';
import {useStore} from 'vuex';
import CreateEditDialog from '@/components/dialog/CreateEditDialog.vue';
import PluginForm from '@/components/plugin/PluginForm.vue';
import usePlugin from '@/components/plugin/plugin';
export default defineComponent({
name: 'CreateEditProjectDialog',
components: {
CreateEditDialog,
PluginForm,
},
setup() {
// store
const store = useStore();
return {
...usePlugin(store),
};
},
});
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,68 @@
<template>
<Form
v-if="form"
ref="formRef"
:model="form"
:selective="isSelectiveForm"
>
<!--Row-->
<FormItem :span="2" :offset="2" label="Name" not-editable prop="name" required>
<el-input v-model="form.name" disabled placeholder="Name" />
</FormItem>
<!--./Row-->
<!--Row-->
<FormItem :span="2" label="Execute Command" prop="cmd">
<el-input
v-model="form.cmd"
disabled
placeholder="cmd"
/>
</FormItem>
<!--./Row-->
<!--Row-->
<FormItem :span="4" label="Description" prop="description">
<el-input
v-model="form.description"
disabled
placeholder="Description"
type="textarea"
/>
</FormItem>
</Form>
<!--./Row-->
</template>
<script lang="ts">
import {defineComponent} from 'vue';
import {useStore} from 'vuex';
import usePlugin from '@/components/plugin/plugin';
import Form from '@/components/form/Form.vue';
import FormItem from '@/components/form/FormItem.vue';
export default defineComponent({
name: 'PluginForm',
props: {
readonly: {
type: Boolean,
}
},
components: {
Form,
FormItem,
},
setup(props, {emit}) {
// store
const store = useStore();
return {
...usePlugin(store),
};
},
});
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,30 @@
import {readonly} from 'vue';
import {Store} from 'vuex';
import useForm from '@/components/form/form';
import usePluginService from '@/services/plugin/pluginService';
import {getDefaultFormComponentData} from '@/utils/form';
type Plugin = CPlugin;
// get new plugin
export const getNewPlugin = (): Plugin => {
return {};
};
// form component data
const formComponentData = getDefaultFormComponentData<Plugin>(getNewPlugin);
const usePlugin = (store: Store<RootStoreState>) => {
// store
const ns = 'plugin';
// form rules
const formRules = readonly<FormRules>({});
return {
...useForm(ns, store, usePluginService(store), formComponentData),
formRules,
};
};
export default usePlugin;

View File

@@ -0,0 +1,2 @@
export const PLUGIN_UI_COMPONENT_TYPE_VIEW = 'view';
export const PLUGIN_UI_COMPONENT_TYPE_TAB = 'tab';

View File

@@ -0,0 +1,20 @@
interface CPlugin extends BaseModel {
name?: string;
description?: string;
type?: string;
proto?: string;
active?: boolean;
endpoint?: string;
cmd?: string;
ui_components?: PluginUIComponent[];
ui_sidebar_navs?: MenuItem[];
}
interface PluginUIComponent {
name?: string;
title?: string;
src?: string;
type?: string;
path?: string;
parent_paths?: string[];
}

View File

@@ -2,6 +2,7 @@ interface ListRequestParams {
page?: number;
size?: number;
conditions?: FilterConditionData[] | string;
all?: boolean | string | number;
}
interface BatchRequestPayload {

View File

@@ -14,6 +14,7 @@ declare global {
schedule: ScheduleStoreState;
user: UserStoreState;
token: TokenStoreState;
plugin: PluginStoreState;
}
type StoreGetter<S, T> = (state: S, getters: StoreGetter<S, T>, rootState: RootStoreState, rootGetters: any) => T;
@@ -103,6 +104,7 @@ declare global {
collapseSidebar: StoreMutation<BaseStoreState<T>>;
expandActions: StoreMutation<BaseStoreState<T>>;
collapseActions: StoreMutation<BaseStoreState<T>>;
setTabs: StoreMutation<BaseStoreState, NavItem[]>;
setAfterSave: StoreMutation<BaseStoreState<T>, (() => Promise)[]>;
}
@@ -133,7 +135,8 @@ declare global {
| 'tag'
| 'dataCollection'
| 'user'
| 'token';
| 'token'
| 'plugin';
type ListStoreNamespace =
'node'
| 'project'
@@ -143,7 +146,8 @@ declare global {
| 'dataCollection'
| 'schedule'
| 'user'
| 'token';
| 'token'
| 'plugin';
interface StoreContext<T> {
namespace: StoreNamespace;

View File

@@ -26,6 +26,7 @@ declare global {
}
interface LayoutStoreMutations extends MutationTree<LayoutStoreState> {
setMenuItems: StoreMutation<LayoutStoreState, MenuItem[]>;
setSideBarCollapsed: StoreMutation<LayoutStoreState, boolean>;
setTabs: StoreMutation<LayoutStoreState, Tab[]>;
setActiveTabId: StoreMutation<LayoutStoreState, number>;

View File

@@ -1,5 +1,3 @@
type Node = CNode;
type NodeStoreModule = BaseModule<NodeStoreState, NodeStoreGetters, NodeStoreMutations, NodeStoreActions>;
type NodeStoreState = BaseStoreState<CNode>;

View File

@@ -0,0 +1,9 @@
type PluginStoreModule = BaseModule<PluginStoreState, PluginStoreGetters, PluginStoreMutations, PluginStoreActions>;
type PluginStoreState = BaseStoreState<CPlugin>;
type PluginStoreGetters = BaseStoreGetters<CPlugin>;
type PluginStoreMutations = BaseStoreMutations<CPlugin>;
type PluginStoreActions = BaseStoreActions<CPlugin>;

View File

@@ -1,22 +1,23 @@
<template>
<el-container class="basic-layout">
<Sidebar/>
<Sidebar />
<el-container :class="sidebarCollapsed ? 'collapsed' : ''" class="container">
<Header/>
<TabsView/>
<Header />
<TabsView />
<div class="container-body">
<router-view/>
<router-view />
</div>
</el-container>
</el-container>
</template>
<script lang="ts">
import {computed, defineComponent} from 'vue';
import {computed, defineComponent, onMounted} from 'vue';
import Header from './components/Header.vue';
import Sidebar from './components/Sidebar.vue';
import {useStore} from 'vuex';
import TabsView from '@/layouts/components/TabsView.vue';
import {initPlugins} from '@/utils/plugin';
export default defineComponent({
name: 'BasicLayout',

View File

@@ -63,7 +63,6 @@ export default defineComponent({
const route = useRoute();
const store = useStore();
const {layout} = store.state as RootStoreState;
const {menuItems} = layout;
const storeNamespace = 'layout';
const activePath = computed<string>(() => {
@@ -72,6 +71,8 @@ export default defineComponent({
const sidebarCollapsed = computed<boolean>(() => layout.sidebarCollapsed);
const menuItems = computed<MenuItem[]>(() => layout.menuItems);
const toggleIcon = computed<string[]>(() => {
if (sidebarCollapsed.value) {
return ['fas', 'indent'];

View File

@@ -6,6 +6,10 @@ import {plainClone} from '@/utils/object';
import {getRoutePathByDepth} from '@/utils/route';
import {ElMessage} from 'element-plus';
const IGNORE_GET_ALL_NS = [
'task',
];
const useDetail = <T = BaseModel>(ns: ListStoreNamespace) => {
const router = useRouter();
const route = useRoute();
@@ -129,6 +133,7 @@ const useDetail = <T = BaseModel>(ns: ListStoreNamespace) => {
onBeforeMount(getForm);
onBeforeMount(async () => {
if (IGNORE_GET_ALL_NS.includes(ns)) return;
await store.dispatch(`${ns}/getAllList`);
});

View File

@@ -4,6 +4,7 @@ const endpoint = '';
export default [
{
name: 'Home',
path: endpoint,
component: () => import('@/views/home/Home.vue'),
},

View File

@@ -10,7 +10,7 @@ import user from '@/router/user';
import tag from '@/router/tag';
import token from '@/router/token';
import plugin from '@/router/plugin';
import {initRouterAuth} from '@/router/auth';
import {initRouterAuth} from '@/router/hooks/auth';
import {sendPv} from '@/utils/admin';
export const routes: Array<RouteRecordRaw> = [

View File

@@ -4,6 +4,7 @@ const endpoint = '/login';
export default [
{
name: 'Login',
path: endpoint,
component: () => import('@/views/login/Login.vue'),
},

View File

@@ -1,13 +1,26 @@
import {defineAsyncComponent} from 'vue';
import {RouteRecordRaw} from 'vue-router';
import {getLoadModuleOptions, loadModule} from '@/utils/sfc';
import {TAB_NAME_OVERVIEW} from '@/constants/tab';
const endpoint = 'plugins';
export default [
{
name: 'PluginList',
path: endpoint,
// component: defineAsyncComponent(() => loadModule('/vue/HelloWorld.vue', getLoadModuleOptions())),
component: defineAsyncComponent(() => loadModule('/vue/App.vue', getLoadModuleOptions())),
component: () => import('@/views/plugin/list/PluginList.vue'),
},
{
name: 'PluginDetail',
path: `${endpoint}/:id`,
redirect: to => {
return {path: to.path + '/' + TAB_NAME_OVERVIEW};
},
component: () => import('@/views/plugin/detail/PluginDetail.vue'),
children: [
{
path: TAB_NAME_OVERVIEW,
component: () => import('@/views/plugin/detail/tabs/PluginDetailTabOverview.vue'),
},
]
},
] as Array<RouteRecordRaw>;

View File

@@ -5,10 +5,12 @@ const endpoint = 'projects';
export default [
{
name: 'ProjectList',
path: endpoint,
component: () => import('@/views/project/list/ProjectList.vue'),
},
{
name: 'ProjectDetail',
path: `${endpoint}/:id`,
redirect: to => {
return {path: to.path + '/overview'};

View File

@@ -3,10 +3,12 @@ import {TAB_NAME_OVERVIEW, TAB_NAME_TASKS} from '@/constants/tab';
export default [
{
name: 'ScheduleList',
path: 'schedules',
component: () => import('@/views/schedule/list/ScheduleList.vue'),
},
{
name: 'ScheduleDetail',
path: 'schedules/:id',
redirect: to => {
return {path: to.path + '/' + TAB_NAME_OVERVIEW};

View File

@@ -10,10 +10,12 @@ import {
export default [
{
name: 'SpiderList',
path: 'spiders',
component: () => import('@/views/spider/list/SpiderList.vue'),
},
{
name: 'SpiderDetail',
path: 'spiders/:id',
redirect: to => {
return {path: to.path + '/' + TAB_NAME_OVERVIEW};

View File

@@ -5,10 +5,12 @@ const endpoint = 'tags';
export default [
{
name: 'TagList',
path: endpoint,
component: () => import('@/views/tag/list/TagList.vue'),
},
{
name: 'TagDetail',
path: `${endpoint}/:id`,
redirect: to => {
return {path: to.path + '/overview'};

View File

@@ -5,10 +5,12 @@ const endpoint = 'tasks';
export default [
{
name: 'TaskList',
path: endpoint,
component: () => import('@/views/task/list/TaskList.vue'),
},
{
name: 'TaskDetail',
path: `${endpoint}/:id`,
redirect: to => {
return {path: to.path + '/overview'};

View File

@@ -4,6 +4,7 @@ const endpoint = 'tokens';
export default [
{
name: 'TokenList',
path: endpoint,
component: () => import('@/views/token/list/TokenList.vue'),
},

View File

@@ -3,10 +3,12 @@ import {TAB_NAME_OVERVIEW} from '@/constants/tab';
export default [
{
name: 'UserList',
path: 'users',
component: () => import('@/views/user/list/UserList.vue'),
},
{
name: 'UserDetail',
path: 'users/:id',
redirect: to => {
return {path: to.path + '/' + TAB_NAME_OVERVIEW};

View File

@@ -0,0 +1,14 @@
import {Store} from 'vuex';
import {getDefaultService} from '@/utils/service';
type Plugin = CPlugin;
const usePluginService = (store: Store<RootStoreState>): Services<Plugin> => {
const ns = 'plugin';
return {
...getDefaultService<Plugin>(ns, store),
};
};
export default usePluginService;

View File

@@ -1,10 +1,11 @@
import axios, {AxiosRequestConfig} from 'axios';
import axios, {AxiosRequestConfig, AxiosResponse} from 'axios';
import {ElMessageBox} from 'element-plus';
import router from '@/router';
import {getRequestBaseUrl} from '@/utils/request';
// TODO: request interception
// TODO: response interception
// response interception
let msgBoxVisible = false;
axios.interceptors.response.use(res => {
return res;
@@ -24,13 +25,9 @@ axios.interceptors.response.use(res => {
});
const useRequest = () => {
// implementation
const baseUrl = process.env.VUE_APP_API_BASE_URL || 'http://localhost:8000';
const request = async <R = any>(opts: AxiosRequestConfig): Promise<R> => {
// base url
const baseURL = baseUrl;
const baseUrl = getRequestBaseUrl();
const getHeaders = (): any => {
// headers
const headers = {} as any;
@@ -40,6 +37,16 @@ const useRequest = () => {
headers['Authorization'] = token;
}
return headers;
}
const request = async <R = any>(opts: AxiosRequestConfig): Promise<R> => {
// base url
const baseURL = baseUrl;
// headers
const headers = getHeaders();
// axios response
const res = await axios.request({
...opts,
@@ -112,7 +119,7 @@ const useRequest = () => {
};
const getAll = async <T = any>(url: string, opts?: AxiosRequestConfig) => {
return await getList(url, {}, opts);
return await getList(url, {all: true}, opts);
};
const postList = async <T = any, R = Response, PM = any>(url: string, data?: BatchRequestPayloadWithJsonStringData, params?: PM, opts?: AxiosRequestConfig): Promise<R> => {
@@ -127,6 +134,31 @@ const useRequest = () => {
return await del<BatchRequestPayload, R, PM>(url, data, params, opts);
};
const requestRaw = async <R = any>(opts: AxiosRequestConfig): Promise<AxiosResponse> => {
// base url
const baseURL = baseUrl;
// headers
const headers = getHeaders();
// axios response
return await axios.request({
...opts,
baseURL,
headers,
});
};
const getRaw = async <T = any, PM = any>(url: string, params?: PM, opts?: AxiosRequestConfig): Promise<AxiosResponse> => {
opts = {
...opts,
method: 'GET',
url,
params,
};
return await requestRaw(opts);
};
return {
// public variables and methods
baseUrl,
@@ -140,6 +172,8 @@ const useRequest = () => {
postList,
putList,
delList,
requestRaw,
getRaw,
};
};

View File

@@ -11,6 +11,7 @@ import dataCollection from '@/store/modules/dataCollection';
import schedule from '@/store/modules/schedule';
import user from '@/store/modules/user';
import token from '@/store/modules/token';
import plugin from '@/store/modules/plugin';
export default createStore<RootStoreState>({
modules: {
@@ -26,5 +27,6 @@ export default createStore<RootStoreState>({
schedule,
user,
token,
plugin,
},
}) as Store<RootStoreState>;

View File

@@ -35,6 +35,9 @@ export default {
}
},
mutations: {
setMenuItems(state: LayoutStoreState, items: MenuItem[]) {
state.menuItems = items;
},
setSideBarCollapsed(state: LayoutStoreState, value: boolean) {
state.sidebarCollapsed = value;
},

View File

@@ -0,0 +1,32 @@
import {
getDefaultStoreActions,
getDefaultStoreGetters,
getDefaultStoreMutations,
getDefaultStoreState
} from '@/utils/store';
type Plugin = CPlugin;
const state = {
...getDefaultStoreState<Plugin>('plugin'),
} as PluginStoreState;
const getters = {
...getDefaultStoreGetters<Plugin>(),
} as PluginStoreGetters;
const mutations = {
...getDefaultStoreMutations<Plugin>(),
} as PluginStoreMutations;
const actions = {
...getDefaultStoreActions<Plugin>('/plugins'),
} as PluginStoreActions;
export default {
namespaced: true,
state,
getters,
mutations,
actions,
} as PluginStoreModule;

View File

@@ -0,0 +1,124 @@
import {Store} from 'vuex';
import {cloneArray} from '@/utils/object';
import router from '@/router';
import {PLUGIN_UI_COMPONENT_TYPE_TAB, PLUGIN_UI_COMPONENT_TYPE_VIEW} from '@/constants/plugin';
import {loadModule} from '@/utils/sfc';
type Plugin = CPlugin;
const PLUGIN_PROXY_ENDPOINT = '/plugin-proxy';
const getStoreNamespaceFromRoutePath = (path: string): ListStoreNamespace => {
const arr = path.split('/');
let ns = arr[1];
if (ns.endsWith('s')) {
ns = ns.substr(0, ns.length - 1);
}
return ns as ListStoreNamespace;
};
const initPluginSidebarMenuItems = (store: Store<RootStoreState>) => {
const {
layout,
plugin: state,
} = store.state;
// sidebar menu items
const menuItems = cloneArray(layout.menuItems);
// add plugin nav to sidebar navs
state.allList.forEach(p => {
p.ui_sidebar_navs?.forEach(nav => {
const sidebarPaths = layout.menuItems.map(d => d.path);
if (!sidebarPaths.includes(nav.path)) {
menuItems.push(nav);
}
});
});
// set sidebar menu items
store.commit(`layout/setMenuItems`, menuItems);
};
const addPluginRouteTab = (store: Store<RootStoreState>, p: Plugin, pc: PluginUIComponent) => {
const routesPaths = router.getRoutes().map(r => r.path);
pc.parent_paths?.forEach(parentPath => {
// plugin route path
const pluginPath = `${parentPath}/${pc.path}`;
// skip if new route path is already in the routes
if (routesPaths.includes(pluginPath)) return;
// parent route
const parentRoute = router.getRoutes().find(r => r.path === parentPath);
if (!parentRoute) return;
// add route
router.addRoute(parentRoute.name?.toString() as string, {
name: `${parentRoute.name?.toString()}-${pc.name}`,
path: pc.path as string,
component: () => loadModule(`${PLUGIN_PROXY_ENDPOINT}/${p.name}/${pc.src}`)
});
// add tab
const ns = getStoreNamespaceFromRoutePath(pluginPath);
const state = store.state[ns];
const tabs = cloneArray(state.tabs);
if (tabs.map(t => t.id).includes(pc.name as string)) return;
tabs.push({
id: pc.name as string,
title: pc.title,
});
store.commit(`${ns}/setTabs`, tabs);
});
};
const addPluginRouteView = (store: Store<RootStoreState>, pc: PluginUIComponent) => {
// TODO: implement
};
const initPluginRoutes = (store: Store<RootStoreState>) => {
// store
const {
plugin: state,
} = store.state as RootStoreState;
// add plugin routes
state.allList.forEach(p => {
p.ui_components?.forEach(pc => {
// skip if path is empty
if (!pc.path) return;
switch (pc.type) {
case PLUGIN_UI_COMPONENT_TYPE_VIEW:
addPluginRouteView(store, pc);
break;
case PLUGIN_UI_COMPONENT_TYPE_TAB:
addPluginRouteTab(store, p, pc);
break;
}
});
});
};
export const initPlugins = async (store: Store<RootStoreState>) => {
// store
const ns = 'plugin';
const {
plugin: state,
} = store.state as RootStoreState;
// skip if not logged-in
if (!localStorage.getItem('token')) return;
// skip if all plugin list is already fetched
if (state.allList.length) return;
// get all plugin list
await store.dispatch(`${ns}/getAllList`);
initPluginSidebarMenuItems(store);
initPluginRoutes(store);
};

View File

@@ -0,0 +1,3 @@
export const getRequestBaseUrl = (): string => {
return process.env.VUE_APP_API_BASE_URL || 'http://localhost:8000';
};

View File

@@ -1,7 +1,14 @@
import * as vue from '@vue/runtime-dom';
import {getRequestBaseUrl} from '@/utils/request';
import useRequest from '@/services/request';
const {loadModule: sfcLoadModule} = window['vue3-sfc-loader'];
export const getLoadModuleOptions = (): any => {
const {
getRaw,
} = useRequest();
const getLoadModuleOptions = (): any => {
return {
moduleCache: {
vue,
@@ -20,12 +27,9 @@ export const getLoadModuleOptions = (): any => {
return String(new URL(relPath.toString(), refPath === undefined ? window.location.toString() : refPath.toString()));
},
async getFile(url: string) {
const res = await fetch(url.toString());
if (!res.ok) {
throw Object.assign(new Error(res.statusText + ' ' + url), {res});
}
const res = await getRaw(url.toString());
return {
getContentData: (asBinary: boolean) => asBinary ? res.arrayBuffer() : res.text(),
getContentData: async (_: boolean) => res.data,
};
},
addStyle(textContent: string) {
@@ -36,4 +40,4 @@ export const getLoadModuleOptions = (): any => {
};
};
export const loadModule = sfcLoadModule;
export const loadModule = (path: string) => sfcLoadModule(`${getRequestBaseUrl()}${path}`, getLoadModuleOptions());

View File

@@ -188,6 +188,9 @@ export const getDefaultStoreMutations = <T = any>(): BaseStoreMutations<T> => {
collapseActions: (state: BaseStoreState<T>) => {
state.actionsCollapsed = true;
},
setTabs: (state: BaseStoreState<T>, tabs) => {
state.tabs = tabs;
},
setAfterSave: (state: BaseStoreState<T>, fnList) => {
state.afterSave = fnList;
},

View File

@@ -0,0 +1,21 @@
<template>
<DetailLayout store-namespace="plugin">
</DetailLayout>
</template>
<script lang="ts">
import {defineComponent} from 'vue';
import DetailLayout from '@/layouts/DetailLayout.vue';
export default defineComponent({
name: 'PluginDetail',
components: {DetailLayout},
setup(props, {emit}) {
return {};
},
});
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,24 @@
<template>
<div class="plugin-detail-tab-overview">
<PluginForm/>
</div>
</template>
<script lang="ts">
import {defineComponent} from 'vue';
import PluginForm from '@/components/plugin/PluginForm.vue';
export default defineComponent({
name: 'PluginDetailTabOverview',
components: {
PluginForm,
},
setup() {
return {};
},
});
</script>
<style lang="scss" scoped>
.plugin-detail-tab-overview {
margin: 20px;
}
</style>

View File

@@ -0,0 +1,41 @@
<template>
<ListLayout
:action-functions="actionFunctions"
:nav-actions="navActions"
:pagination="tablePagination"
:table-columns="tableColumns"
:table-data="tableData"
:table-total="tableTotal"
class="plugin-list"
>
<template #extra>
<!-- Dialogs (handled by store) -->
<CreateEditPluginDialog/>
<!-- ./Dialogs -->
</template>
</ListLayout>
</template>
<script lang="ts">
import {defineComponent} from 'vue';
import ListLayout from '@/layouts/ListLayout.vue';
import usePluginList from '@/views/plugin/list/pluginList';
import CreateEditPluginDialog from '@/components/plugin/CreateEditPluginDialog.vue';
export default defineComponent({
name: 'PluginList',
components: {
ListLayout,
CreateEditPluginDialog,
},
setup() {
return {
...usePluginList(),
};
}
});
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,123 @@
import useList from '@/layouts/list';
import {useStore} from 'vuex';
import {getDefaultUseListOptions, setupListComponent} from '@/utils/list';
import {computed, h} from 'vue';
import {TABLE_COLUMN_NAME_ACTIONS} from '@/constants/table';
import {ElMessageBox} from 'element-plus';
import usePluginService from '@/services/plugin/pluginService';
import NavLink from '@/components/nav/NavLink.vue';
import {useRouter} from 'vue-router';
type Plugin = CPlugin;
const usePluginList = () => {
// router
const router = useRouter();
// store
const ns = 'plugin';
const store = useStore<RootStoreState>();
const {commit} = store;
// services
const {
getList,
deleteById,
} = usePluginService(store);
// nav actions
const navActions = computed<ListActionGroup[]>(() => [
{
name: 'common',
children: [
{
buttonType: 'label',
label: 'New Plugin',
tooltip: 'New Plugin',
icon: ['fa', 'plus'],
type: 'success',
onClick: () => {
commit(`${ns}/showDialog`, 'create');
}
}
]
}
]);
// table columns
const tableColumns = computed<TableColumns<Plugin>>(() => [
{
key: 'name', // name
label: 'Name',
icon: ['fa', 'font'],
width: '150',
value: (row: Plugin) => h(NavLink, {
path: `/plugins/${row._id}`,
label: row.name,
}),
hasSort: true,
hasFilter: true,
allowFilterSearch: true,
},
{
key: 'description',
label: 'Description',
icon: ['fa', 'comment-alt'],
width: 'auto',
hasFilter: true,
allowFilterSearch: true,
},
{
key: TABLE_COLUMN_NAME_ACTIONS,
label: 'Actions',
fixed: 'right',
width: '200',
buttons: [
{
type: 'primary',
icon: ['fa', 'search'],
tooltip: 'View',
onClick: (row) => {
router.push(`/plugins/${row._id}`);
},
},
// {
// type: 'info',
// size: 'mini',
// icon: ['fa', 'clone'],
// tooltip: 'Clone',
// onClick: (row) => {
// console.log('clone', row);
// }
// },
{
type: 'danger',
size: 'mini',
icon: ['fa', 'trash-alt'],
tooltip: 'Delete',
disabled: (row: Plugin) => !!row.active,
onClick: async (row: Plugin) => {
const res = await ElMessageBox.confirm('Are you sure to delete?', 'Delete');
if (res) {
await deleteById(row._id as string);
}
await getList();
},
},
],
disableTransfer: true,
}
]);
// options
const opts = getDefaultUseListOptions<Plugin>(navActions, tableColumns);
// init
setupListComponent(ns, store, []);
return {
...useList<Plugin>(ns, store, opts)
};
};
export default usePluginList;