This commit is contained in:
muwoo
2021-06-02 17:17:09 +08:00
commit ce490acb6a
52 changed files with 16764 additions and 0 deletions

84
src/renderer/App.vue Normal file
View File

@@ -0,0 +1,84 @@
<template>
<a-layout id="components-layout-demo-custom-trigger">
<div class="rubick-select">
<div class="tag-container" v-if="selected">
<a-tag
:key="selected.key"
@close="closeTag"
class="select-tag"
color="green"
closable
>
{{ selected.name }}
</a-tag>
</div>
<a-input
placeholder="Hi, Rubick"
class="main-input"
@change="onSearch"
:value="searchValue"
>
<a-icon class="icon-tool" type="tool" slot="suffix" @click="() => {
showMainUI();
changePath({key: 'market'})
}"/>
}
</a-input>
<div class="options" v-show="options.length && !showMain">
<a-list item-layout="horizontal" :data-source="options">
<a-list-item @click="() => item.click($router)" class="op-item" slot="renderItem" slot-scope="item, index">
<a-list-item-meta
:description="item.desc"
>
<span slot="title" >{{ item.name }}</span>
<a-avatar
slot="avatar"
src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"
/>
</a-list-item-meta>
</a-list-item>
</a-list>
</div>
</div>
<router-view></router-view>
</a-layout>
</template>
<script>
import {mapActions, mapMutations, mapState} from "vuex";
import {ipcRenderer} from "electron";
import {getWindowHeight} from "./assets/common/utils";
export default {
methods: {
...mapActions('main', ['onSearch', 'showMainUI']),
...mapMutations('main', ['commonUpdate']),
changePath({key}) {
this.$router.push({path: `/home/${key}`});
this.commonUpdate({
current: [key]
})
},
closeTag(v) {
this.commonUpdate({
selected: null,
showMain: false,
});
ipcRenderer.send('changeWindowSize', {
height: getWindowHeight([]),
});
this.$router.push({
path: '/home',
});
}
},
computed: {
...mapState('main', ['showMain', 'current', 'options', 'selected', 'searchValue'])
}
};
</script>
<style lang="scss">
* {
margin: 0;
padding: 0;
}
</style>

View File

View File

@@ -0,0 +1,4 @@
export default {
development: 'http://localhost:7001',
// development: 'http://kaer-server.qa.91jkys.com',
};

View File

@@ -0,0 +1,5 @@
import plugin from './list/plugin';
export default {
plugin
}

View File

@@ -0,0 +1,16 @@
import instance from '../request';
export default {
async add(params) {
const result = await instance.post('/plugin/create', params);
return result.data;
},
async update(params) {
const result = await instance.post('/plugin/update', params);
return result.data;
},
async query(params) {
const result = await instance.get('/plugin/query', {params});
return result.data;
}
}

View File

@@ -0,0 +1,10 @@
import axios from 'axios';
import config from "./config";
const instance = axios.create({
baseURL: config[process.env.NODE_ENV],
timeout: 10000,
withCredentials: true
});
export default instance;

View File

@@ -0,0 +1,9 @@
const WINDOW_MAX_HEIGHT = 766;
const WINDOW_MIN_HEIGHT = 60;
const PRE_ITEM_HEIGHT = 60;
export {
WINDOW_MAX_HEIGHT,
WINDOW_MIN_HEIGHT,
PRE_ITEM_HEIGHT,
}

View File

