mirror of
https://github.com/rubickCenter/rubick
synced 2026-02-02 07:41:29 +08:00
feat(插件市场): 添加自定义插件导入功能
新增本地插件导入功能,支持通过本地文件或远程URL导入插件配置。添加了新的路由、页面组件及多语言支持,并扩展了插件存储逻辑以区分本地插件。同时更新了Ant Design组件注册以支持新功能所需的UI组件。
This commit is contained in:
@@ -53,5 +53,8 @@
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
],
|
||||
"volta": {
|
||||
"node": "16.20.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,5 +67,8 @@
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
],
|
||||
"volta": {
|
||||
"node": "16.20.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,12 @@
|
||||
</template>
|
||||
{{ $t('feature.market.systemTool') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="localPlugin">
|
||||
<template #icon>
|
||||
<ApiOutlined style="font-size: 16px" />
|
||||
</template>
|
||||
{{ $t('feature.market.localPlugin') }}
|
||||
</a-menu-item>
|
||||
<a-sub-menu class="user-info">
|
||||
<template #icon>
|
||||
<a-avatar :size="32">
|
||||
@@ -82,6 +88,7 @@
|
||||
'tools',
|
||||
'worker',
|
||||
'system',
|
||||
'localPlugin',
|
||||
].includes(active[0])
|
||||
? 'container'
|
||||
: 'more'
|
||||
@@ -107,6 +114,7 @@ import {
|
||||
SettingOutlined,
|
||||
HeartOutlined,
|
||||
BugOutlined,
|
||||
ApiOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
import { useStore } from 'vuex';
|
||||
import localConfig from '@/confOp';
|
||||
|
||||
@@ -11,6 +11,7 @@ export default {
|
||||
imageTool: 'Image Tools',
|
||||
developTool: 'Develop Tools',
|
||||
systemTool: 'System Tools',
|
||||
localPlugin: 'Custom Plugins',
|
||||
finder: {
|
||||
must: 'Necessary',
|
||||
recommended: 'Recommended',
|
||||
@@ -114,6 +115,28 @@ export default {
|
||||
installSuccess: '{pluginName} Install Successed!',
|
||||
refreshSuccess: '{pluginName} Refresh Successed!',
|
||||
},
|
||||
localPlugin: {
|
||||
title: 'Import Local Plugin',
|
||||
okText: 'Import',
|
||||
cancelText: 'Cancel',
|
||||
importType: 'Import Type',
|
||||
localImport: 'Local Import',
|
||||
remoteImport: 'Remote Import',
|
||||
importUrl: 'Import Url',
|
||||
importUrlPlaceholder:
|
||||
'Please input remote plugin config file url. Refer to the address below',
|
||||
importFile: 'Import File',
|
||||
importFileErrorMsg: 'Please select the plugin config file to import',
|
||||
importUrlErrorMsg:
|
||||
'Please input the correct remote plugin config file url',
|
||||
configFetchSuccess: 'Plugin config file import success',
|
||||
deleteLocalPluginSuccess: 'Plugin delete success',
|
||||
deleteLocalPluginButton: 'Delete Plugin',
|
||||
deleteLocalPluginConfirm: 'Confirm Delete Plugin',
|
||||
deleteLocalPluginConfirmText: 'Confirm',
|
||||
deleteLocalPluginCancelText: 'Cancel',
|
||||
tips: 'Local Import and Remote Import both only support importing Json format configuration files. The specific content of the Json file can refer to the link: https://gitee.com/monkeyWang/rubickdatabase/raw/master/plugins/total-plugins.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -11,6 +11,8 @@ export default {
|
||||
imageTool: '图像',
|
||||
developTool: '开发者',
|
||||
systemTool: '系统',
|
||||
localPlugin: '自定义插件',
|
||||
|
||||
finder: {
|
||||
must: '必备',
|
||||
recommended: '推荐',
|
||||
@@ -112,6 +114,26 @@ export default {
|
||||
installSuccess: '{pluginName}安装成功!',
|
||||
refreshSuccess: '{pluginName}刷新成功!',
|
||||
},
|
||||
localPlugin: {
|
||||
title: '导入配置文件',
|
||||
okText: '导入',
|
||||
cancelText: '关闭',
|
||||
importType: '导入方式',
|
||||
localImport: '本地导入',
|
||||
remoteImport: '远程导入',
|
||||
importUrl: '导入地址',
|
||||
importUrlPlaceholder: '请输入远程插件配置文件地址。参考下面的地址',
|
||||
importFile: '导入文件',
|
||||
importFileErrorMsg: '请选择要导入的插件配置文件',
|
||||
importUrlErrorMsg: '请输入正确的远程插件配置文件地址',
|
||||
configFetchSuccess: '插件配置文件导入成功',
|
||||
deleteLocalPluginSuccess: '插件删除成功',
|
||||
deleteLocalPluginButton: '删除插件',
|
||||
deleteLocalPluginConfirm: '确认删除插件',
|
||||
deleteLocalPluginConfirmText: '确认',
|
||||
deleteLocalPluginCancelText: '取消',
|
||||
tips: '本地导入和远程导入都只支持导入Json 格式的配置文件。Json 文件的具体内容参考链接: https://gitee.com/monkeyWang/rubickdatabase/raw/master/plugins/total-plugins.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -11,15 +11,18 @@ import {
|
||||
Form,
|
||||
Input,
|
||||
Radio,
|
||||
Typography,
|
||||
Select,
|
||||
Switch,
|
||||
Avatar,
|
||||
Popconfirm,
|
||||
Collapse,
|
||||
List,
|
||||
Tooltip,
|
||||
Alert,
|
||||
Drawer,
|
||||
Modal,
|
||||
Upload,
|
||||
Result,
|
||||
Spin,
|
||||
} from 'ant-design-vue';
|
||||
@@ -73,6 +76,9 @@ createApp(App)
|
||||
.use(Modal)
|
||||
.use(Result)
|
||||
.use(Spin)
|
||||
.use(Upload)
|
||||
.use(Popconfirm)
|
||||
.use(Typography)
|
||||
.use(router)
|
||||
.use(Vue3Lottie)
|
||||
.mount('#app');
|
||||
|
||||
@@ -31,6 +31,11 @@ const routes: Array<RouteRecordRaw> = [
|
||||
name: 'system',
|
||||
component: () => import('../views/market/components/system.vue'),
|
||||
},
|
||||
{
|
||||
path: '/localPlugin',
|
||||
name: 'localPlugin',
|
||||
component: () => import('../views/market/components/local-plugin.vue'),
|
||||
},
|
||||
{
|
||||
path: '/finder',
|
||||
name: 'finder',
|
||||
@@ -56,6 +61,7 @@ const routes: Array<RouteRecordRaw> = [
|
||||
name: 'dev',
|
||||
component: () => import('../views/dev/index.vue'),
|
||||
},
|
||||
|
||||
{
|
||||
path: '/:catchAll(.*)',
|
||||
name: 'finder',
|
||||
|
||||
@@ -11,7 +11,7 @@ const isDownload = (item: Market.Plugin, targets: any[]) => {
|
||||
});
|
||||
return isDownload;
|
||||
};
|
||||
|
||||
const LOCAL_PLUGIN_JSON = 'localPluginJson';
|
||||
export default createStore({
|
||||
state: {
|
||||
totalPlugins: [],
|
||||
@@ -30,8 +30,25 @@ export default createStore({
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async saveLocalPlugins({ dispatch, state }, plugins) {
|
||||
// 先移除
|
||||
window.rubick.db.remove(LOCAL_PLUGIN_JSON);
|
||||
window.rubick.db.put({
|
||||
_id: LOCAL_PLUGIN_JSON,
|
||||
data: JSON.stringify(plugins),
|
||||
});
|
||||
await dispatch('init');
|
||||
},
|
||||
async deleteLocalPlugins({ dispatch, state }) {
|
||||
// 先移除
|
||||
window.rubick.db.remove(LOCAL_PLUGIN_JSON);
|
||||
await dispatch('init');
|
||||
},
|
||||
async init({ commit }) {
|
||||
const totalPlugins = await request.getTotalPlugins();
|
||||
const tPlugins = await request.getTotalPlugins();
|
||||
const lTPlugins = window.rubick.db.get(LOCAL_PLUGIN_JSON);
|
||||
const totalPlugins = tPlugins.concat(JSON.parse(lTPlugins?.data || '[]'));
|
||||
|
||||
const localPlugins = window.market.getLocalPlugins();
|
||||
|
||||
totalPlugins.forEach((origin: Market.Plugin) => {
|
||||
|
||||
@@ -22,6 +22,7 @@ const data = ref([]);
|
||||
|
||||
onBeforeMount(async () => {
|
||||
data.value = await request.getImageDetail();
|
||||
console.log(data.value);
|
||||
});
|
||||
|
||||
const system = computed(() => {
|
||||
|
||||
196
feature/src/views/market/components/local-plugin.vue
Normal file
196
feature/src/views/market/components/local-plugin.vue
Normal file
@@ -0,0 +1,196 @@
|
||||
<template>
|
||||
<div class="local-plugin">
|
||||
<div class="local-plugin__header">
|
||||
<h3 class="local-plugin__title">
|
||||
{{ $t('feature.market.localPlugin') }}
|
||||
</h3>
|
||||
|
||||
<div class="local-plugin__action">
|
||||
<a-button type="primary" @click="visible = true">
|
||||
<template #icon><UploadOutlined /></template>
|
||||
{{ $t('feature.localPlugin.title') }}
|
||||
</a-button>
|
||||
|
||||
<a-popconfirm
|
||||
placement="leftBottom"
|
||||
:title="$t('feature.localPlugin.deleteLocalPluginConfirm')"
|
||||
:ok-text="$t('feature.localPlugin.deleteLocalPluginConfirmText')"
|
||||
:cancel-text="$t('feature.localPlugin.deleteLocalPluginCancelText')"
|
||||
@confirm="handleDeleteClick"
|
||||
>
|
||||
<a-button type="danger">
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
{{ $t('feature.localPlugin.deleteLocalPluginButton') }}
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
</div>
|
||||
<div class="local-plugin__list">
|
||||
<PluginList
|
||||
v-if="pluginList && !!pluginList.length"
|
||||
@downloadSuccess="downloadSuccess"
|
||||
:list="pluginList"
|
||||
/>
|
||||
</div>
|
||||
<a-modal
|
||||
v-model:visible="visible"
|
||||
destroy-on-close
|
||||
:title="$t('feature.localPlugin.title')"
|
||||
:ok-text="$t('feature.localPlugin.okText')"
|
||||
:cancel-text="$t('feature.localPlugin.cancelText')"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<a-form>
|
||||
<a-alert :message="$t('feature.localPlugin.tips')" type="success" />
|
||||
|
||||
<a-form-item
|
||||
:label="$t('feature.localPlugin.importType')"
|
||||
name="importType"
|
||||
>
|
||||
<a-radio-group v-model:value="formState.importType">
|
||||
<a-radio :value="1">
|
||||
{{ $t('feature.localPlugin.localImport') }}
|
||||
</a-radio>
|
||||
<a-radio :value="2">
|
||||
{{ $t('feature.localPlugin.remoteImport') }}
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:label="$t('feature.localPlugin.localImport')"
|
||||
v-if="formState.importType == '1'"
|
||||
name="importFile"
|
||||
>
|
||||
<a-upload
|
||||
:maxCount="1"
|
||||
accept=".json"
|
||||
v-model:file-list="formState.importFile"
|
||||
:beforeUpload="() => false"
|
||||
name="file"
|
||||
>
|
||||
<a-button type="primary">
|
||||
<template #icon><UploadOutlined /></template>
|
||||
{{ $t('feature.localPlugin.importFile') }}
|
||||
</a-button>
|
||||
</a-upload>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:label="$t('feature.localPlugin.remoteImport')"
|
||||
v-if="formState.importType == '2'"
|
||||
name="importUrl"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="formState.importUrl"
|
||||
:placeholder="$t('feature.localPlugin.importUrlPlaceholder')"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { UploadOutlined, DeleteOutlined } from '@ant-design/icons-vue';
|
||||
import { ref, reactive, getCurrentInstance, computed } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import PluginList from './plugin-list.vue';
|
||||
import { useStore } from 'vuex';
|
||||
|
||||
const visible = ref(false);
|
||||
const store = useStore();
|
||||
const { proxy } = getCurrentInstance();
|
||||
const pluginList = computed(() => {
|
||||
return store.state.totalPlugins.filter((i) => i['#type'] == 'localPlugin');
|
||||
});
|
||||
const formState = reactive({
|
||||
importType: 1,
|
||||
importFile: [],
|
||||
importUrl: '',
|
||||
});
|
||||
const handleOk = () => {
|
||||
if (formState.importType == 1 && !formState.importFile.length) {
|
||||
message.error(proxy.$t('feature.localPlugin.importFileErrorMsg'));
|
||||
return;
|
||||
}
|
||||
if (formState.importType == 2 && !formState.importUrl) {
|
||||
message.error(proxy.$t('feature.localPlugin.importUrlErrorMsg'));
|
||||
return;
|
||||
}
|
||||
if (formState.importType == 1) {
|
||||
readPlguinJsonByFile();
|
||||
} else {
|
||||
readPlguinJsonByUrl();
|
||||
}
|
||||
};
|
||||
const readPlguinJsonByFile = () => {
|
||||
const file = formState.importFile[0];
|
||||
const reader = new FileReader();
|
||||
reader.readAsText(file.originFileObj);
|
||||
reader.onload = () => {
|
||||
const json = JSON.parse(reader.result);
|
||||
configFetchSuccess(json);
|
||||
};
|
||||
};
|
||||
const readPlguinJsonByUrl = () => {
|
||||
fetch(formState.importUrl)
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
configFetchSuccess(json);
|
||||
});
|
||||
};
|
||||
|
||||
// onMounted(() => {
|
||||
// const json = window.rubick.db.get(LOCAL_PLUGIN_JSON);
|
||||
// pluginList.value = JSON.parse(json.data);
|
||||
// });
|
||||
const configFetchSuccess = (json) => {
|
||||
// 打上标记,表示本地插件
|
||||
const plugins = json.map((i) => ({ ...i, '#type': 'localPlugin' }));
|
||||
message.success(proxy.$t('feature.localPlugin.configFetchSuccess'));
|
||||
visible.value = false;
|
||||
formState.importFile = [];
|
||||
formState.importUrl = '';
|
||||
formState.importType = 1;
|
||||
store.dispatch('saveLocalPlugins', plugins);
|
||||
};
|
||||
const handleCancel = () => {
|
||||
visible.value = false;
|
||||
formState.importFile = [];
|
||||
formState.importUrl = '';
|
||||
formState.importType = 1;
|
||||
};
|
||||
const handleDeleteClick = async () => {
|
||||
await store.dispatch('deleteLocalPlugins');
|
||||
message.success(proxy.$t('feature.localPlugin.deleteLocalPluginSuccess'));
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.local-plugin {
|
||||
&__header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid var(--color-border-light);
|
||||
padding-bottom: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
&__list {
|
||||
background-color: #fff;
|
||||
}
|
||||
&__title {
|
||||
font-weight: 500;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
&__action {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -39,5 +39,8 @@
|
||||
"last 2 versions",
|
||||
"not dead",
|
||||
"not ie 11"
|
||||
]
|
||||
],
|
||||
"volta": {
|
||||
"node": "16.20.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,5 +79,8 @@
|
||||
"resolutions": {
|
||||
"vue-cli-plugin-electron-builder/electron-builder": "^23.0.3",
|
||||
"leveldown": "6.0.3"
|
||||
},
|
||||
"volta": {
|
||||
"node": "16.20.2"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user