支持ui插件下载&运行

This commit is contained in:
muwoo
2021-12-02 17:55:45 +08:00
parent c2f43bea39
commit 0132a11d7e
32 changed files with 951 additions and 244 deletions

View File

@@ -1,30 +1,87 @@
<template>
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
<div class="main-container">
<div class="slider-bar">
<div class="top">
<div class="menu-item avatar">
<a-avatar shape="square" :size="30">
<template #icon><UserOutlined /></template>
</a-avatar>
</div>
<div class="menu-item" @click="changeMenu('market')">
<AppstoreOutlined :class="active === 'market' && 'active'" style="font-size: 24px;" />
</div>
<div class="menu-item" @click="changeMenu('installed')">
<HeartOutlined :class="active === 'installed' && 'active'" style="font-size: 24px;" />
</div>
<div class="menu-item" @click="changeMenu('settings')">
<SettingOutlined :class="active === 'installed' && 'active'" style="font-size: 24px;" />
</div>
<div class="menu-item" @click="changeMenu('dev')">
<BugOutlined :class="active === 'installed' && 'active'" style="font-size: 24px;" />
</div>
</div>
<div class="menu-item bottom" @click="changeMenu('more')">
<MenuOutlined style="font-size: 24px;" />
</div>
</div>
<router-view />
</div>
<router-view />
</template>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
<script setup lang="ts">
import { ref } from "vue";
import { HeartOutlined, UserOutlined, SearchOutlined, MenuOutlined, AppstoreOutlined, SettingOutlined, BugOutlined } from "@ant-design/icons-vue";
const active = ref("market");
</script>
<style lang="less">
* {
margin: 0;
padding: 0;
}
#nav {
padding: 30px;
}
.main-container {
display: flex;
align-items: flex-start;
background: #F2EFEF;
#nav a {
font-weight: bold;
color: #2c3e50;
}
.slider-bar {
-webkit-app-region: drag;
width: 60px;
display: flex;
justify-content: space-between;
align-items: center;
flex-direction: column;
padding-top: 15px;
height: 100vh;
background-image: linear-gradient(to top right, rgba(255, 159, 180, 0.3), rgba(255, 159, 180, 0.2));
color: #7D7170;
box-sizing: border-box;
#nav a.router-link-exact-active {
color: #42b983;
.top {
display: flex;
align-items: center;
flex-direction: column;
}
.menu-item {
cursor: pointer;
margin-bottom: 40px;
-webkit-app-region: no-drag;
.active {
color: #ff4ea4;
}
&.avatar {
margin-bottom: 30px;
}
&.bottom {
margin-bottom: 20px;
}
}
}
}
</style>

View File

@@ -0,0 +1,5 @@
@import '~ant-design-vue/dist/antd.less'; // 引入官方提供的 less 样式入口文件
@primary-color: #ff4ea4; // 全局主色
@link-color: #ff4ea4; // 链接色
@error-color: #ff4ea4; // 错误色

View File

@@ -0,0 +1,17 @@
import axios from "axios";
export default {
async getTotalPlugins() {
const res = await axios.get(
"https://gitee.com/monkeyWang/rubick-database/raw/master/plugins/total-plugins.json"
);
return res.data;
},
async getFinderDetail() {
const res = await axios.get(
"https://gitee.com/monkeyWang/rubick-database/raw/master/plugins/finder.json"
);
return res.data;
},
};

View File