@@ -0,0 +1,94 @@
import {WINDOW_MAX_HEIGHT, WINDOW_MIN_HEIGHT, PRE_ITEM_HEIGHT} from './constans';
import download from 'download-git-repo';
import path from 'path';
import fs from 'fs';
import process from 'child_process';
import Store from 'electron-store';
const store = new Store();
function getWindowHeight(searchList) {
if (!searchList) return WINDOW_MAX_HEIGHT;
if (!searchList.length) return WINDOW_MIN_HEIGHT;
return searchList.length * PRE_ITEM_HEIGHT + WINDOW_MIN_HEIGHT + 5 > WINDOW_MAX_HEIGHT ? WINDOW_MAX_HEIGHT : searchList.length * PRE_ITEM_HEIGHT + WINDOW_MIN_HEIGHT + 5;
}
function searchKeyValues(lists, value){
return lists.filter(item => item.indexOf(value) >= 0)
}
function existOrNot(path) {
return new Promise((resolve, reject) => {
fs.stat(path, async (err, stat) => {
if (err) {
resolve(false);
} else {
resolve(true);
}
});
});
}
function mkdirFolder(name) {
return new Promise((resolve, reject) => {
process.exec(`mkdir ${name}`, async function (error, stdout, stderr) {
if (error) {
reject(false);
} else {
resolve(true);
}
})
});
}
function downloadFunc(downloadRepoUrl, name) {
const plugin_path = path.join(__static, './plugins');
return new Promise(async (resolve, reject) => {
try {
if (!(await existOrNot(plugin_path))) {
await mkdirFolder(plugin_path);
}
// 基础模版所在目录,如果是初始化,则是模板名称,否则是项目名称
const temp_dest = `${plugin_path}/${name}`;
// 下载模板
if (await existOrNot(temp_dest)) {
await process.execSync(`rm -rf ${temp_dest}`);
}
download(`github:clouDr-f2e/${name}`, temp_dest, function (err) {
console.log(err ? 'Error' : 'Success')
if (err) {
console.log(err);
reject('请求模板下载失败');
} else {
resolve('请求模板下载成功');
}
})
} catch (e) {
console.log(e);
}
});
}
const sysFile = {
savePlugins(plugins) {
store.set('user-plugins', plugins);
},
getUserPlugins() {
try {
console.log(store.get('user-plugins').devPlugins)
return store.get('user-plugins').devPlugins;
} catch (e) {
return []
}
}
}
export {
getWindowHeight,
searchKeyValues,
downloadFunc,
sysFile,
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

23
src/renderer/main.js Normal file
View File

@@ -0,0 +1,23 @@
import Vue from 'vue'
import axios from 'axios'
import App from './App'
import router from './router'
import store from './store'
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/antd.css';
if (!process.env.IS_WEB) Vue.use(require('vue-electron'))
Vue.http = Vue.prototype.$http = axios
Vue.config.productionTip = false
Vue.use(Antd);
/* eslint-disable no-new */
new Vue({
components: { App },
router,
store,
template: '<App/>'
}).$mount('#app')

View File

@@ -0,0 +1,62 @@
<template>
<div>
<a-list item-layout="horizontal" :data-source="data">
<a-list-item slot="renderItem" slot-scope="item, index">
<a-list-item-meta>
<div class="desc" slot="description">
Ant Design, a design language for background applications, is refined by Ant UED Team
</div>
<div slot="title">
{{ item.title }}
<a-tag color="green">标签</a-tag>
</div>
<a-avatar
slot="avatar"
src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"
/>
</a-list-item-meta>
<div class="fava-btn">
<a-icon type="star" /> 添加
</div>
</a-list-item>
</a-list>
</div>
</template>
<script>
const data = [
{
title: 'Ant Design Title 1',
},
{
title: 'Ant Design Title 2',
},
{
title: 'Ant Design Title 3',
},
{
title: 'Ant Design Title 4',
},
];
export default {
data() {
return {
data,
};
},
}
</script>
<style lang="scss">
.fava-btn {
cursor: pointer;
&:hover {
color: #f50;
}
}
.desc {
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
width: 400px;
}
</style>

View File

@@ -0,0 +1,37 @@
<template>
<div>
<webview style="width: 100%;height: 100vh" id="webview" :src="path"></webview>
</div>
</template>
<script>
import path from 'path';
import fs from 'fs';
import {getlocalDataFile} from '../../../common/utils';
import { remote } from 'electron';
// import process from 'child_process';
export default {
name: "index.vue",
data() {
return {
path: `File://${this.$route.query.sourceFile}`,
preload: path.join(process.cwd(), './api/index.js')
}
},
mounted() {
console.log(1111, this.$route.query)
// const webview = document.querySelector('webview')
// webview.addEventListener('dom-ready', () => {
// webview.openDevTools()
// })
},
beforeRouteUpdate() {
this.path = `File://${this.$route.query.sourceFile}`
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,115 @@
<template>
<div>
<div v-show="showMain">
<a-menu :selectedKeys="current" mode="horizontal" @select="changePath">
<a-menu-item key="market">
<a-icon type="appstore"/>
插件中心
</a-menu-item>
<a-menu-item key="favo">
<a-icon type="heart"/>
已安装
</a-menu-item>
<a-menu-item key="dev">
<a-icon type="code"/>
开发者
</a-menu-item>
<a-menu-item key="set">
<a-icon type="setting"/>
设置
</a-menu-item>
</a-menu>
</div>
<router-view v-show="showMain"/>
</div>
</template>
<script>
import {mapActions, mapState, mapMutations} from 'vuex';
export default {
name: "search",
methods: {
...mapActions('main', ['onSearch', 'showMainUI']),
...mapMutations('main', ['commonUpdate']),
changePath({key}) {
this.$router.push({path: `/home/${key}`});
this.commonUpdate({
current: [key]
})
},
},
computed: {
...mapState('main', ['showMain', 'current', 'options', 'selected', 'searchValue'])
}
}
</script>
<style lang="scss">
.rubick-select {
display: flex;
padding-left: 10px;
background: #fff;
position: relative;
.tag-container {
display: flex;
align-items: center;
background: #fff;
.select-tag {
height: 36px;
font-size: 20px;
display: flex;
align-items: center;
}
}
.ant-input:focus {
border: none;
box-shadow: none;
}
.options {
position: absolute;
top: 62px;
left: 0;
width: 100%;
z-index: 99;
.op-item {
padding: 0 10px;
height: 60px;
line-height: 50px;
max-height: 500px;
overflow: auto;
background: #fafafa;
}
}
}
.main-input {
-webkit-app-region: drag;
height: 60px !important;
flex: 1;
.ant-select-selection, .ant-input, .ant-select-selection__rendered {
height: 60px !important;
font-size: 22px;
border: none !important;
}
.icon-tool {
font-size: 24px;
background: #314659;
color: #fff;
height: 40px;
width: 40px;
border-radius: 100%;
line-height: 40px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
}
</style>

View File

@@ -0,0 +1,125 @@
<template>
<div class="dev-container">
<div class="dev-detail" v-if="devPlugins.length">
<a-menu v-model="currentSelect" style="width: 256px; height: 100%" mode="vertical">
<a-menu-item @click="currentSelect = [index]" v-for="(plugin, index) in devPlugins" :key="index">
<div>{{ plugin.pluginName }}</div>
<div>{{ plugin.description }}</div>
</a-menu-item>
</a-menu>
<div class="plugin-detail">
<div class="plugin-top">
<div class="left">
<div class="title">
{{pluginDetail.pluginName}}
<a-tag>{{pluginDetail.version}}</a-tag>
</div>
<div class="desc">
开发者{{`${pluginDetail.author || '未知'}`}}
</div>
<div class="desc">
{{pluginDetail.description}}
</div>
</div>
<div class="right">
<a-switch />
</div>
</div>
<a-tabs default-active-key="1">
<a-tab-pane key="1" tab="管理">
<div class="desc-item">
<div class="desc-title">
<p>重新加载</p>
<a-button type="link">重载</a-button>
</div>
<div class="desc-info">
如果你修改了plugin.json文件需要重新加载以应用最近版本
</div>
</div>
<div class="desc-item">
<div class="desc-title">
<p>发布</p>
<a-button type="link">发布</a-button>
</div>
<div class="desc-info">
发布后用户可以通过插件中心下载且享受最新的更新
</div>
</div>
<div class="desc-item">
<div class="desc-title">
<p>删除</p>
<a-button type="link">删除</a-button>
</div>
<div class="desc-info">
删除这个插件不可以恢复
</div>
</div>
</a-tab-pane>
<a-tab-pane key="2" tab="详情介绍">
Content of Tab Pane 2
</a-tab-pane>
</a-tabs>
</div>
</div>
</div>
</template>
<script>
import {mapState} from 'vuex';
export default {
data() {
return {
currentSelect: [0]
}
},
computed: {
...mapState('main', ['devPlugins']),
pluginDetail() {
console.log(this.$store)
return this.devPlugins[this.currentSelect]
}
}
}
</script>
<style lang="scss">
.dev-container {
height: calc(100vh - 110px);
.dev-detail {
display: flex;
align-items: flex-start;
height: 100%;
}
.plugin-detail {
padding: 20px;
box-sizing: border-box;
flex: 1;
.plugin-top {
display: flex;
align-items: flex-start;
justify-content: space-between;
.title {
font-size: 20px;
display: flex;
align-items: center;
}
.desc {
font-size: 13px;
color: #999;
}
}
.desc-item {
border-bottom: 1px solid #ddd;
padding: 10px 0;
.desc-title {
display: flex;
align-items: center;
justify-content: space-between;
}
.desc-info {
color: #999;
}
}
}
}
</style>

View File

@@ -0,0 +1,113 @@
<template>
<div class="market">
<a-carousel arrows>
<div
slot="prevArrow"
slot-scope="props"
class="custom-slick-arrow"
style="left: 10px;zIndex: 1"
>
<a-icon type="left-circle" />
</div>
<div slot="nextArrow" slot-scope="props" class="custom-slick-arrow" style="right: 10px">
<a-icon type="right-circle" />
</div>
<div><h3>1</h3></div>
<div><h3>2</h3></div>
<div><h3>3</h3></div>
<div><h3>4</h3></div>
</a-carousel>
<a-divider></a-divider>
<h2>插件</h2>
<a-list item-layout="horizontal" style="width: 100%" :grid="{ gutter: 16, column: 2 }" :data-source="pluginList">
<a-list-item slot="renderItem" slot-scope="item, index">
<a-button :loading="loading[index]" type="link" slot="actions" @click="download(index, item)">
<a-icon v-show="!loading[index]" style="font-size: 20px;" type="cloud-download" />
</a-button>
<a-list-item-meta
:description="item.description"
>
<a slot="title" href="https://www.antdv.com/">{{ item.title }}</a>
<a-avatar
slot="avatar"
src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"
/>
</a-list-item-meta>
</a-list-item>
</a-list>
</div>
</template>
<script>
import api from '../../../assets/api';
import {mapActions} from 'vuex';
export default {
data() {
return {
pluginList: [],
loading: {},
}
},
async created() {
const result = await api.plugin.query();
this.pluginList = result.result;
},
methods: {
async download(index, item) {
if (this.loading[index]) return;
this.$set(this.loading, index, true);
console.log(this.loading);
await this.downloadPlugin(item);
this.$set(this.loading, index, false);
console.log(this.loading);
},
...mapActions('main', ['downloadPlugin'])
}
}
</script>
<style lang="scss">
.market {
height: calc(100vh - 110px);
background: #fff;
padding: 20px;
box-sizing: border-box;
.ant-carousel .slick-slide {
text-align: center;
height: 200px;
line-height: 160px;
background: #364d79;
overflow: hidden;
}
.ant-carousel .custom-slick-arrow {
width: 25px;
height: 25px;
font-size: 25px;
color: #fff;
background-color: rgba(31, 45, 61, 0.11);
opacity: 0.3;
}
.ant-carousel .custom-slick-arrow:before {
display: none;
}
.ant-carousel .custom-slick-arrow:hover {
opacity: 0.5;
}
.ant-carousel .slick-slide h3 {
color: #fff;
}
.ant-list-item {
display: flex !important;
align-items: center;
justify-content: space-between;
}
}
</style>

View File

@@ -0,0 +1,35 @@
import Vue from 'vue'
import Router from 'vue-router'
import Market from '../pages/search/subpages/market';
import Dev from '../pages/search/subpages/dev';
Vue.use(Router)
export default new Router({
routes: [
{
path: '/home',
name: 'search',
component: require('@/pages/search/index.vue').default,
children: [
{
path: 'market',
component: Market
},
{
path: 'dev',
component: Dev
},
]
},
{
path: '/plugin',
name: 'plugin',
component: require('@/pages/plugins/index.vue').default
},
{
path: '*',
redirect: '/home'
}
]
})

View File

@@ -0,0 +1,10 @@
import Vue from 'vue'
import Vuex from 'vuex'
import modules from './modules'
Vue.use(Vuex)
export default new Vuex.Store({
modules,
strict: process.env.NODE_ENV !== 'production'
})

View File

@@ -0,0 +1,25 @@
const state = {
main: 0
}
const mutations = {
DECREMENT_MAIN_COUNTER (state) {
state.main--
},
INCREMENT_MAIN_COUNTER (state) {
state.main++
}
}
const actions = {
someAsyncTask ({ commit }) {
// do something async
commit('INCREMENT_MAIN_COUNTER')
}
}
export default {
state,
mutations,
actions
}

View File

@@ -0,0 +1,14 @@
/**
* The file enables `@/store/index.js` to import all vuex modules
* in a one-shot manner. There should not be any reason to edit this file.
*/
const files = require.context('.', false, /\.js$/)
const modules = {}
files.keys().forEach(key => {
if (key === './index.js') return
modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default
})
export default modules

View File

@@ -0,0 +1,162 @@
import {clipboard, ipcRenderer} from "electron";
import {getWindowHeight, searchKeyValues, downloadFunc, sysFile} from '../../assets/common/utils';
import fs from "fs";
import path from 'path';
const state = {
selected: null,
options: [],
showMain: false,
current: ['market'],
searchValue: '',
devPlugins: sysFile.getUserPlugins() || [],
}
const mutations = {
commonUpdate (state, payload) {
Object.keys(payload).forEach((key) => {
state[key] = payload[key];
if (key === 'devPlugins') {
sysFile.savePlugins(payload)
}
});
},
}
const actions = {
showMainUI ({ commit, state }, paylpad) {
commit('commonUpdate', {
showMain: true,
selected: {
key: 'market',
name: '插件中心'
}
});
ipcRenderer.send('changeWindowSize', {
height: getWindowHeight(),
});
},
onSearch ({ commit }, paylpad) {
const value = paylpad.target.value;
const fileUrl = clipboard.read('public.file-url').replace('file://', '');
commit('commonUpdate', {searchValue: value})
// 复制文件
if (fileUrl && value === 'plugin.json') {
const config = JSON.parse(fs.readFileSync(fileUrl, 'utf-8'));
const pluginConfig = {
...JSON.parse(fs.readFileSync(fileUrl, 'utf-8')),
sourceFile: path.join(fileUrl, `../${config.main}`)
};
commit('commonUpdate', {
selected: {
key: 'plugin',
name: 'plugin.json'
},
searchValue: '',
devPlugins: [pluginConfig, ...state.devPlugins],
options: [
{
name: '新建rubick开发插件',
value: 'new-plugin',
icon: 'plus-circle',
desc: '新建rubick开发插件',
click: (router) => {
commit('commonUpdate', {
showMain: true,
selected: {
key: 'plugin',
name: '新建rubick开发插件'
},
current: ['dev'],
});
ipcRenderer.send('changeWindowSize', {
height: getWindowHeight(),
});
router.push('/home/dev')
}
},
{
name: '复制路径',
desc: '复制路径',
value: 'copy-path',
icon: 'plus-circle',
click: () => {
clipboard.writeText(fileUrl)
}
}
]
});
// 调整窗口大小
ipcRenderer.send('changeWindowSize', {
height: getWindowHeight(state.options),
});
return
}
let options = [];
// check 是否是插件
state.devPlugins.forEach((plugin) => {
const feature = plugin.features;
feature.forEach(fe => {
const cmds = searchKeyValues(fe.cmds, value);
options = [
...options,
...cmds.map((cmd) => ({
name: cmd,
value: 'plugin',
icon: 'plus-circle',
desc: fe.explain,
click: (router) => {
commit('commonUpdate', {
selected: {
key: cmd,
name: cmd
},
searchValue: '',
showMain: true,
});
ipcRenderer.send('changeWindowSize', {
height: getWindowHeight(),
});
router.push({
path: '/plugin',
query: plugin,
})
}
}))
]
})
});
commit('commonUpdate', {
options
});
ipcRenderer.send('changeWindowSize', {
height: getWindowHeight(state.options),
});
},
async downloadPlugin({commit}, payload) {
await downloadFunc(payload.gitUrl, payload.name);
const fileUrl = path.join(__static, `plugins/${payload.name}`);
// 复制文件
const config = JSON.parse(fs.readFileSync(`${fileUrl}/plugin.json`, 'utf-8'));
const pluginConfig = {
...config,
sourceFile: `${fileUrl}/${config.main}`
};
commit('commonUpdate', {
devPlugins: [pluginConfig, ...state.devPlugins],
});
}
}
export default {
namespaced: true,
state,
mutations,
actions
}