chore: import upstream snapshot with attribution
@@ -0,0 +1,3 @@
|
||||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
||||
@@ -0,0 +1,26 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
},
|
||||
extends: [
|
||||
'plugin:vue/vue3-essential',
|
||||
'eslint:recommended',
|
||||
'@vue/typescript/recommended',
|
||||
'@vue/prettier',
|
||||
'@vue/prettier/@typescript-eslint',
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020,
|
||||
},
|
||||
rules: {
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'prettier/prettier': [
|
||||
'warn',
|
||||
{
|
||||
singleQuote: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: #muwoo
|
||||
custom: ['https://rubickcenter.github.io/rubick/run/#%E8%B5%9E%E5%8A%A9'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: 报告一个bug
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**描述一下这个bug**
|
||||
清楚而简洁地描述了错误是什么
|
||||
|
||||
**复现方式**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**预期行为**
|
||||
清晰简明地描述了您预期的发生。
|
||||
|
||||
**截图**
|
||||
如果可以,请添加屏幕截图以帮助解释您的问题。
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Development Problem
|
||||
about: 任何开发建议、使用问题、交流学习都可以
|
||||
title: ''
|
||||
labels: help wanted
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## 任何开发建议、交流学习都可以
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: 提交一个新特性/功能
|
||||
title: ''
|
||||
labels: feature
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**您的功能请求是否与问题相关? 请简单描述.**
|
||||
清晰简明地描述问题是什么. Ex. I'm always frustrated when [...]
|
||||
|
||||
**请描述一下您想要的解决方案**
|
||||
清晰简明地描述您想要发生的事情。
|
||||
|
||||
**描述你考虑过的替代方案**
|
||||
清晰简洁地描述您所考虑的任何替代解决方案或功能。
|
||||
@@ -0,0 +1,65 @@
|
||||
# main.yml
|
||||
|
||||
# Workflow's name
|
||||
name: Build
|
||||
|
||||
# Workflow's trigger
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
# Workflow's jobs
|
||||
jobs:
|
||||
# job's id
|
||||
release:
|
||||
# job's name
|
||||
name: build and release electron app
|
||||
|
||||
# the type of machine to run the job on
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
# create a build matrix for jobs
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, windows-2022, ubuntu-latest]
|
||||
|
||||
# create steps
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
# step1: check out repository
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# step2: install node env
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
- name: Install system deps
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo apt-get install libxtst-dev libpng++-dev
|
||||
sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils
|
||||
sudo snap install snapcraft --classic
|
||||
# step3: yarn
|
||||
- name: Yarn install
|
||||
run: |
|
||||
yarn
|
||||
yarn global add xvfb-maybe
|
||||
yarn global add @vue/cli@4.5.0 --frozen-lockfile --ignore-engines
|
||||
- name: Build feature
|
||||
run: |
|
||||
cd ./feature
|
||||
yarn
|
||||
npm run build
|
||||
- name: Build & release app
|
||||
run: |
|
||||
npm run release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
@@ -0,0 +1,27 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
build/*
|
||||
!build/icons
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
#Electron-builder output
|
||||
/dist_electron
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"jsxSingleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"bracketSpacing": true,
|
||||
"jsxBracketSameLine": false,
|
||||
"arrowParens": "always",
|
||||
"printWidth": 80,
|
||||
"embeddedLanguageFormatting": "auto",
|
||||
"htmlWhitespaceSensitivity": "ignore",
|
||||
"preserve": "preserve",
|
||||
"insertPragma": false,
|
||||
"quoteProps": "as-needed",
|
||||
"requirePragma": false,
|
||||
"endOfLine": "auto"
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 clouDr
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,111 @@
|
||||
English | [简体中文](./README.zh-CN.md)
|
||||
|
||||
<div align= "center">
|
||||
<img align="center" width=200 src="./public/logo.png" />
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<h1>Rubick</h1>
|
||||
<img alt="downloads" src="https://img.shields.io/github/downloads/rubickCenter/rubick/total" />
|
||||
<a href="https://github.com/rubickCenter/rubick/releases"><img alt="latest release" src="https://img.shields.io/github/package-json/v/rubickCenter/rubick" /></a>
|
||||
<a href="https://github.com/rubickCenter/rubick/actions"><img alt="github action building" src="https://img.shields.io/github/actions/workflow/status/rubickCenter/rubick/main.yml" /></a>
|
||||
<a href="https://github.com/rubickCenter/rubick/blob/master/LICENSE"><img alt="license" src="https://img.shields.io/github/license/rubickCenter/rubick" /></a>
|
||||
<a href="https://github.com/rubickCenter/rubick/stargazers"><img alt="github stars" src="https://img.shields.io/github/stars/rubickCenter/rubick?style=social" /></a>
|
||||
<a href="https://gitee.com/monkeyWang/rubick"><img alt="gitee mirror" src="https://img.shields.io/badge/Gitee--yellow.svg?style=social&logo=data:image/svg+xml;base64,PHN2ZyB0PSIxNTc0ODM3MTM4ODM3IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjE3NzAiICAgICB3aWR0aD0iMTYiIGhlaWdodD0iMTYiPiAgICA8cGF0aCBkPSJNODkxIDQyOC44SDQ2NS44Yy0yMC40IDAtMzcgMTYuNS0zNyAzN3Y5Mi40YzAgMjAuNCAxNi41IDM3IDM3IDM3aDI1OC45YzIwLjQgMCAzNyAxNi42IDM3IDM3djE4LjRjMCA2MS4zLTQ5LjcgMTEwLjktMTEwLjkgMTEwLjlIMjk5LjRjLTIwLjQgMC0zNy0xNi42LTM3LTM3VjM3My4yYzAtNjEuMyA0OS43LTExMC45IDExMC45LTExMC45aDUxNy42YzIwLjQgMCAzNy0xNi41IDM3LTM3bDAuMS05Mi4zYzAtMjAuNC0xNi41LTM3LTM3LTM3SDM3My4zQzIyMC4yIDk2IDk2IDIyMC4yIDk2IDM3My4zVjg5MWMwIDIwLjQgMTYuNiAzNyAzNyAzN2g1NDUuNEM4MTYuMiA5MjggOTI4IDgxNi4zIDkyOCA2NzguNFY0NjUuOGMwLTIwLjQtMTYuNi0zNy0zNy0zN3oiICAgICAgICAgIGZpbGw9IiNkODFlMDYiIHAtaWQ9IjE3NzEiPjwvcGF0aD48L3N2Zz4=" /></a>
|
||||
</div>
|
||||
|
||||
<div align= "center">
|
||||
<img align="center" src="https://picx.zhimg.com/80/v2-f8fe09ef125dac5fdcbef3fe00f92b21_720w.png" />
|
||||
</div>
|
||||
Open-source plugin-based desktop efficiency toolbox. The plugins are installed and uninstalled based on npm, which is very lightweight. The plugin data supports webdav multi-terminal synchronization, which is very secure. It supports internal network deployment and can be customized for further development, which is very flexible.
|
||||
|
||||
## Get Rubick
|
||||
Download the latest release:
|
||||
* [Rubick Mac OS](https://github.com/rubickCenter/rubick/releases)
|
||||
* [Rubick Windows](https://github.com/rubickCenter/rubick/releases)
|
||||
* [Rubick Linux](https://github.com/rubickCenter/rubick/releases)
|
||||
|
||||
## Docs
|
||||
|
||||
[Rubick website](https://rubick.vip)
|
||||
|
||||
[Rubick Docs](https://rubickCenter.github.io/docs/)
|
||||
|
||||
## How To Use Rubick
|
||||
After installing rubick, you can quickly launch the main program by pressing the shortcut keys Alt/Option+R. Entering keywords in the main program input box can search for corresponding apps, plugins, files...
|
||||
Select the ones you want and use them.
|
||||
|
||||
If you don't want the function, you can click the logo on the left to enter the plugin market and find the ones you want to install.
|
||||
|
||||
## Feature list
|
||||
- [x] Plugin management based on the npm package pattern, installing plugins is as simple as installing npm packages.
|
||||
- [x] Supports WebDAV for multi-device data synchronization, ensuring true data security synchronization.
|
||||
- [x] A unique system plugin mode that allows plugins to become an integral part of Rubick.
|
||||
- [x] Supports the quick launch of local apps, files, and folders.
|
||||
- [x] Supports enterprise-level intranet deployment.
|
||||
- [x] Supports multiple languages.
|
||||
|
||||
## Core functionality showcase.
|
||||
### 1. Search system application
|
||||
Support pinyin and abbreviations to search system applications:
|
||||
|
||||

|
||||
|
||||
### 2. UI plug-in installation
|
||||
Click the `rubick` icon on the right side of the search box to enter the plug-in market, select the desired plug-in, and click the download button to download. After the download is complete, you can find the installed plug-in under the Installed tab
|
||||
|
||||
After the installation is complete, enter the plug-in call up command to use the corresponding plug-in:
|
||||
|
||||

|
||||
|
||||
### 3. System plug-in installation
|
||||
The system plug-in installation method is the same as that of the UI category. In the plug-in market, select the `system category` and find the system plug-in that suits you to install it.
|
||||
```
|
||||
After the system plug-in is installed successfully, rubick needs to be restarted to take effect
|
||||
```
|
||||
|
||||
### 4. Multi-device data synchronization based on WebDAV.
|
||||
In "Rubick," search for "Preferences," go to "Account and Settings," and then select "Multi-Device Data Synchronization." You can export and import data for the use of Rubick plugins.
|
||||
|
||||

|
||||
|
||||
## Related Repositories
|
||||
|
||||
[Rubick Plugins Repositories](https://gitee.com/rubick-center)
|
||||
|
||||
[Rubick Plugins Database](https://gitcode.net/rubickcenter/rubick-database)
|
||||
|
||||
[Rubick Plugin CLI](https://github.com/rubickCenter/rubick-plugin-cli)
|
||||
|
||||
## Sponsor
|
||||
### 1. Join the Knowledge Planet
|
||||
We have accumulated a lot of knowledge and common issues about rubick on the Knowledge Planet. You can pay to join our knowledge community to discuss with us. We will answer at any time!
|
||||
|
||||
<img width=400 src=https://picx.zhimg.com/80/v2-6deabf65175d18080439ef813102d18c_720w.png />
|
||||
|
||||
### 2. Buy me a cup of coffee
|
||||
If the project is helpful to you, you can buy me a cup of coffee as a reward!
|
||||
<div align= "left">
|
||||
<img width="180" src="https://picx.zhimg.com/80/v2-911d249dc454f3460451a4e1ecceeb14_720w.png">
|
||||
<img width="180" src="https://picx.zhimg.com/80/v2-3160247d6099053405e6cd2cb6afb5e5_720w.png">
|
||||
</div>
|
||||
|
||||
## 友情链接
|
||||
|
||||
<a href="https://pro.kuaitu.cc/" target="_blank">
|
||||
<img width="100" src="https://github.com/user-attachments/assets/6127488e-466b-4e71-98ab-00fb3c76553e" />
|
||||
</a>
|
||||
|
||||
|
||||
## Feedback
|
||||
Those who are interested in this project or want to exchange and learn can scan the QR code and add the following WeChat, with the comment rubick, to help us grow better.
|
||||
|
||||

|
||||
|
||||
<a href="https://hellogithub.com/repository/0a3e2484b44e481e9dcf1850e45193cd" target="_blank"><img src="https://api.hellogithub.com/v1/widgets/recommend.svg?rid=0a3e2484b44e481e9dcf1850e45193cd&claim_uid=vXGwjpmYNsBex0C" alt="Featured|HelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
|
||||
## Contribute
|
||||
This project exists thanks to all the people who contribute. [[Contribute](https://github.com/rubickCenter/rubick/graphs/contributors)]. <a href="https://github.com/rubickCenter/rubick/graphs/contributors"><img src="https://opencollective.com/rubick/contributors.svg?width=890&button=false" /></a>
|
||||
|
||||
## License
|
||||
This project is licensed under the MIT License - see the [LICENSE](https://github.com/rubickCenter/rubick/blob/master/LICENSE) file for details.
|
||||
@@ -0,0 +1,7 @@
|
||||
# WeHub 来源说明
|
||||
|
||||
- 原始项目:`rubickCenter/rubick`
|
||||
- 原始仓库:https://github.com/rubickCenter/rubick
|
||||
- 导入方式:上游默认分支的最新快照
|
||||
- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准
|
||||
- 本文件仅用于记录来源,不代表 WeHub 是原项目作者
|
||||
@@ -0,0 +1,106 @@
|
||||
[English](./README.md) | 简体中文
|
||||
|
||||
|
||||
<div align= "center">
|
||||
<img align="center" width=200 src="./public/logo.png" />
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<h1>Rubick</h1>
|
||||
<img alt="累计下载数" src="https://img.shields.io/github/downloads/rubickCenter/rubick/total" />
|
||||
<a href="https://github.com/rubickCenter/rubick/releases"><img alt="最新发布版本" src="https://img.shields.io/github/package-json/v/rubickCenter/rubick" /></a>
|
||||
<a href="https://github.com/rubickCenter/rubick/actions"><img alt="github action 构建" src="https://img.shields.io/github/actions/workflow/status/rubickCenter/rubick/main.yml" /></a>
|
||||
<a href="https://github.com/rubickCenter/rubick/blob/master/LICENSE"><img alt="许可证" src="https://img.shields.io/github/license/rubickCenter/rubick" /></a>
|
||||
<a href="https://github.com/rubickCenter/rubick/stargazers"><img alt="github 收藏数" src="https://img.shields.io/github/stars/rubickCenter/rubick?style=social" /></a>
|
||||
<a href="https://gitee.com/monkeyWang/rubick"><img alt="gitee 镜像源" src="https://img.shields.io/badge/Gitee--yellow.svg?style=social&logo=data:image/svg+xml;base64,PHN2ZyB0PSIxNTc0ODM3MTM4ODM3IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjE3NzAiICAgICB3aWR0aD0iMTYiIGhlaWdodD0iMTYiPiAgICA8cGF0aCBkPSJNODkxIDQyOC44SDQ2NS44Yy0yMC40IDAtMzcgMTYuNS0zNyAzN3Y5Mi40YzAgMjAuNCAxNi41IDM3IDM3IDM3aDI1OC45YzIwLjQgMCAzNyAxNi42IDM3IDM3djE4LjRjMCA2MS4zLTQ5LjcgMTEwLjktMTEwLjkgMTEwLjlIMjk5LjRjLTIwLjQgMC0zNy0xNi42LTM3LTM3VjM3My4yYzAtNjEuMyA0OS43LTExMC45IDExMC45LTExMC45aDUxNy42YzIwLjQgMCAzNy0xNi41IDM3LTM3bDAuMS05Mi4zYzAtMjAuNC0xNi41LTM3LTM3LTM3SDM3My4zQzIyMC4yIDk2IDk2IDIyMC4yIDk2IDM3My4zVjg5MWMwIDIwLjQgMTYuNiAzNyAzNyAzN2g1NDUuNEM4MTYuMiA5MjggOTI4IDgxNi4zIDkyOCA2NzguNFY0NjUuOGMwLTIwLjQtMTYuNi0zNy0zNy0zN3oiICAgICAgICAgIGZpbGw9IiNkODFlMDYiIHAtaWQ9IjE3NzEiPjwvcGF0aD48L3N2Zz4=" /></a>
|
||||
</div>
|
||||
|
||||
<div align= "center">
|
||||
<img align="center" src="https://picx.zhimg.com/80/v2-f8fe09ef125dac5fdcbef3fe00f92b21_720w.png" />
|
||||
</div>
|
||||
|
||||
开源的插件化桌面端效率工具箱。插件是基于 npm 进行安装和卸载,非常轻便。插件数据支持 webdav 多端同步,非常安全。支持内网部署,可二次定制化开发,非常灵活。
|
||||
|
||||
## 获取 rubick
|
||||
下载最新的安装包:
|
||||
|
||||
* [Rubick Mac OS](https://github.com/rubickCenter/rubick/releases)
|
||||
* [Rubick Windows](https://github.com/rubickCenter/rubick/releases)
|
||||
* [Rubick Linux](https://github.com/rubickCenter/rubick/releases)
|
||||
|
||||
## 使用文档
|
||||
|
||||
[Rubick 官网](https://rubick.vip)
|
||||
|
||||
[Rubick Docs](https://rubickCenter.github.io/rubick/)
|
||||
|
||||
## 如何使用 rubick
|
||||
|
||||
安装完成 rubick 后,可以通过快捷键 Alt/Option+R 可以快速呼起主程序。主程序输入框内输入关键词可以搜索出对应的 App、插件、文件... 选择即可使用。如果没有想要的功能,可以点击左侧的 logo 进入插件市场寻找自己想要的插件进行安装。
|
||||
|
||||
## 支持能力
|
||||
- [x] 基于 npm 包模式的插件管理,安装插件和安装 npm 包一样简单
|
||||
- [x] 支持 webdav 多端数据同步,真正的数据安全同步
|
||||
- [x] 独一无二的系统插件模式,让插件成为 rubick 的一部分
|
||||
- [x] 支持快速启动本地 app、文件、文件夹
|
||||
- [x] 支持企业化内网部署
|
||||
- [x] 支持多语言
|
||||
|
||||
|
||||
## 核心功能展示
|
||||
### 1. 搜索系统应用
|
||||
支持拼音和缩写来搜索系统安装应用:
|
||||
|
||||

|
||||
|
||||
### 2. UI类插件安装
|
||||
点击搜索框右侧 `rubick` 图标,进入插件市场,选择所需插件,点击下载按钮即可下载,下载完成后在已安装 tab 下可以找到安装插件。
|
||||
安装完成后,输入插件呼起命令即可使用对应插件:
|
||||
|
||||

|
||||
|
||||
### 3. 系统类插件安装
|
||||
系统插件安装方式和UI类一样,在插件市场选择`系统分类`,寻找适合自己的系统插件安装即可。
|
||||
|
||||
```
|
||||
系统插件安装成功后,需要重启 rubick 才能生效
|
||||
```
|
||||
|
||||
### 4. 基于 webdav 的多端数据同步
|
||||
在 `rubick` 内搜索`偏好设置` 进入 `账户和设置` -> `多端数据同步`;即可对 `rubick` 插件使用数据进行 `导出` 和 `导入`。
|
||||
|
||||

|
||||
|
||||
## 关联仓库
|
||||
|
||||
[Rubick 插件仓库](https://gitee.com/rubick-center)
|
||||
|
||||
[Rubick 插件数据库](https://gitcode.net/rubickcenter/rubick-database)
|
||||
|
||||
[Rubick Plugin CLI](https://github.com/rubickCenter/rubick-plugin-cli)
|
||||
|
||||
## 赞助
|
||||
### 1. 加入知识星球
|
||||
我们在知识星球积累了大量的关于 rubick 的知识和常见问题,您可以付费加入我们的知识星球来一起讨论。我们将随时解答!
|
||||
|
||||
<img width=400 src=https://picx.zhimg.com/80/v2-6deabf65175d18080439ef813102d18c_720w.png />
|
||||
|
||||
### 2. 打赏喝杯咖啡
|
||||
如果项目对你有帮助,可以请我喝杯咖啡赞赏!
|
||||
|
||||
<div align= "left">
|
||||
<img width="180" src="https://picx.zhimg.com/80/v2-911d249dc454f3460451a4e1ecceeb14_720w.png">
|
||||
<img width="180" src="https://picx.zhimg.com/80/v2-3160247d6099053405e6cd2cb6afb5e5_720w.png">
|
||||
</div>
|
||||
|
||||
## 反馈
|
||||
对本项目有兴趣或者想要交流学习的同学可以扫码加下面的微信,备注 rubick,帮助我们更好的成长:
|
||||
|
||||

|
||||
|
||||
## 贡献
|
||||
This project exists thanks to all the people who contribute. [[Contribute](https://github.com/rubickCenter/rubick/graphs/contributors)]. <a href="https://github.com/rubickCenter/rubick/graphs/contributors"><img src="https://opencollective.com/rubick/contributors.svg?width=890&button=false" /></a>
|
||||
|
||||
## License
|
||||
This project is licensed under the MIT License - see the [LICENSE](https://github.com/rubickCenter/rubick/blob/master/LICENSE) file for details.
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
module.exports = {
|
||||
presets: ['@vue/cli-plugin-babel/preset'],
|
||||
plugins: [
|
||||
[
|
||||
'import',
|
||||
{
|
||||
libraryName: 'ant-design-vue',
|
||||
libraryDirectory: 'es',
|
||||
style: 'css', // or 'css'
|
||||
},
|
||||
'ant-design-vue',
|
||||
],
|
||||
[
|
||||
'import',
|
||||
{
|
||||
libraryName: '@ant-design/icons-vue',
|
||||
libraryDirectory: 'lib/icons',
|
||||
camel2DashComponentName: false,
|
||||
},
|
||||
'@ant-design/icons-vue',
|
||||
],
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@@ -0,0 +1,24 @@
|
||||
# detach
|
||||
|
||||
## Project setup
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
presets: ["@vue/cli-plugin-babel/preset"],
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"name": "detach",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve --port 8082",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": "^3.6.5",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"vue": "^3.0.0",
|
||||
"vue-router": "^4.0.0-0",
|
||||
"vuex": "^4.0.0-0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^4.18.0",
|
||||
"@typescript-eslint/parser": "^4.18.0",
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-plugin-router": "~4.5.0",
|
||||
"@vue/cli-plugin-typescript": "~4.5.0",
|
||||
"@vue/cli-plugin-vuex": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/compiler-sfc": "^3.0.0",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"@vue/eslint-config-typescript": "^7.0.0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"eslint-plugin-vue": "^7.0.0",
|
||||
"prettier": "^2.2.1",
|
||||
"typescript": "~4.1.5"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/vue3-essential",
|
||||
"eslint:recommended",
|
||||
"@vue/typescript/recommended",
|
||||
"@vue/prettier",
|
||||
"@vue/prettier/@typescript-eslint"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020
|
||||
},
|
||||
"rules": {}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
],
|
||||
"volta": {
|
||||
"node": "16.20.2"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,262 @@
|
||||
<template>
|
||||
<div :class="[process.platform, 'detach']">
|
||||
<div class="info">
|
||||
<img :src="plugInfo.logo"/>
|
||||
<input
|
||||
autofocus
|
||||
@input="changeValue"
|
||||
v-if="showInput"
|
||||
:value="plugInfo.subInput?.value"
|
||||
:placeholder="plugInfo.subInput?.placeholder"
|
||||
/>
|
||||
<span v-else>{{ plugInfo.pluginName }}</span>
|
||||
</div>
|
||||
<div class="handle-container">
|
||||
<div class="handle">
|
||||
<div class="devtool" @click="openDevTool" title="开发者工具"></div>
|
||||
</div>
|
||||
<div class="window-handle" v-if="process.platform !== 'darwin'">
|
||||
<div class="minimize" @click="minimize"></div>
|
||||
<div class="maximize" @click="maximize"></div>
|
||||
<div class="close" @click="close"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import throttle from 'lodash.throttle';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
|
||||
const process = window.require('process');
|
||||
const showInput = ref(false);
|
||||
|
||||
const storeInfo = localStorage.getItem('rubick-system-detach') || '{}';
|
||||
const plugInfo = ref({});
|
||||
|
||||
window.initDetach = (pluginInfo) => {
|
||||
plugInfo.value = pluginInfo;
|
||||
showInput.value =
|
||||
pluginInfo.subInput &&
|
||||
(!!pluginInfo.subInput.value || !!pluginInfo.subInput.placeholder);
|
||||
localStorage.setItem('rubick-system-detach', JSON.stringify(pluginInfo));
|
||||
};
|
||||
|
||||
try {
|
||||
window.initDetach(JSON.parse(storeInfo));
|
||||
} catch (e) {
|
||||
// ...
|
||||
}
|
||||
|
||||
const changeValue = throttle((e) => {
|
||||
ipcRenderer.send('msg-trigger', {
|
||||
type: 'detachInputChange',
|
||||
data: {
|
||||
text: e.target.value,
|
||||
},
|
||||
});
|
||||
}, 500);
|
||||
|
||||
const openDevTool = () => {
|
||||
ipcRenderer.send('msg-trigger', { type: 'openPluginDevTools' });
|
||||
};
|
||||
|
||||
const minimize = () => {
|
||||
ipcRenderer.send('detach:service', { type: 'minimize' });
|
||||
};
|
||||
|
||||
const maximize = () => {
|
||||
ipcRenderer.send('detach:service', { type: 'maximize' });
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
ipcRenderer.send('detach:service', { type: 'close' });
|
||||
};
|
||||
|
||||
Object.assign(window, {
|
||||
setSubInputValue: ({ value }) => {
|
||||
plugInfo.value.subInput.value = value;
|
||||
},
|
||||
setSubInput: (placeholder) => {
|
||||
plugInfo.value.subInput.placeholder = placeholder;
|
||||
},
|
||||
removeSubInput: () => {
|
||||
plugInfo.value.subInput = null;
|
||||
},
|
||||
});
|
||||
|
||||
window.enterFullScreenTrigger = () => {
|
||||
document.querySelector('.detach').classList.remove('darwin');
|
||||
};
|
||||
window.leaveFullScreenTrigger = () => {
|
||||
const titleDom = document.querySelector('.detach');
|
||||
if (!titleDom.classList.contains('darwin')) {
|
||||
titleDom.classList.add('darwin');
|
||||
}
|
||||
};
|
||||
|
||||
window.maximizeTrigger = () => {
|
||||
const btnMaximize = document.querySelector('.maximize')
|
||||
if (!btnMaximize || btnMaximize.classList.contains('unmaximize')) return;
|
||||
btnMaximize.classList.add('unmaximize');
|
||||
};
|
||||
|
||||
window.unmaximizeTrigger = () => {
|
||||
const btnMaximize = document.querySelector('.maximize');
|
||||
if (!btnMaximize) return;
|
||||
btnMaximize.classList.remove('unmaximize');
|
||||
};
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
window.onkeydown = (e) => {
|
||||
if (e.code === 'Escape') {
|
||||
ipcRenderer.send('detach:service', { type: 'endFullScreen' });
|
||||
return;
|
||||
}
|
||||
if (e.metaKey && (e.code === 'KeyW' || e.code === 'KeyQ')) {
|
||||
window.handle.close()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
window.onkeydown = (e) => {
|
||||
if (e.ctrlKey && e.code === 'KeyW') {
|
||||
window.handle.close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: system-ui, "PingFang SC", "Helvetica Neue", "Microsoft Yahei", sans-serif;
|
||||
user-select: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.detach {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.detach {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 18px;
|
||||
padding-left: 10px;
|
||||
font-weight: 500;
|
||||
box-sizing: border-box;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.detach.darwin {
|
||||
padding-left: 80px;
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
||||
.detach.win32 {
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
||||
.detach img {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.detach input {
|
||||
background-color: var(--color-body-bg);
|
||||
color: var(--color-text-primary);
|
||||
width: 360px;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
border: none;
|
||||
padding: 0 10px;
|
||||
outline: none;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
.detach input::-webkit-input-placeholder {
|
||||
color: #aaa;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.detach .info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.handle {
|
||||
display: flex;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
.handle > div {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 18px;
|
||||
cursor: pointer;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.handle > div:hover {
|
||||
background-color: #dee2e6;
|
||||
}
|
||||
|
||||
.handle .devtool {
|
||||
background: center no-repeat url("./assets/tool.svg")
|
||||
}
|
||||
|
||||
.handle-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.window-handle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
.window-handle > div {
|
||||
width: 48px;
|
||||
height: 56px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.window-handle > div:hover {
|
||||
background-color: #dee2e6;
|
||||
}
|
||||
|
||||
.window-handle .minimize {
|
||||
background: center / 20px no-repeat url("./assets/minimize.svg");
|
||||
}
|
||||
|
||||
.window-handle .maximize {
|
||||
background: center / 20px no-repeat url("./assets/maximize.svg");
|
||||
}
|
||||
|
||||
.window-handle .unmaximize {
|
||||
background: center / 20px no-repeat url("./assets/unmaximize.svg");
|
||||
}
|
||||
|
||||
.window-handle .close {
|
||||
background: center / 20px no-repeat url("./assets/close.svg");
|
||||
}
|
||||
|
||||
.window-handle .close:hover {
|
||||
background-color: #e53935 !important;
|
||||
background-image: url("./assets/close-hover.svg") !important;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1618205429990" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2034" width="64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M529.066667 524.8l241.066666-241.066667c8.533333-8.533333 8.533333-21.333333 0-29.866666s-21.333333-8.533333-29.866666 0L499.2 494.933333 258.133333 253.866667c-8.533333-8.533333-21.333333-8.533333-29.866666 0s-8.533333 21.333333 0 29.866666L469.333333 524.8 228.266667 765.866667c-8.533333 8.533333-8.533333 21.333333 0 29.866666 4.266667 4.266667 10.666667 6.4 14.933333 6.4s10.666667-2.133333 14.933333-6.4L499.2 554.666667l241.066667 241.066666c4.266667 4.266667 10.666667 6.4 14.933333 6.4s10.666667-2.133333 14.933333-6.4c8.533333-8.533333 8.533333-21.333333 0-29.866666L529.066667 524.8z" fill="#ffffff" p-id="2035"></path></svg>
|
||||
|
After Width: | Height: | Size: 1011 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1618205429990" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2034" width="64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M529.066667 524.8l241.066666-241.066667c8.533333-8.533333 8.533333-21.333333 0-29.866666s-21.333333-8.533333-29.866666 0L499.2 494.933333 258.133333 253.866667c-8.533333-8.533333-21.333333-8.533333-29.866666 0s-8.533333 21.333333 0 29.866666L469.333333 524.8 228.266667 765.866667c-8.533333 8.533333-8.533333 21.333333 0 29.866666 4.266667 4.266667 10.666667 6.4 14.933333 6.4s10.666667-2.133333 14.933333-6.4L499.2 554.666667l241.066667 241.066666c4.266667 4.266667 10.666667 6.4 14.933333 6.4s10.666667-2.133333 14.933333-6.4c8.533333-8.533333 8.533333-21.333333 0-29.866666L529.066667 524.8z" fill="#888888" p-id="2035"></path></svg>
|
||||
|
After Width: | Height: | Size: 1011 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1576121932768" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2610" width="32" height="32" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M344.792 518.575L303.4 477.184a26.947 26.947 0 0 1 38.13-38.13l60.174 60.173a26.947 26.947 0 0 1 0.27 37.834L114.392 833.16a26.947 26.947 0 0 0 0.27 37.834l68.984 68.958a26.947 26.947 0 0 0 38.077 0l291.301-291.3a26.947 26.947 0 0 1 38.104 0l146.324 146.323a26.947 26.947 0 1 1-38.104 38.13L532.076 705.833 259.853 978.055a80.842 80.842 0 0 1-114.337 0L76.53 909.096a80.842 80.842 0 0 1-0.809-113.475l269.043-277.046z m473.546 155.54a26.947 26.947 0 1 1-38.104 38.104L597.288 529.273a26.947 26.947 0 0 1 0-38.103l148.13-148.103a26.947 26.947 0 0 1 15.36-7.653l88.603-12.18 89.627-170.927-56.697-60.39-167.37 97.254-16.546 85.53a26.947 26.947 0 0 1-7.384 13.96l-148.13 148.102a26.947 26.947 0 0 1-38.103 0l-77.474-77.474a26.947 26.947 0 1 1 38.104-38.103l58.422 58.422 123.23-123.23 17.273-89.466a26.947 26.947 0 0 1 12.935-18.19l196.5-114.175a26.947 26.947 0 0 1 33.173 4.85l84.48 90.004a26.947 26.947 0 0 1 4.203 30.963l-104.96 200.165a26.947 26.947 0 0 1-20.21 14.201l-93.346 12.854-122.637 122.637 163.867 163.894z" p-id="2611" fill="#888888"></path><path d="M610.816 784.573a26.947 26.947 0 0 1 38.104-38.104l52.089 52.09a26.947 26.947 0 0 1-38.104 38.103l-52.089-52.09zM368.371 543.42a26.947 26.947 0 1 1 37.995-38.185L705.671 803.22a26.947 26.947 0 0 1 7.814 21.45 111.373 111.373 0 0 0 31.475 87.471 107.79 107.79 0 1 0 68.662-183.727c-2.129 0.135-3.934 0.081-5.578-0.054a26.947 26.947 0 0 1-19.537-7.868L485.24 417.954a26.947 26.947 0 1 1 38.05-38.158l295.181 294.481A161.684 161.684 0 1 1 706.83 950.272a165.16 165.16 0 0 1-47.642-117.275L368.37 543.421z" p-id="2612" fill="#888888"></path><path d="M783.076 874.036a53.895 53.895 0 1 0 76.22-76.219 53.895 53.895 0 1 0-76.22 76.219zM421.807 588.989a26.947 26.947 0 0 1 38.104 38.13L221.723 865.28a26.947 26.947 0 1 1-38.104-38.104L421.807 588.99z m81.597-229.808a26.947 26.947 0 1 1-38.104 38.104l-37.996-37.996a26.947 26.947 0 0 1-5.847-29.345c0.808-1.914 1.05-2.426 3.368-7.06l0.189-0.432c0.754-1.509 1.24-2.506 1.159-2.263a188.632 188.632 0 0 0-43.601-198.818 187.877 187.877 0 0 0-129.698-55.215 189.736 189.736 0 0 0-73.135 13.15l-2.506 0.97-1.752 0.728a26.947 26.947 0 0 1-21.073-49.61c1.887-0.809 1.887-0.809 3.423-1.402l2.102-0.808a242.068 242.068 0 0 1 93.992-16.896 241.772 241.772 0 0 1 166.723 70.98 242.526 242.526 0 0 1 57.722 250.88l25.007 25.033zM25.869 160.013a26.947 26.947 0 0 1 49.61 21.02 187.284 187.284 0 0 0-14.74 65.374 188.039 188.039 0 0 0 55.054 141.743 188.632 188.632 0 0 0 44.463 33.037 26.947 26.947 0 1 1-25.411 47.536 242.526 242.526 0 0 1-57.129-42.47A241.907 241.907 0 0 1 6.9 244.035a243.443 243.443 0 0 1 18.97-84.022z m224.337 337.274a26.947 26.947 0 0 1-0.215-53.895 189.17 189.17 0 0 0 61.79-10.644c4.366-1.51 7.168-2.21 10.94-1.563a26.947 26.947 0 0 1 18.81 7.895l33.145 33.146a26.947 26.947 0 0 1-38.103 38.13l-21.99-22.016a243.308 243.308 0 0 1-64.377 8.947z" p-id="2613" fill="#888888"></path><path d="M148.48 77.824a26.947 26.947 0 1 1 38.104-38.104l161.792 161.82a26.947 26.947 0 0 1 7.087 25.6l-22.986 91.35a26.947 26.947 0 0 1-19.564 19.565L221.56 361.04a26.947 26.947 0 0 1-25.6-7.06L30.343 188.362a26.947 26.947 0 1 1 38.13-38.103L223.26 305.044l60.901-15.306 15.306-60.9L148.48 77.823z" p-id="2614" fill="#888888"></path></svg>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg width="20" height="20" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="48" height="48" fill="white" fill-opacity="0.01"/><path d="M24 44C29.5228 44 34.5228 41.7614 38.1421 38.1421C41.7614 34.5228 44 29.5228 44 24C44 18.4772 41.7614 13.4772 38.1421 9.85786C34.5228 6.23858 29.5228 4 24 4C18.4772 4 13.4772 6.23858 9.85786 9.85786C6.23858 13.4772 4 18.4772 4 24C4 29.5228 6.23858 34.5228 9.85786 38.1421C13.4772 41.7614 18.4772 44 24 44Z" fill="none" stroke="#888" stroke-width="3" stroke-linejoin="round"/><path fill-rule="evenodd" clip-rule="evenodd" d="M24 11C25.3807 11 26.5 12.1193 26.5 13.5C26.5 14.8807 25.3807 16 24 16C22.6193 16 21.5 14.8807 21.5 13.5C21.5 12.1193 22.6193 11 24 11Z" fill="#888"/><path d="M24.5 34V20H23.5H22.5" stroke="#888" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><path d="M21 34H28" stroke="#888" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
|
After Width: | Height: | Size: 995 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1618205323520" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1376" width="64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M832 832H192V192h640v640z m-597.333333-42.666667h554.666666V234.666667H234.666667v554.666666z" fill="#888888" p-id="1377"></path></svg>
|
||||
|
After Width: | Height: | Size: 510 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1618205449254" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2381" width="64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M768 533.333333H256c-12.8 0-21.333333-8.533333-21.333333-21.333333s8.533333-21.333333 21.333333-21.333333h512c12.8 0 21.333333 8.533333 21.333333 21.333333s-8.533333 21.333333-21.333333 21.333333z" fill="#888888" p-id="2382"></path></svg>
|
||||
|
After Width: | Height: | Size: 613 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg width="20" height="20" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#icon-3d122e4c72881c3e)"><path d="M10.6963 17.5042C13.3347 14.8657 16.4701 14.9387 19.8781 16.8076L32.62 9.74509L31.8989 4.78683L43.2126 16.1005L38.2656 15.3907L31.1918 28.1214C32.9752 31.7589 33.1337 34.6647 30.4953 37.3032C30.4953 37.3032 26.235 33.0429 22.7171 29.525L6.44305 41.5564L18.4382 25.2461C14.9202 21.7281 10.6963 17.5042 10.6963 17.5042Z" fill="#888" stroke="#888" stroke-width="3" stroke-linejoin="round"/></g><defs><clipPath id="icon-3d122e4c72881c3e"><rect width="48" height="48" fill="#FFF"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 685 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg width="20" height="20" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="48" height="48" fill="white" fill-opacity="0.01"/><path d="M34.0003 41L44 24L34.0003 7H14.0002L4 24L14.0002 41H34.0003Z" fill="none" stroke="#888" stroke-width="3" stroke-linejoin="round"/><path d="M24 29C26.7614 29 29 26.7614 29 24C29 21.2386 26.7614 19 24 19C21.2386 19 19 21.2386 19 24C19 26.7614 21.2386 29 24 29Z" fill="none" stroke="#888" stroke-width="3" stroke-linejoin="round"/></svg>
|
||||
|
After Width: | Height: | Size: 539 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg width="20" height="20" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="48" height="48" fill="white" fill-opacity="0.01"/><path d="M44 16C44 22.6274 38.6274 28 32 28C29.9733 28 28.0639 27.4975 26.3896 26.6104L9 44L4 39L21.3896 21.6104C20.5025 19.9361 20 18.0267 20 16C20 9.37258 25.3726 4 32 4C34.0267 4 35.9361 4.50245 37.6104 5.38959L30 13L35 18L42.6104 10.3896C43.4975 12.0639 44 13.9733 44 16Z" fill="none" stroke="#888" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
|
After Width: | Height: | Size: 570 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1618205464128" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2718" width="64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M855.466667 168.533333h-554.666667V277.333333H192v554.666667h554.666667v-108.8h108.8v-554.666667zM704 789.333333H234.666667V320h469.333333v469.333333z m108.8-108.8H746.666667V277.333333H343.466667V211.2h469.333333v469.333333z" fill="#888888" p-id="2719"></path></svg>
|
||||
|
After Width: | Height: | Size: 642 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg width="20" height="20" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#icon-7599d19572881c3e)"><path d="M10.6963 17.5042C13.3347 14.8657 16.4701 14.9387 19.8781 16.8076L32.62 9.74509L31.8989 4.78683L43.2126 16.1005L38.2656 15.3907L31.1918 28.1214C32.9752 31.7589 33.1337 34.6647 30.4953 37.3032C30.4953 37.3032 26.235 33.0429 22.7171 29.525L6.44305 41.5564L18.4382 25.2461C14.9202 21.7281 10.6963 17.5042 10.6963 17.5042Z" fill="none" stroke="#888" stroke-width="3" stroke-linejoin="round"/></g><defs><clipPath id="icon-7599d19572881c3e"><rect width="48" height="48" fill="#888"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 685 B |
@@ -0,0 +1,25 @@
|
||||
:root {
|
||||
--color-text-primary: rgba(0, 0, 0, 0.85);
|
||||
--color-text-content: #141414;
|
||||
--color-text-desc: rgba(0, 0, 0, 0.45);
|
||||
// 背景色
|
||||
--color-body-bg: #fff;
|
||||
--color-menu-bg: #f3efef;
|
||||
--color-list-hover: #e2e2e2;
|
||||
--color-input-hover: #fff;
|
||||
// 边框
|
||||
--color-border-light: #f0f0f0;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--color-text-primary: #e8e8f0;
|
||||
--color-text-content: #ccccd8;
|
||||
--color-text-desc: #8f8fa6;
|
||||
// 背景色
|
||||
--color-body-bg: #1c1c28;
|
||||
--color-menu-bg: #1c1c28;
|
||||
--color-list-hover: #33333d;
|
||||
--color-input-hover: #33333d;
|
||||
// 边框
|
||||
--color-border-light: #33333d;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg width="20" height="20" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="48" height="48" fill="white" fill-opacity="0.01"/><path d="M21 38C30.3888 38 38 30.3888 38 21C38 11.6112 30.3888 4 21 4C11.6112 4 4 11.6112 4 21C4 30.3888 11.6112 38 21 38Z" fill="none" stroke="#888" stroke-width="3" stroke-linejoin="round"/><path d="M21 15L21 27" stroke="#888" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><path d="M15 21L27 21" stroke="#888" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><path d="M33.2218 33.2218L41.7071 41.7071" stroke="#888" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
|
After Width: | Height: | Size: 720 B |
@@ -0,0 +1,5 @@
|
||||
import { createApp } from 'vue';
|
||||
import App from './App.vue';
|
||||
import './assets/var.less';
|
||||
|
||||
createApp(App).mount('#app');
|
||||
@@ -0,0 +1,6 @@
|
||||
/* eslint-disable */
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"importHelpers": true,
|
||||
"moduleResolution": "node",
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"types": [
|
||||
"webpack-env"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue",
|
||||
"tests/**/*.ts",
|
||||
"tests/**/*.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
css: {
|
||||
// 配置css模块
|
||||
loaderOptions: {
|
||||
// 向预处理器 Loader 传递配置选项
|
||||
less: {
|
||||
// 配置less(其他样式解析用法一致)
|
||||
javascriptEnabled: true, // 设置为true
|
||||
},
|
||||
},
|
||||
},
|
||||
productionSourceMap: false,
|
||||
outputDir: path.join(__dirname, '../public/detach'),
|
||||
publicPath: process.env.NODE_ENV === 'production' ? '' : '/',
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
VUE_APP_API_BASE=http://localhost:7001/
|
||||
@@ -0,0 +1 @@
|
||||
VUE_APP_API_BASE=https://rubick.vip/api/
|
||||
@@ -0,0 +1,23 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@@ -0,0 +1,24 @@
|
||||
# feature
|
||||
|
||||
## Project setup
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||
@@ -0,0 +1,22 @@
|
||||
module.exports = {
|
||||
presets: ['@vue/cli-plugin-babel/preset'],
|
||||
plugins: [
|
||||
[
|
||||
'import',
|
||||
{
|
||||
libraryName: 'ant-design-vue',
|
||||
style: 'css', // or 'css'
|
||||
},
|
||||
'ant-design-vue',
|
||||
],
|
||||
[
|
||||
'import',
|
||||
{
|
||||
libraryName: '@ant-design/icons-vue',
|
||||
libraryDirectory: 'lib/icons',
|
||||
camel2DashComponentName: false,
|
||||
},
|
||||
'@ant-design/icons-vue',
|
||||
],
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"name": "feature",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve --port 8081",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons-vue": "^6.0.1",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"ant-design-vue": "3.2.14",
|
||||
"axios": "^0.24.0",
|
||||
"babel-plugin-import": "^1.13.8",
|
||||
"core-js": "^3.6.5",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"markdown-it": "^12.2.0",
|
||||
"nanoid": "^4.0.2",
|
||||
"vue": "3.2.45",
|
||||
"vue-i18n": "9.2.2",
|
||||
"vue-router": "^4.0.0-0",
|
||||
"vue3-carousel": "^0.3.1",
|
||||
"vue3-lottie": "^3.1.0",
|
||||
"vuex": "^4.0.0-0",
|
||||
"webpack-bundle-analyzer": "^4.9.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^4.18.0",
|
||||
"@typescript-eslint/parser": "^4.18.0",
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-plugin-router": "~4.5.0",
|
||||
"@vue/cli-plugin-typescript": "~4.5.0",
|
||||
"@vue/cli-plugin-vuex": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/compiler-sfc": "^3.0.0",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"@vue/eslint-config-typescript": "^7.0.0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"eslint-plugin-vue": "^7.0.0",
|
||||
"less": "^4.1.3",
|
||||
"less-loader": "^6.2.0",
|
||||
"prettier": "^2.2.1",
|
||||
"typescript": "~4.1.5"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/vue3-essential",
|
||||
"eslint:recommended",
|
||||
"@vue/typescript/recommended",
|
||||
"@vue/prettier",
|
||||
"@vue/prettier/@typescript-eslint"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020
|
||||
},
|
||||
"rules": {}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
],
|
||||
"volta": {
|
||||
"node": "16.20.2"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "rubick-system-feature",
|
||||
"pluginName": "系统菜单",
|
||||
"description": "系统菜单",
|
||||
"main": "index.html",
|
||||
"logo": "https://pic1.zhimg.com/80/v2-29152fe716010751db835adf591421f8_720w.png",
|
||||
"version": "0.0.0",
|
||||
"preload":"preload.js",
|
||||
"pluginType": "ui",
|
||||
"features": [
|
||||
{
|
||||
"code": "finder",
|
||||
"explain": "插件市场",
|
||||
"cmds":[
|
||||
"插件市场"
|
||||
]
|
||||
},{
|
||||
"code": "installed",
|
||||
"explain": "已安装插件",
|
||||
"cmds":[
|
||||
"已安装插件", "installed"
|
||||
]
|
||||
},{
|
||||
"code": "settings",
|
||||
"explain": "偏好设置",
|
||||
"cmds":[
|
||||
"偏好设置"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
const remote = require('@electron/remote');
|
||||
const { ipcRenderer } = require('electron');
|
||||
|
||||
const ipcSendSync = (type, data) => {
|
||||
const returnValue = ipcRenderer.sendSync('msg-trigger', {
|
||||
type,
|
||||
data,
|
||||
});
|
||||
if (returnValue instanceof Error) throw returnValue;
|
||||
return returnValue;
|
||||
};
|
||||
|
||||
const ipcSend = (type, data) => {
|
||||
ipcRenderer.send('msg-trigger', {
|
||||
type,
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
window.market = {
|
||||
getLocalPlugins() {
|
||||
return remote.getGlobal('LOCAL_PLUGINS').getLocalPlugins();
|
||||
},
|
||||
downloadPlugin(plugin) {
|
||||
return remote.getGlobal('LOCAL_PLUGINS').downloadPlugin(plugin);
|
||||
},
|
||||
deletePlugin(plugin) {
|
||||
return remote.getGlobal('LOCAL_PLUGINS').deletePlugin(plugin);
|
||||
},
|
||||
refreshPlugin(plugin) {
|
||||
return remote.getGlobal('LOCAL_PLUGINS').refreshPlugin(plugin);
|
||||
},
|
||||
addLocalStartPlugin(plugin) {
|
||||
ipcSend('addLocalStartPlugin', { plugin });
|
||||
},
|
||||
removeLocalStartPlugin(plugin) {
|
||||
ipcSend('removeLocalStartPlugin', { plugin });
|
||||
},
|
||||
dbDump(target) {
|
||||
ipcSend('dbDump', { target });
|
||||
},
|
||||
|
||||
dbImport(target) {
|
||||
ipcSend('dbImport', { target });
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,264 @@
|
||||
<template>
|
||||
<div class="main-container">
|
||||
<div class="left-menu">
|
||||
<a-menu
|
||||
@select="({ key }) => changeMenu(key)"
|
||||
:selectedKeys="active"
|
||||
mode="vertical"
|
||||
>
|
||||
<a-menu-item key="finder">
|
||||
<template #icon>
|
||||
<StarOutlined style="font-size: 16px" />
|
||||
</template>
|
||||
{{ $t('feature.market.explore') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="worker">
|
||||
<template #icon>
|
||||
<SendOutlined style="transform: rotate(-45deg); font-size: 16px" />
|
||||
</template>
|
||||
{{ $t('feature.market.efficiency') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="tools">
|
||||
<template #icon>
|
||||
<SearchOutlined style="font-size: 16px" />
|
||||
</template>
|
||||
{{ $t('feature.market.searchTool') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="image">
|
||||
<template #icon>
|
||||
<FileImageOutlined style="font-size: 16px" />
|
||||
</template>
|
||||
{{ $t('feature.market.imageTool') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="devPlugin">
|
||||
<template #icon>
|
||||
<CodeOutlined style="font-size: 16px" />
|
||||
</template>
|
||||
{{ $t('feature.market.developTool') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="system">
|
||||
<template #icon>
|
||||
<DatabaseOutlined style="font-size: 16px" />
|
||||
</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">
|
||||
<template #icon>
|
||||
<img :src="perf.custom.logo" />
|
||||
</template>
|
||||
</a-avatar>
|
||||
</template>
|
||||
<template #title>{{ perf.custom.username }}</template>
|
||||
<a-menu-item key="settings">
|
||||
<template #icon>
|
||||
<SettingOutlined />
|
||||
</template>
|
||||
{{ $t('feature.settings.title') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="installed">
|
||||
<template #icon>
|
||||
<HeartOutlined />
|
||||
</template>
|
||||
{{ $t('feature.installed.title') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="dev">
|
||||
<template #icon>
|
||||
<BugOutlined />
|
||||
</template>
|
||||
{{ $t('feature.dev.title') }}
|
||||
</a-menu-item>
|
||||
</a-sub-menu>
|
||||
</a-menu>
|
||||
</div>
|
||||
<div
|
||||
:class="
|
||||
[
|
||||
'finder',
|
||||
'result',
|
||||
'devPlugin',
|
||||
'image',
|
||||
'tools',
|
||||
'worker',
|
||||
'system',
|
||||
'localPlugin',
|
||||
].includes(active[0])
|
||||
? 'container'
|
||||
: 'more'
|
||||
"
|
||||
>
|
||||
<keep-alive>
|
||||
<router-view />
|
||||
</keep-alive>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import {
|
||||
StarOutlined,
|
||||
SendOutlined,
|
||||
SearchOutlined,
|
||||
FileImageOutlined,
|
||||
DatabaseOutlined,
|
||||
CodeOutlined,
|
||||
SettingOutlined,
|
||||
HeartOutlined,
|
||||
BugOutlined,
|
||||
ApiOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
import { useStore } from 'vuex';
|
||||
import localConfig from '@/confOp';
|
||||
|
||||
const store = useStore();
|
||||
const router = useRouter();
|
||||
const active = computed(() => store.state.active);
|
||||
const { perf } = localConfig.getConfig();
|
||||
|
||||
const changeMenu = (key: any) => {
|
||||
store.commit('commonUpdate', { active: [key] });
|
||||
router.push(key);
|
||||
};
|
||||
|
||||
window.rubick.onPluginEnter(({ code }: { code: string }) => {
|
||||
code = code === '已安装插件' ? 'installed' : code;
|
||||
changeMenu(code);
|
||||
store.commit('commonUpdate', { active: [code] });
|
||||
});
|
||||
|
||||
window.rubick.setSubInput((e: any) => {
|
||||
if (
|
||||
[
|
||||
'finder',
|
||||
'result',
|
||||
'devPlugin',
|
||||
'image',
|
||||
'tools',
|
||||
'worker',
|
||||
'system',
|
||||
].includes(active.value[0])
|
||||
) {
|
||||
if (e.text) {
|
||||
store.commit('setSearchValue', e.text);
|
||||
router.push('result');
|
||||
} else {
|
||||
store.commit('commonUpdate', { active: ['finder'] });
|
||||
router.push('finder');
|
||||
}
|
||||
}
|
||||
}, '搜索插件');
|
||||
|
||||
const init = () => store.dispatch('init');
|
||||
init();
|
||||
</script>
|
||||
<style lang="less">
|
||||
.ant-menu-submenu-popup {
|
||||
.ant-menu {
|
||||
background: var(--color-body-bg2) !important;
|
||||
height: 100%;
|
||||
border-right: none;
|
||||
.ant-menu-item,
|
||||
.ant-menu-submenu,
|
||||
.ant-menu-submenu-arrow {
|
||||
color: var(--color-text-content);
|
||||
&:active {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
.ant-menu-item-selected,
|
||||
.ant-menu-submenu-selected {
|
||||
background-color: var(--color-list-hover);
|
||||
color: var(--ant-primary-color);
|
||||
.ant-menu-submenu-arrow {
|
||||
color: var(--ant-primary-color);
|
||||
}
|
||||
&:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="less" scoped>
|
||||
@import '~@/assets/common.less';
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.main-container {
|
||||
-webkit-app-region: no-drag;
|
||||
display: flex;
|
||||
background: var(--color-body-bg);
|
||||
border-top: 1px solid var(--color-border-light);
|
||||
height: 100vh;
|
||||
box-sizing: border-box;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
background: var(--color-menu-bg);
|
||||
.search {
|
||||
:deep(.ant-btn),
|
||||
:deep(.ant-input),
|
||||
:deep(.ant-input-group-addon) {
|
||||
color: var(--ant-primary-color) !important;
|
||||
background: var(--color-input-hover);
|
||||
border-color: var(--color-border-light);
|
||||
}
|
||||
}
|
||||
.container,
|
||||
.more {
|
||||
background: var(--color-body-bg);
|
||||
width: calc(~'100% - 183px');
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
}
|
||||
.more {
|
||||
background: var(--color-body-bg2);
|
||||
}
|
||||
.left-menu {
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
height: 100vh;
|
||||
:deep(.ant-menu) {
|
||||
width: 100%;
|
||||
}
|
||||
:deep(.ant-menu-item) {
|
||||
padding-left: 12px !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
:deep(.ant-menu-item-selected),
|
||||
:deep(.ant-menu-submenu-selected) {
|
||||
background-color: var(--color-list-hover);
|
||||
border-radius: 6px;
|
||||
color: var(--ant-primary-color);
|
||||
}
|
||||
:deep(.user-info) {
|
||||
position: absolute;
|
||||
bottom: 16px;
|
||||
width: calc(100% - 32px);
|
||||
.ant-menu-submenu-title {
|
||||
padding: 0 32px 0 8px;
|
||||
.ant-menu-title-content {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.ant-avatar) {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,31 @@
|
||||
:root {
|
||||
--color-text-primary: rgba(0, 0, 0, 0.85);
|
||||
--color-text-content: #141414;
|
||||
--color-text-desc: rgba(0, 0, 0, 0.45);
|
||||
// 背景色
|
||||
--color-body-bg2: #eee;
|
||||
--color-body-bg: #fff;
|
||||
--color-menu-bg: rgba(248, 248, 248, 1);
|
||||
--color-list-hover: rgba(233, 233, 233, 1);
|
||||
--color-input-hover: #fff;
|
||||
// 边框
|
||||
--color-border-light: #f0f0f0;
|
||||
// 输入框附带icon
|
||||
--color-action-color: rgba(0, 0, 0, 0.25);;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--color-text-primary: #e8e8f0;
|
||||
--color-text-content: #ccccd8;
|
||||
--color-text-desc: #8f8fa6;
|
||||
// 背景色
|
||||
--color-body-bg: #1c1c28;
|
||||
--color-body-bg2: #2c2f3b;
|
||||
--color-menu-bg: #1c1c28;
|
||||
--color-list-hover: #2c2f3b;
|
||||
--color-input-hover: #444d;
|
||||
// 边框
|
||||
--color-border-light: #444d;
|
||||
// 输入框附带icon
|
||||
--color-action-color: #ffffff4d;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
.left-menu {
|
||||
width: 183px;
|
||||
// height: 100vh;
|
||||
border-right: 1px solid var(--color-border-light);
|
||||
.search-container {
|
||||
padding: 10px;
|
||||
}
|
||||
.ant-input-affix-wrapper {
|
||||
border: none;
|
||||
background: var(--color-input-hover);
|
||||
:deep(input) {
|
||||
background: none;
|
||||
color: var(--color-text-desc);
|
||||
}
|
||||
:deep(.anticon) {
|
||||
color: var(--color-text-desc);
|
||||
}
|
||||
}
|
||||
:deep(.ant-menu) {
|
||||
background: var(--color-menu-bg);
|
||||
height: 100%;
|
||||
border-right: none;
|
||||
.ant-menu-item, .ant-menu-submenu, .ant-menu-submenu-arrow {
|
||||
color: var(--color-text-content);
|
||||
&:active {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
.ant-menu-item-selected, .ant-menu-submenu-selected {
|
||||
background-color: var(--color-list-hover);
|
||||
color: var(--ant-primary-color);
|
||||
.ant-menu-submenu-arrow {
|
||||
color: var(--ant-primary-color);
|
||||
}
|
||||
&:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
export const SPRING = {
|
||||
theme: 'SPRING',
|
||||
primaryColor: '#ff4ea4',
|
||||
errorColor: '#ed6d46',
|
||||
warningColor: '#e5a84b',
|
||||
successColor: '#c0d695',
|
||||
infoColor: '#aa8eeB',
|
||||
};
|
||||
|
||||
export const SUMMER = {
|
||||
theme: 'SUMMER',
|
||||
primaryColor: '#6078ea',
|
||||
errorColor: '#ed6d46',
|
||||
warningColor: '#e5a84b',
|
||||
successColor: '#c0d695',
|
||||
infoColor: '#aa8eeB',
|
||||
};
|
||||
|
||||
export const AUTUMN = {
|
||||
theme: 'AUTUMN',
|
||||
primaryColor: '#f55555',
|
||||
errorColor: '#ed6d46',
|
||||
warningColor: '#e5a84b',
|
||||
successColor: '#c0d695',
|
||||
infoColor: '#aa8eeB',
|
||||
};
|
||||
|
||||
export const WINTER = {
|
||||
theme: 'WINTER',
|
||||
primaryColor: '#00b294',
|
||||
errorColor: '#e94829',
|
||||
warningColor: '#ed6d3d',
|
||||
successColor: '#c3d94e',
|
||||
infoColor: '#bfa782',
|
||||
};
|
||||
|
After Width: | Height: | Size: 845 B |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,100 @@
|
||||
import axios from 'axios';
|
||||
|
||||
let baseURL = 'https://gitee.com/monkeyWang/rubickdatabase/raw/master';
|
||||
let access_token = '';
|
||||
|
||||
try {
|
||||
const dbdata = window.rubick.db.get('rubick-localhost-config');
|
||||
baseURL = dbdata.data.database;
|
||||
access_token = dbdata.data.access_token;
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
const instance = axios.create({
|
||||
timeout: 4000,
|
||||
baseURL:
|
||||
baseURL || 'https://gitee.com/monkeyWang/rubickdatabase/raw/master',
|
||||
});
|
||||
|
||||
export default {
|
||||
async getTotalPlugins() {
|
||||
let targetPath = 'plugins/total-plugins.json';
|
||||
if (access_token) {
|
||||
targetPath = `${encodeURIComponent(
|
||||
targetPath
|
||||
)}?access_token=${access_token}&ref=master`;
|
||||
}
|
||||
const res = await instance.get(targetPath);
|
||||
console.log('total plugsin', res);
|
||||
return res.data;
|
||||
},
|
||||
|
||||
async getFinderDetail() {
|
||||
let targetPath = 'plugins/finder.json';
|
||||
if (access_token) {
|
||||
targetPath = `${encodeURIComponent(
|
||||
targetPath
|
||||
)}?access_token=${access_token}&ref=master`;
|
||||
}
|
||||
const res = await instance.get(targetPath);
|
||||
return res.data;
|
||||
},
|
||||
|
||||
async getSystemDetail() {
|
||||
let targetPath = 'plugins/system.json';
|
||||
if (access_token) {
|
||||
targetPath = `${encodeURIComponent(
|
||||
targetPath
|
||||
)}?access_token=${access_token}&ref=master`;
|
||||
}
|
||||
const res = await instance.get(targetPath);
|
||||
return res.data;
|
||||
},
|
||||
async getWorkerDetail() {
|
||||
let targetPath = 'plugins/worker.json';
|
||||
if (access_token) {
|
||||
targetPath = `${encodeURIComponent(
|
||||
targetPath
|
||||
)}?access_token=${access_token}&ref=master`;
|
||||
}
|
||||
const res = await instance.get(targetPath);
|
||||
return res.data;
|
||||
},
|
||||
|
||||
async getPluginDetail(url: string) {
|
||||
const res = await instance.get(url);
|
||||
return res.data;
|
||||
},
|
||||
|
||||
async getSearchDetail() {
|
||||
let targetPath = 'plugins/search.json';
|
||||
if (access_token) {
|
||||
targetPath = `${encodeURIComponent(
|
||||
targetPath
|
||||
)}?access_token=${access_token}&ref=master`;
|
||||
}
|
||||
const res = await instance.get(targetPath);
|
||||
return res.data;
|
||||
},
|
||||
async getDevDetail() {
|
||||
let targetPath = 'plugins/dev.json';
|
||||
if (access_token) {
|
||||
targetPath = `${encodeURIComponent(
|
||||
targetPath
|
||||
)}?access_token=${access_token}&ref=master`;
|
||||
}
|
||||
const res = await instance.get(targetPath);
|
||||
return res.data;
|
||||
},
|
||||
async getImageDetail() {
|
||||
let targetPath = 'plugins/image.json';
|
||||
if (access_token) {
|
||||
targetPath = `${encodeURIComponent(
|
||||
targetPath
|
||||
)}?access_token=${access_token}&ref=master`;
|
||||
}
|
||||
const res = await instance.get(targetPath);
|
||||
return res.data;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import axios from 'axios';
|
||||
|
||||
const instance = axios.create({
|
||||
baseURL: process.env.VUE_APP_API_BASE,
|
||||
});
|
||||
|
||||
export default {
|
||||
async getScanCode({ scene }: { scene: string }) {
|
||||
const res = await instance.get('/users/getScanCode', {
|
||||
params: {
|
||||
scene,
|
||||
},
|
||||
});
|
||||
return res.data;
|
||||
},
|
||||
|
||||
async checkLoginStatus({ scene }: { scene: string }) {
|
||||
const res = await instance.post('/users/checkLoginStatus', {
|
||||
scene,
|
||||
});
|
||||
return res.data;
|
||||
},
|
||||
|
||||
async getUserInfo({ openId }: { openId: string }) {
|
||||
const res = await instance.post('/users/getUserInfo', {
|
||||
openId,
|
||||
});
|
||||
return res.data;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
const LOCAL_CONFIG_KEY = 'rubick-local-config';
|
||||
|
||||
const localConfig = {
|
||||
getConfig(): Promise<any> {
|
||||
const data: any = window.rubick.db.get(LOCAL_CONFIG_KEY) || {};
|
||||
return data.data;
|
||||
},
|
||||
|
||||
setConfig(data: any) {
|
||||
const localConfig: any = window.rubick.db.get(LOCAL_CONFIG_KEY) || {};
|
||||
window.rubick.db.put({
|
||||
_id: LOCAL_CONFIG_KEY,
|
||||
_rev: localConfig._rev,
|
||||
data: {
|
||||
...localConfig.data,
|
||||
...data,
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default localConfig;
|
||||
@@ -0,0 +1,16 @@
|
||||
import { createI18n } from 'vue-i18n';
|
||||
import messages from './langs';
|
||||
import localConfig from '@/confOp';
|
||||
const { perf }: any = localConfig.getConfig();
|
||||
|
||||
// 2. Create i18n instance with options
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: perf.common.lang || 'zh-CN', // set locale
|
||||
fallbackLocale: 'zh-CN', // set fallback locale
|
||||
messages, // set locale messages
|
||||
// If you need to specify other options, you can set other options
|
||||
// ...
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
@@ -0,0 +1,142 @@
|
||||
export default {
|
||||
'en-US': {
|
||||
feature: {
|
||||
market: {
|
||||
title: 'Market',
|
||||
search: 'Search Plugins',
|
||||
searchResult: 'Search Results',
|
||||
explore: 'Explore',
|
||||
efficiency: 'Efficiency',
|
||||
searchTool: 'Search Tools',
|
||||
imageTool: 'Image Tools',
|
||||
developTool: 'Develop Tools',
|
||||
systemTool: 'System Tools',
|
||||
localPlugin: 'Custom Plugins',
|
||||
finder: {
|
||||
must: 'Necessary',
|
||||
recommended: 'Recommended',
|
||||
lastUpdated: 'Total',
|
||||
},
|
||||
install: 'Install',
|
||||
},
|
||||
installed: {
|
||||
title: 'Installed',
|
||||
tips1: 'There are no plug-ins.',
|
||||
tips2: 'Go to the plugin market and choose the plugin to install!',
|
||||
developer: 'Developer',
|
||||
unknown: 'Unknown',
|
||||
remove: 'Remove',
|
||||
functionKey: 'Function Key',
|
||||
detailInfo: 'Detail Info',
|
||||
addToPanel: 'Click the + sign to pin the keyword to the super panel',
|
||||
removeFromPanel:
|
||||
'Click the - sign to remove the keyword from the super panel',
|
||||
},
|
||||
settings: {
|
||||
title: 'Account And Setting',
|
||||
account: {
|
||||
accountInfo: 'Account Info',
|
||||
tips1: 'rubick',
|
||||
tips2:
|
||||
'After the software preferences are set, please restart the software. Please go to the mini program set avatar and nickname.',
|
||||
themeColor: 'Theme Setting',
|
||||
spring: 'Spring',
|
||||
summer: 'Summer',
|
||||
autumn: 'Autumn',
|
||||
winter: 'Winter',
|
||||
personalized: 'Personalized',
|
||||
greeting: 'Search Box Greeting',
|
||||
logo: 'Avatar',
|
||||
replace: 'Repalce Logo',
|
||||
reset: 'Reset Default',
|
||||
name: 'User Name',
|
||||
},
|
||||
basic: {
|
||||
title: 'Basic',
|
||||
shortcutKey: 'Shortcut Key',
|
||||
showOrHiddle: 'Show Or Hiddle',
|
||||
screenCapture: 'Screen Capture',
|
||||
common: 'Common',
|
||||
autoPaste: 'Auto Paste',
|
||||
autoBoot: 'Auto Boot Up',
|
||||
spaceExec: 'Space Execution',
|
||||
on: 'on',
|
||||
off: 'off',
|
||||
theme: 'Theme',
|
||||
darkMode: 'Dark Mode',
|
||||
language: 'Language',
|
||||
changeLang: 'Change Language',
|
||||
cn: '简体中文',
|
||||
en: 'English',
|
||||
history: 'keywords search history',
|
||||
},
|
||||
global: {
|
||||
title: 'Global Shortcut Key',
|
||||
instructions: 'Instructions and examples',
|
||||
tips: 'Press the shortcut key, automatically search for the corresponding keyword, when the keyword result is exactly matched, and the result is unique, it will point directly to this function.',
|
||||
example: 'Example',
|
||||
example1: 'Shortcut key 「Alt + W」 keyword 「Wechat」',
|
||||
tips1: 'Press 「Alt + W」 to open the local Wechat app directly',
|
||||
example2: 'Shortcut key 「Ctrl + Alt + A」 keyword 「screenshot」',
|
||||
tips2: 'Press 「Ctrl + Alt + A」 to take a screenshot',
|
||||
shortcutKey: 'Shortcut Key',
|
||||
funtionKey: 'Funtion Key',
|
||||
addShortcutKey: 'ADD Global Shortcut Key',
|
||||
addShortcutKeyTips:
|
||||
'Press the function keys (Ctrl, Shift, {optionKeyName}) first, and then press other normal keys. Or press the F1-F12 button.',
|
||||
},
|
||||
superPanel: {
|
||||
title: 'Super Panel',
|
||||
tips: 'Please select the common plug-ins that need to be added to the super panel.',
|
||||
add: 'Add',
|
||||
remove: 'Revome',
|
||||
},
|
||||
intranet: {
|
||||
title: 'Intranet Deployment',
|
||||
tips: "If publishing plug-ins to the public network npm does not meet your company's security requirements, rubick supports private network private sources and private plug-in libraries. If you need private network deployment, you can configure the following rules.",
|
||||
npmMirror: 'npm mirror',
|
||||
dbUrl: 'database url',
|
||||
accessToken: 'access token',
|
||||
placeholder: 'required for private network gitlab warehouse',
|
||||
},
|
||||
localstart: {
|
||||
title: 'Local Start',
|
||||
},
|
||||
database: {
|
||||
title: 'Data Synchronization',
|
||||
},
|
||||
},
|
||||
dev: {
|
||||
title: 'Developer',
|
||||
tips: 'The rubick plug-in system relies on npm management. Local debugging needs to first execute npm link in the current directory of the local plug-in.',
|
||||
pluginName: 'Plugin Name',
|
||||
install: 'Install',
|
||||
refreshPlugins: 'Refresh Plugins',
|
||||
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',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
import en from './en-US';
|
||||
import cn from './zh-CN';
|
||||
|
||||
const langs = {
|
||||
...en,
|
||||
...cn,
|
||||
};
|
||||
|
||||
export default langs;
|
||||
@@ -0,0 +1,139 @@
|
||||
export default {
|
||||
'zh-CN': {
|
||||
feature: {
|
||||
market: {
|
||||
title: '插件市场',
|
||||
search: '搜索插件',
|
||||
searchResult: '搜索结果',
|
||||
explore: '探索',
|
||||
efficiency: '效率',
|
||||
searchTool: '搜索工具',
|
||||
imageTool: '图像',
|
||||
developTool: '开发者',
|
||||
systemTool: '系统',
|
||||
localPlugin: '自定义插件',
|
||||
|
||||
finder: {
|
||||
must: '必备',
|
||||
recommended: '推荐',
|
||||
lastUpdated: '全部',
|
||||
},
|
||||
install: '安装',
|
||||
},
|
||||
installed: {
|
||||
title: '已安装',
|
||||
tips1: '暂无任何插件',
|
||||
tips2: '去插件市场选择安装合适的插件吧!',
|
||||
developer: '开发者',
|
||||
unknown: '未知',
|
||||
remove: '移除',
|
||||
functionKey: '功能关键字',
|
||||
detailInfo: '详情介绍',
|
||||
addToPanel: '点击+号,固定关键词到超级面板',
|
||||
removeFromPanel: '点击-号,从超级面板移除关键词',
|
||||
},
|
||||
settings: {
|
||||
title: '账户和设置',
|
||||
account: {
|
||||
accountInfo: '账户信息',
|
||||
tips1: 'rubick 用户',
|
||||
tips2: '软件偏好设置完成后需重启软件,头像和昵称请前往小程序设置',
|
||||
themeColor: '主题设置',
|
||||
spring: '立春',
|
||||
summer: '立夏',
|
||||
autumn: '立秋',
|
||||
winter: '立冬',
|
||||
personalized: '用户设置',
|
||||
greeting: '搜索框欢迎语',
|
||||
logo: '头像',
|
||||
replace: '替换',
|
||||
reset: '恢复默认设置',
|
||||
name: '用户名',
|
||||
},
|
||||
basic: {
|
||||
title: '基本设置',
|
||||
shortcutKey: '快捷键',
|
||||
showOrHiddle: '显示/隐藏快捷键',
|
||||
screenCapture: '截屏',
|
||||
common: '通用',
|
||||
autoPaste: '输入框自动粘贴',
|
||||
autoBoot: '开机启动',
|
||||
spaceExec: '空格执行',
|
||||
on: '开',
|
||||
off: '关',
|
||||
theme: '主题',
|
||||
darkMode: '暗黑模式',
|
||||
language: '语言',
|
||||
changeLang: '切换语言',
|
||||
cn: '简体中文',
|
||||
en: 'English',
|
||||
history: '关键词搜索记录',
|
||||
},
|
||||
global: {
|
||||
title: '全局快捷键',
|
||||
instructions: '说明及示例',
|
||||
tips: '按下快捷键,自动搜索对应关键字,当关键字结果完全匹配,且结果唯一时,会直接指向该功能。',
|
||||
example: '示例',
|
||||
example1: '快捷键 「 Alt + W」 关键字 「 微信」',
|
||||
tips1: '按下Alt + W 直接打开本地微信应用',
|
||||
example2: '快捷键 「 Ctrl + Alt + A」 关键字 「 截屏」',
|
||||
tips2: '按下 Ctrl + Alt + A 进行截屏',
|
||||
shortcutKey: '快捷键',
|
||||
funtionKey: '功能关键字',
|
||||
addShortcutKey: '新增全局快捷功能',
|
||||
addShortcutKeyTips:
|
||||
'先按功能键(Ctrl、Shift、{optionKeyName}),再按其他普通键。或按 F1-F12 单键。',
|
||||
},
|
||||
superPanel: {
|
||||
title: '超级面板设置',
|
||||
tips: '请选择需要添加到超级面板中的常用插件',
|
||||
add: '添加',
|
||||
remove: '移除',
|
||||
},
|
||||
intranet: {
|
||||
title: '内网部署配置',
|
||||
tips: '把插件发布到公网 npm 如果不符合您的公司安全要求,rubick 支持内网私有源和私有插件库,如果您需要内网部署使用,可以自行配置以下规则。',
|
||||
npmMirror: 'npm 源',
|
||||
dbUrl: 'database url',
|
||||
accessToken: 'access token',
|
||||
placeholder: '内网gitlab仓库必填',
|
||||
},
|
||||
localstart: {
|
||||
title: '本地启动',
|
||||
},
|
||||
database: {
|
||||
title: '多端数据同步',
|
||||
},
|
||||
},
|
||||
dev: {
|
||||
title: '开发者',
|
||||
tips: 'rubick 插件系统依托于 npm 管理,本地调试需要先在本地插件当前目录执行 npm link',
|
||||
pluginName: '插件名称',
|
||||
install: '安装',
|
||||
refreshPlugins: '刷新插件',
|
||||
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',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
import { createApp } from 'vue';
|
||||
import Vue3Lottie from 'vue3-lottie';
|
||||
import {
|
||||
ConfigProvider,
|
||||
Button,
|
||||
Divider,
|
||||
Row,
|
||||
Col,
|
||||
Dropdown,
|
||||
Menu,
|
||||
Form,
|
||||
Input,
|
||||
Radio,
|
||||
Typography,
|
||||
Select,
|
||||
Switch,
|
||||
Avatar,
|
||||
Popconfirm,
|
||||
Collapse,
|
||||
List,
|
||||
Tooltip,
|
||||
Alert,
|
||||
Drawer,
|
||||
Modal,
|
||||
Upload,
|
||||
Result,
|
||||
Spin,
|
||||
} from 'ant-design-vue';
|
||||
import App from './App.vue';
|
||||
import router from './router';
|
||||
import store from './store';
|
||||
import './assets/ant-reset.less';
|
||||
import 'ant-design-vue/dist/antd.variable.min.css';
|
||||
import registerI18n from './languages/i18n';
|
||||
import localConfig from './confOp';
|
||||
|
||||
const config: any = localConfig.getConfig();
|
||||
|
||||
// 暗夜模式
|
||||
if (config.perf.common.darkMode) {
|
||||
document.body.classList.add('dark');
|
||||
window.rubick.theme = 'dark';
|
||||
}
|
||||
|
||||
ConfigProvider.config({
|
||||
theme: config.perf.custom || {},
|
||||
});
|
||||
|
||||
window.rubick.changeTheme = () => {
|
||||
const config: any = localConfig.getConfig();
|
||||
ConfigProvider.config({
|
||||
theme: config.perf.custom || {},
|
||||
});
|
||||
};
|
||||
|
||||
createApp(App)
|
||||
.use(registerI18n)
|
||||
.use(store)
|
||||
.use(Button)
|
||||
.use(Divider)
|
||||
.use(Row)
|
||||
.use(Col)
|
||||
.use(Dropdown)
|
||||
.use(Menu)
|
||||
.use(Form)
|
||||
.use(Input)
|
||||
.use(Radio)
|
||||
.use(Select)
|
||||
.use(Switch)
|
||||
.use(Avatar)
|
||||
.use(Collapse)
|
||||
.use(List)
|
||||
.use(Tooltip)
|
||||
.use(Alert)
|
||||
.use(Drawer)
|
||||
.use(Modal)
|
||||
.use(Result)
|
||||
.use(Spin)
|
||||
.use(Upload)
|
||||
.use(Popconfirm)
|
||||
.use(Typography)
|
||||
.use(router)
|
||||
.use(Vue3Lottie)
|
||||
.mount('#app');
|
||||
@@ -0,0 +1,77 @@
|
||||
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
|
||||
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/result',
|
||||
name: 'result',
|
||||
component: () => import('../views/market/components/result.vue'),
|
||||
},
|
||||
{
|
||||
path: '/devPlugin',
|
||||
name: 'devPlugin',
|
||||
component: () => import('../views/market/components/devlopment.vue'),
|
||||
},
|
||||
{
|
||||
path: '/image',
|
||||
name: 'image',
|
||||
component: () => import('../views/market/components/image.vue'),
|
||||
},
|
||||
{
|
||||
path: '/tools',
|
||||
name: 'tools',
|
||||
component: () => import('../views/market/components/tools.vue'),
|
||||
},
|
||||
{
|
||||
path: '/worker',
|
||||
name: 'worker',
|
||||
component: () => import('../views/market/components/worker.vue'),
|
||||
},
|
||||
{
|
||||
path: '/system',
|
||||
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',
|
||||
component: () => import('../views/market/components/finder.vue'),
|
||||
},
|
||||
{
|
||||
path: '/installed',
|
||||
name: 'installed',
|
||||
component: () => import('../views/installed/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/account',
|
||||
name: 'account',
|
||||
component: () => import('../views/account/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/settings',
|
||||
name: 'settings',
|
||||
component: () => import('../views/settings/user.vue'),
|
||||
},
|
||||
{
|
||||
path: '/dev',
|
||||
name: 'dev',
|
||||
component: () => import('../views/dev/index.vue'),
|
||||
},
|
||||
|
||||
{
|
||||
path: '/:catchAll(.*)',
|
||||
name: 'finder',
|
||||
component: () => import('../views/market/components/finder.vue'),
|
||||
},
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes,
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,21 @@
|
||||
/* eslint-disable */
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
|
||||
declare module 'axios'
|
||||
|
||||
interface Window {
|
||||
rubick: any;
|
||||
market: any
|
||||
}
|
||||
|
||||
namespace Market {
|
||||
interface Plugin {
|
||||
isdownload?: boolean;
|
||||
name?: string;
|
||||
isloading: boolean
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import { createStore } from 'vuex';
|
||||
import request from '@/assets/request';
|
||||
|
||||
const isDownload = (item: Market.Plugin, targets: any[]) => {
|
||||
let isDownload = false;
|
||||
targets.some((plugin) => {
|
||||
if (plugin.name === item.name) {
|
||||
isDownload = true;
|
||||
}
|
||||
return isDownload;
|
||||
});
|
||||
return isDownload;
|
||||
};
|
||||
const LOCAL_PLUGIN_JSON = 'localPluginJson';
|
||||
export default createStore({
|
||||
state: {
|
||||
totalPlugins: [],
|
||||
localPlugins: [],
|
||||
searchValue: '',
|
||||
active: ['finder'],
|
||||
},
|
||||
mutations: {
|
||||
commonUpdate(state: any, payload) {
|
||||
Object.keys(payload).forEach((key) => {
|
||||
state[key] = payload[key];
|
||||
});
|
||||
},
|
||||
setSearchValue(state: any, payload) {
|
||||
state.searchValue = payload;
|
||||
},
|
||||
},
|
||||
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 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) => {
|
||||
origin.isdownload = isDownload(origin, localPlugins);
|
||||
origin.isloading = false;
|
||||
});
|
||||
// 修复卸载失败,一直转圈的问题。
|
||||
localPlugins.forEach((origin: Market.Plugin) => {
|
||||
origin.isloading = false;
|
||||
});
|
||||
|
||||
commit('commonUpdate', {
|
||||
localPlugins,
|
||||
totalPlugins,
|
||||
});
|
||||
},
|
||||
startDownload({ commit, state }, name) {
|
||||
const totalPlugins = JSON.parse(JSON.stringify(state.totalPlugins));
|
||||
totalPlugins.forEach((origin: Market.Plugin) => {
|
||||
if (origin.name === name) {
|
||||
origin.isloading = true;
|
||||
}
|
||||
});
|
||||
commit('commonUpdate', {
|
||||
totalPlugins,
|
||||
});
|
||||
},
|
||||
|
||||
startUnDownload({ commit, state }, name) {
|
||||
const localPlugins = window.market.getLocalPlugins();
|
||||
localPlugins.forEach((origin: Market.Plugin) => {
|
||||
if (origin.name === name) {
|
||||
origin.isloading = true;
|
||||
}
|
||||
});
|
||||
commit('commonUpdate', {
|
||||
localPlugins,
|
||||
});
|
||||
},
|
||||
|
||||
errorUnDownload({ commit, state }, name) {
|
||||
const localPlugins = window.market.getLocalPlugins();
|
||||
// 修复卸载失败,一直转圈的问题。
|
||||
localPlugins.forEach((origin: Market.Plugin) => {
|
||||
if (origin.name === name) {
|
||||
origin.isloading = false;
|
||||
}
|
||||
});
|
||||
|
||||
commit('commonUpdate', {
|
||||
localPlugins,
|
||||
});
|
||||
},
|
||||
|
||||
successDownload({ commit, state }, name) {
|
||||
const totalPlugins = JSON.parse(JSON.stringify(state.totalPlugins));
|
||||
totalPlugins.forEach((origin: Market.Plugin) => {
|
||||
if (origin.name === name) {
|
||||
origin.isloading = false;
|
||||
origin.isdownload = true;
|
||||
}
|
||||
});
|
||||
const localPlugins = window.market.getLocalPlugins();
|
||||
|
||||
commit('commonUpdate', {
|
||||
totalPlugins,
|
||||
localPlugins,
|
||||
});
|
||||
},
|
||||
|
||||
async updateLocalPlugin({ commit }) {
|
||||
const localPlugins = window.market.getLocalPlugins();
|
||||
const totalPlugins = await request.getTotalPlugins();
|
||||
|
||||
totalPlugins.forEach((origin: Market.Plugin) => {
|
||||
origin.isdownload = isDownload(origin, localPlugins);
|
||||
origin.isloading = false;
|
||||
});
|
||||
|
||||
commit('commonUpdate', {
|
||||
localPlugins,
|
||||
totalPlugins,
|
||||
});
|
||||
},
|
||||
},
|
||||
modules: {},
|
||||
});
|
||||
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<div class="account">
|
||||
<a-result
|
||||
v-if="!userInfo"
|
||||
title="请先登录"
|
||||
sub-title="用户暂未登录,无法体验更多设置"
|
||||
>
|
||||
<template #extra>
|
||||
<a-button @click="showModal" type="primary">
|
||||
使用微信小程序登录
|
||||
</a-button>
|
||||
</template>
|
||||
</a-result>
|
||||
<a-modal :footer="null" v-model:visible="visible">
|
||||
<a-result
|
||||
title="请使用微信扫码登录!"
|
||||
sub-title="使用微信扫描上面的 rubick 小程序二维码进行授权登录"
|
||||
>
|
||||
<template #icon>
|
||||
<img width="200" :src="imgCode" />
|
||||
</template>
|
||||
</a-result>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { nanoid } from 'nanoid';
|
||||
import { ref, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import service from '../../assets/service';
|
||||
|
||||
const userInfo = ref(window.rubick.dbStorage.getItem('rubick-user-info'));
|
||||
|
||||
const imgCode = ref('');
|
||||
const scene = nanoid();
|
||||
|
||||
const visible = ref(false);
|
||||
const showModal = () => {
|
||||
visible.value = true;
|
||||
if (!imgCode.value && !userInfo.value) {
|
||||
service.getScanCode({ scene }).then((res) => {
|
||||
imgCode.value = `data:image/png;base64,${res.dataUrl}`;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let timer = null;
|
||||
watch([visible], () => {
|
||||
if (visible.value) {
|
||||
timer = setInterval(() => {
|
||||
service.checkLoginStatus({ scene }).then((res) => {
|
||||
console.log(res);
|
||||
if (res.openId) {
|
||||
window.rubick.dbStorage.setItem('rubick-user-info', res);
|
||||
userInfo.value = res;
|
||||
message.success('登录成功!');
|
||||
visible.value = false;
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
});
|
||||
}, 2000);
|
||||
} else {
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.account {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
background: var(--color-body-bg);
|
||||
height: calc(~'100vh - 46px');
|
||||
:deep(.ant-result-title) {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
:deep(.ant-result-subtitle) {
|
||||
color: var(--color-text-desc);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<div class="dev">
|
||||
<div class="view-title">{{ $t('feature.dev.title') }}</div>
|
||||
<div class="view-container">
|
||||
<a-alert
|
||||
style="margin-bottom: 40px"
|
||||
:message="$t('feature.dev.tips')"
|
||||
type="warning"
|
||||
/>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formState"
|
||||
:rules="rules"
|
||||
:label-col="labelCol"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-form-item :label="$t('feature.dev.pluginName')" name="name">
|
||||
<a-input v-model:value="formState.name" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :wrapper-col="{ span: 14, offset: 4 }">
|
||||
<a-button :loading="loading" type="primary" @click="onSubmit">
|
||||
{{ $t('feature.dev.install') }}
|
||||
</a-button>
|
||||
<a-button @click="refresh" style="margin-left: 10px">
|
||||
{{ $t('feature.dev.refreshPlugins') }}
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const { t } = useI18n();
|
||||
|
||||
const formRef = ref();
|
||||
const formState = reactive({
|
||||
name: undefined,
|
||||
});
|
||||
const rules = {
|
||||
name: {
|
||||
required: true,
|
||||
message: 'Please input name',
|
||||
},
|
||||
};
|
||||
const onSubmit = () => {
|
||||
formRef.value.validate().then(() => {
|
||||
downloadPlugin(formState.name);
|
||||
});
|
||||
};
|
||||
|
||||
const loading = ref(false);
|
||||
const downloadPlugin = async (pluginName) => {
|
||||
loading.value = true;
|
||||
await window.market.downloadPlugin({
|
||||
name: pluginName,
|
||||
isDev: true,
|
||||
});
|
||||
message.success(t('feature.dev.installSuccess', { pluginName: pluginName }));
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const refresh = () => {
|
||||
formRef.value.validate().then(() => {
|
||||
window.market.refreshPlugin({
|
||||
name: formState.name,
|
||||
});
|
||||
message.success(
|
||||
t('feature.dev.refreshSuccess', { pluginName: formState.name })
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const labelCol = { span: 4 };
|
||||
const wrapperCol = { span: 14 };
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.dev {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
height: calc(~'100vh - 34px');
|
||||
.view-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 16px;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.view-container {
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 8px;
|
||||
background: var(--color-body-bg);
|
||||
overflow: auto;
|
||||
height: calc(~'100vh - 84px');
|
||||
}
|
||||
:deep(label) {
|
||||
color: var(--color-text-content);
|
||||
}
|
||||
:deep(.ant-input) {
|
||||
background: var(--color-input-hover) !important;
|
||||
color: var(--color-text-content);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,397 @@
|
||||
<template>
|
||||
<div class="installed">
|
||||
<div class="view-title">{{ $t('feature.installed.title') }}</div>
|
||||
<div class="view-container">
|
||||
<div v-if="!localPlugins.length">
|
||||
<a-result
|
||||
class="error-content"
|
||||
sub-title="哎呀,暂时还没有安装任何插件!"
|
||||
>
|
||||
<template #icon>
|
||||
<Vue3Lottie :animationData="emptyJson" :height="240" :width="240" />
|
||||
</template>
|
||||
<template #extra>
|
||||
<a-button @click="gotoFinder" key="console" type="primary">去插件市场看看吧</a-button>
|
||||
</template>
|
||||
</a-result>
|
||||
</div>
|
||||
<div class="container" v-else>
|
||||
<div class="installed-list">
|
||||
<div
|
||||
:class="currentSelect[0] === plugin.name ? 'item active' : 'item'"
|
||||
:key="index"
|
||||
@click="currentSelect = [plugin.name]"
|
||||
v-for="(plugin, index) in localPlugins"
|
||||
>
|
||||
<img :src="plugin.logo" />
|
||||
<div class="info">
|
||||
<div class="title">
|
||||
{{ plugin.pluginName }}
|
||||
</div>
|
||||
<div class="desc">{{ plugin.description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
{{ $t('feature.installed.developer') }}:{{
|
||||
`${pluginDetail.author || $t('feature.installed.unknown')}`
|
||||
}}
|
||||
</div>
|
||||
<div class="desc">
|
||||
{{ pluginDetail.description }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<a-button
|
||||
type="primary"
|
||||
size="small"
|
||||
shape="round"
|
||||
:loading="pluginDetail.isloading"
|
||||
@click="deletePlugin(pluginDetail)"
|
||||
>
|
||||
{{ $t('feature.installed.remove') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="feature-container">
|
||||
<template
|
||||
:key="index"
|
||||
v-for="(item, index) in pluginDetail.features"
|
||||
>
|
||||
<div
|
||||
class="desc-item"
|
||||
v-if="item.cmds.filter(cmd => !cmd.label).length > 0"
|
||||
>
|
||||
<div>{{ item.explain }}</div>
|
||||
<template :key="cmd" v-for="cmd in item.cmds">
|
||||
<a-dropdown
|
||||
v-if="!cmd.label"
|
||||
:class="{ executable: !cmd.label }"
|
||||
>
|
||||
<template #overlay>
|
||||
<a-menu @click="({key}) => handleMenuClick(key, item, cmd)">
|
||||
<a-menu-item key="open">
|
||||
<CaretRightOutlined />
|
||||
运行
|
||||
</a-menu-item>
|
||||
<a-menu-item v-if="!hasAdded(cmd)" key="add">
|
||||
<PushpinOutlined />
|
||||
固定到超级面板
|
||||
</a-menu-item>
|
||||
<a-menu-item v-else key="remove">
|
||||
<PushpinFilled />
|
||||
从超级面板中取消固定
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button size="small" class="keyword-tag">
|
||||
{{ cmd.label || cmd }}
|
||||
<DownOutlined />
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useStore } from 'vuex';
|
||||
import { computed, ref, toRaw, watch } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import path from 'path';
|
||||
import {
|
||||
PushpinOutlined,
|
||||
PushpinFilled,
|
||||
CaretRightOutlined,
|
||||
DownOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import emptyJson from '@/assets/lottie/empty.json';
|
||||
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
|
||||
const remote = window.require('@electron/remote');
|
||||
const fs = window.require('fs');
|
||||
|
||||
const appPath = remote.app.getPath('userData');
|
||||
const baseDir = path.join(appPath, './rubick-plugins');
|
||||
|
||||
const store = useStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const localPlugins = computed(() =>
|
||||
store.state.localPlugins.filter(
|
||||
(plugin) => plugin.name !== 'rubick-system-feature'
|
||||
)
|
||||
);
|
||||
const updateLocalPlugin = () => store.dispatch('updateLocalPlugin');
|
||||
const startUnDownload = (name) => store.dispatch('startUnDownload', name);
|
||||
const errorUnDownload = (name) => store.dispatch('errorUnDownload', name);
|
||||
|
||||
const currentSelect = ref([route.query.plugin || localPlugins?.value[0]?.name]);
|
||||
|
||||
watch(localPlugins, () => {
|
||||
currentSelect.value = [localPlugins?.value[0]?.name];
|
||||
});
|
||||
|
||||
const pluginDetail = computed(() => {
|
||||
return (
|
||||
localPlugins.value.find((v) => v.name === currentSelect.value[0]) || {}
|
||||
);
|
||||
});
|
||||
|
||||
const superPanelPlugins = ref(
|
||||
window.rubick.db.get('super-panel-user-plugins') || {
|
||||
data: [],
|
||||
_id: 'super-panel-user-plugins',
|
||||
}
|
||||
);
|
||||
|
||||
const handleMenuClick = (key, item, cmd) => {
|
||||
if(key === 'open') {
|
||||
openPlugin({
|
||||
code: item.code,
|
||||
cmd,
|
||||
});
|
||||
} else if (key === 'add') {
|
||||
addCmdToSuperPanel({cmd, code: item.code});
|
||||
} else {
|
||||
removePluginToSuperPanel({cmd, name: item.name})
|
||||
}
|
||||
};
|
||||
|
||||
const addCmdToSuperPanel = ({ cmd, code }) => {
|
||||
const plugin = {
|
||||
...toRaw(pluginDetail.value),
|
||||
cmd,
|
||||
ext: {
|
||||
code,
|
||||
type: 'text',
|
||||
payload: null,
|
||||
},
|
||||
};
|
||||
superPanelPlugins.value.data.push(plugin);
|
||||
const { rev } = window.rubick.db.put(JSON.parse(JSON.stringify(superPanelPlugins.value)));
|
||||
superPanelPlugins.value._rev = rev;
|
||||
};
|
||||
|
||||
const removePluginToSuperPanel = ({ cmd, name }) => {
|
||||
superPanelPlugins.value.data = toRaw(superPanelPlugins.value).data.filter(
|
||||
(item) => {
|
||||
if (name) return item.name !== name;
|
||||
return item.cmd !== cmd;
|
||||
}
|
||||
);
|
||||
const { rev } = window.rubick.db.put(toRaw(superPanelPlugins.value));
|
||||
superPanelPlugins.value._rev = rev;
|
||||
};
|
||||
|
||||
const hasAdded = (cmd) => {
|
||||
let added = false;
|
||||
superPanelPlugins.value.data.some((item) => {
|
||||
if (item.cmd === cmd) {
|
||||
added = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return added;
|
||||
};
|
||||
|
||||
const openPlugin = ({ cmd, code }) => {
|
||||
window.rubick.openPlugin(
|
||||
JSON.parse(
|
||||
JSON.stringify({
|
||||
...pluginDetail.value,
|
||||
cmd,
|
||||
ext: {
|
||||
code,
|
||||
type: 'text',
|
||||
payload: null,
|
||||
},
|
||||
})
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const deletePlugin = async (plugin) => {
|
||||
startUnDownload(plugin.name);
|
||||
const timer = setTimeout(() => {
|
||||
errorUnDownload(plugin.name);
|
||||
message.error('卸载超时,请重试!');
|
||||
}, 20000);
|
||||
await window.market.deletePlugin(plugin);
|
||||
removePluginToSuperPanel({ name: plugin.name });
|
||||
updateLocalPlugin();
|
||||
clearTimeout(timer);
|
||||
};
|
||||
|
||||
const gotoFinder = () => {
|
||||
router.push('/finder');
|
||||
store.commit('commonUpdate', { active: ['finder'] });
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.installed {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
height: calc(~'100vh - 34px');
|
||||
.view-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 16px;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.view-container {
|
||||
border-radius: 8px;
|
||||
background: var(--color-body-bg);
|
||||
overflow: auto;
|
||||
height: calc(~'100vh - 84px');
|
||||
}
|
||||
:deep(.ant-result-title) {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
:deep(.ant-result-subtitle) {
|
||||
color: var(--color-text-desc);
|
||||
}
|
||||
.keyword-tag {
|
||||
font-size: 13px;
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
.container {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
background: #f3efef;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.installed-list {
|
||||
width: 38%;
|
||||
background: var(--color-body-bg);
|
||||
height: 100%;
|
||||
padding: 10px 0;
|
||||
border-right: 1px solid var(--color-border-light);
|
||||
overflow: auto;
|
||||
|
||||
.item {
|
||||
padding: 10px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--color-text-content);
|
||||
border-bottom: 1px dashed var(--color-border-light);
|
||||
cursor: pointer;
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
img {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 12px;
|
||||
color: var(--color-text-desc);
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
-webkit-line-clamp: 2;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--ant-primary-color);
|
||||
background: var(--color-list-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.plugin-detail {
|
||||
padding: 20px 20px 0 20px;
|
||||
box-sizing: border-box;
|
||||
width: 62%;
|
||||
height: 100%;
|
||||
background: var(--color-body-bg);
|
||||
.plugin-top {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 12px;
|
||||
margin-bottom: 12px;
|
||||
.title {
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--color-text-primary);
|
||||
.ant-tag {
|
||||
background: var(--color-input-hover);
|
||||
border: 1px solid var(--color-border-light);
|
||||
color: var(--color-text-content);
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 13px;
|
||||
color: var(--color-text-desc);
|
||||
}
|
||||
}
|
||||
|
||||
.detail-container,
|
||||
.feature-container {
|
||||
height: 380px;
|
||||
overflow: auto;
|
||||
color: var(--color-text-content);
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.desc-item {
|
||||
padding: 10px 0;
|
||||
color: var(--color-text-content);
|
||||
.ant-tag {
|
||||
margin-top: 6px;
|
||||
|
||||
&.executable {
|
||||
cursor: pointer;
|
||||
color: var(--ant-info-color);
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.desc-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.desc-info {
|
||||
color: var(--color-text-desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<div class="system">
|
||||
<PluginList
|
||||
v-if="dev && !!dev.length"
|
||||
@downloadSuccess="downloadSuccess"
|
||||
:title="$t('feature.market.developTool')"
|
||||
:list="dev"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onBeforeMount } 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([]);
|
||||
|
||||
onBeforeMount(async () => {
|
||||
data.value = await request.getDevDetail();
|
||||
});
|
||||
|
||||
const dev = computed(() => {
|
||||
const defaultData = data.value || [];
|
||||
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">
|
||||
.system {
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<div class="finder">
|
||||
<Carousel :itemsToShow="2" :transition="500">
|
||||
<Slide :key="index" v-for="(banner, index) in data.banners || []">
|
||||
<img class="carousel__item" @click="jumpTo(banner.link)" :src="banner.src" />
|
||||
</Slide>
|
||||
</Carousel>
|
||||
<a-divider />
|
||||
<PluginList
|
||||
v-if="must && !!must.length"
|
||||
@downloadSuccess="downloadSuccess"
|
||||
:title="$t('feature.market.finder.must')"
|
||||
:list="must"
|
||||
/>
|
||||
<PluginList
|
||||
v-if="recommend && !!recommend.length"
|
||||
@downloadSuccess="downloadSuccess"
|
||||
:title="$t('feature.market.finder.recommended')"
|
||||
:list="recommend"
|
||||
/>
|
||||
<PluginList
|
||||
v-if="newList && !!newList.length"
|
||||
:title="$t('feature.market.finder.lastUpdated')"
|
||||
:list="newList"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onBeforeMount } from 'vue';
|
||||
import 'vue3-carousel/dist/carousel.css';
|
||||
import { Carousel, Slide } from 'vue3-carousel';
|
||||
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([]);
|
||||
|
||||
onBeforeMount(async () => {
|
||||
data.value = await request.getFinderDetail();
|
||||
});
|
||||
|
||||
const must = computed(() => {
|
||||
const defaultData = data.value.must || [];
|
||||
if (!defaultData.length) return [];
|
||||
return defaultData.map((plugin) => {
|
||||
let searchInfo = null;
|
||||
totalPlugins.value.forEach((t) => {
|
||||
if (t.name === plugin) {
|
||||
searchInfo = t;
|
||||
}
|
||||
});
|
||||
return searchInfo;
|
||||
});
|
||||
});
|
||||
|
||||
const jumpTo = (url) => {
|
||||
window.rubick.shellOpenExternal(url);
|
||||
};
|
||||
|
||||
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 {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
box-sizing: border-box;
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
}
|
||||
.ant-divider-horizontal {
|
||||
margin: 17px 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.carousel__item {
|
||||
cursor: pointer;
|
||||
min-height: 180px;
|
||||
width: 100%;
|
||||
background-color: var(--vc-clr-primary);
|
||||
color: var(--vc-clr-white);
|
||||
font-size: 20px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.carousel__track {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.carousel__slide {
|
||||
padding-right: 6px;
|
||||
&:last-child {
|
||||
padding-left: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.carousel__prev,
|
||||
.carousel__next {
|
||||
box-sizing: content-box;
|
||||
border: 5px solid white;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<div class="system">
|
||||
<PluginList
|
||||
v-if="system && !!system.length"
|
||||
@downloadSuccess="downloadSuccess"
|
||||
:title="$t('feature.market.imageTool')"
|
||||
:list="system"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onBeforeMount } 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([]);
|
||||
|
||||
onBeforeMount(async () => {
|
||||
data.value = await request.getImageDetail();
|
||||
console.log(data.value);
|
||||
});
|
||||
|
||||
const system = computed(() => {
|
||||
const defaultData = data.value || [];
|
||||
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">
|
||||
.system {
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,284 @@
|
||||
<template>
|
||||
<div class="panel-item">
|
||||
<h3 class="title">{{ title }}</h3>
|
||||
<div class="list-item">
|
||||
<a-list
|
||||
:grid="{ gutter: 16, column: 2 }"
|
||||
:data-source="list.filter((item) => !!item)"
|
||||
>
|
||||
<template #renderItem="{ item, index }">
|
||||
<a-list-item v-if="item" @click="showDetail(index)">
|
||||
<template #actions>
|
||||
<a-button
|
||||
class="download-plugin-btn"
|
||||
type="text"
|
||||
:loading="item.isloading"
|
||||
>
|
||||
<CloudDownloadOutlined
|
||||
v-if="!item.isloading && !item.isdownload"
|
||||
@click.stop="downloadPlugin(item, index)"
|
||||
style="font-size: 20px; cursor: pointer"
|
||||
/>
|
||||
<SelectOutlined
|
||||
v-if="!item.isloading && item.isdownload"
|
||||
@click.stop="openPlugin(item)"
|
||||
style="font-size: 18px; cursor: pointer"
|
||||
/>
|
||||
</a-button>
|
||||
</template>
|
||||
<a-list-item-meta>
|
||||
<template #description>
|
||||
<span class="ellipse desc">{{ item.description }}</span>
|
||||
</template>
|
||||
<template #title>
|
||||
<span class="ellipse">{{ item.pluginName }}</span>
|
||||
</template>
|
||||
<template #avatar>
|
||||
<a-avatar :src="item.logo" />
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</div>
|
||||
<a-drawer
|
||||
width="77%"
|
||||
v-if="visible"
|
||||
placement="right"
|
||||
:closable="false"
|
||||
:visible="visible"
|
||||
class="plugin-info"
|
||||
:style="{ position: 'absolute' }"
|
||||
@close="visible = false"
|
||||
>
|
||||
<template #title>
|
||||
<div class="plugin-title-info">
|
||||
<div class="info">
|
||||
<img :src="detail.logo" class="plugin-icon" />
|
||||
<div class="plugin-desc">
|
||||
<div>
|
||||
<div class="title">
|
||||
{{ detail.pluginName }}
|
||||
</div>
|
||||
<div class="desc">
|
||||
{{ detail.description }}
|
||||
</div>
|
||||
</div>
|
||||
<a-button
|
||||
v-if="!detail.isdownload"
|
||||
@click.stop="downloadPlugin(detail)"
|
||||
shape="round"
|
||||
type="primary"
|
||||
:loading="detail.isloading"
|
||||
>
|
||||
<template #icon>
|
||||
<CloudDownloadOutlined
|
||||
v-show="!detail.isloading && !detail.isdownload"
|
||||
/>
|
||||
</template>
|
||||
{{ $t('feature.market.install') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<a-spin :spinning="!content" tip="内容加载中...">
|
||||
<div v-if="content !== 'error'" v-html="content" class="home-page-container"></div>
|
||||
<a-result
|
||||
class="error-content"
|
||||
v-else
|
||||
sub-title="插件主页内容走丢啦!"
|
||||
>
|
||||
<template #icon>
|
||||
<Vue3Lottie :animationData="notFountJson" :height="240" :width="240" />
|
||||
</template>
|
||||
</a-result>
|
||||
</a-spin>
|
||||
</a-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
CloudDownloadOutlined,
|
||||
ArrowLeftOutlined,
|
||||
SelectOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
|
||||
import { defineProps, ref, computed } from 'vue';
|
||||
import { useStore } from 'vuex';
|
||||
import { message } from 'ant-design-vue';
|
||||
import MarkdownIt from 'markdown-it';
|
||||
import { useRouter } from 'vue-router';
|
||||
import request from '@/assets/request/index';
|
||||
import notFountJson from '@/assets/lottie/404.json';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const { t } = useI18n();
|
||||
|
||||
const store = useStore();
|
||||
const router = useRouter();
|
||||
|
||||
const startDownload = (name) => store.dispatch('startDownload', name);
|
||||
const successDownload = (name) => store.dispatch('successDownload', name);
|
||||
|
||||
const props = defineProps({
|
||||
list: {
|
||||
type: [Array],
|
||||
default: () => [],
|
||||
},
|
||||
title: String,
|
||||
});
|
||||
|
||||
const downloadPlugin = async (plugin) => {
|
||||
startDownload(plugin.name);
|
||||
await window.market.downloadPlugin(plugin);
|
||||
message.success(t('feature.dev.installSuccess', { pluginName: plugin.pluginName }));
|
||||
successDownload(plugin.name);
|
||||
};
|
||||
|
||||
const visible = ref(false);
|
||||
const showIndex = ref(0);
|
||||
const markdown = new MarkdownIt();
|
||||
const content = ref('');
|
||||
|
||||
const showDetail = async (index) => {
|
||||
const item = props.list[index];
|
||||
visible.value = true;
|
||||
showIndex.value = index;
|
||||
content.value = '';
|
||||
let mdContent = '暂无内容';
|
||||
try {
|
||||
if (item.homePage) {
|
||||
mdContent = await request.getPluginDetail(item.homePage);
|
||||
}
|
||||
content.value = markdown.render(mdContent);
|
||||
} catch (e) {
|
||||
content.value = 'error';
|
||||
}
|
||||
};
|
||||
|
||||
const detail = computed(() => props.list[showIndex.value]);
|
||||
|
||||
const openPlugin = (item) => {
|
||||
store.commit('commonUpdate', {active: ['installed']})
|
||||
router.push({
|
||||
path: '/installed',
|
||||
query: {plugin: item.name}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
}
|
||||
.panel-item {
|
||||
margin-bottom: 17px;
|
||||
.download-plugin-btn {
|
||||
color: var(--ant-primary-color);
|
||||
}
|
||||
.title {
|
||||
margin-bottom: 30px;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.ellipse {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
color: var(--color-text-content);
|
||||
&.desc {
|
||||
color: var(--color-text-desc);
|
||||
}
|
||||
}
|
||||
&:after {
|
||||
content: ' ';
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
border-bottom: 1px solid var(--color-border-light);
|
||||
transform: scaleY(0.5);
|
||||
}
|
||||
.ant-list-item {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
&:after {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.plugin-info {
|
||||
width: 100%;
|
||||
|
||||
.ant-drawer-content-wrapper {
|
||||
box-shadow: none !important;
|
||||
.ant-drawer-content {
|
||||
background: var(--color-body-bg);
|
||||
}
|
||||
.ant-drawer-header {
|
||||
background: var(--color-body-bg);
|
||||
border-bottom: 1px solid var(--color-border-light);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
.plugin-title-info {
|
||||
.back-icon {
|
||||
filter: invert(1) brightness(200%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.plugin-title-info {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
.info {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
||||
.plugin-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.plugin-desc {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
color: var(--color-text-desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.error-content {
|
||||
&.ant-result {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
.home-page-container {
|
||||
min-height: 200px;
|
||||
* {
|
||||
color: var(--color-text-content);
|
||||
}
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<div class="result">
|
||||
<PluginList
|
||||
v-if="result && !!result.length"
|
||||
@downloadSuccess="downloadSuccess"
|
||||
:title="$t('feature.market.searchResult')"
|
||||
:list="result"
|
||||
/>
|
||||
<a-result
|
||||
class="error-content"
|
||||
v-else
|
||||
sub-title="哎呀,暂时还没有这个插件哟!"
|
||||
>
|
||||
<template #icon>
|
||||
<Vue3Lottie :animationData="emptyJson" :height="240" :width="240" />
|
||||
</template>
|
||||
</a-result>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import emptyJson from '@/assets/lottie/empty.json';
|
||||
import PluginList from './plugin-list.vue';
|
||||
|
||||
import { useStore } from 'vuex';
|
||||
const store = useStore();
|
||||
|
||||
const totalPlugins = computed(() => store.state.totalPlugins);
|
||||
const searchValue = computed(() => store.state.searchValue);
|
||||
|
||||
const result = computed(() => {
|
||||
if (searchValue.value.trim().length > 0) {
|
||||
const pattern = new RegExp(searchValue.value.toLowerCase());
|
||||
return totalPlugins.value.filter((plugin) => {
|
||||
if (plugin.pluginName.toLowerCase().match(pattern)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return totalPlugins.value;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.result {
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
box-sizing: border-box;
|
||||
.error-content {
|
||||
padding-top: 40px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<div class="system">
|
||||
<PluginList
|
||||
v-if="system && !!system.length"
|
||||
@downloadSuccess="downloadSuccess"
|
||||
:title="$t('feature.market.systemTool')"
|
||||
:list="system"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onBeforeMount } 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([]);
|
||||
|
||||
onBeforeMount(async () => {
|
||||
data.value = await request.getSystemDetail();
|
||||
});
|
||||
|
||||
const system = computed(() => {
|
||||
const defaultData = data.value || [];
|
||||
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">
|
||||
.system {
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<div class="tools">
|
||||
<PluginList
|
||||
v-if="tools && !!tools.length"
|
||||
@downloadSuccess="downloadSuccess"
|
||||
:title="$t('feature.market.searchTool')"
|
||||
:list="tools"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onBeforeMount } 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([]);
|
||||
|
||||
onBeforeMount(async () => {
|
||||
data.value = await request.getSearchDetail();
|
||||
});
|
||||
|
||||
const tools = computed(() => {
|
||||
const defaultData = data.value || [];
|
||||
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">
|
||||
.worker {
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<div class="worker">
|
||||
<PluginList
|
||||
v-if="system && !!system.length"
|
||||
@downloadSuccess="downloadSuccess"
|
||||
:title="$t('feature.market.efficiency')"
|
||||
:list="system"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onBeforeMount } 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([]);
|
||||
|
||||
onBeforeMount(async () => {
|
||||
data.value = await request.getWorkerDetail();
|
||||
});
|
||||
|
||||
const system = computed(() => {
|
||||
const defaultData = data.value || [];
|
||||
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">
|
||||
.worker {
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,320 @@
|
||||
<template>
|
||||
<div class="export-header">
|
||||
<a-button @click="exportData" size="small" type="primary" style="margin-right: 10px;">
|
||||
导出数据
|
||||
<template #icon>
|
||||
<ExportOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
<a-button @click="importData" danger size="small" style="margin-right: 10px; background-color: var(--color-input-hover)">
|
||||
导入数据
|
||||
<template #icon>
|
||||
<ImportOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
<a-button type="link" size="small" @click="showSetting = true">
|
||||
<template #icon>
|
||||
<SettingOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</div>
|
||||
<a-list item-layout="horizontal" :data-source="dataPlugins">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<template #actions>
|
||||
<a v-if="!item.plugin?.isdownload && !item.plugin?.isloading" key="list-loadmore-edit"
|
||||
@click="() => downloadPlugin(item.plugin)">
|
||||
<CloudDownloadOutlined style="font-size: 18px;"/>
|
||||
</a>
|
||||
<a v-if="item.plugin?.isloading" key="list-loadmore-edit">
|
||||
<LoadingOutlined style="font-size: 18px;"/>
|
||||
</a>
|
||||
<a key="list-loadmore-edit" @click="() => showKeys(item)">
|
||||
<DatabaseOutlined style="font-size: 18px;"/>
|
||||
</a>
|
||||
</template>
|
||||
<a-list-item-meta>
|
||||
<template #title>
|
||||
<div style="color: var(--color-text-content)">
|
||||
<span>{{ item.plugin?.pluginName }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #avatar>
|
||||
<a-avatar shape="square" :src="item.plugin?.logo"/>
|
||||
</template>
|
||||
<template #description>
|
||||
<div style="color: var(--color-text-desc)">
|
||||
<span>{{ item.keys.length }} 份文档</span>
|
||||
</div>
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
<a-drawer
|
||||
v-model:visible="open"
|
||||
width="400"
|
||||
:closable="false"
|
||||
:title="currentSelect.plugin.pluginName"
|
||||
placement="right"
|
||||
class="exportDrawer"
|
||||
>
|
||||
<p
|
||||
class="key-item"
|
||||
:key="key"
|
||||
@click="() => showDetail(key)"
|
||||
v-for="key in currentSelect.keys"
|
||||
>
|
||||
{{ key }}
|
||||
</p>
|
||||
</a-drawer>
|
||||
<a-modal
|
||||
centered
|
||||
:bodyStyle="{
|
||||
maxHeight: '500px',
|
||||
overflow: 'auto',
|
||||
backgroundColor: 'var(--color-body-bg)',
|
||||
color: 'var(--color-text-primary)'
|
||||
}"
|
||||
:footer="null"
|
||||
:closable="false"
|
||||
v-model:visible="show"
|
||||
>
|
||||
<pre>{{ JSON.stringify(detail, null, 2) }}</pre>
|
||||
</a-modal>
|
||||
<a-modal
|
||||
v-model:visible="showSetting"
|
||||
title="webdav 账户配置"
|
||||
:footer="null"
|
||||
class="webdavModel"
|
||||
>
|
||||
<a-alert v-if="formState.suport === 'jianguo'" style="margin-bottom: 20px;" type="info" show-icon>
|
||||
<template #message>
|
||||
<div
|
||||
@click="openHelp"
|
||||
class="openHelp">
|
||||
点击查看如何获取坚果云账号密码
|
||||
</div>
|
||||
</template>
|
||||
</a-alert>
|
||||
<a-form
|
||||
:model="formState"
|
||||
name="basic"
|
||||
:label-col="{ span: 8 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
autocomplete="off"
|
||||
@finish="handleOk"
|
||||
>
|
||||
<a-form-item
|
||||
label="webdav 提供商"
|
||||
name="suport"
|
||||
>
|
||||
<a-select v-model:value="formState.suport">
|
||||
<a-select-option value="jianguo">坚果云</a-select-option>
|
||||
<a-select-option value="auto">自定义</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="服务器地址"
|
||||
name="url"
|
||||
v-show="formState.suport === 'auto'"
|
||||
:rules="[{ required: true, message: '请填写服务器地址!' }]"
|
||||
>
|
||||
<a-input v-model:value="formState.url" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="账户"
|
||||
name="username"
|
||||
:rules="[{ required: true, message: '请填写 username!' }]"
|
||||
>
|
||||
<a-input v-model:value="formState.username" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="密码"
|
||||
name="password"
|
||||
:rules="[{ required: true, message: '请填写 password!' }]"
|
||||
>
|
||||
<a-input-password v-model:value="formState.password" />
|
||||
</a-form-item>
|
||||
<a-form-item :wrapper-col="{ offset: 8, span: 16 }">
|
||||
<a-button type="primary" html-type="submit">保存设置</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script setup>
|
||||
import { useStore } from 'vuex';
|
||||
import { computed, ref, reactive } from 'vue';
|
||||
import {
|
||||
DatabaseOutlined,
|
||||
CloudDownloadOutlined,
|
||||
LoadingOutlined,
|
||||
ExportOutlined,
|
||||
ImportOutlined,
|
||||
SettingOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const open = ref(false);
|
||||
const show = ref(false);
|
||||
const showSetting = ref(false);
|
||||
const currentSelect = ref({ plugin: {} });
|
||||
const detail = ref({});
|
||||
|
||||
const defaultConfig = window.rubick.dbStorage.getItem(
|
||||
'rubick-db-jg-webdav'
|
||||
) || {
|
||||
url: 'https://dav.jianguoyun.com/dav/',
|
||||
username: '',
|
||||
password: '',
|
||||
};
|
||||
|
||||
if (!defaultConfig.suport) {
|
||||
defaultConfig.suport = 'jianguo';
|
||||
}
|
||||
|
||||
const formState = reactive(defaultConfig);
|
||||
|
||||
const showKeys = (item) => {
|
||||
open.value = true;
|
||||
currentSelect.value = item;
|
||||
};
|
||||
|
||||
const handleOk = () => {
|
||||
window.rubick.dbStorage.setItem(
|
||||
'rubick-db-jg-webdav',
|
||||
JSON.parse(JSON.stringify(formState))
|
||||
);
|
||||
message.success('保存成功');
|
||||
showSetting.value = false;
|
||||
};
|
||||
|
||||
const showDetail = (key) => {
|
||||
show.value = true;
|
||||
detail.value = window.rubick.db.get(key);
|
||||
};
|
||||
|
||||
const exportData = () => {
|
||||
if (!formState.password || !formState.username) {
|
||||
return showSetting.value = true;
|
||||
}
|
||||
window.market.dbDump(JSON.parse(JSON.stringify(formState)));
|
||||
};
|
||||
|
||||
const importData = () => {
|
||||
if (!formState.password || !formState.username) {
|
||||
return showSetting.value = true;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '导入确认',
|
||||
content: '导入坚果云数据将会覆盖本地数据,是否确认导入?',
|
||||
onOk() {
|
||||
window.market.dbImport(JSON.parse(JSON.stringify(formState)));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const openHelp = () => {
|
||||
window.rubick.shellOpenExternal('https://help.jianguoyun.com/?p=2064');
|
||||
};
|
||||
|
||||
const store = useStore();
|
||||
|
||||
const pluginsData = window.rubick.db.get('RUBICK_PLUGIN_INFO');
|
||||
|
||||
const totalPlugins = computed(() => store.state.totalPlugins);
|
||||
|
||||
const dataPlugins = computed(() => {
|
||||
if (!pluginsData) return [];
|
||||
return pluginsData.data.map((item) => {
|
||||
let plugin = null;
|
||||
if (['rubick-system-feature'].includes(item.name)) {
|
||||
plugin = {
|
||||
pluginName: '主程序',
|
||||
isdownload: true,
|
||||
logo: require('../../assets/logo.png'),
|
||||
};
|
||||
} else {
|
||||
plugin = totalPlugins.value.find((p) => p.name === item.name);
|
||||
}
|
||||
const data = item.keys.map((key) => window.rubick.db.get(key));
|
||||
return {
|
||||
...item,
|
||||
plugin,
|
||||
data,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const startDownload = (name) => store.dispatch('startDownload', name);
|
||||
const successDownload = (name) => store.dispatch('successDownload', name);
|
||||
|
||||
const downloadPlugin = async (plugin) => {
|
||||
startDownload(plugin.name);
|
||||
await window.market.downloadPlugin(plugin);
|
||||
message.success(t('feature.dev.installSuccess', { pluginName: plugin.name }));
|
||||
successDownload(plugin.name);
|
||||
};
|
||||
</script>
|
||||
<style lang="less">
|
||||
.openHelp {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.export-header {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
border-bottom: 1px solid var(--color-border-light);
|
||||
text-align: right;
|
||||
}
|
||||
.key-item {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--ant-primary-color);
|
||||
}
|
||||
}
|
||||
.exportDrawer{
|
||||
.ant-drawer-header{
|
||||
background-color: var(--color-body-bg);
|
||||
border-bottom: 1px solid var(--color-border-light);
|
||||
.ant-drawer-title{
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
}
|
||||
.ant-drawer-body{
|
||||
background-color: var(--color-body-bg);
|
||||
color: var(--color-text-content)
|
||||
}
|
||||
}
|
||||
.webdavModel{
|
||||
.ant-modal-close-x{
|
||||
color: var(--color-text-content);
|
||||
}
|
||||
.ant-modal-header{
|
||||
background-color: var(--color-body-bg);
|
||||
border-bottom: 1px solid var(--color-border-light);
|
||||
.ant-modal-title{
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
}
|
||||
.ant-form-item-label>label {
|
||||
color: var(--color-text-content);
|
||||
}
|
||||
.ant-modal-body {
|
||||
background-color: var(--color-body-bg);
|
||||
.ant-input,
|
||||
.ant-input-password,
|
||||
.ant-select-selector {
|
||||
background: var(--color-input-hover) !important;
|
||||
color: var(--color-text-content);
|
||||
}
|
||||
.ant-input-password-icon, .ant-select-arrow {
|
||||
color: var(--color-action-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,653 @@
|
||||
<template>
|
||||
<div class="settings">
|
||||
<div class="view-title"> {{ $t('feature.settings.title') }}</div>
|
||||
<div class="view-container">
|
||||
<a-menu v-model:selectedKeys="currentSelect" mode="horizontal">
|
||||
<a-menu-item key="userInfo">
|
||||
{{ $t('feature.settings.account.accountInfo') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="normal">
|
||||
{{ $t('feature.settings.basic.title') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="localstart">
|
||||
{{ $t('feature.settings.localstart.title') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="global">
|
||||
{{ $t('feature.settings.global.title') }}
|
||||
</a-menu-item>
|
||||
<!-- <a-menu-item key="superpanel">-->
|
||||
<!-- <template #icon>-->
|
||||
<!-- <FileAddOutlined />-->
|
||||
<!-- </template>-->
|
||||
<!-- {{ $t('feature.settings.superPanel.title') }}-->
|
||||
<!-- </a-menu-item>-->
|
||||
<a-menu-item key="database">
|
||||
{{ $t('feature.settings.database.title') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="localhost">
|
||||
{{ $t('feature.settings.intranet.title') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
<div class="settings-detail">
|
||||
<UserInfo v-if="currentSelect[0] === 'userInfo'" />
|
||||
<div v-if="currentSelect[0] === 'normal'">
|
||||
<div class="setting-item">
|
||||
<div class="title">
|
||||
{{ $t('feature.settings.basic.shortcutKey') }}
|
||||
</div>
|
||||
<div class="settings-item-li">
|
||||
<div class="label">
|
||||
{{ $t('feature.settings.basic.showOrHiddle') }}
|
||||
</div>
|
||||
<a-tooltip placement="top" trigger="click">
|
||||
<template #title>
|
||||
<span>{{ tipText }}</span>
|
||||
<template v-if="isWindows">
|
||||
<br />
|
||||
<span
|
||||
style="cursor: pointer; text-decoration: underline"
|
||||
@click="resetDefault('Alt')"
|
||||
>
|
||||
Alt+Space
|
||||
</span>
|
||||
<span
|
||||
style="
|
||||
cursor: pointer;
|
||||
margin-left: 8px;
|
||||
text-decoration: underline;
|
||||
"
|
||||
@click="resetDefault('Ctrl')"
|
||||
>
|
||||
Ctrl+Space
|
||||
</span>
|
||||
</template>
|
||||
</template>
|
||||
<div
|
||||
class="value"
|
||||
tabIndex="-1"
|
||||
@keyup="(e) => changeShortCut(e, 'showAndHidden')"
|
||||
>
|
||||
{{ shortCut.showAndHidden }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div class="settings-item-li">
|
||||
<div class="label">
|
||||
{{ $t('feature.settings.basic.screenCapture') }}
|
||||
</div>
|
||||
<a-tooltip placement="top" trigger="click">
|
||||
<template #title>
|
||||
<span>{{ tipText }}</span>
|
||||
</template>
|
||||
<div
|
||||
class="value"
|
||||
tabIndex="-1"
|
||||
@keyup="(e) => changeShortCut(e, 'capture')"
|
||||
>
|
||||
{{ shortCut.capture }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="title">{{ $t('feature.settings.basic.common') }}</div>
|
||||
<div class="settings-item-li">
|
||||
<div class="label">
|
||||
{{ $t('feature.settings.basic.autoPaste') }}
|
||||
</div>
|
||||
<a-switch
|
||||
v-model:checked="common.autoPast"
|
||||
:checked-children="$t('feature.settings.basic.on')"
|
||||
:un-checked-children="$t('feature.settings.basic.off')"
|
||||
></a-switch>
|
||||
</div>
|
||||
<div class="settings-item-li">
|
||||
<div class="label">{{ $t('feature.settings.basic.autoBoot') }}</div>
|
||||
<a-switch
|
||||
v-model:checked="common.start"
|
||||
:checked-children="$t('feature.settings.basic.on')"
|
||||
:un-checked-children="$t('feature.settings.basic.off')"
|
||||
></a-switch>
|
||||
</div>
|
||||
<div class="settings-item-li">
|
||||
<div class="label">
|
||||
{{ $t('feature.settings.basic.spaceExec') }}
|
||||
</div>
|
||||
<a-switch
|
||||
v-model:checked="common.space"
|
||||
:checked-children="$t('feature.settings.basic.on')"
|
||||
:un-checked-children="$t('feature.settings.basic.off')"
|
||||
></a-switch>
|
||||
</div>
|
||||
<div class="settings-item-li">
|
||||
<div class="label">
|
||||
{{ $t('feature.settings.basic.history') }}
|
||||
</div>
|
||||
<a-switch
|
||||
v-model:checked="common.history"
|
||||
:checked-children="$t('feature.settings.basic.on')"
|
||||
:un-checked-children="$t('feature.settings.basic.off')"
|
||||
></a-switch>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="title">{{ $t('feature.settings.basic.theme') }}</div>
|
||||
<div class="settings-item-li">
|
||||
<div class="label">{{ $t('feature.settings.basic.darkMode') }}</div>
|
||||
<a-switch
|
||||
v-model:checked="common.darkMode"
|
||||
:checked-children="$t('feature.settings.basic.on')"
|
||||
:un-checked-children="$t('feature.settings.basic.off')"
|
||||
></a-switch>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="title">{{ $t('feature.settings.basic.language') }}</div>
|
||||
<div class="settings-item-li">
|
||||
<div class="label">
|
||||
{{ $t('feature.settings.basic.changeLang') }}
|
||||
</div>
|
||||
<a-select
|
||||
v-model:value="state.common.lang"
|
||||
label-in-value
|
||||
style="width: 240px"
|
||||
:options="options"
|
||||
@change="changeLanguage"
|
||||
></a-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="currentSelect[0] === 'global'">
|
||||
<a-collapse>
|
||||
<a-collapse-panel
|
||||
key="1"
|
||||
:header="$t('feature.settings.global.instructions')"
|
||||
>
|
||||
<div>
|
||||
{{ $t('feature.settings.global.tips') }}
|
||||
</div>
|
||||
<h3 style="margin-top: 10px">
|
||||
{{ $t('feature.settings.global.example') }}
|
||||
</h3>
|
||||
<a-divider style="margin: 5px 0" />
|
||||
<a-list item-layout="horizontal" :data-source="examples">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<a-list-item-meta :description="item.desc">
|
||||
<template #title>
|
||||
<div>{{ item.title }}</div>
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
<div class="feature-container">
|
||||
<div class="keywords item">
|
||||
<div>{{ $t('feature.settings.global.shortcutKey') }}</div>
|
||||
<template :key="index" v-for="(item, index) in global">
|
||||
<a-tooltip placement="top" trigger="click">
|
||||
<template #title>
|
||||
<span>{{ tipText }}</span>
|
||||
</template>
|
||||
<div
|
||||
class="value"
|
||||
tabIndex="2"
|
||||
@keyup="(e) => changeGlobalKey(e, index)"
|
||||
>
|
||||
{{ item.key }}
|
||||
<MinusCircleOutlined
|
||||
@click.stop="deleteGlobalKey(e, index)"
|
||||
/>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</div>
|
||||
<div class="short-cut item">
|
||||
<div>{{ $t('feature.settings.global.funtionKey') }}</div>
|
||||
<template v-for="(item, index) in global" :key="index">
|
||||
<a-input
|
||||
:value="item.value"
|
||||
class="value"
|
||||
allowClear
|
||||
:disabled="!item.key"
|
||||
@change="(e) => changeGlobalValue(index, e.target.value)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div @click="addConfig" class="add-global">
|
||||
<PlusCircleOutlined />
|
||||
{{ $t('feature.settings.global.addShortcutKey') }}
|
||||
</div>
|
||||
</div>
|
||||
<Localhost v-if="currentSelect[0] === 'localhost'" />
|
||||
<LocalStart v-if="currentSelect[0] === 'localstart'" />
|
||||
<DataBase v-if="currentSelect[0] === 'database'" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
ToolOutlined,
|
||||
LaptopOutlined,
|
||||
DatabaseOutlined,
|
||||
MinusCircleOutlined,
|
||||
PlusCircleOutlined,
|
||||
UserOutlined,
|
||||
FolderOpenOutlined,
|
||||
SafetyOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
import debounce from 'lodash.debounce';
|
||||
import { ref, reactive, watch, toRefs, computed } from 'vue';
|
||||
import keycodes from './keycode';
|
||||
import Localhost from './localhost.vue';
|
||||
import UserInfo from './user-info';
|
||||
import LocalStart from './local-start';
|
||||
import DataBase from './database';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import localConfig from '@/confOp';
|
||||
|
||||
const { locale, t } = useI18n();
|
||||
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
|
||||
const examples = [
|
||||
{
|
||||
title: t('feature.settings.global.example1'),
|
||||
desc: t('feature.settings.global.tips1'),
|
||||
},
|
||||
{
|
||||
title: t('feature.settings.global.example2'),
|
||||
desc: t('feature.settings.global.tips2'),
|
||||
},
|
||||
];
|
||||
|
||||
const state = reactive({
|
||||
shortCut: {},
|
||||
common: {},
|
||||
local: {},
|
||||
global: [],
|
||||
custom: {},
|
||||
});
|
||||
|
||||
// 添加lastKeyPressTime变量来跟踪按键时间
|
||||
const lastKeyPressTime = ref(0);
|
||||
const DOUBLE_CLICK_THRESHOLD = 300; // 双击时间阈值(毫秒)
|
||||
|
||||
const isWindows = window?.rubick?.isWindows();
|
||||
const tipText = computed(() => {
|
||||
const optionKeyName = isWindows ? 'Alt' : 'Option、Command';
|
||||
return t('feature.settings.global.addShortcutKeyTips', {
|
||||
optionKeyName: optionKeyName,
|
||||
}) + `此外你也可以双击修饰键如(Ctrl+Ctrl)`;
|
||||
});
|
||||
|
||||
const currentSelect = ref(['userInfo']);
|
||||
|
||||
const { perf, global: defaultGlobal } = localConfig.getConfig();
|
||||
|
||||
state.shortCut = perf.shortCut;
|
||||
state.custom = perf.custom;
|
||||
state.common = perf.common;
|
||||
state.local = perf.local;
|
||||
state.global = defaultGlobal;
|
||||
|
||||
const setConfig = debounce(() => {
|
||||
const { perf } = localConfig.getConfig();
|
||||
localConfig.setConfig(
|
||||
JSON.parse(
|
||||
JSON.stringify({
|
||||
perf: {
|
||||
...perf,
|
||||
shortCut: state.shortCut,
|
||||
common: state.common,
|
||||
local: state.local,
|
||||
},
|
||||
global: state.global,
|
||||
})
|
||||
)
|
||||
);
|
||||
ipcRenderer.send('re-register');
|
||||
}, 500);
|
||||
|
||||
watch(state, setConfig);
|
||||
|
||||
const changeShortCut = (e, key) => {
|
||||
let compose = '';
|
||||
const currentTime = Date.now();
|
||||
const isDoubleClick = currentTime - lastKeyPressTime.value < DOUBLE_CLICK_THRESHOLD;
|
||||
lastKeyPressTime.value = currentTime;
|
||||
|
||||
// 处理 F1-F12 功能键
|
||||
if (e.keyCode >= 112 && e.keyCode <= 123) {
|
||||
state.shortCut[key] = keycodes[e.keyCode].toUpperCase();
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理双击功能键的情况
|
||||
if (isDoubleClick) {
|
||||
if (e.keyCode === 17) { // Ctrl
|
||||
state.shortCut[key] = 'Ctrl+Ctrl';
|
||||
return;
|
||||
}
|
||||
if (e.keyCode === 18) { // Alt
|
||||
state.shortCut[key] = 'Option+Option';
|
||||
return;
|
||||
}
|
||||
if (e.keyCode === 16) { // Shift
|
||||
state.shortCut[key] = 'Shift+Shift';
|
||||
return;
|
||||
}
|
||||
if (e.keyCode === 93) { // Command
|
||||
state.shortCut[key] = 'Command+Command';
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理功能键+普通键的组合
|
||||
let hasModifierKey = false;
|
||||
|
||||
if (e.ctrlKey && e.keyCode !== 17) {
|
||||
compose += '+Ctrl';
|
||||
hasModifierKey = true;
|
||||
}
|
||||
if (e.shiftKey && e.keyCode !== 16) {
|
||||
compose += '+Shift';
|
||||
hasModifierKey = true;
|
||||
}
|
||||
if (e.altKey && e.keyCode !== 18) {
|
||||
compose += '+Option';
|
||||
hasModifierKey = true;
|
||||
}
|
||||
if (e.metaKey && e.keyCode !== 93) {
|
||||
compose += '+Command';
|
||||
hasModifierKey = true;
|
||||
}
|
||||
|
||||
// 只有当有修饰键时才添加普通键
|
||||
if (hasModifierKey) {
|
||||
compose += '+' + keycodes[e.keyCode].toUpperCase();
|
||||
compose = compose.substring(1);
|
||||
state.shortCut[key] = compose;
|
||||
} else {
|
||||
// 不做处理
|
||||
}
|
||||
};
|
||||
|
||||
const changeGlobalKey = (e, index) => {
|
||||
let compose = '';
|
||||
// 添加是否包含功能键的判断
|
||||
let incluFuncKeys = false;
|
||||
if (e.ctrlKey && e.keyCode !== 17) {
|
||||
compose += '+Ctrl';
|
||||
incluFuncKeys = true;
|
||||
}
|
||||
if (e.shiftKey && e.keyCode !== 16) {
|
||||
compose += '+Shift';
|
||||
incluFuncKeys = true;
|
||||
}
|
||||
if (e.altKey && e.keyCode !== 18) {
|
||||
compose += '+Option';
|
||||
incluFuncKeys = true;
|
||||
}
|
||||
if (e.metaKey && e.keyCode !== 93) {
|
||||
compose += '+Command';
|
||||
incluFuncKeys = true;
|
||||
}
|
||||
compose += '+' + keycodes[e.keyCode].toUpperCase();
|
||||
compose = compose.substring(1);
|
||||
if (
|
||||
incluFuncKeys &&
|
||||
e.keyCode !== 16 &&
|
||||
e.keyCode !== 17 &&
|
||||
e.keyCode !== 18 &&
|
||||
e.keyCode !== 93
|
||||
) {
|
||||
state.global[index].key = compose;
|
||||
} else {
|
||||
// 不做处理
|
||||
}
|
||||
// f1 - f12
|
||||
if (!incluFuncKeys && e.keyCode >= 112 && e.keyCode <= 123) {
|
||||
compose = keycodes[e.keyCode].toUpperCase();
|
||||
state.global[index].key = compose;
|
||||
}
|
||||
};
|
||||
|
||||
const resetDefault = (key) => {
|
||||
switch (key) {
|
||||
case 'Alt':
|
||||
state.shortCut['showAndHidden'] = 'Option+SPACE';
|
||||
// copyValue.value = "Option+SPACE";
|
||||
break;
|
||||
case 'Ctrl':
|
||||
state.shortCut['showAndHidden'] = 'Ctrl+SPACE';
|
||||
// copyValue.value = "Ctrl+SPACE";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
setConfig();
|
||||
};
|
||||
|
||||
const changeGlobalValue = (index, value) => {
|
||||
state.global[index].value = value;
|
||||
};
|
||||
|
||||
const deleteGlobalKey = (e, index) => {
|
||||
state.global.splice(index, 1);
|
||||
// delete state.global[index];
|
||||
};
|
||||
|
||||
const addConfig = () => {
|
||||
state.global.push({
|
||||
key: '',
|
||||
value: '',
|
||||
});
|
||||
};
|
||||
|
||||
const { shortCut, common, local, global } = toRefs(state);
|
||||
|
||||
const options = ref([
|
||||
{
|
||||
value: 'zh-CN',
|
||||
label: t('feature.settings.basic.cn'),
|
||||
},
|
||||
{
|
||||
value: 'en-US',
|
||||
label: t('feature.settings.basic.en'),
|
||||
},
|
||||
]);
|
||||
|
||||
const changeLanguage = (value) => {
|
||||
state.common.lang = value.key;
|
||||
locale.value = value.key;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/assets/common.less';
|
||||
|
||||
.settings {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
background: var(--color-body-bg2);
|
||||
height: calc(~'100vh - 34px');
|
||||
.ant-menu-horizontal {
|
||||
border-bottom: 1px solid var(--color-border-light);
|
||||
}
|
||||
.view-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 16px;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.view-container {
|
||||
border-radius: 8px;
|
||||
background: var(--color-body-bg);
|
||||
overflow: auto;
|
||||
height: calc(~'100vh - 84px');
|
||||
}
|
||||
.ant-menu {
|
||||
background: var(--color-body-bg) !important;
|
||||
color: var(--color-text-content) !important;
|
||||
}
|
||||
|
||||
.settings-detail {
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
background: var(--color-body-bg);
|
||||
|
||||
.setting-item {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.ant-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: var(--ant-primary-color);
|
||||
font-size: 14px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.settings-item-li {
|
||||
padding-left: 20px;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.label {
|
||||
color: var(--color-text-content);
|
||||
}
|
||||
|
||||
.value {
|
||||
width: 300px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
border: 1px solid var(--color-border-light);
|
||||
color: var(--ant-primary-color);
|
||||
font-size: 14px;
|
||||
height: 24px;
|
||||
font-weight: lighter;
|
||||
background: var(--color-input-hover);
|
||||
.ant-input {
|
||||
text-align: center;
|
||||
color: var(--ant-primary-color);
|
||||
font-size: 14px;
|
||||
font-weight: lighter;
|
||||
background: var(--color-input-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-switch {
|
||||
&:not(.ant-switch-checked) {
|
||||
background: var(--color-list-hover);
|
||||
}
|
||||
}
|
||||
.ant-select-selector {
|
||||
background: var(--color-input-hover) !important;
|
||||
color: var(--color-text-content);
|
||||
}
|
||||
.ant-input-password-icon, .ant-select-arrow {
|
||||
color: var(--color-action-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.feature-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
|
||||
.item {
|
||||
flex: 1;
|
||||
color: var(--color-text-content);
|
||||
}
|
||||
|
||||
.short-cut {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.value {
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
border: 1px solid var(--color-border-light);
|
||||
color: var(--ant-primary-color);
|
||||
font-size: 14px;
|
||||
height: 24px;
|
||||
font-weight: lighter;
|
||||
margin-top: 10px;
|
||||
position: relative;
|
||||
background: var(--color-input-hover);
|
||||
|
||||
.ant-input {
|
||||
color: var(--ant-primary-color);
|
||||
font-weight: lighter;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.anticon {
|
||||
color: var(--color-text-desc);
|
||||
}
|
||||
|
||||
&.ant-input-affix-wrapper {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.anticon {
|
||||
display: block;
|
||||
color: var(--color-text-content);
|
||||
}
|
||||
}
|
||||
|
||||
.anticon {
|
||||
position: absolute;
|
||||
display: none;
|
||||
right: 4px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-global {
|
||||
color: var(--ant-primary-color);
|
||||
margin-top: 20px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ant-collapse {
|
||||
background: var(--color-input-hover);
|
||||
.ant-collapse-content {
|
||||
background: var(--color-input-hover);
|
||||
color: var(--color-text-content);
|
||||
}
|
||||
|
||||
h3,
|
||||
.ant-collapse-header,
|
||||
.ant-list-item-meta-title {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.ant-list-item-meta-description {
|
||||
color: var(--color-text-desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,103 @@
|
||||
export default {
|
||||
0: "That key has no keycode",
|
||||
3: "break",
|
||||
8: "backspace / delete",
|
||||
9: "tab",
|
||||
12: "clear",
|
||||
13: "enter",
|
||||
16: "shift",
|
||||
17: "ctrl",
|
||||
18: "alt",
|
||||
19: "pause/break",
|
||||
20: "caps lock",
|
||||
21: "hangul",
|
||||
25: "hanja",
|
||||
27: "escape",
|
||||
28: "conversion",
|
||||
29: "non-conversion",
|
||||
32: "space",
|
||||
33: "page up",
|
||||
34: "page down",
|
||||
35: "End",
|
||||
36: "Home",
|
||||
37: "Left",
|
||||
38: "Up",
|
||||
39: "Right",
|
||||
40: "Down",
|
||||
45: "Insert",
|
||||
46: "Delete",
|
||||
48: "0",
|
||||
49: "1",
|
||||
50: "2",
|
||||
51: "3",
|
||||
52: "4",
|
||||
53: "5",
|
||||
54: "6",
|
||||
55: "7",
|
||||
56: "8",
|
||||
57: "9",
|
||||
65: "A",
|
||||
66: "B",
|
||||
67: "C",
|
||||
68: "D",
|
||||
69: "E",
|
||||
70: "F",
|
||||
71: "G",
|
||||
72: "H",
|
||||
73: "I",
|
||||
74: "J",
|
||||
75: "K",
|
||||
76: "L",
|
||||
77: "M",
|
||||
78: "N",
|
||||
79: "O",
|
||||
80: "P",
|
||||
81: "Q",
|
||||
82: "R",
|
||||
83: "S",
|
||||
84: "T",
|
||||
85: "U",
|
||||
86: "V",
|
||||
87: "W",
|
||||
88: "X",
|
||||
89: "Y",
|
||||
90: "Z",
|
||||
112: "F1",
|
||||
113: "F2",
|
||||
114: "F3",
|
||||
115: "F4",
|
||||
116: "F5",
|
||||
117: "F6",
|
||||
118: "F7",
|
||||
119: "F8",
|
||||
120: "F9",
|
||||
121: "F10",
|
||||
122: "F11",
|
||||
123: "F12",
|
||||
186: ";",
|
||||
187: "=",
|
||||
188: ",",
|
||||
189: "-",
|
||||
190: ".",
|
||||
191: "/",
|
||||
192: "`",
|
||||
219: "[",
|
||||
220: "\\",
|
||||
221: "]",
|
||||
222: "'",
|
||||
223: "`",
|
||||
224: "left or right ⌘ key (firefox)",
|
||||
225: "altgr",
|
||||
226: "< /git >, left back slash",
|
||||
230: "GNOME Compose Key",
|
||||
231: "ç",
|
||||
233: "XF86Forward",
|
||||
234: "XF86Back",
|
||||
235: "non-conversion",
|
||||
240: "alphanumeric",
|
||||
242: "hiragana/katakana",
|
||||
243: "half-width/full-width",
|
||||
244: "kanji",
|
||||
251: "unlock trackpad (Chrome/Edge)",
|
||||
255: "toggle touchpad"
|
||||
};
|
||||
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<div class="file-container" @drop.prevent="dropFile" @dragenter="checkDrop" @dragover="checkDrop">
|
||||
<a-alert message="可拖放文件夹到这里加入启动" type="info" show-icon />
|
||||
<a-list item-layout="horizontal" :data-source="localStartList">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<template #actions>
|
||||
<a key="list-loadmore-edit" @click="() => remove(item)">移除</a>
|
||||
</template>
|
||||
<a-list-item-meta :description="item.desc">
|
||||
<template #title>
|
||||
<div>
|
||||
<span :class="item.del ? 'del-title' : ''">{{item.name}}</span>
|
||||
<span v-if="item.del" class="has-del">文件不存在</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #avatar>
|
||||
<a-avatar shape="square" :src="item.icon" />
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
const fs = window.require('fs');
|
||||
const process = window.require('process');
|
||||
|
||||
const dbId = 'rubick-local-start-app';
|
||||
|
||||
const localStartList = ref(window.rubick.dbStorage.getItem(dbId) || []);
|
||||
|
||||
const checkFileExists = () => {
|
||||
localStartList.value = localStartList.value.map((plugin) => {
|
||||
if (!fs.existsSync(plugin.desc)) {
|
||||
return {
|
||||
...plugin,
|
||||
del: true,
|
||||
};
|
||||
}
|
||||
return plugin;
|
||||
});
|
||||
};
|
||||
|
||||
checkFileExists();
|
||||
|
||||
const dropFile = (e) => {
|
||||
const files = Array.from(e.dataTransfer.files).map((file) => {
|
||||
const action =
|
||||
process.platform === 'win32'
|
||||
? `start "dummyclient" "${file.path}"`
|
||||
: `open ${file.path.replace(/ /g, '\\ ')}`;
|
||||
const plugin = {
|
||||
icon: window.rubick.getFileIcon(file.path),
|
||||
value: 'plugin',
|
||||
desc: file.path,
|
||||
pluginType: 'app',
|
||||
name: file.name,
|
||||
action,
|
||||
keyWords: [file.name],
|
||||
names: [file.name],
|
||||
};
|
||||
window.market.addLocalStartPlugin(plugin);
|
||||
return plugin;
|
||||
});
|
||||
localStartList.value = [
|
||||
...localStartList.value,
|
||||
...files,
|
||||
];
|
||||
window.rubick.dbStorage.setItem(
|
||||
dbId,
|
||||
JSON.parse(JSON.stringify(localStartList.value))
|
||||
);
|
||||
};
|
||||
|
||||
const remove = (item) => {
|
||||
localStartList.value = localStartList.value.filter(
|
||||
(app) => app.desc !== item.desc
|
||||
);
|
||||
window.rubick.dbStorage.setItem(
|
||||
dbId,
|
||||
JSON.parse(JSON.stringify(localStartList.value))
|
||||
);
|
||||
window.market.removeLocalStartPlugin(JSON.parse(JSON.stringify(item)));
|
||||
};
|
||||
|
||||
const checkDrop = (e) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.file-container {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
background: var(--color-body-bg);
|
||||
height: calc(~'100vh - 180px');
|
||||
.del-title {
|
||||
text-decoration-line: line-through;
|
||||
text-decoration-color: var(--ant-error-color);
|
||||
}
|
||||
.has-del {
|
||||
color: var(--ant-error-color);
|
||||
font-size: 12px;
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<a-alert
|
||||
:message="$t('feature.settings.intranet.tips')"
|
||||
type="warning"
|
||||
style="margin-bottom: 20px"
|
||||
/>
|
||||
<a-form
|
||||
name="custom-validation"
|
||||
ref="formRef"
|
||||
:model="formState"
|
||||
:rules="rules"
|
||||
v-bind="layout"
|
||||
>
|
||||
<a-form-item
|
||||
has-feedback
|
||||
:label="$t('feature.settings.intranet.npmMirror')"
|
||||
name="register"
|
||||
>
|
||||
<a-input
|
||||
placeholder="https://registry.npmmirror.com"
|
||||
v-model:value="formState.register"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
has-feedback
|
||||
:label="$t('feature.settings.intranet.dbUrl')"
|
||||
name="database"
|
||||
>
|
||||
<a-input
|
||||
placeholder="https://gitee.com/monkeyWang/rubickdatabase/raw/master"
|
||||
v-model:value="formState.database"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
has-feedback
|
||||
:label="$t('feature.settings.intranet.accessToken')"
|
||||
name="access_token"
|
||||
>
|
||||
<a-input
|
||||
:placeholder="$t('feature.settings.intranet.placeholder')"
|
||||
v-model:value="formState.access_token"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :wrapper-col="{ span: 18, offset: 6 }">
|
||||
<a-button @click="submit" type="primary">确定</a-button>
|
||||
<a-button style="margin-left: 10px" @click="resetForm">恢复默认</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, toRaw } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
let _rev: any;
|
||||
|
||||
let defaultConfig = {
|
||||
register: 'https://registry.npmmirror.com',
|
||||
database: 'https://gitee.com/monkeyWang/rubickdatabase/raw/master',
|
||||
access_token: '',
|
||||
};
|
||||
|
||||
try {
|
||||
const dbdata = window.rubick.db.get('rubick-localhost-config');
|
||||
defaultConfig = dbdata.data;
|
||||
_rev = dbdata._rev;
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
const formState = ref(JSON.parse(JSON.stringify(defaultConfig)));
|
||||
|
||||
const rules = {
|
||||
register: [{ required: true, trigger: 'change' }],
|
||||
database: [{ required: true, trigger: 'change' }],
|
||||
};
|
||||
const layout = {
|
||||
labelCol: { span: 6 },
|
||||
wrapperCol: { span: 18 },
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
formState.value = {
|
||||
register: 'https://registry.npmmirror.com',
|
||||
database: 'https://gitee.com/monkeyWang/rubickdatabase/raw/master',
|
||||
access_token: '',
|
||||
};
|
||||
};
|
||||
|
||||
const submit = () => {
|
||||
const changeData: any = {
|
||||
_id: 'rubick-localhost-config',
|
||||
data: toRaw(formState.value),
|
||||
};
|
||||
|
||||
if (_rev) {
|
||||
changeData._rev = _rev;
|
||||
}
|
||||
|
||||
window.rubick.db.put(changeData);
|
||||
message.success('设置成功!重启插件市场后生效!');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ant-form {
|
||||
:deep(.ant-form-item) {
|
||||
label {
|
||||
color: var(--color-text-content);
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.ant-input) {
|
||||
background: var(--color-input-hover);
|
||||
color: var(--color-text-content);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<p>{{ $t('feature.settings.superPanel.tips') }}</p>
|
||||
<div class="super-list-item panel-item">
|
||||
<a-list
|
||||
:grid="{ gutter: 16, column: 2 }"
|
||||
:data-source="localPlugins.filter((item) => !!item)"
|
||||
>
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item v-if="item">
|
||||
<template #actions>
|
||||
<a-button
|
||||
v-if="!hasAdded(item)"
|
||||
@click="addPluginToSuperPanel(item)"
|
||||
style="color: #7ec699"
|
||||
type="text"
|
||||
>
|
||||
{{ $t('feature.settings.superPanel.add') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
v-else
|
||||
@click="removePluginToSuperPanel(item)"
|
||||
style="color: #ff4ea4"
|
||||
type="text"
|
||||
>
|
||||
{{ $t('feature.settings.superPanel.remove') }}
|
||||
</a-button>
|
||||
</template>
|
||||
<a-list-item-meta>
|
||||
<template #description>
|
||||
<span class="ellipse">{{ item.description }}</span>
|
||||
</template>
|
||||
<template #title>
|
||||
<span class="ellipse">{{ item.pluginName }}</span>
|
||||
</template>
|
||||
<template #avatar>
|
||||
<a-avatar :src="item.logo" />
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { useStore } from 'vuex';
|
||||
import { computed, ref, toRaw } from 'vue';
|
||||
|
||||
const store = useStore();
|
||||
const localPlugins = computed(() =>
|
||||
store.state.localPlugins.filter(
|
||||
(plugin) =>
|
||||
plugin.name !== 'rubick-system-feature' &&
|
||||
plugin.name !== 'rubick-system-super-panel'
|
||||
)
|
||||
);
|
||||
|
||||
const hasAdded = (plugin) => {
|
||||
let added = false;
|
||||
superPanelPlugins.value.data.some((item) => {
|
||||
if (item.name === plugin.name) {
|
||||
added = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return added;
|
||||
};
|
||||
|
||||
const superPanelPlugins = ref(
|
||||
window.rubick.db.get('super-panel-db') || {
|
||||
data: [],
|
||||
_id: 'super-panel-db',
|
||||
}
|
||||
);
|
||||
|
||||
const addPluginToSuperPanel = (plugin) => {
|
||||
superPanelPlugins.value.data.push(toRaw(plugin));
|
||||
window.rubick.db.put(toRaw(superPanelPlugins.value));
|
||||
};
|
||||
|
||||
const removePluginToSuperPanel = (plugin) => {
|
||||
superPanelPlugins.value.data = toRaw(superPanelPlugins.value).data.filter(
|
||||
(item) => {
|
||||
return item.name !== plugin.name;
|
||||
}
|
||||
);
|
||||
window.rubick.db.put(toRaw(superPanelPlugins.value));
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
p {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.super-list-item.panel-item {
|
||||
&:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<div class="user-info">
|
||||
<div class="settings-container">
|
||||
<div class="setting-item">
|
||||
<div class="title">
|
||||
{{ $t('feature.settings.account.themeColor') }}
|
||||
</div>
|
||||
<div class="settings-item-li">
|
||||
<a-radio-group @change="changeTheme" v-model:value="theme" button-style="solid">
|
||||
<a-radio-button value="SPRING">{{ $t('feature.settings.account.spring') }}</a-radio-button>
|
||||
<a-radio-button value="SUMMER">{{ $t('feature.settings.account.summer') }}</a-radio-button>
|
||||
<a-radio-button value="AUTUMN">{{ $t('feature.settings.account.autumn') }}</a-radio-button>
|
||||
<a-radio-button value="WINTER">{{ $t('feature.settings.account.winter') }}</a-radio-button>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="title">
|
||||
{{ $t('feature.settings.account.personalized') }}
|
||||
</div>
|
||||
<div class="settings-item-li">
|
||||
<div class="label">
|
||||
{{ $t('feature.settings.account.greeting') }}
|
||||
</div>
|
||||
<a-input v-model:value="custom.placeholder" class="value"></a-input>
|
||||
</div>
|
||||
<div class="settings-item-li">
|
||||
<div class="label">
|
||||
{{ $t('feature.settings.account.name') }}
|
||||
</div>
|
||||
<a-input v-model:value="custom.username" class="value"></a-input>
|
||||
</div>
|
||||
<div class="settings-item-li">
|
||||
<div class="label">
|
||||
{{ $t('feature.settings.account.logo') }}
|
||||
</div>
|
||||
<div class="img-container">
|
||||
<img class="custom-img" :src="custom.logo" />
|
||||
<a-button class="btn" @click="changeLogo" shape="round" size="small" type="primary">
|
||||
{{ $t('feature.settings.account.replace') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, toRefs, watch, ref } from 'vue';
|
||||
import debounce from 'lodash.debounce';
|
||||
import localConfig from '@/confOp';
|
||||
import * as Themes from '@/assets/constans';
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
|
||||
const state = reactive({
|
||||
custom: {},
|
||||
});
|
||||
|
||||
const { perf } = localConfig.getConfig();
|
||||
|
||||
const theme = ref(perf.custom.theme);
|
||||
|
||||
state.custom = perf.custom || {};
|
||||
|
||||
const setConfig = debounce(() => {
|
||||
const { perf } = localConfig.getConfig();
|
||||
|
||||
localConfig.setConfig(
|
||||
JSON.parse(
|
||||
JSON.stringify({
|
||||
perf: {
|
||||
...perf,
|
||||
custom: state.custom,
|
||||
},
|
||||
})
|
||||
)
|
||||
);
|
||||
ipcRenderer.send('re-register');
|
||||
}, 500);
|
||||
|
||||
watch(state, setConfig);
|
||||
const { custom } = toRefs(state);
|
||||
|
||||
const changeLogo = () => {
|
||||
const [logoPath] = window.rubick.showOpenDialog({
|
||||
title: '请选择 logo 路径',
|
||||
filters: [{ name: 'images', extensions: ['png'] }],
|
||||
properties: ['openFile'],
|
||||
});
|
||||
state.custom.logo = `file://${logoPath}`;
|
||||
};
|
||||
|
||||
const changeTheme = () => {
|
||||
state.custom = {
|
||||
...state.custom,
|
||||
...Themes[theme.value],
|
||||
};
|
||||
};
|
||||
|
||||
// const reset = () => {
|
||||
// Modal.warning({
|
||||
// title: '确定恢复默认设置吗?',
|
||||
// content: '回复后之前的设置将会被清空',
|
||||
// onOk() {
|
||||
// const defaultcustom = remote.getGlobal('OP_CONFIG').getDefaultConfig()
|
||||
// .perf.custom;
|
||||
// state.custom = JSON.parse(JSON.stringify(defaultcustom));
|
||||
// },
|
||||
// });
|
||||
// };
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.ant-radio-button-wrapper {
|
||||
background: var(--color-body-bg);
|
||||
color: var(--color-text-content);
|
||||
}
|
||||
.user-info-result {
|
||||
padding: 0;
|
||||
.theme-preview {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 100%;
|
||||
}
|
||||
&.ant-result {
|
||||
padding: 24px;
|
||||
}
|
||||
.icon {
|
||||
font-size: 48px;
|
||||
}
|
||||
.ant-result-icon {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.ant-result-title {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
.img-container {
|
||||
width: 300px;
|
||||
.btn {
|
||||
margin-left: 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
.custom-img {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.footer-btn {
|
||||
text-align: right;
|
||||
border-top: 1px dashed #ddd;
|
||||
padding-top: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<div class="account">
|
||||
<!-- todo 暂时先去掉登录,等小程序做好了再加回来吧 -->
|
||||
<!-- <a-result-->
|
||||
<!-- v-if="!userInfo"-->
|
||||
<!-- title="请先登录"-->
|
||||
<!-- sub-title="登录后可开启用户个性化设置"-->
|
||||
<!-- >-->
|
||||
<!-- <template #extra>-->
|
||||
<!-- <a-button @click="showModal" type="primary">-->
|
||||
<!-- 使用微信小程序登录-->
|
||||
<!-- </a-button>-->
|
||||
<!-- </template>-->
|
||||
<!-- </a-result>-->
|
||||
<Index />
|
||||
<a-modal :footer="null" v-model:visible="visible">
|
||||
<a-result
|
||||
title="请使用微信扫码登录!"
|
||||
sub-title="使用微信扫描上面的 rubick 小程序二维码进行授权登录"
|
||||
>
|
||||
<template #icon>
|
||||
<img width="200" :src="imgCode" />
|
||||
</template>
|
||||
</a-result>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { nanoid } from 'nanoid';
|
||||
import { ref, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import Index from './index';
|
||||
import service from '../../assets/service';
|
||||
|
||||
const userInfo = ref(window.rubick.dbStorage.getItem('rubick-user-info'));
|
||||
|
||||
const imgCode = ref('');
|
||||
const scene = nanoid();
|
||||
|
||||
const visible = ref(false);
|
||||
const showModal = () => {
|
||||
visible.value = true;
|
||||
if (!imgCode.value && !userInfo.value) {
|
||||
service.getScanCode({ scene }).then((res) => {
|
||||
imgCode.value = `data:image/png;base64,${res.dataUrl}`;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let timer = null;
|
||||
watch([visible], () => {
|
||||
if (visible.value) {
|
||||
timer = setInterval(() => {
|
||||
service.checkLoginStatus({ scene }).then((res) => {
|
||||
console.log(res);
|
||||
if (res.openId) {
|
||||
window.rubick.dbStorage.setItem('rubick-user-info', res);
|
||||
userInfo.value = res;
|
||||
message.success('登录成功!');
|
||||
visible.value = false;
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
});
|
||||
}, 2000);
|
||||
} else {
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.account {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
background: var(--color-body-bg);
|
||||
:deep(.ant-result-title) {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
:deep(.ant-result-subtitle) {
|
||||
color: var(--color-text-desc);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"importHelpers": true,
|
||||
"moduleResolution": "node",
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"types": [
|
||||
"webpack-env"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue",
|
||||
"tests/**/*.ts",
|
||||
"tests/**/*.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
css: {
|
||||
// 配置css模块
|
||||
loaderOptions: {
|
||||
// 向预处理器 Loader 传递配置选项
|
||||
less: {
|
||||
// 配置less(其他样式解析用法一致)
|
||||
lessOptions: {
|
||||
javascriptEnabled: true, // 设置为true
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
productionSourceMap: false,
|
||||
outputDir: path.join(__dirname, '../public/feature'),
|
||||
publicPath: process.env.NODE_ENV === 'production' ? '' : '/',
|
||||
// chainWebpack: (config) => {
|
||||
// // 查看打包文件体积大小
|
||||
// config
|
||||
// .plugin('webpack-bundle-analyzer')
|
||||
// .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin);
|
||||
// },
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@@ -0,0 +1,24 @@
|
||||
# guide
|
||||
|
||||
## Project setup
|
||||
```
|
||||
yarn install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
yarn serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
yarn build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
yarn lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "esnext",
|
||||
"baseUrl": "./",
|
||||
"moduleResolution": "node",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "guide",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve --port 8084",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": "^3.8.3",
|
||||
"vue": "^3.2.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.16",
|
||||
"@babel/eslint-parser": "^7.12.16",
|
||||
"@vue/cli-plugin-babel": "~5.0.0",
|
||||
"@vue/cli-plugin-eslint": "~5.0.0",
|
||||
"@vue/cli-service": "~5.0.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-vue": "^8.0.3"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/vue3-essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "@babel/eslint-parser"
|
||||
},
|
||||
"rules": {}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead",
|
||||
"not ie 11"
|
||||
],
|
||||
"volta": {
|
||||
"node": "16.20.2"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 4.2 KiB |