@@ -1,140 +0,0 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br />
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener"
>vue-cli documentation</a
>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel"
target="_blank"
rel="noopener"
>babel</a
>
</li>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router"
target="_blank"
rel="noopener"
>router</a
>
</li>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex"
target="_blank"
rel="noopener"
>vuex</a
>
</li>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint"
target="_blank"
rel="noopener"
>eslint</a
>
</li>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript"
target="_blank"
rel="noopener"
>typescript</a
>
</li>
</ul>
<h3>Essential Links</h3>
<ul>
<li>
<a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a>
</li>
<li>
<a href="https://forum.vuejs.org" target="_blank" rel="noopener"
>Forum</a
>
</li>
<li>
<a href="https://chat.vuejs.org" target="_blank" rel="noopener"
>Community Chat</a
>
</li>
<li>
<a href="https://twitter.com/vuejs" target="_blank" rel="noopener"
>Twitter</a
>
</li>
<li>
<a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a>
</li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li>
<a href="https://router.vuejs.org" target="_blank" rel="noopener"
>vue-router</a
>
</li>
<li>
<a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a>
</li>
<li>
<a
href="https://github.com/vuejs/vue-devtools#vue-devtools"
target="_blank"
rel="noopener"
>vue-devtools</a
>
</li>
<li>
<a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener"
>vue-loader</a
>
</li>
<li>
<a
href="https://github.com/vuejs/awesome-vue"
target="_blank"
rel="noopener"
>awesome-vue</a
>
</li>
</ul>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
name: "HelloWorld",
props: {
msg: String,
},
});
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

View File

@@ -1,6 +1,8 @@
import { createApp } from "vue";
import Antd from "ant-design-vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import "./assets/ant-reset.less";
createApp(App).use(store).use(router).mount("#app");
createApp(App).use(store).use(Antd).use(router).mount("#app");

View File

