登录验证码使用
52
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@ -0,0 +1,52 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="myValues">
|
||||
<value>
|
||||
<list size="7">
|
||||
<item index="0" class="java.lang.String" itemvalue="nobr" />
|
||||
<item index="1" class="java.lang.String" itemvalue="noembed" />
|
||||
<item index="2" class="java.lang.String" itemvalue="comment" />
|
||||
<item index="3" class="java.lang.String" itemvalue="noscript" />
|
||||
<item index="4" class="java.lang.String" itemvalue="embed" />
|
||||
<item index="5" class="java.lang.String" itemvalue="script" />
|
||||
<item index="6" class="java.lang.String" itemvalue="testb" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
<option name="myCustomValuesEnabled" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="HttpUrlsUsage" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="ignoredUrls">
|
||||
<list>
|
||||
<option value="http://localhost" />
|
||||
<option value="http://127.0.0.1" />
|
||||
<option value="http://0.0.0.0" />
|
||||
<option value="http://www.w3.org/" />
|
||||
<option value="http://json-schema.org/draft" />
|
||||
<option value="http://java.sun.com/" />
|
||||
<option value="http://xmlns.jcp.org/" />
|
||||
<option value="http://javafx.com/javafx/" />
|
||||
<option value="http://javafx.com/fxml" />
|
||||
<option value="http://maven.apache.org/xsd/" />
|
||||
<option value="http://maven.apache.org/POM/" />
|
||||
<option value="http://www.springframework.org/schema/" />
|
||||
<option value="http://www.springframework.org/tags" />
|
||||
<option value="http://www.springframework.org/security/tags" />
|
||||
<option value="http://www.thymeleaf.org" />
|
||||
<option value="http://www.jboss.org/j2ee/schema/" />
|
||||
<option value="http://www.jboss.com/xml/ns/" />
|
||||
<option value="http://www.ibm.com/webservices/xsd" />
|
||||
<option value="http://activemq.apache.org/schema/" />
|
||||
<option value="http://schema.cloudfoundry.org/spring/" />
|
||||
<option value="http://schemas.xmlsoap.org/" />
|
||||
<option value="http://cxf.apache.org/schemas/" />
|
||||
<option value="http://primefaces.org/ui" />
|
||||
<option value="http://tiles.apache.org/" />
|
||||
<option value="http://${process.env[" />
|
||||
</list>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
10
client-pc/.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
.debug.env
|
||||
|
||||
tmp
|
||||
**/.tmp
|
||||
release
|
13
client-pc/CHANGELOG.md
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
## 22-01-27
|
||||
- Refactor the scripts part.
|
||||
- Remove `configs` directory.
|
||||
|
||||
## 21-11-11
|
||||
- Refactor the project. Use vite.config.ts build `Main-process`, `Preload-script` and `Renderer-process` alternative rollup.
|
||||
- Scenic `Vue>=3.2.13`, `@vue/compiler-sfc` is no longer necessary.
|
||||
- If you prefer Rollup, Use rollup branch.
|
||||
|
||||
```bash
|
||||
Error: @vitejs/plugin-vue requires vue (>=3.2.13) or @vue/compiler-sfc to be present in the dependency tree.
|
||||
```
|
24
client-pc/Dockerfile
Normal file
@ -0,0 +1,24 @@
|
||||
# use the version that corresponds to your electron version
|
||||
FROM node:14.16
|
||||
|
||||
LABEL NAME="electron-wrapper"
|
||||
LABEL RUN="docker run --rm -it electron-wrapper bash"
|
||||
|
||||
# install electron dependencies or more if your library has other dependencies
|
||||
RUN apt-get update && apt-get install \
|
||||
git libx11-xcb1 libxcb-dri3-0 libxtst6 libnss3 libatk-bridge2.0-0 libgtk-3-0 libxss1 libasound2 \
|
||||
-yq --no-install-suggests --no-install-recommends \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# copy the source into /app
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN chown -R node /app
|
||||
|
||||
# install node modules and perform an electron rebuild
|
||||
USER node
|
||||
RUN npm install
|
||||
RUN npm run build
|
||||
|
||||
USER node
|
||||
CMD bash
|
74
client-pc/README.md
Normal file
@ -0,0 +1,74 @@
|
||||
# electron-vite-vue
|
||||
|
||||
[](https://github.com/vitejs/awesome-vite)
|
||||
[](https://app.netlify.com/sites/electron-vite/deploys)
|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
**English | [简体中文](README.zh-CN.md)**
|
||||
|
||||
🥳 Real simple `Electron` + `Vue` + `Vite` boilerplate.
|
||||
|
||||
## Overview
|
||||
|
||||
📦 Out of the box
|
||||
💪 Support C/C++ addons
|
||||
🔩 Support Use Electron、Node.js API in Renderer-process
|
||||
🌱 Simple directory structure,real flexible
|
||||
🖥 It's easy to implement multiple windows
|
||||
|
||||
## Quick Start
|
||||
|
||||
```sh
|
||||
npm create electron-vite
|
||||
```
|
||||
|
||||
<!-- [](https://asciinema.org/a/483731) -->
|
||||
|
||||

|
||||
|
||||
## Debug
|
||||
|
||||

|
||||
|
||||
## Directory
|
||||
|
||||
A `dist` folder will be generated everytime when `dev` or `build` command is executed. File structure of `dist` is identical to the `packages` directory to avoid any potential path calculation errors.
|
||||
|
||||
```tree
|
||||
├── dist Will be generated following the structure of "packages" directory
|
||||
| ├── main
|
||||
| ├── preload
|
||||
| └── renderer
|
||||
|
|
||||
├── scripts
|
||||
| ├── build.mjs Build script -> npm run build
|
||||
| └── watch.mjs Develop script -> npm run dev
|
||||
|
|
||||
├── packages
|
||||
| ├── main Main-process source code
|
||||
| | └── vite.config.ts
|
||||
| ├── preload Preload-script source code
|
||||
| | └── vite.config.ts
|
||||
| └── renderer Renderer-process source code
|
||||
| └── vite.config.ts
|
||||
```
|
||||
|
||||
## List the modules you may use as far as possible
|
||||
|
||||
Used in `Main-process` 👉 [electron-vite-boilerplate](https://github.com/caoxiemeihao/electron-vite-boilerplate)
|
||||
|
||||
Used in `Renderer-process` 👉 [electron-vite-boilerplate/tree/nodeIntegration](https://github.com/caoxiemeihao/electron-vite-boilerplate/tree/nodeIntegration)
|
||||
|
||||
**ES Modules**
|
||||
|
||||
- [execa](https://www.npmjs.com/package/execa)
|
||||
- [node-fetch](https://www.npmjs.com/package/node-fetch)
|
||||
- [file-type](https://www.npmjs.com/package/file-type)
|
||||
|
||||
**Native Addons(C/C++)**
|
||||
|
||||
- [sqlite3](https://www.npmjs.com/package/sqlite3)
|
||||
- [serialport](https://www.npmjs.com/package/serialport)
|
71
client-pc/README.zh-CN.md
Normal file
@ -0,0 +1,71 @@
|
||||
# electron-vite-vue
|
||||
|
||||
[](https://github.com/vitejs/awesome-vite)
|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
**[English](README.md) | 简体中文**
|
||||
|
||||
🥳 Electron + Vite + Vue 整合模板 -- **结构简单,容易上手!**
|
||||
|
||||
## 概述
|
||||
|
||||
📦 开箱即用
|
||||
💪 支持 C/C++ 模块
|
||||
🔩 支持在渲染进程中使用 Electron、Node.js API
|
||||
🌱 结构清晰,可塑性强
|
||||
🖥 很容易实现多窗口
|
||||
|
||||
## 快速开始
|
||||
|
||||
```sh
|
||||
npm create electron-vite
|
||||
```
|
||||
|
||||
<!-- [](https://asciinema.org/a/483731) -->
|
||||
|
||||

|
||||
|
||||
## 调试
|
||||
|
||||

|
||||
|
||||
## 目录结构
|
||||
|
||||
```tree
|
||||
├── dist 构建后,根据 packages 目录生成
|
||||
| ├── main
|
||||
| ├── preload
|
||||
| └── renderer
|
||||
|
|
||||
├── scripts
|
||||
| ├── build.mjs 项目开发脚本 npm run build
|
||||
| └── watch.mjs 项目开发脚本 npm run dev
|
||||
|
|
||||
├── packages
|
||||
| ├── main 主进程源码
|
||||
| | └── vite.config.ts
|
||||
| ├── preload 预加载脚本源码
|
||||
| | └── vite.config.ts
|
||||
| └── renderer 渲染进程源码
|
||||
| └── vite.config.ts
|
||||
```
|
||||
|
||||
## 一些常见的案例
|
||||
|
||||
在 Main-process 中使用 👉 [electron-vite-boilerplate](https://github.com/caoxiemeihao/electron-vite-boilerplate)
|
||||
|
||||
在 Renderer-process 中使用 👉 [electron-vite-boilerplate/tree/nodeIntegration](https://github.com/caoxiemeihao/electron-vite-boilerplate/tree/nodeIntegration)
|
||||
|
||||
**ES Modules**
|
||||
|
||||
- [execa](https://www.npmjs.com/package/execa)
|
||||
- [node-fetch](https://www.npmjs.com/package/node-fetch)
|
||||
- [file-type](https://www.npmjs.com/package/file-type)
|
||||
|
||||
**Native Addons(C/C++)**
|
||||
|
||||
- [sqlite3](https://www.npmjs.com/package/sqlite3)
|
||||
- [serialport](https://www.npmjs.com/package/serialport)
|
36
client-pc/electron-builder.json5
Normal file
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* @see https://www.electron.build/configuration/configuration
|
||||
*/
|
||||
{
|
||||
"appId": "YourAppID",
|
||||
"asar": true,
|
||||
"directories": {
|
||||
"output": "release/${version}"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"mac": {
|
||||
"artifactName": "${productName}_${version}.${ext}",
|
||||
"target": [
|
||||
"dmg"
|
||||
]
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
{
|
||||
"target": "nsis",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"artifactName": "${productName}_${version}.${ext}"
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
"perMachine": false,
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"deleteAppDataOnUninstall": false
|
||||
}
|
||||
}
|
7
client-pc/nano-staged.mjs
Normal file
@ -0,0 +1,7 @@
|
||||
export default {
|
||||
// eslint
|
||||
'*.{js,ts,tsx,vue}': 'eslint --cache --fix',
|
||||
// typecheck
|
||||
'packages/renderer/**/{*.ts,*.tsx,*.vue,tsconfig.json}': ({ filenames }) =>
|
||||
'npm run typecheck',
|
||||
}
|
11216
client-pc/package-lock.json
generated
Normal file
51
client-pc/package.json
Normal file
@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "electron-vue-vite",
|
||||
"version": "1.0.0",
|
||||
"main": "dist/main/index.cjs",
|
||||
"author": "草鞋没号 <308487730@qq.com>",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"client_dev": "node scripts/watch.mjs",
|
||||
"prebuild": "vue-tsc --noEmit --p packages/renderer/tsconfig.json && node scripts/build.mjs",
|
||||
"build": "electron-builder",
|
||||
"init": "git config core.hooksPath .git/hooks/ && rm -rf .git/hooks && npx simple-git-hooks",
|
||||
"test:e2e": "npx playwright test",
|
||||
"test:e2e:headless": "npx playwright test --headed"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.21.1",
|
||||
"@vitejs/plugin-vue": "^2.3.2",
|
||||
"electron": "18.2.0",
|
||||
"electron-builder": "^23.0.3",
|
||||
"nano-staged": "^0.8.0",
|
||||
"simple-git-hooks": "^2.7.0",
|
||||
"typescript": "^4.6.4",
|
||||
"vite": "^2.9.8",
|
||||
"vite-plugin-electron": "^0.4.4",
|
||||
"vite-plugin-resolve": "^2.1.1",
|
||||
"vue": "^3.2.33",
|
||||
"vue-tsc": "^0.34.11"
|
||||
},
|
||||
"env": {
|
||||
"VITE_DEV_SERVER_HOST": "127.0.0.1",
|
||||
"VITE_DEV_SERVER_PORT": 3344,
|
||||
"PORT": 3344
|
||||
},
|
||||
"keywords": [
|
||||
"electron",
|
||||
"rollup",
|
||||
"vite",
|
||||
"vue3",
|
||||
"vue"
|
||||
],
|
||||
"dependencies": {
|
||||
"ant-design-vue": "^3.2.3",
|
||||
"dayjs": "^1.11.2",
|
||||
"less": "^4.1.2",
|
||||
"vue-router": "^4.0.15",
|
||||
"vuex": "^4.0.2"
|
||||
}
|
||||
}
|
75
client-pc/packages/main/index.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import { app, BrowserWindow, shell } from 'electron'
|
||||
import { release } from 'os'
|
||||
import { join } from 'path'
|
||||
|
||||
// Disable GPU Acceleration for Windows 7
|
||||
if (release().startsWith('6.1')) app.disableHardwareAcceleration()
|
||||
|
||||
// Set application name for Windows 10+ notifications
|
||||
if (process.platform === 'win32') app.setAppUserModelId(app.getName())
|
||||
|
||||
if (!app.requestSingleInstanceLock()) {
|
||||
app.quit()
|
||||
process.exit(0)
|
||||
}
|
||||
process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'
|
||||
|
||||
let win: BrowserWindow | null = null
|
||||
|
||||
async function createWindow() {
|
||||
win = new BrowserWindow({
|
||||
title: '天牛网盘',
|
||||
minWidth:1000,
|
||||
minHeight:600,
|
||||
webPreferences: {
|
||||
preload: join(__dirname, '../preload/index.cjs'),
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
},
|
||||
})
|
||||
|
||||
if (app.isPackaged) {
|
||||
await win.loadFile(join(__dirname, '../renderer/index.html'))
|
||||
} else {
|
||||
// 🚧 Use ['ENV_NAME'] avoid vite:define plugin
|
||||
const url = `http://${process.env['VITE_DEV_SERVER_HOST']}:${process.env['VITE_DEV_SERVER_PORT']}`
|
||||
|
||||
await win.loadURL(url)
|
||||
// win.webContents.openDevTools()
|
||||
}
|
||||
|
||||
// Test active push message to Renderer-process
|
||||
win.webContents.on('did-finish-load', () => {
|
||||
win?.webContents.send('main-process-message', 'load content finish from main process!')
|
||||
})
|
||||
|
||||
// Make all links open with the browser, not with the application
|
||||
win.webContents.setWindowOpenHandler(({ url }) => {
|
||||
if (url.startsWith('https:')) shell.openExternal(url)
|
||||
return { action: 'deny' }
|
||||
})
|
||||
}
|
||||
|
||||
app.whenReady().then(createWindow)
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
win = null
|
||||
if (process.platform !== 'darwin') app.quit()
|
||||
})
|
||||
|
||||
app.on('second-instance', () => {
|
||||
if (win) {
|
||||
// Focus on the main window if the user tried to open another
|
||||
if (win.isMinimized()) win.restore()
|
||||
win.focus()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
const allWindows = BrowserWindow.getAllWindows()
|
||||
if (allWindows.length) {
|
||||
allWindows[0].focus()
|
||||
} else {
|
||||
createWindow()
|
||||
}
|
||||
})
|
26
client-pc/packages/main/vite.config.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { builtinModules } from 'module'
|
||||
import { defineConfig } from 'vite'
|
||||
import pkg from '../../package.json'
|
||||
|
||||
export default defineConfig({
|
||||
root: __dirname,
|
||||
build: {
|
||||
outDir: '../../dist/main',
|
||||
emptyOutDir: true,
|
||||
minify: process.env./* from mode option */NODE_ENV === 'production',
|
||||
sourcemap: true,
|
||||
lib: {
|
||||
entry: 'index.ts',
|
||||
formats: ['cjs'],
|
||||
fileName: () => '[name].cjs',
|
||||
},
|
||||
rollupOptions: {
|
||||
external: [
|
||||
'electron',
|
||||
...builtinModules,
|
||||
// @ts-ignore
|
||||
...Object.keys(pkg.dependencies || {}),
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
7
client-pc/packages/preload/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { domReady } from './utils'
|
||||
import { useLoading } from './loading'
|
||||
|
||||
const { appendLoading, removeLoading } = useLoading()
|
||||
window.removeLoading = removeLoading
|
||||
|
||||
domReady().then(appendLoading)
|
67
client-pc/packages/preload/loading.ts
Normal file
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* https://tobiasahlin.com/spinkit
|
||||
* https://connoratherton.com/loaders
|
||||
* https://projects.lukehaas.me/css-loaders
|
||||
* https://matejkustec.github.io/SpinThatShit
|
||||
*/
|
||||
export function useLoading() {
|
||||
const className = `loaders-css__square-spin`
|
||||
const styleContent = `
|
||||
@keyframes square-spin {
|
||||
25% { transform: perspective(100px) rotateX(180deg) rotateY(0); }
|
||||
50% { transform: perspective(100px) rotateX(180deg) rotateY(180deg); }
|
||||
75% { transform: perspective(100px) rotateX(0) rotateY(180deg); }
|
||||
100% { transform: perspective(100px) rotateX(0) rotateY(0); }
|
||||
}
|
||||
.${className} > div {
|
||||
animation-fill-mode: both;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background: #282c34;
|
||||
animation: square-spin 3s 0s cubic-bezier(0.09, 0.57, 0.49, 0.9) infinite;
|
||||
}
|
||||
.app-loading-wrap {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #fff;
|
||||
z-index: 9;
|
||||
}
|
||||
`
|
||||
const oStyle = document.createElement('style')
|
||||
const oDiv = document.createElement('div')
|
||||
|
||||
oStyle.id = 'app-loading-style'
|
||||
oStyle.innerHTML = styleContent
|
||||
oDiv.className = 'app-loading-wrap'
|
||||
oDiv.innerHTML = `<div class="${className}"><div></div></div>`
|
||||
|
||||
return {
|
||||
appendLoading() {
|
||||
safe.append(document.head, oStyle)
|
||||
safe.append(document.body, oDiv)
|
||||
},
|
||||
removeLoading() {
|
||||
safe.remove(document.head, oStyle)
|
||||
safe.remove(document.body, oDiv)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const safe = {
|
||||
append(parent: HTMLElement, child: HTMLElement) {
|
||||
if (!Array.from(parent.children).find(e => e === child)) {
|
||||
return parent.appendChild(child)
|
||||
}
|
||||
},
|
||||
remove(parent: HTMLElement, child: HTMLElement) {
|
||||
if (Array.from(parent.children).find(e => e === child)) {
|
||||
return parent.removeChild(child)
|
||||
}
|
||||
},
|
||||
}
|
15
client-pc/packages/preload/utils.ts
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
/** docoment ready */
|
||||
export function domReady(condition: DocumentReadyState[] = ['complete', 'interactive']) {
|
||||
return new Promise(resolve => {
|
||||
if (condition.includes(document.readyState)) {
|
||||
resolve(true)
|
||||
} else {
|
||||
document.addEventListener('readystatechange', () => {
|
||||
if (condition.includes(document.readyState)) {
|
||||
resolve(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
32
client-pc/packages/preload/vite.config.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { join } from 'path'
|
||||
import { builtinModules } from 'module'
|
||||
import { defineConfig } from 'vite'
|
||||
import pkg from '../../package.json'
|
||||
|
||||
export default defineConfig({
|
||||
root: __dirname,
|
||||
build: {
|
||||
outDir: '../../dist/preload',
|
||||
emptyOutDir: true,
|
||||
minify: process.env./* from mode option */NODE_ENV === 'production',
|
||||
// https://github.com/caoxiemeihao/electron-vue-vite/issues/61
|
||||
sourcemap: 'inline',
|
||||
rollupOptions: {
|
||||
input: {
|
||||
// multiple entry
|
||||
index: join(__dirname, 'index.ts'),
|
||||
},
|
||||
output: {
|
||||
format: 'cjs',
|
||||
entryFileNames: '[name].cjs',
|
||||
manualChunks: {},
|
||||
},
|
||||
external: [
|
||||
'electron',
|
||||
...builtinModules,
|
||||
// @ts-ignore
|
||||
...Object.keys(pkg.dependencies || {}),
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
14
client-pc/packages/renderer/index.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
|
||||
<title>天牛网盘</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
BIN
client-pc/packages/renderer/public/electron-vite-vue.gif
Normal file
After Width: | Height: | Size: 3.3 MiB |
BIN
client-pc/packages/renderer/public/favicon.ico
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
client-pc/packages/renderer/public/images/node.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
client-pc/packages/renderer/public/images/quick-start.gif
Normal file
After Width: | Height: | Size: 5.9 MiB |
30
client-pc/packages/renderer/src/App.vue
Normal file
@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import {useStore} from "vuex";
|
||||
import api from "./service/api";
|
||||
|
||||
const store = useStore();
|
||||
if (store.getters.userToken) {
|
||||
api.user.info().then(info => {
|
||||
store.commit('setLoginUserInfo', info)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="app">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less">
|
||||
#app {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
#page-login {
|
||||
.ant-input-affix-wrapper, .ant-btn {
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
BIN
client-pc/packages/renderer/src/assets/electron.png
Normal file
After Width: | Height: | Size: 62 KiB |
2
client-pc/packages/renderer/src/assets/logo.svg
Normal file
@ -0,0 +1,2 @@
|
||||
<?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="1651895924070" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2966" xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" height="1000"><defs><style type="text/css"></style></defs><path d="M549.766 97.925c19.974 4.609 50.703 35.339 62.995 55.312 35.339-13.828 36.875 41.484 46.094 55.312 23.047-16.901 46.094-35.339 46.094-55.312 0-21.51-30.729-35.339-30.729-41.484 0-12.292 38.411-12.292 41.484-12.292 50.703 0 106.016 39.948 106.016 96.797 0 27.656-9.219 41.484-33.802 70.677h32.266c21.51 0 27.656-3.073 55.312 7.682C923.125 294.591 960 348.367 960 389.852c0 3.073 0 9.219-1.536 13.828-4.609 3.073-12.292 3.073-16.901 4.609-15.365 1.536-32.266 6.146-47.63 7.682-47.63 0-50.703 0-53.776-1.536-1.536 1.536-1.536 0-1.536 4.609 0 3.073 7.682 44.557 10.755 67.604 9.219 56.849 12.292 115.234 19.974 173.62 1.536 7.682 7.682 15.365 9.219 23.047 3.073 13.828 6.146 27.656 6.146 39.948 0 153.646-245.833 208.958-321.119 208.958h-86.042c-115.234-9.219-248.906-52.24-301.145-138.281-4.609-7.682-18.437-39.948-18.437-50.703v-36.875c3.073-16.901 7.682-33.802 21.51-50.703v-84.505l12.292-132.135c-12.292 1.536-33.802 1.536-38.411 1.536-21.51 0-38.411-3.073-61.458-6.146-7.682-1.536-18.437-3.073-24.583-6.146-4.609-1.536-3.073-12.292-3.073-13.828 0-44.557 44.557-107.552 98.333-119.844 4.609-1.536 13.828-1.536 19.974-3.073l41.484-1.536c-13.828-10.755-36.875-46.094-36.875-59.922v-29.193c13.828-58.385 62.995-82.969 106.016-82.969 1.536 0 41.484 0 41.484 12.292 0 6.146-30.729 19.974-30.729 41.484 0 1.536 6.146 32.266 16.901 32.266 3.073 0-1.536-3.073 3.073-3.073 3.073 0 32.266 10.755 36.875 10.755h12.292l3.073-3.073c-19.974-16.901-30.729-36.875-36.875-53.776 12.292 7.682 18.438 9.219 29.193 9.219 27.656 0 46.094-15.365 78.359-29.193 24.583-10.755 52.24-13.828 78.359-18.437-12.292-9.219-24.583-16.901-36.875-23.047l38.411-1.536c7.68 1.537 15.362 4.61 23.044 6.146z" p-id="2967"></path></svg>
|
After Width: | Height: | Size: 2.0 KiB |
112
client-pc/packages/renderer/src/assets/style.less
Normal file
@ -0,0 +1,112 @@
|
||||
html,body{
|
||||
user-select: none;
|
||||
}
|
||||
/*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/
|
||||
::-webkit-scrollbar
|
||||
{
|
||||
width: 4px;
|
||||
height: 10px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/*定义滚动条轨道 内阴影+圆角*/
|
||||
::-webkit-scrollbar-track
|
||||
{
|
||||
border-radius: 2px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/*定义滑块 内阴影+圆角*/
|
||||
::-webkit-scrollbar-thumb
|
||||
{
|
||||
border-radius: 2px;
|
||||
background-color: #ccc;
|
||||
}
|
||||
.d-flex{
|
||||
display: flex;
|
||||
}
|
||||
.flex-1{
|
||||
flex: 1;
|
||||
}
|
||||
.page-main{
|
||||
.avatar{
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.main-header{
|
||||
background-color:#fff;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
padding:0 20px;
|
||||
.header-item{
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
.main-layout{
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.main-side-bar{
|
||||
background-color:#fff;
|
||||
}
|
||||
.file-content-wrapper{
|
||||
padding:10px 0;
|
||||
background-color: #fff;
|
||||
overflow: auto;
|
||||
}
|
||||
.files-wrapper{
|
||||
.header{
|
||||
padding:10px;
|
||||
}
|
||||
.more-action{
|
||||
display: none;
|
||||
font-size:12px;
|
||||
}
|
||||
|
||||
.action-item{
|
||||
margin-left: 10px;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
&:hover,&:active{
|
||||
color:#2579f8;
|
||||
}
|
||||
.action{
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
.list-item{
|
||||
line-height: 36px;
|
||||
&.list-item{
|
||||
border-top:solid 1px #efefef;
|
||||
}
|
||||
&:hover{
|
||||
background-color: #fafafa;
|
||||
.more-action{
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
.file-name{
|
||||
flex:1;
|
||||
.name{
|
||||
flex:1
|
||||
}
|
||||
}
|
||||
.file-type{
|
||||
width: 100px;
|
||||
}
|
||||
.file-size{
|
||||
width: 100px;
|
||||
}
|
||||
.file-date{
|
||||
width: 100px;
|
||||
}
|
||||
.content-wrapper{
|
||||
.file-name{
|
||||
padding: 0 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
15
client-pc/packages/renderer/src/assets/vite.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/>
|
||||
<path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#41D1FF"/>
|
||||
<stop offset="1" stop-color="#BD34FE"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFEA83"/>
|
||||
<stop offset="0.0833333" stop-color="#FFDD35"/>
|
||||
<stop offset="1" stop-color="#FFA800"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
BIN
client-pc/packages/renderer/src/assets/vue.png
Normal file
After Width: | Height: | Size: 6.7 KiB |
144
client-pc/packages/renderer/src/components/DragValidator.vue
Normal file
@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<div class="drag-wrapper" ref="dragDiv">
|
||||
<div class="drag_bg" ref="dragBg"></div>
|
||||
<div class="drag_text f14" :style="{'color':confirmSuccess?'#fff':'inherit'}">{{ confirmWords }}</div>
|
||||
<!-- 移动的模块 -->
|
||||
<div ref="dragItem" :class="{'handler_ok_bg': confirmSuccess}" class="handler handler_bg"
|
||||
@mousedown="mousedown($event)"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "DragValidator",
|
||||
props: {
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请按住滑块拖动到最右边'
|
||||
},
|
||||
successText: {
|
||||
type: String,
|
||||
default: '验证通过'
|
||||
}
|
||||
},
|
||||
emits: ['onValidation'],
|
||||
data() {
|
||||
return {
|
||||
beginClientX: 0, /* 距离屏幕左端距离 */
|
||||
allowDrag: false, /*触发拖动状态 判断*/
|
||||
maxWidth: 0, /*拖动最大宽度,依据滑块宽度算出来的*/
|
||||
confirmWords: '', /*滑块文字*/
|
||||
confirmSuccess: false, /*验证成功判断*/
|
||||
/**
|
||||
* @type {HTMLElement}
|
||||
*/
|
||||
rootHtml: null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.confirmWords = this.placeholder;
|
||||
this.maxWidth = this.$refs.dragDiv.clientWidth - this.$refs.dragItem.clientWidth
|
||||
this.rootHtml = document.getElementsByTagName('html')[0]
|
||||
// 绑定移动事件
|
||||
this.rootHtml.addEventListener('mousemove', this.mousemove)
|
||||
// 绑定鼠标释放事件
|
||||
this.rootHtml.addEventListener('mouseup', this.moseup)
|
||||
},
|
||||
methods: {
|
||||
mousedown: function (e) {
|
||||
if (!this.confirmSuccess) {
|
||||
e.preventDefault && e.preventDefault() //阻止文字选中等 浏览器默认事件
|
||||
this.allowDrag = true // 开始拖动
|
||||
this.beginClientX = e.clientX
|
||||
this.$refs.dragBg.style.transition = ''
|
||||
this.$refs.dragItem.style.transition = ''
|
||||
}
|
||||
},
|
||||
success() {
|
||||
this.confirmSuccess = true
|
||||
this.confirmWords = this.successText
|
||||
this.$emit('onValidation', true)
|
||||
if (window.addEventListener) {
|
||||
this.rootHtml.removeEventListener('mousemove', this.mousemove)
|
||||
this.rootHtml.removeEventListener('mouseup', this.moseup)
|
||||
}
|
||||
this.$refs.dragItem.style.left = (this.maxWidth - 2) + 'px'
|
||||
this.$refs.dragBg.style.width = (this.maxWidth + 20) + 'px'
|
||||
},
|
||||
//验证成功函数
|
||||
mousemove(e) {
|
||||
if (this.allowDrag) {
|
||||
let width = e.clientX - this.beginClientX // 计算当前拖动距离
|
||||
if (width > 0 && width < this.maxWidth) {
|
||||
this.$refs.dragItem.style.left = (width - 2) + 'px'
|
||||
this.$refs.dragBg.style.width = (width + 20) + 'px'
|
||||
} else if (width >= this.maxWidth) {
|
||||
this.success()
|
||||
}
|
||||
}
|
||||
},
|
||||
//mousemove事件
|
||||
moseup(e) {
|
||||
this.allowDrag = false
|
||||
const width = e.clientX - this.beginClientX
|
||||
if (width < this.maxWidth) {
|
||||
|
||||
this.$refs.dragBg.style.transition = 'width .5s'
|
||||
this.$refs.dragItem.style.transition = 'left .5s'
|
||||
this.$refs.dragItem.style.left = 0 + 'px'
|
||||
this.$refs.dragBg.style.width = 0 + 'px'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.drag-wrapper {
|
||||
position: relative;
|
||||
background-color: #e8e8e8;
|
||||
width: 100%;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.handler {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 1px solid #ccc;
|
||||
cursor: move;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.handler_bg {
|
||||
background: #fff url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NTc3MiwgMjAxNC8wMS8xMy0xOTo0NDowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo0ZDhlNWY5My05NmI0LTRlNWQtOGFjYi03ZTY4OGYyMTU2ZTYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTEyNTVEMURGMkVFMTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTEyNTVEMUNGMkVFMTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo2MTc5NzNmZS02OTQxLTQyOTYtYTIwNi02NDI2YTNkOWU5YmUiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NGQ4ZTVmOTMtOTZiNC00ZTVkLThhY2ItN2U2ODhmMjE1NmU2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+YiRG4AAAALFJREFUeNpi/P//PwMlgImBQkA9A+bOnfsIiBOxKcInh+yCaCDuByoswaIOpxwjciACFegBqZ1AvBSIS5OTk/8TkmNEjwWgQiUgtQuIjwAxUF3yX3xyGIEIFLwHpKyAWB+I1xGSwxULIGf9A7mQkBwTlhBXAFLHgPgqEAcTkmNCU6AL9d8WII4HOvk3ITkWJAXWUMlOoGQHmsE45ViQ2KuBuASoYC4Wf+OUYxz6mQkgwAAN9mIrUReCXgAAAABJRU5ErkJggg==") no-repeat center;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.handler_ok_bg {
|
||||
background: #fff url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NTc3MiwgMjAxNC8wMS8xMy0xOTo0NDowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo0ZDhlNWY5My05NmI0LTRlNWQtOGFjYi03ZTY4OGYyMTU2ZTYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NDlBRDI3NjVGMkQ2MTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NDlBRDI3NjRGMkQ2MTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDphNWEzMWNhMC1hYmViLTQxNWEtYTEwZS04Y2U5NzRlN2Q4YTEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NGQ4ZTVmOTMtOTZiNC00ZTVkLThhY2ItN2U2ODhmMjE1NmU2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+k+sHwwAAASZJREFUeNpi/P//PwMyKD8uZw+kUoDYEYgloMIvgHg/EM/ptHx0EFk9I8wAoEZ+IDUPiIMY8IN1QJwENOgj3ACo5gNAbMBAHLgAxA4gQ5igAnNJ0MwAVTsX7IKyY7L2UNuJAf+AmAmJ78AEDTBiwGYg5gbifCSxFCZoaBMCy4A4GOjnH0D6DpK4IxNSVIHAfSDOAeLraJrjgJp/AwPbHMhejiQnwYRmUzNQ4VQgDQqXK0ia/0I17wJiPmQNTNBEAgMlQIWiQA2vgWw7QppBekGxsAjIiEUSBNnsBDWEAY9mEFgMMgBk00E0iZtA7AHEctDQ58MRuA6wlLgGFMoMpIG1QFeGwAIxGZo8GUhIysmwQGSAZgwHaEZhICIzOaBkJkqyM0CAAQDGx279Jf50AAAAAABJRU5ErkJggg==") no-repeat center;
|
||||
}
|
||||
|
||||
.drag_bg {
|
||||
background-color: #7ac23c;
|
||||
height: 40px;
|
||||
width: 0px;
|
||||
}
|
||||
|
||||
.drag_text {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
-o-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
</style>
|
39
client-pc/packages/renderer/src/components/MainLogo.vue
Normal file
@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<div class="app-logo">
|
||||
<svg class="logo-img" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="1000" height="1000">
|
||||
<path
|
||||
d="M549.766 97.925c19.974 4.609 50.703 35.339 62.995 55.312 35.339-13.828 36.875 41.484 46.094 55.312 23.047-16.901 46.094-35.339 46.094-55.312 0-21.51-30.729-35.339-30.729-41.484 0-12.292 38.411-12.292 41.484-12.292 50.703 0 106.016 39.948 106.016 96.797 0 27.656-9.219 41.484-33.802 70.677h32.266c21.51 0 27.656-3.073 55.312 7.682C923.125 294.591 960 348.367 960 389.852c0 3.073 0 9.219-1.536 13.828-4.609 3.073-12.292 3.073-16.901 4.609-15.365 1.536-32.266 6.146-47.63 7.682-47.63 0-50.703 0-53.776-1.536-1.536 1.536-1.536 0-1.536 4.609 0 3.073 7.682 44.557 10.755 67.604 9.219 56.849 12.292 115.234 19.974 173.62 1.536 7.682 7.682 15.365 9.219 23.047 3.073 13.828 6.146 27.656 6.146 39.948 0 153.646-245.833 208.958-321.119 208.958h-86.042c-115.234-9.219-248.906-52.24-301.145-138.281-4.609-7.682-18.437-39.948-18.437-50.703v-36.875c3.073-16.901 7.682-33.802 21.51-50.703v-84.505l12.292-132.135c-12.292 1.536-33.802 1.536-38.411 1.536-21.51 0-38.411-3.073-61.458-6.146-7.682-1.536-18.437-3.073-24.583-6.146-4.609-1.536-3.073-12.292-3.073-13.828 0-44.557 44.557-107.552 98.333-119.844 4.609-1.536 13.828-1.536 19.974-3.073l41.484-1.536c-13.828-10.755-36.875-46.094-36.875-59.922v-29.193c13.828-58.385 62.995-82.969 106.016-82.969 1.536 0 41.484 0 41.484 12.292 0 6.146-30.729 19.974-30.729 41.484 0 1.536 6.146 32.266 16.901 32.266 3.073 0-1.536-3.073 3.073-3.073 3.073 0 32.266 10.755 36.875 10.755h12.292l3.073-3.073c-19.974-16.901-30.729-36.875-36.875-53.776 12.292 7.682 18.438 9.219 29.193 9.219 27.656 0 46.094-15.365 78.359-29.193 24.583-10.755 52.24-13.828 78.359-18.437-12.292-9.219-24.583-16.901-36.875-23.047l38.411-1.536c7.68 1.537 15.362 4.61 23.044 6.146z"
|
||||
p-id="2967"></path>
|
||||
</svg>
|
||||
<span class="logo-text">天牛网盘</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "MainLogo"
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.app-logo{
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
.logo-img {
|
||||
width: 60px;
|
||||
height: 100%;
|
||||
margin-right: 10px;
|
||||
fill: #666;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
text-shadow: 0 0 1px rgba(0, 0, 0, 0.3);
|
||||
position: absolute;
|
||||
top:50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
</style>
|
8
client-pc/packages/renderer/src/env.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import { DefineComponent } from 'vue'
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
8
client-pc/packages/renderer/src/global.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
|
||||
export { }
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
removeLoading: () => void
|
||||
}
|
||||
}
|
16
client-pc/packages/renderer/src/main.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { createApp } from 'vue'
|
||||
import Antd from 'ant-design-vue';
|
||||
import router from "./service/router";
|
||||
import App from './App.vue';
|
||||
import 'ant-design-vue/dist/antd.css';
|
||||
import './assets/style.less'
|
||||
import './service/node-api'
|
||||
import store from "./service/store";
|
||||
|
||||
createApp(App)
|
||||
.use(store)
|
||||
.use(Antd)
|
||||
.use(router)
|
||||
.mount('#app')
|
||||
.$nextTick(window.removeLoading)
|
||||
.then( r =>console.log('app render finish'))
|
158
client-pc/packages/renderer/src/service/api.ts
Normal file
@ -0,0 +1,158 @@
|
||||
import store from "./store";
|
||||
import router from "./router";
|
||||
|
||||
const API_PATH = "http://localhost:8080"
|
||||
|
||||
/**
|
||||
*
|
||||
* @param api
|
||||
* @param {'GET'|'POST'|'FILE'|string} method
|
||||
* @param postData
|
||||
*/
|
||||
function request<T>(api:string, method = 'GET', postData:{[key:string]:string}|any = {}) {
|
||||
//return fetch(API_PATH + api).then(res => res.json());
|
||||
|
||||
let options:RequestInit = {
|
||||
method
|
||||
};
|
||||
if (method.toUpperCase() == 'GET') {
|
||||
// post => 'key=value&key1=value1'
|
||||
const params = [];
|
||||
for (let key in postData) {
|
||||
params.push(`${key}=${postData[key]}`);
|
||||
}
|
||||
api += (api.includes('?') ? '&' : '?') + params.join('&');
|
||||
} else {
|
||||
options = {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(postData) // 参数
|
||||
}
|
||||
}
|
||||
let Authorization = store.getters.userToken
|
||||
if (Authorization) {
|
||||
options = {
|
||||
headers: {
|
||||
Authorization
|
||||
},
|
||||
...options
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
const processResult = (result:ApiResult) => {
|
||||
if (result.code === 0) { // 判断响应码是否正常
|
||||
resolve(result.data)
|
||||
} else {
|
||||
const route = router.currentRoute.value;
|
||||
// 未登录或token无效
|
||||
if (result.code == 401) {
|
||||
store.commit('clearToken') // 清除token
|
||||
if (route.path != '/login') router.replace('/login').then(() => console.log('auth show login')).catch();
|
||||
reject(null)
|
||||
return;
|
||||
}
|
||||
// 其他异常 直接抛出错误
|
||||
reject(new Error(result.message))
|
||||
}
|
||||
}
|
||||
fetch(API_PATH + api, options)
|
||||
.then(res => res.json())
|
||||
.then(processResult)
|
||||
.catch(e => {
|
||||
console.log(e)
|
||||
reject(e);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
user: {
|
||||
login(params:LoginForm) {
|
||||
return request<LoginUser>('/api/user/login', 'POST', {
|
||||
device:'client',
|
||||
...params
|
||||
});
|
||||
},
|
||||
signup(params:any) {
|
||||
return request('/api/user/reg', 'POST', params);
|
||||
},
|
||||
info(){
|
||||
return request<LoginUser>('/api/user/info');
|
||||
}
|
||||
},
|
||||
folder: {
|
||||
/**
|
||||
* 列出所有的子文件列表
|
||||
* @param path
|
||||
* @returns {Promise<Array>}
|
||||
*/
|
||||
list(folderPath = '/') {
|
||||
return request<FileItem[]>(`/api/folder/list`, 'GET', {folderPath})
|
||||
},
|
||||
create(parent:string, name:string) {
|
||||
return request(`/api/folder/create`, 'POST', {parent, name});
|
||||
}
|
||||
},
|
||||
file: {
|
||||
/**
|
||||
* 快速上传
|
||||
* @param params
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
fastUpload(params:any) {
|
||||
return request('/api/fast-upload', 'POST', params);
|
||||
},
|
||||
delete(file:string) {
|
||||
return request('/api/file/delete', 'POST', file);
|
||||
},
|
||||
rename(file:string) {
|
||||
return request('/api/file/rename', 'POST', file);
|
||||
},
|
||||
upload(parent:string, file:File, onProcess = null) {
|
||||
// 上传文件到某个目录
|
||||
const postData = new FormData(); // 将数据封装成form表单
|
||||
postData.append("parent", parent); // 父目录
|
||||
postData.append("file", file);// 文件
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new XMLHttpRequest();
|
||||
request.open('POST', API_PATH + '/api/upload');
|
||||
request.upload.addEventListener('progress', function (e) {
|
||||
// upload progress as percentage
|
||||
let progress = (e.loaded / e.total) * 100;
|
||||
// @ts-ignore
|
||||
onProcess({
|
||||
uploaded: e.loaded,
|
||||
total: e.total,
|
||||
progress
|
||||
})
|
||||
});
|
||||
request.addEventListener('load', function (e) {
|
||||
// HTTP status message (200, 404 etc)
|
||||
let ret = {message: null}
|
||||
try {
|
||||
ret = JSON.parse(request.response)
|
||||
if (request.status == 200) {
|
||||
resolve(JSON.parse(request.response));
|
||||
} else {
|
||||
reject(Error(ret.message || '上传文件出错!'));
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
reject(Error('上传文件异常'))
|
||||
}
|
||||
});
|
||||
request.send(postData);
|
||||
})
|
||||
// return request('/api/upload', 'FILE', postData, onProcess)
|
||||
}
|
||||
},
|
||||
share: {
|
||||
|
||||
create(info:ShareInfo) {
|
||||
return request('/api/share/create', 'POST', info)
|
||||
}
|
||||
}
|
||||
}
|
14
client-pc/packages/renderer/src/service/node-api.ts
Normal file
@ -0,0 +1,14 @@
|
||||
// import {lstat, readdir} from 'fs/promises'
|
||||
// import {cwd} from 'process'
|
||||
import {ipcRenderer} from 'electron'
|
||||
|
||||
// Usage of ipcRenderer.on
|
||||
ipcRenderer.on('main-process-message', (_event, ...args) => {
|
||||
console.log('[Receive Main-process message]:', ...args)
|
||||
})
|
||||
|
||||
// readdir(cwd()).then(stats => {
|
||||
// console.log('[readdir]', stats)
|
||||
// }).catch(err => {
|
||||
// console.error(err)
|
||||
// })
|
31
client-pc/packages/renderer/src/service/router.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import {createMemoryHistory, createRouter} from "vue-router";
|
||||
import store from "./store";
|
||||
|
||||
const router = createRouter({
|
||||
history: createMemoryHistory(),
|
||||
routes: [
|
||||
{
|
||||
name: 'Main',
|
||||
path: '/',
|
||||
component: () => import('../views/Main.vue')
|
||||
},
|
||||
{
|
||||
name: "Login",
|
||||
path: "/login",
|
||||
component: () => import('../views/Login.vue')
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const loginPath = '/login' // 登录路径
|
||||
router.beforeEach((to, from, next) => {
|
||||
const toPath = to.path; // 获取要展示得路径
|
||||
if (toPath == loginPath) next() // 判断是否可匿名访问
|
||||
else {
|
||||
// 验证token
|
||||
if (!store.getters.userToken) next(loginPath) // 没有登录直接打开登录页面
|
||||
else next();
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
57
client-pc/packages/renderer/src/service/store.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import {createStore, Store} from 'vuex'
|
||||
import api from "./api";
|
||||
|
||||
const TOKEN_KEY = 'user_auth_token';
|
||||
declare type StoreState = {
|
||||
/**
|
||||
* 用户信息
|
||||
*/
|
||||
loginUser: UserInfo | null,
|
||||
token: string | null
|
||||
}
|
||||
|
||||
const store = createStore<StoreState>({
|
||||
state() {
|
||||
return {
|
||||
loginUser: null,
|
||||
token: null
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
// 此选项中的方法必须同步
|
||||
setLoginUserInfo(state:StoreState, info:UserInfo) {
|
||||
state.loginUser = info;
|
||||
},
|
||||
setToken(state:StoreState, token:string) {
|
||||
state.token = token;
|
||||
// sessionStorage.setItem(TOKEN_KEY,token);
|
||||
localStorage.setItem(TOKEN_KEY, token);
|
||||
},
|
||||
clearToken(state:StoreState) {
|
||||
state.token = null;
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
sessionStorage.removeItem(TOKEN_KEY)
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
login({commit}, data: LoginForm) {
|
||||
return new Promise((resolve, reject) => {
|
||||
api.user.login(data).then(
|
||||
(ret: LoginUser) => {
|
||||
commit('setToken', ret.token);
|
||||
commit('setLoginUserInfo', ret.userInfo)
|
||||
resolve(ret)
|
||||
}).catch((e:Error) => reject(e))
|
||||
});
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
userToken(state:StoreState) {
|
||||
const token = state.token || sessionStorage.getItem(TOKEN_KEY) || localStorage.getItem(TOKEN_KEY);
|
||||
// 如果内存中没有token 存储一份
|
||||
if (!state.token) state.token = token;
|
||||
return token
|
||||
}
|
||||
}
|
||||
})
|
||||
export default store
|
65
client-pc/packages/renderer/src/service/types.d.ts
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
declare type ApiResult = { code: number, message: string, data: any }
|
||||
declare type FileItem = {
|
||||
createTime: string,
|
||||
id: number,
|
||||
name: string,
|
||||
path: string,
|
||||
/**
|
||||
* 缩略图
|
||||
*/
|
||||
thumb: string,
|
||||
size: number,
|
||||
type: 'folder' | string,
|
||||
updateTime: string
|
||||
}
|
||||
declare type FileIconInstance = {
|
||||
file: FileItem,
|
||||
ext: String,
|
||||
/**
|
||||
* 显示预览
|
||||
*/
|
||||
showPreview: Function
|
||||
}
|
||||
/**
|
||||
* 分享信息
|
||||
*/
|
||||
declare type ShareInfo = {
|
||||
id?: string,
|
||||
title: string,
|
||||
uid: number,
|
||||
fileId: number,
|
||||
type: 1 | 2,
|
||||
password: string,
|
||||
live: number,
|
||||
createTime?: string,
|
||||
updateTime?: string,
|
||||
status: number
|
||||
}
|
||||
|
||||
declare type UserInfo = {
|
||||
account: string
|
||||
avatar: string
|
||||
createTime: string
|
||||
email: string
|
||||
id: number
|
||||
lastIp: string
|
||||
lastLogin: string
|
||||
nickname: string
|
||||
password: string
|
||||
salt: string
|
||||
sex: number
|
||||
status: number
|
||||
updateTime: string
|
||||
}
|
||||
declare type LoginUser = {
|
||||
account: string
|
||||
permissions: string[]
|
||||
roles: string[]
|
||||
token: string
|
||||
userInfo: UserInfo
|
||||
}
|
||||
interface LoginForm {
|
||||
username: string;
|
||||
password: string;
|
||||
remember: boolean;
|
||||
}
|
152
client-pc/packages/renderer/src/views/Login.vue
Normal file
@ -0,0 +1,152 @@
|
||||
<script lang="ts">
|
||||
import {UserOutlined, LockOutlined} from '@ant-design/icons-vue';
|
||||
import {computed, defineComponent, reactive, ref} from "vue";
|
||||
import DragValidator from "../components/DragValidator.vue";
|
||||
import {message} from "ant-design-vue";
|
||||
import {useStore} from "vuex";
|
||||
import {useRouter} from "vue-router";
|
||||
import MainLogo from "../components/MainLogo.vue";
|
||||
|
||||
interface FormState {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MainLogo,
|
||||
DragValidator,
|
||||
UserOutlined,
|
||||
LockOutlined,
|
||||
},
|
||||
setup() {
|
||||
const formState = reactive<FormState>({
|
||||
username: '',
|
||||
password: ''
|
||||
});
|
||||
const dragChecked = ref<boolean>(false);
|
||||
const store = useStore(),router = useRouter();
|
||||
const onFinish = async (values: any) => {
|
||||
// if(!dragChecked){
|
||||
// message.info('请先拖动滑块验证')
|
||||
// return;
|
||||
// }
|
||||
try {
|
||||
await store.dispatch('login',values)
|
||||
router.replace('/').then(()=>console.log('login success')).catch((e)=>console.log(e));
|
||||
}catch (e:Error|any) {
|
||||
message.error(e.message || '登录失败')
|
||||
}
|
||||
};
|
||||
|
||||
const onFinishFailed = (errorInfo: any) => {
|
||||
console.log('Failed:', errorInfo);
|
||||
};
|
||||
const disabled = computed(() => {
|
||||
return !(formState.username && formState.password && dragChecked.value);
|
||||
});
|
||||
return {
|
||||
formState,
|
||||
onFinish,
|
||||
onFinishFailed,
|
||||
disabled,
|
||||
onValidate(state:boolean){
|
||||
dragChecked.value = state
|
||||
}
|
||||
};
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="page-login">
|
||||
<div class="logo">
|
||||
<main-logo class="login-logo" />
|
||||
</div>
|
||||
<div id="login-form">
|
||||
<h3 class="title">登录</h3>
|
||||
<div class="wrapper">
|
||||
<a-form
|
||||
:model="formState"
|
||||
name="normal_login"
|
||||
class="login-form"
|
||||
@finish="onFinish"
|
||||
@finishFailed="onFinishFailed"
|
||||
>
|
||||
<a-form-item
|
||||
name="username"
|
||||
:rules="[{ required: true, message: 'Please input your username!' }]"
|
||||
>
|
||||
<a-input size="large" v-model:value="formState.username">
|
||||
<template #prefix>
|
||||
<UserOutlined class="site-form-item-icon"/>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<drag-validator @onValidation="onValidate" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
name="password"
|
||||
:rules="[{ required: true, message: 'Please input your password!' }]"
|
||||
>
|
||||
<a-input-password size="large" v-model:value="formState.password">
|
||||
<template #prefix>
|
||||
<LockOutlined class="site-form-item-icon"/>
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item style="margin-top:40px;">
|
||||
<a-button size="large" :disabled="disabled" type="primary" block
|
||||
html-type="submit" class="login-form-button">登 录</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
#page-login {
|
||||
height: 100vh;
|
||||
background-image: url("https://g.alicdn.com/uc-cloud-drive-web-system/cloud-drive-web/3.5.96/assets/a7cb99cdcd7cf16599f3ca6a554a0e44.jpg");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 34px;
|
||||
position: absolute;
|
||||
left: 30px;
|
||||
top: 30px;
|
||||
display: flex;
|
||||
line-height: 60px;
|
||||
color: #333;
|
||||
}
|
||||
.login-logo{
|
||||
height: 60px;
|
||||
}
|
||||
.title {
|
||||
background-color: #eaeaea;
|
||||
padding: 10px 30px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
#login-form {
|
||||
width: 400px;
|
||||
background-color: #fff;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
right: 50px;
|
||||
box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
padding: 30px;
|
||||
}
|
||||
</style>
|
113
client-pc/packages/renderer/src/views/Main.vue
Normal file
@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<a-layout class="page-main">
|
||||
<a-layout-header class="main-header d-flex">
|
||||
<div class="flex-1">
|
||||
<main-logo class="main-logo" />
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<div class="header-item">
|
||||
<a-dropdown :trigger="['click']">
|
||||
<div>
|
||||
<img class="avatar" :src="userinfo.avatar"/>
|
||||
<span>{{ userinfo.nickname }}</span>
|
||||
</div>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="0">
|
||||
<a href="http://www.alipay.com/">1st menu item</a>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="1">
|
||||
<a href="http://www.taobao.com/">2nd menu item</a>
|
||||
</a-menu-item>
|
||||
<a-menu-divider/>
|
||||
<a-menu-item key="3">3rd menu item</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
<div class="header-item">
|
||||
<setting-filled style="font-size: 24px" />
|
||||
</div>
|
||||
</div>
|
||||
</a-layout-header>
|
||||
<a-layout class="main-layout">
|
||||
<a-layout-sider class="main-side-bar">
|
||||
<a-button @click="reload">Reload</a-button>
|
||||
</a-layout-sider>
|
||||
<a-layout-content class="file-content-wrapper">
|
||||
<div class="tool-bar"></div>
|
||||
<div class="files-wrapper">
|
||||
<div class="header d-flex">
|
||||
<div class="file-name">文件名</div>
|
||||
<div class="file-type">类型</div>
|
||||
<div class="file-size">大小</div>
|
||||
<div class="file-date">修改日期</div>
|
||||
</div>
|
||||
<div class="content-wrapper">
|
||||
<div class="list-item d-flex" v-for="f in fileList" :key="f.id">
|
||||
<div class="file-name d-flex">
|
||||
<div class="name">{{ f.name }}</div>
|
||||
<div class="more-action">
|
||||
<span class="action-item"><download-outlined/>下载</span>
|
||||
<span class="action-item"><redo-outlined/>重命名</span>
|
||||
<span class="action-item"><share-alt-outlined/>分享</span>
|
||||
<span class="action-item"><delete-outlined/>删除</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="file-type">{{ f.type }}</div>
|
||||
<div class="file-size">{{ f.size }}</div>
|
||||
<div class="file-date">{{ formatDate(f.createTime) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
<!-- <a-layout-footer>Footer</a-layout-footer>-->
|
||||
</a-layout>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import api from "../service/api";
|
||||
import {computed, onMounted, ref} from "vue";
|
||||
import dayjs from "dayjs";
|
||||
import {DownloadOutlined, RedoOutlined, DeleteOutlined, ShareAltOutlined,SettingFilled} from '@ant-design/icons-vue';
|
||||
import {useStore} from "vuex";
|
||||
import MainLogo from "../components/MainLogo.vue";
|
||||
|
||||
export default {
|
||||
name: "Main",
|
||||
components: {MainLogo, DownloadOutlined, RedoOutlined, DeleteOutlined, ShareAltOutlined,SettingFilled},
|
||||
setup() {
|
||||
const fileList = ref<FileItem[]>([])
|
||||
const store = useStore()
|
||||
const userinfo = computed(() => store.state.loginUser)
|
||||
const loadFolderFiles = () => {
|
||||
api.folder.list().then(list => {
|
||||
fileList.value = list
|
||||
})
|
||||
};
|
||||
onMounted(loadFolderFiles)
|
||||
|
||||
return {
|
||||
userinfo,
|
||||
fileList,
|
||||
reload() {
|
||||
location.reload()
|
||||
},
|
||||
formatDate(time) {
|
||||
return dayjs(time).format('MM-DD HH:mm')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-main {
|
||||
height: 100vh;
|
||||
overflow: auto;
|
||||
}
|
||||
.main-logo{
|
||||
font-size: 24px;
|
||||
}
|
||||
</style>
|
16
client-pc/packages/renderer/tsconfig.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": ["esnext", "dom"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
|
||||
}
|
59
client-pc/packages/renderer/vite.config.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import resolve, { lib2esm } from 'vite-plugin-resolve'
|
||||
import electron from 'vite-plugin-electron/renderer'
|
||||
import pkg from '../../package.json'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
mode: process.env.NODE_ENV,
|
||||
root: __dirname,
|
||||
plugins: [
|
||||
vue(),
|
||||
electron(),
|
||||
resolve(
|
||||
/**
|
||||
* Here you can specify other modules
|
||||
* 🚧 You have to make sure that your module is in `dependencies` and not in the` devDependencies`,
|
||||
* which will ensure that the electron-builder can package it correctly
|
||||
*/
|
||||
{
|
||||
// If you use the following modules, the following configuration will work
|
||||
// What they have in common is that they will return - ESM format code snippets
|
||||
|
||||
// ESM format string
|
||||
'electron-store': 'export default require("electron-store");',
|
||||
// Use lib2esm() to easy to convert ESM
|
||||
// Equivalent to
|
||||
/**
|
||||
* sqlite3: () => `
|
||||
* const _M_ = require('sqlite3');
|
||||
* const _D_ = _M_.default || _M_;
|
||||
* export { _D_ as default }
|
||||
* `
|
||||
*/
|
||||
sqlite3: lib2esm('sqlite3', { format: 'cjs' }),
|
||||
serialport: lib2esm(
|
||||
// CJS lib name
|
||||
'serialport',
|
||||
// export memebers
|
||||
[
|
||||
'SerialPort',
|
||||
'SerialPortMock',
|
||||
],
|
||||
{ format: 'cjs' },
|
||||
),
|
||||
}
|
||||
),
|
||||
],
|
||||
base: './',
|
||||
build: {
|
||||
outDir: '../../dist/renderer',
|
||||
emptyOutDir: true,
|
||||
sourcemap: true,
|
||||
},
|
||||
server: {
|
||||
host: pkg.env.VITE_DEV_SERVER_HOST,
|
||||
port: pkg.env.VITE_DEV_SERVER_PORT,
|
||||
},
|
||||
})
|
16
client-pc/playwright.config.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { PlaywrightTestConfig, devices } from '@playwright/test';
|
||||
|
||||
const config: PlaywrightTestConfig = {
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
use: {
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
],
|
||||
};
|
||||
export default config;
|
5
client-pc/scripts/build.mjs
Normal file
@ -0,0 +1,5 @@
|
||||
import { build } from 'vite'
|
||||
|
||||
await build({ configFile: 'packages/main/vite.config.ts' })
|
||||
await build({ configFile: 'packages/preload/vite.config.ts' })
|
||||
await build({ configFile: 'packages/renderer/vite.config.ts' })
|
99
client-pc/scripts/watch.mjs
Normal file
@ -0,0 +1,99 @@
|
||||
import { spawn } from 'child_process'
|
||||
import { createServer, build } from 'vite'
|
||||
import electron from 'electron'
|
||||
import readline from 'readline'
|
||||
|
||||
const query = new URLSearchParams(import.meta.url.split('?')[1])
|
||||
const debug = query.has('debug')
|
||||
|
||||
/** The log will display on the next screen */
|
||||
function clearConsole() {
|
||||
const blank = '\n'.repeat(process.stdout.rows)
|
||||
console.log(blank)
|
||||
readline.cursorTo(process.stdout, 0, 0)
|
||||
readline.clearScreenDown(process.stdout)
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {(server: import('vite').ViteDevServer) => Promise<import('rollup').RollupWatcher>}
|
||||
*/
|
||||
function watchMain(server) {
|
||||
/**
|
||||
* @type {import('child_process').ChildProcessWithoutNullStreams | null}
|
||||
*/
|
||||
let electronProcess = null
|
||||
const address = server.httpServer.address()
|
||||
const env = Object.assign(process.env, {
|
||||
VITE_DEV_SERVER_HOST: address.address,
|
||||
VITE_DEV_SERVER_PORT: address.port,
|
||||
})
|
||||
|
||||
/**
|
||||
* @type {import('vite').Plugin}
|
||||
*/
|
||||
const startElectron = {
|
||||
name: 'electron-main-watcher',
|
||||
writeBundle() {
|
||||
clearConsole()
|
||||
|
||||
if (electronProcess) {
|
||||
electronProcess.removeAllListeners()
|
||||
electronProcess.kill()
|
||||
electronProcess = null
|
||||
}
|
||||
|
||||
electronProcess = spawn(electron, ['.'], { env })
|
||||
electronProcess.on('exit', process.exit)
|
||||
// https://github.com/electron-vite/electron-vite-vue/pull/129
|
||||
electronProcess.stdout.on('data', (data) => {
|
||||
const str = data.toString().trim()
|
||||
str && console.log(str)
|
||||
})
|
||||
electronProcess.stderr.on('data', (data) => {
|
||||
const str = data.toString().trim()
|
||||
str && console.error(str)
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
return build({
|
||||
configFile: 'packages/main/vite.config.ts',
|
||||
mode: 'development',
|
||||
plugins: [!debug && startElectron].filter(Boolean),
|
||||
build: {
|
||||
watch: {},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {(server: import('vite').ViteDevServer) => Promise<import('rollup').RollupWatcher>}
|
||||
*/
|
||||
function watchPreload(server) {
|
||||
return build({
|
||||
configFile: 'packages/preload/vite.config.ts',
|
||||
mode: 'development',
|
||||
plugins: [{
|
||||
name: 'electron-preload-watcher',
|
||||
writeBundle() {
|
||||
clearConsole()
|
||||
server.ws.send({ type: 'full-reload' })
|
||||
},
|
||||
}],
|
||||
build: {
|
||||
watch: {},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Block the CTRL + C shortcut on a Windows terminal and exit the application without displaying a query
|
||||
if (process.platform === 'win32') {
|
||||
readline.createInterface({ input: process.stdin, output: process.stdout }).on('SIGINT', process.exit)
|
||||
}
|
||||
|
||||
// bootstrap
|
||||
const server = await createServer({ configFile: 'packages/renderer/vite.config.ts' })
|
||||
|
||||
await server.listen()
|
||||
await watchPreload(server)
|
||||
await watchMain(server)
|
16
client-pc/test/example.spec.ts
Normal file
@ -0,0 +1,16 @@
|
||||
// example.spec.ts
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { env } from '../package.json'
|
||||
const VITE_SERVER_ADDRESS = `http://127.0.0.1:${env.PORT || 3344}`
|
||||
|
||||
test('example test case', async ({ page }) => {
|
||||
await page.goto(VITE_SERVER_ADDRESS)
|
||||
|
||||
// Expect a title "to contain" a substring.
|
||||
await expect(page).toHaveTitle(/Vite App/)
|
||||
|
||||
// Expect an attribute "Hello Vue 3 + TypeScript + Vite" to be visible on the page.
|
||||
await expect(
|
||||
page.locator('text=Hello Vue 3 + TypeScript + Vite').first(),
|
||||
).toBeVisible()
|
||||
})
|
17
client-pc/tsconfig.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"jsx": "preserve",
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"sourceMap": true,
|
||||
"baseUrl": "./",
|
||||
"strict": true,
|
||||
"paths": {},
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
8
client-pc/types.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
|
||||
declare namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
NODE_ENV: 'development' | 'production'
|
||||
readonly VITE_DEV_SERVER_HOST: string
|
||||
readonly VITE_DEV_SERVER_PORT: string
|
||||
}
|
||||
}
|
2600
client-pc/yarn.lock
Normal file
@ -0,0 +1,77 @@
|
||||
package xyz.longicorn.driver.controller;
|
||||
|
||||
import cn.hutool.captcha.CaptchaUtil;
|
||||
import cn.hutool.captcha.LineCaptcha;
|
||||
import cn.hutool.captcha.generator.RandomGenerator;
|
||||
import cn.hutool.core.lang.UUID;
|
||||
import lombok.SneakyThrows;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import xyz.longicorn.driver.dto.ApiResult;
|
||||
import xyz.longicorn.driver.util.RedisUtil;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RestController
|
||||
public class CodeController {
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@RequestMapping("/code-id")
|
||||
public String createCodeId() {
|
||||
return UUID.fastUUID().toString();
|
||||
}
|
||||
|
||||
@RequestMapping("/code")
|
||||
@SneakyThrows
|
||||
public void createImage(String id, HttpServletResponse response) {
|
||||
// 自定义纯数字的验证码(随机4位数字,可重复)
|
||||
RandomGenerator randomGenerator = new RandomGenerator("0123456789", 4);
|
||||
LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100);
|
||||
lineCaptcha.setGenerator(randomGenerator);
|
||||
final ServletOutputStream os = response.getOutputStream();
|
||||
String code = lineCaptcha.getCode();
|
||||
// 使用id保存对应的验证码
|
||||
stringRedisTemplate.opsForValue().set("code:" + id, code, 180, TimeUnit.SECONDS);
|
||||
System.out.println("当前验证码:" + code);
|
||||
lineCaptcha.write(os);
|
||||
}
|
||||
|
||||
@RequestMapping("/verify")
|
||||
public String verify(String id, String code) {
|
||||
String key = "code:" + id;
|
||||
String code1 = stringRedisTemplate.opsForValue().get(key);
|
||||
if (code1 != null && code1.equals(code)) {
|
||||
// 使原始验证码失效
|
||||
stringRedisTemplate.delete(key);
|
||||
return "true";
|
||||
}
|
||||
return "false";
|
||||
}
|
||||
|
||||
@RequestMapping("/create")
|
||||
public ApiResult createBase64Code() {
|
||||
RandomGenerator randomGenerator = new RandomGenerator("0123456789", 4);
|
||||
LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100);
|
||||
lineCaptcha.setGenerator(randomGenerator);
|
||||
String code = lineCaptcha.getCode();
|
||||
String id = UUID.fastUUID().toString();
|
||||
// 使用id保存对应的验证码
|
||||
stringRedisTemplate.opsForValue().set("code:" + id, code, 180, TimeUnit.SECONDS);
|
||||
// 获取验证码的base64
|
||||
String image = lineCaptcha.getImageBase64Data();
|
||||
Map<String, String> data = new HashMap<>();
|
||||
data.put("id", id);
|
||||
data.put("image", image);
|
||||
return ApiResult.success(data);
|
||||
}
|
||||
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
package xyz.longicorn.driver.controller;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import lombok.SneakyThrows;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
@ -19,6 +21,8 @@ import java.util.Map;
|
||||
@RestController
|
||||
@RequestMapping("/api/user")
|
||||
public class UserController {
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
@Resource
|
||||
private UserService userService;
|
||||
|
||||
@ -27,9 +31,23 @@ public class UserController {
|
||||
@SneakyThrows
|
||||
@PostMapping("/login")
|
||||
public ApiResult login(@Validated @RequestBody LoginModel model) {
|
||||
Thread.sleep(3);
|
||||
|
||||
String key = "code:" + model.getCodeId();
|
||||
String code1 = stringRedisTemplate.opsForValue().get(key);
|
||||
|
||||
if (code1 == null || !code1.equals(model.getCode())) {
|
||||
return ApiResult.error(1,"验证码无效");
|
||||
}
|
||||
// 使原始验证码失效
|
||||
stringRedisTemplate.delete(key);
|
||||
Thread.sleep(2);
|
||||
final LoginUser user = userService.login(model.getUsername(), model.getPassword());
|
||||
|
||||
return ApiResult.success(user);
|
||||
}
|
||||
|
||||
@RequestMapping("/info")
|
||||
public ApiResult loginUserInfo() {
|
||||
return ApiResult.success(StpUtil.getSession().get("user"));
|
||||
}
|
||||
}
|
||||
|
@ -11,5 +11,8 @@ public class LoginModel {
|
||||
@NotBlank(message = "密码必须填写")
|
||||
private String password;
|
||||
// 验证码
|
||||
@NotBlank(message = "验证码必须填写")
|
||||
private String code;
|
||||
@NotBlank(message = "验证码必须填写")
|
||||
private String codeId;
|
||||
}
|
||||
|
@ -1,11 +1,22 @@
|
||||
<template>
|
||||
<!-- 所有子页面显示在这里 -->
|
||||
<router-view />
|
||||
<router-view/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api from "./service/api";
|
||||
|
||||
export default {
|
||||
name: "App.vue"
|
||||
name: "App.vue",
|
||||
mounted() {
|
||||
const store = this.$store
|
||||
const route = this.$route;
|
||||
if(store.getters.userToken){
|
||||
api.user.info().then(info=>{
|
||||
store.commit('setLoginUserInfo',info)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -63,15 +63,7 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatDate(time, format = 'MM-DD') {
|
||||
return dayjs(time).format(format);
|
||||
},
|
||||
formatSize(a, b = 2) {
|
||||
if (0 == a) return "0 B";
|
||||
let c = 1024, d = b || 2, e = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"],
|
||||
f = Math.floor(Math.log(a) / Math.log(c));
|
||||
return parseFloat((a / Math.pow(c, f)).toFixed(d)) + " " + e[f];
|
||||
},
|
||||
...strings,
|
||||
// 对文件名进行处理
|
||||
formatName(name) {
|
||||
if (!name || name.length < 15) return name;
|
||||
|
11
web/src/components/TestA.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<h1>TestA ==> {{ data.state.username }}</h1>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import data from '../service/data'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
11
web/src/components/TestB.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<h1>TestB ==> {{ data.state.username }}</h1>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import data from '../service/data'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -11,10 +11,12 @@ import router from './router'
|
||||
|
||||
// 导入入口组件
|
||||
import App from "./App.vue";
|
||||
import data from "./service/data";
|
||||
|
||||
const app = createApp(App)
|
||||
app.component('ContextMenu', ContextMenu);
|
||||
app.use(ElementPlus)
|
||||
.use(router) // 使用路由实例
|
||||
.use(store)
|
||||
.use(router) // 使用路由实例
|
||||
// .use(data) // 使用vuex创建的store
|
||||
.mount("#app");
|
||||
|
@ -58,10 +58,14 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="type" label="类型" width="200"/>
|
||||
<el-table-column prop="size" label="大小" width="200"/>
|
||||
<el-table-column prop="size" label="大小" width="200">
|
||||
<template #default="file">
|
||||
<span class="list-file-size">{{ formatSize(file.row.size) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" width="200">
|
||||
<template #default="file">
|
||||
<span class="list-file-time">{{ formatDate(file.row.createTime) }}</span>
|
||||
<span class="list-file-time">{{ formatDate(file.row.createTime,'YYYY-MM-DD HH:mm') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@ -132,6 +136,7 @@ import {dayjs, ElMessage, ElMessageBox} from 'element-plus'
|
||||
import api from "../service/api";
|
||||
import qs from "qs";
|
||||
import FileBlockItem from "../components/FileBlockItem.vue";
|
||||
import strings from "../service/strings";
|
||||
|
||||
export default {
|
||||
name: "All",
|
||||
@ -234,6 +239,7 @@ export default {
|
||||
// window.removeEventListener('hashchange', this.handleHashChange); // 取消监听
|
||||
},
|
||||
methods: {
|
||||
...strings,
|
||||
// 加载目录下的所有文件
|
||||
async loadFileByPath(path = '/') {
|
||||
this.currentPath = path // 保存了当前的请求路径
|
||||
@ -364,5 +370,17 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.list-file-info{
|
||||
display: flex;
|
||||
line-height: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.list-file-icon{
|
||||
width: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.list-file-name{
|
||||
margin-left: 5px;
|
||||
}
|
||||
</style>
|
18
web/src/pages/Forget.vue
Normal file
@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2 style="color: red">{{ store.state.username }}</h2>
|
||||
<button @click="changeName">change username</button>
|
||||
<TestA/>
|
||||
sss
|
||||
<TestB v-if="store.state.username == '456456'" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import TestA from "../components/TestA.vue";
|
||||
import TestB from "../components/TestB.vue";
|
||||
import {useStore} from 'vuex'
|
||||
const store = useStore();
|
||||
function changeName() {
|
||||
store.commit('changeUsername','456789')
|
||||
}
|
||||
</script>
|
@ -49,6 +49,17 @@
|
||||
text-decoration: none;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.input-code-item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.verify-code {
|
||||
height: 35px;
|
||||
position: absolute;
|
||||
right: 2px;
|
||||
top: 2px;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div id="page-login">
|
||||
@ -77,6 +88,10 @@
|
||||
<el-form-item label="" prop="password" class="input-item">
|
||||
<el-input v-model="loginForm.password" placeholder="登录密码" type="password" autocomplete="off"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="" prop="code" class="input-item input-code-item">
|
||||
<el-input v-model="loginForm.code" placeholder="验证码" autocomplete="off"/>
|
||||
<img @click="refreshCode" :src="currentCodeSrc" class="verify-code" alt="">
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button :loading="loginLoading" style="width: 100%" type="primary" @click="submitForm">登录
|
||||
</el-button>
|
||||
@ -91,10 +106,11 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {defineComponent, reactive, ref} from 'vue'
|
||||
import {defineComponent, onMounted, reactive, ref} from 'vue'
|
||||
import {ElMessage, ElMessageBox} from "element-plus";
|
||||
import {useRouter} from 'vue-router'
|
||||
import {useStore} from "vuex";
|
||||
import api from "../service/api";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Login',
|
||||
@ -109,10 +125,31 @@ export default defineComponent({
|
||||
* @type {Ref<FormInstance>}
|
||||
*/
|
||||
const form = ref(null);
|
||||
// 生成验证码所需要的标识
|
||||
const codeId = crypto.randomUUID();
|
||||
// 使用reactive将对象转换成响应对象
|
||||
const loginForm = reactive({
|
||||
password: '',
|
||||
username: ''
|
||||
username: '',
|
||||
code: '',
|
||||
codeId
|
||||
})
|
||||
// 验证码的图片路径
|
||||
const currentCodeSrc = ref('')
|
||||
// 刷新验证码
|
||||
const refreshCode = ()=>{
|
||||
// 客户端自己生成的标识 创建验证码
|
||||
//currentCodeSrc.value = '//localhost:8080/code?id=' + loginForm.codeId + '&r=' + Math.random();
|
||||
api.code().then(ret=>{
|
||||
// console.log(ret)
|
||||
loginForm.codeId = ret.id
|
||||
currentCodeSrc.value = ret.image
|
||||
});
|
||||
}
|
||||
|
||||
// 已加载 刷新验证码
|
||||
onMounted(()=>{
|
||||
refreshCode()
|
||||
})
|
||||
|
||||
const checkPassword = (rule, value, callback) => {
|
||||
@ -131,19 +168,22 @@ export default defineComponent({
|
||||
const store = useStore();
|
||||
const loginLoading = ref(false)
|
||||
const submitForm = () => {
|
||||
if(loginLoading.value) return;
|
||||
if (loginLoading.value) return;
|
||||
// 验证表单
|
||||
form.value.validate(async (isValid) => {
|
||||
if (isValid) {
|
||||
// 设置loading未true
|
||||
loginLoading.value = true
|
||||
try {
|
||||
// 调用action中对应的一些方法
|
||||
// 由于整体采用同步代码 所以action的方法必须返回Promise对象
|
||||
const _data = await store.dispatch('login', loginForm)
|
||||
// 登录成功后 需要保存token
|
||||
// localStorage.setItem('token',_data);
|
||||
ElMessage.success('登录成功')
|
||||
router.replace('/').then().catch();
|
||||
} catch (e) {
|
||||
refreshCode()
|
||||
ElMessage.error(e.message || '登录错误')
|
||||
} finally {
|
||||
loginLoading.value = false
|
||||
@ -152,7 +192,7 @@ export default defineComponent({
|
||||
})
|
||||
};
|
||||
return {
|
||||
loginForm, submitForm, rules, form, loginLoading
|
||||
loginForm, submitForm, rules, form, loginLoading,refreshCode,currentCodeSrc
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -21,6 +21,11 @@ export default {
|
||||
mounted() {
|
||||
this.currentActiveIndex = this.$route.path
|
||||
},
|
||||
computed:{
|
||||
userinfo(){
|
||||
return this.$store.state.loginUser
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
//记载所有的子文件
|
||||
handleHashChange() {
|
||||
@ -28,6 +33,9 @@ export default {
|
||||
title: '成功 加载页面'
|
||||
})
|
||||
},
|
||||
logout(){
|
||||
this.$router.replace('/login');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -93,8 +101,8 @@ export default {
|
||||
<el-dropdown>
|
||||
<div class="el-dropdown-link">
|
||||
<el-avatar class="avatar" :size="30"
|
||||
src="https://cube.elemecdn.com/9/c2/f0ee8a3c7c9638a54940382568c9dpng.png"/>
|
||||
<span class="username">张三</span>
|
||||
:src="userinfo.avatar"/>
|
||||
<span class="username">{{ userinfo.nickname }}</span>
|
||||
<el-icon class="el-icon--right">
|
||||
<arrow-down/>
|
||||
</el-icon>
|
||||
@ -103,7 +111,7 @@ export default {
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item>修改资料</el-dropdown-item>
|
||||
<el-dropdown-item>登录日志查看</el-dropdown-item>
|
||||
<el-dropdown-item>退出</el-dropdown-item>
|
||||
<el-dropdown-item @click="logout">退出</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
|
@ -43,6 +43,11 @@ const routes = [
|
||||
path: '/reg',
|
||||
component: () => import('../pages/Reg.vue')
|
||||
},
|
||||
{
|
||||
name: 'Forget',
|
||||
path: '/forget',
|
||||
component: () => import('../pages/Forget.vue')
|
||||
},
|
||||
{
|
||||
name: 'Login',
|
||||
path: '/login',
|
||||
|
@ -5,7 +5,13 @@ import {
|
||||
import router from "../router";
|
||||
|
||||
const API_PATH = "http://localhost:8080"
|
||||
|
||||
class BizError extends Error{
|
||||
code = -1;
|
||||
constructor(message,code = -1) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param api
|
||||
@ -71,7 +77,7 @@ function request(api, method = 'GET', postData = {}, progressChange = null) {
|
||||
return;
|
||||
}
|
||||
// 其他异常 直接抛出错误
|
||||
reject(new Error(result.message))
|
||||
reject(new BizError(result.message,result.code))
|
||||
}
|
||||
}
|
||||
fetch(API_PATH + api, options)
|
||||
@ -91,6 +97,9 @@ export default {
|
||||
},
|
||||
signup(params) {
|
||||
return request('/api/user/reg', 'POST', params);
|
||||
},
|
||||
info(){
|
||||
return request('/api/user/info');
|
||||
}
|
||||
},
|
||||
folder: {
|
||||
@ -166,5 +175,8 @@ export default {
|
||||
create(info) {
|
||||
return request('/api/share/create', 'POST', info)
|
||||
}
|
||||
},
|
||||
code(){
|
||||
return request('/create');
|
||||
}
|
||||
}
|
15
web/src/service/data.js
Normal file
@ -0,0 +1,15 @@
|
||||
import {createStore} from 'vuex'
|
||||
|
||||
const store = createStore({
|
||||
state() {
|
||||
return {
|
||||
username: '123123'
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
changeUsername(state, newName) {
|
||||
state.username = newName // 更新变量的值
|
||||
}
|
||||
}
|
||||
})
|
||||
export default store
|
@ -3,12 +3,16 @@ import api from "./api";
|
||||
|
||||
const TOKEN_KEY = 'user_auth_token';
|
||||
const store = createStore({
|
||||
state: {
|
||||
/**
|
||||
* @type {UserInfo}
|
||||
*/
|
||||
loginUser: null,
|
||||
token: null
|
||||
state() {
|
||||
return {
|
||||
/**
|
||||
* 用户信息
|
||||
* @type {UserInfo}
|
||||
*/
|
||||
loginUser: null,
|
||||
// 所有的state在内存中
|
||||
token: null
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
// 此选项中的方法必须同步
|
||||
@ -20,7 +24,7 @@ const store = createStore({
|
||||
// sessionStorage.setItem(TOKEN_KEY,token);
|
||||
localStorage.setItem(TOKEN_KEY, token);
|
||||
},
|
||||
clearToken(state){
|
||||
clearToken(state) {
|
||||
state.token = null;
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
sessionStorage.removeItem(TOKEN_KEY)
|
||||
@ -44,8 +48,9 @@ const store = createStore({
|
||||
},
|
||||
getters: {
|
||||
userToken(state) {
|
||||
const token = state.token || sessionStorage.getItem(TOKEN_KEY) || localStorage.getItem(TOKEN_KEY);
|
||||
if(!state.token) state.token = token;
|
||||
const token = state.token || sessionStorage.getItem(TOKEN_KEY) || localStorage.getItem(TOKEN_KEY);
|
||||
// 如果内存中没有token 存储一份
|
||||
if (!state.token) state.token = token;
|
||||
return token
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import {dayjs} from "element-plus";
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} type
|
||||
@ -7,6 +9,17 @@ export function isImage(type) {
|
||||
return type ? ['png', 'jpg', 'jpeg', 'gif', 'webp'].includes(type.toLowerCase()) : false;
|
||||
}
|
||||
|
||||
export default {
|
||||
isImage
|
||||
export function formatDate(time, format = 'MM-DD') {
|
||||
return dayjs(time).format(format);
|
||||
}
|
||||
|
||||
export function formatSize(a, b = 2) {
|
||||
if (0 == a) return "0 B";
|
||||
let c = 1024, d = b || 2, e = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"],
|
||||
f = Math.floor(Math.log(a) / Math.log(c));
|
||||
return parseFloat((a / Math.pow(c, f)).toFixed(d)) + " " + e[f];
|
||||
}
|
||||
|
||||
export default {
|
||||
isImage, formatSize, formatDate
|
||||
}
|