@@ -1,21 +1,12 @@
import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
import Home from "../views/Home.vue";
import Market from "../views/market/index.vue";
const routes: Array<RouteRecordRaw> = [
{
path: "/",
name: "Home",
component: Home,
},
{
path: "/about",
name: "About",
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () =>
import(/* webpackChunkName: "about" */ "../views/About.vue"),
},
name: "Market",
component: Market,
}
];
const router = createRouter({

View File

@@ -4,3 +4,5 @@ declare module '*.vue' {
const component: DefineComponent<{}, {}, any>
export default component
}
declare module 'axios'

View File

@@ -1,8 +1,73 @@
import { createStore } from "vuex";
import request from "@/assets/request";
const isDownload = (item: any, targets: any[]) => {
let isDownload = false;
targets.some((plugin) => {
if (plugin.name === item.name) {
isDownload = true;
}
return isDownload;
});
return isDownload;
};
export default createStore({
state: {},
mutations: {},
actions: {},
state: {
totalPlugins: [],
localPlugins: [],
},
mutations: {
commonUpdate(state: any, payload) {
Object.keys(payload).forEach((key) => {
state[key] = payload[key];
});
},
},
actions: {
async init({ commit }) {
const totalPlugins = await request.getTotalPlugins();
const localPlugins = (window as any).rubick.getLocalPlugins();
totalPlugins.forEach(
(origin: { isdwonload?: any; name?: any; isloading: boolean }) => {
origin.isdwonload = isDownload(origin, localPlugins);
origin.isloading = false;
}
);
commit("commonUpdate", {
localPlugins,
totalPlugins,
});
},
startDownload({ commit, state }, name) {
const totalPlugins = JSON.parse(JSON.stringify(state.totalPlugins));
totalPlugins.forEach(
(origin: { isdwonload?: any; name?: any; isloading: boolean }) => {
if (origin.name === name) {
origin.isloading = true;
}
}
);
commit("commonUpdate", {
totalPlugins,
});
},
successDownload({ commit, state }, name) {
const totalPlugins = JSON.parse(JSON.stringify(state.totalPlugins));
totalPlugins.forEach(
(origin: { isdwonload?: any; name?: any; isloading: boolean }) => {
if (origin.name === name) {
origin.isloading = false;
origin.isdwonload = true;
}
}
);
commit("commonUpdate", {
totalPlugins,
});
},
},
modules: {},
});

View File

@@ -1,5 +0,0 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>

View File

@@ -1,18 +0,0 @@
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png" />
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import HelloWorld from "@/components/HelloWorld.vue"; // @ is an alias to /src
export default defineComponent({
name: "Home",
components: {
HelloWorld,
},
});
</script>

View File

@@ -0,0 +1,13 @@
<template>
</template>
<script>
export default {
name: "devlopment"
};
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,115 @@
<template>
<div class="finder">
<a-carousel arrows>
<template #prevArrow>
<div class="custom-slick-arrow" style="left: 10px; z-index: 1">
<left-circle-outlined />
</div>
</template>
<template #nextArrow>
<div class="custom-slick-arrow" style="right: 10px">
<right-circle-outlined />
</div>
</template>
<div :key="index" v-for="(banner, index) in (data.banners || [])">
<img @click="jumpTo(banner.link)" width="100%" :src="banner.src" />
</div>
</a-carousel>
<PluginList @downloadSuccess="downloadSuccess" title="推荐" :list="recommend || []" />
<PluginList title="最近更新" :list="newList || []" />
</div>
</template>
<script setup>
import {
LeftCircleOutlined,
RightCircleOutlined,
} from "@ant-design/icons-vue";
import { ref, computed } from "vue";
import request from "../../../assets/request/index";
import PluginList from "./plugin-list.vue";
import { useStore } from "vuex";
const store = useStore();
const totalPlugins = computed(() => store.state.totalPlugins);
const data = ref([]);
request.getFinderDetail().then(res => {
data.value = res;
});
const recommend = computed(() => {
const defaultData = data.value.recommend || [];
if (!defaultData.length) return [];
return defaultData.map((plugin) => {
let searchInfo = null;
totalPlugins.value.forEach((t) => {
if (t.name === plugin) {
searchInfo = t;
}
});
return searchInfo;
});
});
const newList = computed(() => {
const defaultData = data.value.new || [];
if (!defaultData.length) return [];
return defaultData.map((plugin) => {
let searchInfo = null;
totalPlugins.value.forEach((t) => {
if (t.name === plugin) {
searchInfo = t;
}
});
return searchInfo;
});
});
</script>
<style lang="less">
.finder {
width: 100%;
height: 100vh;
overflow-x: hidden;
box-sizing: border-box;
.ant-carousel .slick-slide {
text-align: center;
height: 235px;
line-height: 160px;
overflow: hidden;
border-radius: 4px;
img {
width: 100%;
height: 235px;
}
}
.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.slick-next:focus {
color: #fff;
}
.ant-carousel .custom-slick-arrow:before {
display: none;
}
.ant-carousel .custom-slick-arrow:hover {
opacity: 0.5;
}
.ant-carousel .slick-slide h3 {
color: #fff;
}
}
</style>

View File

@@ -0,0 +1,13 @@
<template>
</template>
<script>
export default {
name: "image"
};
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,165 @@
<template>
<div class="panel-item">
<h3 class="title">{{ title }}</h3>
<div class="list-item">
<a-list :grid="{ gutter: 16, column: 2 }" :data-source="list">
<template #renderItem="{ item, index }">
<a-list-item @click="visible=true">
<template #actions>
<a-button style="color: #ff4ea4;" type="text" :loading="item.isloading">
<CloudDownloadOutlined
v-show="!item.isloading && !item.isdwonload"
@click.stop="downloadPlugin(item, index)"
style="font-size: 20px; cursor: pointer"
/>
</a-button>
</template>
<a-list-item-meta
:description="item.description"
>
<template #title>
<span>{{ item.pluginName }}</span>
</template>
<template #avatar>
<a-avatar :src="item.logo" />
</template>
</a-list-item-meta>
</a-list-item>
</template>
</a-list>
</div>
</div>
<a-drawer
width="100%"
placement="right"
:closable="false"
:visible="visible"
:get-container="false"
class="plugin-info"
:style="{ position: 'absolute' }"
@close="visible=false"
>
<template #title>
<div class="plugin-title-info">
<div class="back-icon" @click="visible=false">
<ArrowLeftOutlined />
</div>
<div class="info">
<img src="https://static.91jkys.com/activity/img/2adb63c2e5d54dc1b26001958fcdb044.jpg"
class="plugin-icon" />
<div class="plugin-desc">
<div class="title">
备忘快贴
</div>
<div class="desc">
适合每个人的专业图像编辑软件
</div>
<a-button shape="round" type="primary">
<template #icon>
<CloudDownloadOutlined />
</template>
获取
</a-button>
</div>
</div>
</div>
</template>
</a-drawer>
</template>
<script setup>
import {
CloudDownloadOutlined,
ArrowLeftOutlined,
} from "@ant-design/icons-vue";
import { computed, defineProps, ref } from "vue";
import { useStore } from "vuex";
const store = useStore();
const startDownload = (name) => store.dispatch("startDownload", name);
const successDownload = (name) => store.dispatch("successDownload", name);
defineProps({
list: {
type: [Array],
default: () => [],
},
title: String,
});
const downloadPlugin = async (plugin) => {
startDownload(plugin.name);
await window.rubick.downloadPlugin(plugin);
successDownload(plugin.name);
};
const visible = ref(false);
</script>
<style lang="less">
.panel-item {
margin: 20px 0;
.title {
margin-bottom: 30px;
}
&:after{
content: " ";
display: block;
width: 100%;
height: 1px;
border-bottom: 1px solid #eee;
transform: scaleY(0.5);
}
.ant-list-item {
display: flex !important;
}
&:last-child {
border-bottom: none;
}
}
.plugin-info {
width: 100%;
.ant-drawer-content-wrapper {
box-shadow: none !important;
}
}
.plugin-title-info {
display: flex;
align-items: flex-start;
.back-icon {
font-size: 16px;
margin-right: 40px;
}
.info {
display: flex;
align-items: center;
.plugin-icon {
width: 100px;
height: 100px;
margin-right: 20px;
}
.plugin-desc {
.title {
font-size: 18px;
font-weight: bold;
}
.desc {
font-size: 12px;
font-weight: normal;
margin-top: 5px;
margin-bottom: 20px;
}
}
}
}
</style>

View File

@@ -0,0 +1,13 @@
<template>
</template>
<script>
export default {
name: "tools"
};
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,13 @@
<template>
</template>
<script>
export default {
name: "worker"
};
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,125 @@
<template>
<div class="market">
<div class="left-menu">
<div class="search-container">
<a-input-search
v-model:value="searchValue"
placeholder="搜索插件"
style="width: 100%"
@search="onSearch"
/>
</div>
<a-menu v-model:selectedKeys="current" mode="inline">
<a-menu-item key="finder">
<template #icon>
<StarOutlined />
</template>
探索
</a-menu-item>
<a-menu-item key="worker">
<template #icon>
<SendOutlined style="transform: rotate(-45deg)" />
</template>
效率
</a-menu-item>
<a-menu-item key="tools">
<template #icon>
<SearchOutlined />
</template>
搜索工具
</a-menu-item>
<a-menu-item key="image">
<template #icon>
<FileImageOutlined />
</template>
图像
</a-menu-item>
<a-menu-item key="dev">
<template #icon>
<CodeOutlined />
</template>
开发
</a-menu-item>
<a-menu-item key="system">
<template #icon>
<DatabaseOutlined />
</template>
系统
</a-menu-item>
</a-menu>
</div>
<div class="container">
<component :totalPlugins="totalPlugins" :is="Components[current[0]]" />
</div>
</div>
</template>
<script lang="ts" setup>
import {
StarOutlined,
SendOutlined,
SearchOutlined,
FileImageOutlined,
DatabaseOutlined,
CodeOutlined,
} from "@ant-design/icons-vue";
import { reactive, toRefs, computed } from "vue";
import { useStore } from "vuex";
import Finder from "./components/finder.vue";
const Components = {
finder: Finder,
};
const state = reactive({
searchValue: "",
current: ["finder"],
});
const store = useStore();
const init = () => store.dispatch("init");
init();
const totalPlugins = computed(() => store.state.totalPlugins);
const { searchValue, current } = toRefs(state);
</script>
<style lang="less">
.market {
display: flex;
box-sizing: border-box;
align-items: flex-start;
width: 100%;
overflow: hidden;
background: #F3EFEF;
.left-menu {
width: 200px;
height: 100vh;
.search-container {
padding: 10px;
}
.ant-input-affix-wrapper {
border: none;
}
.ant-menu {
background: #F3EFEF;
.ant-menu-item-selected {
background-color: #E2E2E2;
color: #141414;
&:after {
display: none;
}
}
}
}
.container {
background: #fff;
width: calc(~'100% - 200px');
height: 100vh;
box-sizing: border-box;
padding: 10px 20px;
position: relative;
}
}
</style>