正在显示
82 个修改的文件
包含
2823 行增加
和
0 行删除
.DS_Store
0 → 100644
不能预览此文件类型
.editorconfig
0 → 100644
.eslintignore
0 → 100644
.eslintrc.cjs
0 → 100644
| 1 | +/* eslint-env node */ | ||
| 2 | +require('@rushstack/eslint-patch/modern-module-resolution') | ||
| 3 | + | ||
| 4 | +module.exports = { | ||
| 5 | + extends: [ | ||
| 6 | + 'eslint:recommended', | ||
| 7 | + 'plugin:vue/vue3-recommended', | ||
| 8 | + '@electron-toolkit', | ||
| 9 | + '@vue/eslint-config-prettier' | ||
| 10 | + ], | ||
| 11 | + rules: { | ||
| 12 | + 'vue/require-default-prop': 'off', | ||
| 13 | + 'vue/multi-word-component-names': 'off' | ||
| 14 | + } | ||
| 15 | +} |
.gitignore
0 → 100644
.prettierignore
0 → 100644
.prettierrc.yaml
0 → 100644
License
0 → 100644
| 1 | +MIT License | ||
| 2 | + | ||
| 3 | +Copyright (c) 2024 typsusan | ||
| 4 | + | ||
| 5 | +Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| 6 | +of this software and associated documentation files (the "Software"), to deal | ||
| 7 | +in the Software without restriction, including without limitation the rights | ||
| 8 | +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| 9 | +copies of the Software, and to permit persons to whom the Software is | ||
| 10 | +furnished to do so, subject to the following conditions: | ||
| 11 | + | ||
| 12 | +The above copyright notice and this permission notice shall be included in all | ||
| 13 | +copies or substantial portions of the Software. | ||
| 14 | + | ||
| 15 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| 16 | +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| 17 | +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| 18 | +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| 19 | +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| 20 | +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| 21 | +SOFTWARE. |
build/.env.development
0 → 100644
build/.env.production
0 → 100644
| 1 | +# 页面标题 | ||
| 2 | +VITE_APP_TITLE = 广西车险投保登记平台 | ||
| 3 | + | ||
| 4 | +# 生产环境配置 | ||
| 5 | +VITE_APP_ENV = 'production' | ||
| 6 | + | ||
| 7 | +# 若依管理系统/生产环境 | ||
| 8 | +VITE_APP_BASE_API = 'https://xfwbzshd.crgx.net' | ||
| 9 | + | ||
| 10 | +# 是否在打包时开启压缩,支持 gzip 和 brotli | ||
| 11 | +VITE_BUILD_COMPRESS = gzip | ||
| 12 | + | ||
| 13 | +# 开打新的tab的url前缀 | ||
| 14 | +VITE_APP_TAB_URL_PREFIX = 'https://xfwbzshd.crgx.net' |
build/entitlements.mac.plist
0 → 100644
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
| 3 | +<plist version="1.0"> | ||
| 4 | + <dict> | ||
| 5 | + <key>com.apple.security.cs.allow-jit</key> | ||
| 6 | + <true/> | ||
| 7 | + <key>com.apple.security.cs.allow-unsigned-executable-memory</key> | ||
| 8 | + <true/> | ||
| 9 | + <key>com.apple.security.cs.allow-dyld-environment-variables</key> | ||
| 10 | + <true/> | ||
| 11 | + </dict> | ||
| 12 | +</plist> |
build/icon.icns
0 → 100644
不能预览此文件类型
build/icon.ico
0 → 100644
不能预览此文件类型
build/icon.png
0 → 100644
35.1 KB
electron-builder.yml
0 → 100644
| 1 | +appId: com.electron.app | ||
| 2 | +productName: signature-app | ||
| 3 | +directories: | ||
| 4 | + buildResources: build | ||
| 5 | +files: | ||
| 6 | + - '!**/.vscode/*' | ||
| 7 | + - '!src/*' | ||
| 8 | + - '!electron.vite.config.{js,ts,mjs,cjs}' | ||
| 9 | + - '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}' | ||
| 10 | + - '!{.env,.env.*,.npmrc,pnpm-lock.yaml}' | ||
| 11 | +asarUnpack: | ||
| 12 | + - resources/** | ||
| 13 | +win: | ||
| 14 | + executableName: signature-app | ||
| 15 | +nsis: | ||
| 16 | + artifactName: ${name}-${version}-setup.${ext} | ||
| 17 | + shortcutName: ${productName} | ||
| 18 | + uninstallDisplayName: ${productName} | ||
| 19 | + createDesktopShortcut: always | ||
| 20 | +mac: | ||
| 21 | + entitlementsInherit: build/entitlements.mac.plist | ||
| 22 | + extendInfo: | ||
| 23 | + - NSCameraUsageDescription: Application requests access to the device's camera. | ||
| 24 | + - NSMicrophoneUsageDescription: Application requests access to the device's microphone. | ||
| 25 | + - NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder. | ||
| 26 | + - NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder. | ||
| 27 | + notarize: false | ||
| 28 | +dmg: | ||
| 29 | + artifactName: ${name}-${version}.${ext} | ||
| 30 | +linux: | ||
| 31 | + target: | ||
| 32 | + - AppImage | ||
| 33 | + - snap | ||
| 34 | + - deb | ||
| 35 | + maintainer: electronjs.org | ||
| 36 | + category: Utility | ||
| 37 | +appImage: | ||
| 38 | + artifactName: ${name}-${version}.${ext} | ||
| 39 | +npmRebuild: false | ||
| 40 | +publish: | ||
| 41 | + provider: generic | ||
| 42 | + url: https://example.com/auto-updates |
electron.vite.config.js
0 → 100644
| 1 | +import { resolve } from 'path' | ||
| 2 | +import { defineConfig, externalizeDepsPlugin, loadEnv } from 'electron-vite' | ||
| 3 | +import vue from '@vitejs/plugin-vue' | ||
| 4 | + | ||
| 5 | +// 导出环境变量 | ||
| 6 | +const envDir = resolve('build') | ||
| 7 | +process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true' | ||
| 8 | +export default defineConfig(({mode}) => { | ||
| 9 | + return { | ||
| 10 | + main: { | ||
| 11 | + plugins: [externalizeDepsPlugin()] | ||
| 12 | + }, | ||
| 13 | + preload: { | ||
| 14 | + plugins: [externalizeDepsPlugin()] | ||
| 15 | + }, | ||
| 16 | + renderer: { | ||
| 17 | + envDir, | ||
| 18 | + envPrefix: 'VITE_', | ||
| 19 | + resolve: { | ||
| 20 | + alias: { | ||
| 21 | + '@': resolve('src/renderer/src') | ||
| 22 | + } | ||
| 23 | + }, | ||
| 24 | + plugins: [vue()], | ||
| 25 | + server:{ | ||
| 26 | + host: true, | ||
| 27 | + port:5441, | ||
| 28 | + proxy:{ | ||
| 29 | + '/dev-api': { | ||
| 30 | + target: 'https://xfwbzshd.crgx.net', | ||
| 31 | + changeOrigin: true, | ||
| 32 | + rewrite: (p) => p.replace(/^\/dev-api/, '') | ||
| 33 | + } | ||
| 34 | + } | ||
| 35 | + } | ||
| 36 | + } | ||
| 37 | + } | ||
| 38 | +}) |
package.json
0 → 100644
| 1 | +{ | ||
| 2 | + "name": "signature-app", | ||
| 3 | + "version": "1.0.0", | ||
| 4 | + "description": "An Electron application with Vue", | ||
| 5 | + "main": "./out/main/index.js", | ||
| 6 | + "author": "example.com", | ||
| 7 | + "homepage": "https://www.electronjs.org", | ||
| 8 | + "scripts": { | ||
| 9 | + "format": "prettier --write .", | ||
| 10 | + "lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix", | ||
| 11 | + "start": "electron-vite preview", | ||
| 12 | + "dev": "electron-vite dev", | ||
| 13 | + "build": "electron-vite build", | ||
| 14 | + "postinstall": "electron-builder install-app-deps", | ||
| 15 | + "build:win": "npm run build && electron-builder --win --config", | ||
| 16 | + "build:mac": "npm run build && electron-builder --mac --config", | ||
| 17 | + "build:linux": "npm run build && electron-builder --linux --config" | ||
| 18 | + }, | ||
| 19 | + "dependencies": { | ||
| 20 | + "@electron-toolkit/preload": "^2.0.0", | ||
| 21 | + "@electron-toolkit/utils": "^2.0.0", | ||
| 22 | + "axios": "^1.7.8", | ||
| 23 | + "element-plus": "^2.11.5", | ||
| 24 | + "face-effet": "^1.5.5", | ||
| 25 | + "js-cookie": "^3.0.5", | ||
| 26 | + "jsencrypt": "^3.3.2", | ||
| 27 | + "pinia": "^3.0.1", | ||
| 28 | + "typeface-roboto": "^1.1.13", | ||
| 29 | + "vue-router": "^4.5.0" | ||
| 30 | + }, | ||
| 31 | + "devDependencies": { | ||
| 32 | + "@electron-toolkit/eslint-config": "^1.0.1", | ||
| 33 | + "@rushstack/eslint-patch": "^1.3.3", | ||
| 34 | + "@vitejs/plugin-vue": "^4.3.1", | ||
| 35 | + "@vue/eslint-config-prettier": "^8.0.0", | ||
| 36 | + "autoprefixer": "^10.4.21", | ||
| 37 | + "electron": "^25.6.0", | ||
| 38 | + "electron-builder": "^24.6.3", | ||
| 39 | + "electron-vite": "^1.0.27", | ||
| 40 | + "eslint": "^8.47.0", | ||
| 41 | + "eslint-plugin-vue": "^9.17.0", | ||
| 42 | + "postcss": "^8.5.6", | ||
| 43 | + "prettier": "^3.0.2", | ||
| 44 | + "sass": "^1.85.1", | ||
| 45 | + "vite": "^4.4.9", | ||
| 46 | + "vue": "^3.3.4" | ||
| 47 | + } | ||
| 48 | +} |
resources/icon.png
0 → 100644
8.3 KB
src/.DS_Store
0 → 100644
不能预览此文件类型
src/main/index.js
0 → 100644
| 1 | +import { app, shell, BrowserWindow, screen, ipcMain} from 'electron' | ||
| 2 | +import { join } from 'path' | ||
| 3 | +import { electronApp, optimizer, is } from '@electron-toolkit/utils' | ||
| 4 | +import icon from '../../resources/icon.png?asset' | ||
| 5 | + | ||
| 6 | +function createWindow(width, height) { | ||
| 7 | + // Create the browser window. | ||
| 8 | + const mainWindow = new BrowserWindow({ | ||
| 9 | + icon:icon, | ||
| 10 | + width, | ||
| 11 | + height: height, | ||
| 12 | + center: true, | ||
| 13 | + show: false, | ||
| 14 | + transparent:true, | ||
| 15 | + autoHideMenuBar: true, | ||
| 16 | + resizable: false, | ||
| 17 | + titleBarStyle:'hidden', | ||
| 18 | + webPreferences: { | ||
| 19 | + preload: join(__dirname, '../preload/index.js'), | ||
| 20 | + sandbox: false, | ||
| 21 | + contextIsolation:false, | ||
| 22 | + webSecurity: false, | ||
| 23 | + } | ||
| 24 | + }) | ||
| 25 | + // 点击最小化 | ||
| 26 | + ipcMain.on('minimizing',(event,args)=>{ | ||
| 27 | + event.preventDefault(); // 阻止默认最小化行为 | ||
| 28 | + mainWindow.minimize(); // 最小化到任务栏 | ||
| 29 | + }) | ||
| 30 | + | ||
| 31 | + // 点击全屏显示 | ||
| 32 | + ipcMain.on('toggle-fullscreen', (event) => { | ||
| 33 | + if (mainWindow.isFullScreen()) { | ||
| 34 | + mainWindow.setFullScreen(false) | ||
| 35 | + } else { | ||
| 36 | + mainWindow.setFullScreen(true) | ||
| 37 | + } | ||
| 38 | + }); | ||
| 39 | + | ||
| 40 | + // 在主进程中 | ||
| 41 | + mainWindow.webContents.on('before-input-event', (event, input) => { | ||
| 42 | + if (input.key === 'Escape' && mainWindow.isFullScreen()) { | ||
| 43 | + mainWindow.setFullScreen(false) | ||
| 44 | + event.preventDefault() // 阻止默认行为 | ||
| 45 | + } | ||
| 46 | + }) | ||
| 47 | + | ||
| 48 | + mainWindow.on('ready-to-show', () => { | ||
| 49 | + mainWindow.show() | ||
| 50 | + mainWindow.setFullScreen(true) | ||
| 51 | + }) | ||
| 52 | + | ||
| 53 | + mainWindow.webContents.setWindowOpenHandler((details) => { | ||
| 54 | + shell.openExternal(details.url) | ||
| 55 | + return { action: 'deny' } | ||
| 56 | + }) | ||
| 57 | + | ||
| 58 | + if (is.dev && process.env['ELECTRON_RENDERER_URL']) { | ||
| 59 | + // 窗口调试 mainWindow.webContents.openDevTools() | ||
| 60 | + mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL']) | ||
| 61 | + } else { | ||
| 62 | + mainWindow.loadFile(join(__dirname, '../renderer/index.html')) | ||
| 63 | + } | ||
| 64 | +} | ||
| 65 | + | ||
| 66 | +app.whenReady().then(() => { | ||
| 67 | + // 主进程获取工作区宽高 | ||
| 68 | + const primaryDisplay = screen.getPrimaryDisplay() | ||
| 69 | + const { width, height } = primaryDisplay.bounds | ||
| 70 | + electronApp.setAppUserModelId('com.electron') | ||
| 71 | + | ||
| 72 | + app.on('browser-window-created', (_, window) => { | ||
| 73 | + optimizer.watchWindowShortcuts(window) | ||
| 74 | + }) | ||
| 75 | + | ||
| 76 | + createWindow(width, height) | ||
| 77 | + | ||
| 78 | + app.on('activate', function () { | ||
| 79 | + if (BrowserWindow.getAllWindows().length === 0) createWindow(width, height) | ||
| 80 | + }) | ||
| 81 | +}) | ||
| 82 | + | ||
| 83 | + | ||
| 84 | +app.on('window-all-closed', () => { | ||
| 85 | + if (process.platform !== 'darwin') { | ||
| 86 | + app.quit() | ||
| 87 | + } | ||
| 88 | +}) |
src/preload/index.js
0 → 100644
| 1 | +import { contextBridge,ipcRenderer } from 'electron' | ||
| 2 | +import { electronAPI } from '@electron-toolkit/preload' | ||
| 3 | +const api = {} | ||
| 4 | +if (process.contextIsolated) { | ||
| 5 | + try { | ||
| 6 | + contextBridge.exposeInMainWorld('electron', electronAPI) | ||
| 7 | + contextBridge.exposeInMainWorld('api', api) | ||
| 8 | + } catch (error) { | ||
| 9 | + console.error(error) | ||
| 10 | + } | ||
| 11 | +} else { | ||
| 12 | + window.electron = electronAPI | ||
| 13 | + window.api = api | ||
| 14 | +} |
src/renderer/.DS_Store
0 → 100644
不能预览此文件类型
src/renderer/index.html
0 → 100644
src/renderer/src/.DS_Store
0 → 100644
不能预览此文件类型
src/renderer/src/App.vue
0 → 100644
| 1 | +<script setup> | ||
| 2 | +import bar from './components/bar/index.vue' | ||
| 3 | +</script> | ||
| 4 | + | ||
| 5 | +<template> | ||
| 6 | + <bar /> | ||
| 7 | + <router-view /> | ||
| 8 | +</template> | ||
| 9 | + | ||
| 10 | +<style> | ||
| 11 | +* { | ||
| 12 | + margin: 0; | ||
| 13 | + padding: 0; | ||
| 14 | +} | ||
| 15 | +div { | ||
| 16 | + box-sizing: border-box; | ||
| 17 | +} | ||
| 18 | +body::-webkit-scrollbar { | ||
| 19 | + display: none; /* Chrome/Safari/Edge */ | ||
| 20 | +} | ||
| 21 | + | ||
| 22 | +body { | ||
| 23 | + scrollbar-width: none; /* Firefox */ | ||
| 24 | + -ms-overflow-style: none; /* IE 10+ */ | ||
| 25 | +} | ||
| 26 | +</style> |
src/renderer/src/api/login.js
0 → 100644
| 1 | +import request from '@/utils/request' | ||
| 2 | + | ||
| 3 | +// 登录方法 | ||
| 4 | +export function login(username, password) { | ||
| 5 | + const data = { | ||
| 6 | + username, | ||
| 7 | + password | ||
| 8 | + } | ||
| 9 | + return request({ | ||
| 10 | + url: '/login', | ||
| 11 | + headers: { | ||
| 12 | + isToken: false, | ||
| 13 | + constentType: 'application/json;charset=UTF-8' | ||
| 14 | + }, | ||
| 15 | + method: 'post', | ||
| 16 | + data: data | ||
| 17 | + }) | ||
| 18 | +} | ||
| 19 | + | ||
| 20 | +// 注册方法 | ||
| 21 | +export function register(data) { | ||
| 22 | + return request({ | ||
| 23 | + url: '/register', | ||
| 24 | + headers: { | ||
| 25 | + isToken: false | ||
| 26 | + }, | ||
| 27 | + method: 'post', | ||
| 28 | + data: data | ||
| 29 | + }) | ||
| 30 | +} | ||
| 31 | + | ||
| 32 | +// 获取用户详细信息 | ||
| 33 | +export function getInfo() { | ||
| 34 | + return request({ | ||
| 35 | + url: '/getInfo', | ||
| 36 | + method: 'get' | ||
| 37 | + }) | ||
| 38 | +} | ||
| 39 | + | ||
| 40 | +// 上传头像方法 | ||
| 41 | +export function uploadAvatar(data) { | ||
| 42 | + return request({ | ||
| 43 | + url: '/system/user/profile/avatar', | ||
| 44 | + method: 'post', | ||
| 45 | + headers: { | ||
| 46 | + 'Content-Type': 'multipart/form-data' | ||
| 47 | + }, | ||
| 48 | + data: data | ||
| 49 | + }) | ||
| 50 | +} | ||
| 51 | + | ||
| 52 | +// 退出方法 | ||
| 53 | +export function logout() { | ||
| 54 | + return request({ | ||
| 55 | + url: '/logout', | ||
| 56 | + method: 'post' | ||
| 57 | + }) | ||
| 58 | +} | ||
| 59 | + | ||
| 60 | +// 获取验证码 | ||
| 61 | +export function getCodeImg() { | ||
| 62 | + return request({ | ||
| 63 | + url: '/captchaImage', | ||
| 64 | + headers: { | ||
| 65 | + isToken: false | ||
| 66 | + }, | ||
| 67 | + method: 'get', | ||
| 68 | + timeout: 20000 | ||
| 69 | + }) | ||
| 70 | +} |
src/renderer/src/api/menu.js
0 → 100644
src/renderer/src/api/signature.js
0 → 100644
| 1 | +import request from '@/utils/request' | ||
| 2 | + | ||
| 3 | +// 获取签名列表 | ||
| 4 | +export const getSignatureList = (query) => { | ||
| 5 | + return request({ | ||
| 6 | + url: '/system/sign/list', | ||
| 7 | + method: 'get', | ||
| 8 | + params: query | ||
| 9 | + }) | ||
| 10 | +} | ||
| 11 | + | ||
| 12 | +// 获取应用背景 | ||
| 13 | +export const getUseBg = () => { | ||
| 14 | + return request({ | ||
| 15 | + url: '/system/bg/getUsedBg', | ||
| 16 | + method: 'get', | ||
| 17 | + }) | ||
| 18 | +} |
src/renderer/src/assets/.DS_Store
0 → 100644
不能预览此文件类型
src/renderer/src/assets/bar/close.svg
0 → 100644
| 1 | +<svg t="1732849122721" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="18087" width="200" height="200"><path d="M240.512 180.181333l271.530667 271.488 271.530666-271.488a42.666667 42.666667 0 0 1 56.32-3.541333l4.010667 3.541333a42.666667 42.666667 0 0 1 0 60.330667l-271.530667 271.530667 271.530667 271.530666a42.666667 42.666667 0 0 1-56.32 63.872l-4.010667-3.541333-271.530666-271.530667-271.530667 271.530667-4.010667 3.541333a42.666667 42.666667 0 0 1-56.32-63.872l271.488-271.530666-271.488-271.530667a42.666667 42.666667 0 0 1 60.330667-60.330667z" fill="#151717" p-id="18088"></path></svg> |
src/renderer/src/assets/bar/expand.svg
0 → 100644
| 1 | +<svg t="1732848995457" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15178" width="200" height="200"><path d="M217.6 320l486.144 486.4H217.6V320z m601.344 358.4L332.8 192h486.144v486.4z" fill="#151717" p-id="15179"></path></svg> |
src/renderer/src/assets/bar/logo.png
0 → 100644
455.6 KB
src/renderer/src/assets/bar/logo.svg
0 → 100644
| 1 | +<svg t="1732780895530" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5154" width="200" height="200"><path d="M919.9 417.7c-22.1-54.8-54.5-103.8-96.4-145.7L579.7 28.1C561.6 10 537.6 0 512 0s-49.6 10-67.7 28.1L200.5 272c-41.9 41.9-74.3 90.9-96.4 145.7C82.8 470.5 72 526.4 72 583.7s10.8 113.2 32.1 166c22.1 54.8 54.5 103.8 96.4 145.7 41.9 41.9 90.9 74.4 145.6 96.5 52.8 21.3 108.6 32.1 165.9 32.1 57.3 0 113.1-10.8 165.9-32.1 54.7-22.1 103.7-54.6 145.6-96.5 41.9-41.9 74.3-90.9 96.4-145.7 21.3-52.8 32.1-108.7 32.1-166s-10.8-113.2-32.1-166z m-55.7 309.5c-19.1 47.3-47.1 89.6-83.2 125.7-36.1 36.2-78.4 64.2-125.6 83.3C609.8 954.7 561.5 964 512 964s-97.8-9.3-143.4-27.8c-47.2-19.1-89.5-47.1-125.6-83.3-36.1-36.2-64.1-78.5-83.2-125.7-18.4-45.7-27.8-94-27.8-143.5s9.3-97.9 27.8-143.5c19.1-47.3 47.1-89.6 83.2-125.7l243.7-244C493.5 63.7 502.5 60 512 60s18.5 3.7 25.3 10.5L781 314.4c36.1 36.2 64.1 78.5 83.2 125.7 18.4 45.7 27.8 94 27.8 143.5s-9.3 98-27.8 143.6z" fill="#151717" p-id="5155"></path><path d="M741.1 514.5c-11.7-11.7-30.8-11.7-42.4 0L581.3 631.9 463.9 514.5c-11.7-11.7-30.8-11.7-42.4 0L282.9 653.1c-11.7 11.7-11.7 30.8 0 42.4 11.7 11.7 30.8 11.7 42.4 0l117.4-117.4 117.4 117.4c11.7 11.7 30.8 11.7 42.4 0l138.6-138.6c11.7-11.7 11.7-30.7 0-42.4z" fill="#151717" p-id="5156"></path></svg> |
src/renderer/src/assets/bar/minimum.svg
0 → 100644
| 1 | +<svg t="1732849065701" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="17032" width="200" height="200"><path d="M863.7 552.5H160.3c-10.6 0-19.2-8.6-19.2-19.2v-41.7c0-10.6 8.6-19.2 19.2-19.2h703.3c10.6 0 19.2 8.6 19.2 19.2v41.7c0 10.6-8.5 19.2-19.1 19.2z" p-id="17033" fill="#151717"></path></svg> |
src/renderer/src/assets/css/styles.less
0 → 100644
src/renderer/src/assets/img/.DS_Store
0 → 100644
不能预览此文件类型
src/renderer/src/assets/img/1.webp
0 → 100644
不能预览此文件类型
src/renderer/src/assets/img/10.jpeg
0 → 100644
43.4 KB
src/renderer/src/assets/img/11.jpeg
0 → 100644
34.6 KB
src/renderer/src/assets/img/12.jpeg
0 → 100644
35.0 KB
src/renderer/src/assets/img/13.jpeg
0 → 100644
23.2 KB
src/renderer/src/assets/img/14.jpeg
0 → 100644
29.3 KB
src/renderer/src/assets/img/15.jpeg
0 → 100644
35.7 KB
src/renderer/src/assets/img/2.webp
0 → 100644
不能预览此文件类型
src/renderer/src/assets/img/3.webp
0 → 100644
不能预览此文件类型
src/renderer/src/assets/img/4.webp
0 → 100644
不能预览此文件类型
src/renderer/src/assets/img/6.webp
0 → 100644
不能预览此文件类型
src/renderer/src/assets/img/7.webp
0 → 100644
不能预览此文件类型
src/renderer/src/assets/img/8.webp
0 → 100644
不能预览此文件类型
src/renderer/src/assets/img/9.webp
0 → 100644
不能预览此文件类型
src/renderer/src/assets/img/bg.png
0 → 100644
2.7 MB
src/renderer/src/components/bar/index.css
0 → 100644
| 1 | +.title-bar { | ||
| 2 | + position: relative; /* 为定位子元素提供参照 */ | ||
| 3 | + height: 45px; | ||
| 4 | + display: flex; | ||
| 5 | + align-items: center; | ||
| 6 | + padding: 0; | ||
| 7 | + -webkit-app-region: drag; | ||
| 8 | + user-select: none; | ||
| 9 | + overflow: hidden; /* 防止内容溢出 */ | ||
| 10 | + | ||
| 11 | + border-top-left-radius: 20px; | ||
| 12 | + border-top-right-radius: 20px; | ||
| 13 | + border-top: 1px solid #d9d9d9; | ||
| 14 | + border-left: 1px solid #d9d9d9; | ||
| 15 | + border-right: 1px solid #d9d9d9; | ||
| 16 | +} | ||
| 17 | + | ||
| 18 | +.title-bar-left,.title-bar-right{ | ||
| 19 | + width: 25%; | ||
| 20 | + height: 45px; | ||
| 21 | + float: left; | ||
| 22 | + display: flex; | ||
| 23 | + justify-content: center; | ||
| 24 | + align-items: center; | ||
| 25 | + | ||
| 26 | +} | ||
| 27 | +.title-bar-left img{ | ||
| 28 | + margin-left: 10px; | ||
| 29 | +} | ||
| 30 | + | ||
| 31 | +.title-bar-right{ | ||
| 32 | + width: 75%; | ||
| 33 | + float: right; | ||
| 34 | + display: flex; | ||
| 35 | + justify-content: right; | ||
| 36 | + background-color: white; | ||
| 37 | +} | ||
| 38 | + | ||
| 39 | + | ||
| 40 | +.title-bar .icon { | ||
| 41 | + width: 16px; | ||
| 42 | + height: 16px; | ||
| 43 | + margin-right: 10px; | ||
| 44 | + -webkit-app-region: drag; | ||
| 45 | + user-select: none; | ||
| 46 | +} | ||
| 47 | + | ||
| 48 | +.title-bar .title { | ||
| 49 | + flex-grow: 1; | ||
| 50 | + font-weight: bold; | ||
| 51 | + white-space: nowrap; | ||
| 52 | + overflow: hidden; | ||
| 53 | + text-overflow: ellipsis; | ||
| 54 | + -webkit-app-region: drag; | ||
| 55 | + color: #151717; | ||
| 56 | + font-size: 13px; | ||
| 57 | + margin-top: -2px; | ||
| 58 | +} | ||
| 59 | + | ||
| 60 | +.title-bar .operating-button { | ||
| 61 | + width: 15px; | ||
| 62 | + height: 15px; | ||
| 63 | + display: flex; | ||
| 64 | + justify-content: center; | ||
| 65 | + align-items: center; | ||
| 66 | + cursor: pointer; | ||
| 67 | + -webkit-app-region: no-drag; | ||
| 68 | + transition: background-color 0.2s ease, opacity 0.2s ease; | ||
| 69 | + border-radius: 50%; | ||
| 70 | + background-color: var(--button-bg); | ||
| 71 | + margin-right: 8px; | ||
| 72 | +} | ||
| 73 | + | ||
| 74 | +.title-bar .operating-button img { | ||
| 75 | + width: 10px; | ||
| 76 | + height: 10px; | ||
| 77 | + opacity: 0; | ||
| 78 | + transition: opacity 0.2s ease; | ||
| 79 | +} | ||
| 80 | + | ||
| 81 | +.title-bar .operating-button:hover img { | ||
| 82 | + opacity: 1; | ||
| 83 | +} | ||
| 84 | + | ||
| 85 | +.title-bar .close-button { | ||
| 86 | + --button-bg: #f56057; | ||
| 87 | +} | ||
| 88 | + | ||
| 89 | +.title-bar .minimum-button { | ||
| 90 | + --button-bg: #fec428; | ||
| 91 | +} | ||
| 92 | + | ||
| 93 | +.title-bar .expand-button { | ||
| 94 | + --button-bg: #1fd42a; | ||
| 95 | +} |
src/renderer/src/components/bar/index.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <div class="title-bar"> | ||
| 3 | + <div class="title-bar-left" :style="{ backgroundColor: '#fff' }"> | ||
| 4 | + <img src="@/assets/bar/logo.png" alt="图标" class="icon" /> | ||
| 5 | + <div class="title">签名墙</div> | ||
| 6 | + </div> | ||
| 7 | + | ||
| 8 | + <div class="title-bar-right"> | ||
| 9 | + <div class="operating-button close-button" @click="closeWindow"> | ||
| 10 | + <img src="~@/assets/bar/close.svg" /> | ||
| 11 | + </div> | ||
| 12 | + <div class="operating-button minimum-button" @click="minimumWindow"> | ||
| 13 | + <img src="~@/assets/bar/minimum.svg" /> | ||
| 14 | + </div> | ||
| 15 | + <div class="operating-button expand-button"> | ||
| 16 | + <img src="~@/assets/bar/expand.svg" @click="expandWindow" /> | ||
| 17 | + </div> | ||
| 18 | + <div style="width: 10px"></div> | ||
| 19 | + </div> | ||
| 20 | + </div> | ||
| 21 | +</template> | ||
| 22 | + | ||
| 23 | +<script setup> | ||
| 24 | +function closeWindow() { | ||
| 25 | + window.close() // 关闭窗口 | ||
| 26 | +} | ||
| 27 | +function minimumWindow() { | ||
| 28 | + electron.ipcRenderer.send('minimizing') | ||
| 29 | +} | ||
| 30 | + | ||
| 31 | +function expandWindow() { | ||
| 32 | + electron.ipcRenderer.send('toggle-fullscreen') | ||
| 33 | +} | ||
| 34 | +</script> | ||
| 35 | + | ||
| 36 | +<style scoped> | ||
| 37 | +@import './index.css'; | ||
| 38 | +</style> |
src/renderer/src/main.js
0 → 100644
| 1 | +import { createApp } from 'vue'; | ||
| 2 | +import App from './App.vue'; | ||
| 3 | +import store from './store' | ||
| 4 | +import router from './router'; | ||
| 5 | +import ElementPlus, { ElMessage } from 'element-plus'; | ||
| 6 | +import zhCn from 'element-plus/es/locale/lang/zh-cn' | ||
| 7 | +import * as ElementPlusIconsVue from '@element-plus/icons-vue' | ||
| 8 | +import 'element-plus/dist/index.css'; | ||
| 9 | + | ||
| 10 | +const app = createApp(App); | ||
| 11 | + | ||
| 12 | +// 将 ElementPlus 和路由器挂载到应用实例 | ||
| 13 | +app.use(ElementPlus, { | ||
| 14 | + locale: zhCn | ||
| 15 | +}) | ||
| 16 | +// 全局注册ElementPlus图标 | ||
| 17 | +for (const [key, component] of Object.entries(ElementPlusIconsVue)) { | ||
| 18 | + app.component(key, component) | ||
| 19 | +} | ||
| 20 | + | ||
| 21 | +app.config.globalProperties.$message = ElMessage; | ||
| 22 | +app.use(router); | ||
| 23 | +app.use(store); | ||
| 24 | + | ||
| 25 | + | ||
| 26 | +// 挂载应用 | ||
| 27 | +app.mount('#app'); |
src/renderer/src/plugins/auth.js
0 → 100644
| 1 | +import useUserStore from '@/store/modules/user' | ||
| 2 | + | ||
| 3 | +function authPermission(permission) { | ||
| 4 | + const all_permission = "*:*:*"; | ||
| 5 | + const permissions = useUserStore().permissions | ||
| 6 | + if (permission && permission.length > 0) { | ||
| 7 | + return permissions.some(v => { | ||
| 8 | + return all_permission === v || v === permission | ||
| 9 | + }) | ||
| 10 | + } else { | ||
| 11 | + return false | ||
| 12 | + } | ||
| 13 | +} | ||
| 14 | + | ||
| 15 | +function authRole(role) { | ||
| 16 | + const super_admin = "admin"; | ||
| 17 | + const roles = useUserStore().roles | ||
| 18 | + if (role && role.length > 0) { | ||
| 19 | + return roles.some(v => { | ||
| 20 | + return super_admin === v || v === role | ||
| 21 | + }) | ||
| 22 | + } else { | ||
| 23 | + return false | ||
| 24 | + } | ||
| 25 | +} | ||
| 26 | + | ||
| 27 | +export default { | ||
| 28 | + // 验证用户是否具备某权限 | ||
| 29 | + hasPermi(permission) { | ||
| 30 | + return authPermission(permission); | ||
| 31 | + }, | ||
| 32 | + // 验证用户是否含有指定权限,只需包含其中一个 | ||
| 33 | + hasPermiOr(permissions) { | ||
| 34 | + return permissions.some(item => { | ||
| 35 | + return authPermission(item) | ||
| 36 | + }) | ||
| 37 | + }, | ||
| 38 | + // 验证用户是否含有指定权限,必须全部拥有 | ||
| 39 | + hasPermiAnd(permissions) { | ||
| 40 | + return permissions.every(item => { | ||
| 41 | + return authPermission(item) | ||
| 42 | + }) | ||
| 43 | + }, | ||
| 44 | + // 验证用户是否具备某角色 | ||
| 45 | + hasRole(role) { | ||
| 46 | + return authRole(role); | ||
| 47 | + }, | ||
| 48 | + // 验证用户是否含有指定角色,只需包含其中一个 | ||
| 49 | + hasRoleOr(roles) { | ||
| 50 | + return roles.some(item => { | ||
| 51 | + return authRole(item) | ||
| 52 | + }) | ||
| 53 | + }, | ||
| 54 | + // 验证用户是否含有指定角色,必须全部拥有 | ||
| 55 | + hasRoleAnd(roles) { | ||
| 56 | + return roles.every(item => { | ||
| 57 | + return authRole(item) | ||
| 58 | + }) | ||
| 59 | + } | ||
| 60 | +} |
src/renderer/src/plugins/cache.js
0 → 100644
| 1 | +const sessionCache = { | ||
| 2 | + set (key, value) { | ||
| 3 | + if (!sessionStorage) { | ||
| 4 | + return | ||
| 5 | + } | ||
| 6 | + if (key != null && value != null) { | ||
| 7 | + sessionStorage.setItem(key, value) | ||
| 8 | + } | ||
| 9 | + }, | ||
| 10 | + get (key) { | ||
| 11 | + if (!sessionStorage) { | ||
| 12 | + return null | ||
| 13 | + } | ||
| 14 | + if (key == null) { | ||
| 15 | + return null | ||
| 16 | + } | ||
| 17 | + return sessionStorage.getItem(key) | ||
| 18 | + }, | ||
| 19 | + setJSON (key, jsonValue) { | ||
| 20 | + if (jsonValue != null) { | ||
| 21 | + this.set(key, JSON.stringify(jsonValue)) | ||
| 22 | + } | ||
| 23 | + }, | ||
| 24 | + getJSON (key) { | ||
| 25 | + const value = this.get(key) | ||
| 26 | + if (value != null) { | ||
| 27 | + return JSON.parse(value) | ||
| 28 | + } | ||
| 29 | + }, | ||
| 30 | + remove (key) { | ||
| 31 | + sessionStorage.removeItem(key); | ||
| 32 | + } | ||
| 33 | +} | ||
| 34 | +const localCache = { | ||
| 35 | + set (key, value) { | ||
| 36 | + if (!localStorage) { | ||
| 37 | + return | ||
| 38 | + } | ||
| 39 | + if (key != null && value != null) { | ||
| 40 | + localStorage.setItem(key, value) | ||
| 41 | + } | ||
| 42 | + }, | ||
| 43 | + get (key) { | ||
| 44 | + if (!localStorage) { | ||
| 45 | + return null | ||
| 46 | + } | ||
| 47 | + if (key == null) { | ||
| 48 | + return null | ||
| 49 | + } | ||
| 50 | + return localStorage.getItem(key) | ||
| 51 | + }, | ||
| 52 | + setJSON (key, jsonValue) { | ||
| 53 | + if (jsonValue != null) { | ||
| 54 | + this.set(key, JSON.stringify(jsonValue)) | ||
| 55 | + } | ||
| 56 | + }, | ||
| 57 | + getJSON (key) { | ||
| 58 | + const value = this.get(key) | ||
| 59 | + if (value != null) { | ||
| 60 | + return JSON.parse(value) | ||
| 61 | + } | ||
| 62 | + }, | ||
| 63 | + remove (key) { | ||
| 64 | + localStorage.removeItem(key); | ||
| 65 | + } | ||
| 66 | +} | ||
| 67 | + | ||
| 68 | +export default { | ||
| 69 | + /** | ||
| 70 | + * 会话级缓存 | ||
| 71 | + */ | ||
| 72 | + session: sessionCache, | ||
| 73 | + /** | ||
| 74 | + * 本地缓存 | ||
| 75 | + */ | ||
| 76 | + local: localCache | ||
| 77 | +} |
src/renderer/src/plugins/download.js
0 → 100644
| 1 | +import axios from 'axios' | ||
| 2 | +import { ElMessage } from 'element-plus' | ||
| 3 | +import { saveAs } from 'file-saver' | ||
| 4 | +import { getToken } from '@/utils/auth' | ||
| 5 | +import errorCode from '@/utils/errorCode' | ||
| 6 | +import { blobValidate } from '@/utils/ruoyi' | ||
| 7 | + | ||
| 8 | +const baseURL = import.meta.env.VITE_APP_BASE_API | ||
| 9 | + | ||
| 10 | +export default { | ||
| 11 | + name(name, isDelete = true) { | ||
| 12 | + var url = baseURL + "/common/download?fileName=" + encodeURIComponent(name) + "&delete=" + isDelete | ||
| 13 | + axios({ | ||
| 14 | + method: 'get', | ||
| 15 | + url: url, | ||
| 16 | + responseType: 'blob', | ||
| 17 | + headers: { 'Authorization': 'Bearer ' + getToken() } | ||
| 18 | + }).then(async (res) => { | ||
| 19 | + const isLogin = await blobValidate(res.data); | ||
| 20 | + if (isLogin) { | ||
| 21 | + const blob = new Blob([res.data]) | ||
| 22 | + this.saveAs(blob, decodeURIComponent(res.headers['download-filename'])) | ||
| 23 | + } else { | ||
| 24 | + this.printErrMsg(res.data); | ||
| 25 | + } | ||
| 26 | + }) | ||
| 27 | + }, | ||
| 28 | + resource(resource) { | ||
| 29 | + var url = baseURL + "/common/download/resource?resource=" + encodeURIComponent(resource); | ||
| 30 | + axios({ | ||
| 31 | + method: 'get', | ||
| 32 | + url: url, | ||
| 33 | + responseType: 'blob', | ||
| 34 | + headers: { 'Authorization': 'Bearer ' + getToken() } | ||
| 35 | + }).then(async (res) => { | ||
| 36 | + const isLogin = await blobValidate(res.data); | ||
| 37 | + if (isLogin) { | ||
| 38 | + const blob = new Blob([res.data]) | ||
| 39 | + this.saveAs(blob, decodeURIComponent(res.headers['download-filename'])) | ||
| 40 | + } else { | ||
| 41 | + this.printErrMsg(res.data); | ||
| 42 | + } | ||
| 43 | + }) | ||
| 44 | + }, | ||
| 45 | + zip(url, name) { | ||
| 46 | + var url = baseURL + url | ||
| 47 | + axios({ | ||
| 48 | + method: 'get', | ||
| 49 | + url: url, | ||
| 50 | + responseType: 'blob', | ||
| 51 | + headers: { 'Authorization': 'Bearer ' + getToken() } | ||
| 52 | + }).then(async (res) => { | ||
| 53 | + const isLogin = await blobValidate(res.data); | ||
| 54 | + if (isLogin) { | ||
| 55 | + const blob = new Blob([res.data], { type: 'application/zip' }) | ||
| 56 | + this.saveAs(blob, name) | ||
| 57 | + } else { | ||
| 58 | + this.printErrMsg(res.data); | ||
| 59 | + } | ||
| 60 | + }) | ||
| 61 | + }, | ||
| 62 | + saveAs(text, name, opts) { | ||
| 63 | + saveAs(text, name, opts); | ||
| 64 | + }, | ||
| 65 | + async printErrMsg(data) { | ||
| 66 | + const resText = await data.text(); | ||
| 67 | + const rspObj = JSON.parse(resText); | ||
| 68 | + const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'] | ||
| 69 | + ElMessage.error(errMsg); | ||
| 70 | + } | ||
| 71 | +} | ||
| 72 | + |
src/renderer/src/plugins/index.js
0 → 100644
| 1 | +import tab from './tab' | ||
| 2 | +import auth from './auth' | ||
| 3 | +import cache from './cache' | ||
| 4 | +import modal from './modal' | ||
| 5 | +import download from './download' | ||
| 6 | + | ||
| 7 | +export default function installPlugins(app){ | ||
| 8 | + // 页签操作 | ||
| 9 | + app.config.globalProperties.$tab = tab | ||
| 10 | + // 认证对象 | ||
| 11 | + app.config.globalProperties.$auth = auth | ||
| 12 | + // 缓存对象 | ||
| 13 | + app.config.globalProperties.$cache = cache | ||
| 14 | + // 模态框对象 | ||
| 15 | + app.config.globalProperties.$modal = modal | ||
| 16 | + // 下载文件 | ||
| 17 | + app.config.globalProperties.$download = download | ||
| 18 | +} |
src/renderer/src/plugins/modal.js
0 → 100644
| 1 | +import { ElMessage, ElMessageBox, ElNotification, ElLoading } from 'element-plus' | ||
| 2 | + | ||
| 3 | +let loadingInstance; | ||
| 4 | + | ||
| 5 | +export default { | ||
| 6 | + // 消息提示 | ||
| 7 | + msg(content) { | ||
| 8 | + ElMessage.info(content) | ||
| 9 | + }, | ||
| 10 | + // 错误消息 | ||
| 11 | + msgError(content) { | ||
| 12 | + ElMessage.error(content) | ||
| 13 | + }, | ||
| 14 | + // 成功消息 | ||
| 15 | + msgSuccess(content) { | ||
| 16 | + ElMessage.success(content) | ||
| 17 | + }, | ||
| 18 | + // 警告消息 | ||
| 19 | + msgWarning(content) { | ||
| 20 | + ElMessage.warning(content) | ||
| 21 | + }, | ||
| 22 | + // 弹出提示 | ||
| 23 | + alert(content) { | ||
| 24 | + ElMessageBox.alert(content, "系统提示") | ||
| 25 | + }, | ||
| 26 | + // 错误提示 | ||
| 27 | + alertError(content) { | ||
| 28 | + ElMessageBox.alert(content, "系统提示", { type: 'error' }) | ||
| 29 | + }, | ||
| 30 | + // 成功提示 | ||
| 31 | + alertSuccess(content) { | ||
| 32 | + ElMessageBox.alert(content, "系统提示", { type: 'success' }) | ||
| 33 | + }, | ||
| 34 | + // 警告提示 | ||
| 35 | + alertWarning(content) { | ||
| 36 | + ElMessageBox.alert(content, "系统提示", { type: 'warning' }) | ||
| 37 | + }, | ||
| 38 | + // 通知提示 | ||
| 39 | + notify(content) { | ||
| 40 | + ElNotification.info(content) | ||
| 41 | + }, | ||
| 42 | + // 错误通知 | ||
| 43 | + notifyError(content) { | ||
| 44 | + ElNotification.error(content); | ||
| 45 | + }, | ||
| 46 | + // 成功通知 | ||
| 47 | + notifySuccess(content) { | ||
| 48 | + ElNotification.success(content) | ||
| 49 | + }, | ||
| 50 | + // 警告通知 | ||
| 51 | + notifyWarning(content) { | ||
| 52 | + ElNotification.warning(content) | ||
| 53 | + }, | ||
| 54 | + // 确认窗体 | ||
| 55 | + confirm(content) { | ||
| 56 | + return ElMessageBox.confirm(content, "系统提示", { | ||
| 57 | + confirmButtonText: '确定', | ||
| 58 | + cancelButtonText: '取消', | ||
| 59 | + type: "warning", | ||
| 60 | + }) | ||
| 61 | + }, | ||
| 62 | + // 提交内容 | ||
| 63 | + prompt(content) { | ||
| 64 | + return ElMessageBox.prompt(content, "系统提示", { | ||
| 65 | + confirmButtonText: '确定', | ||
| 66 | + cancelButtonText: '取消', | ||
| 67 | + type: "warning", | ||
| 68 | + }) | ||
| 69 | + }, | ||
| 70 | + // 打开遮罩层 | ||
| 71 | + loading(content) { | ||
| 72 | + loadingInstance = ElLoading.service({ | ||
| 73 | + lock: true, | ||
| 74 | + text: content, | ||
| 75 | + background: "rgba(0, 0, 0, 0.7)", | ||
| 76 | + }) | ||
| 77 | + }, | ||
| 78 | + // 关闭遮罩层 | ||
| 79 | + closeLoading() { | ||
| 80 | + loadingInstance.close(); | ||
| 81 | + } | ||
| 82 | +} |
src/renderer/src/plugins/tab.js
0 → 100644
| 1 | +import useTagsViewStore from '@/store/modules/tagsView' | ||
| 2 | +import router from '@/router' | ||
| 3 | + | ||
| 4 | +export default { | ||
| 5 | + // 刷新当前tab页签 | ||
| 6 | + refreshPage(obj) { | ||
| 7 | + const { path, query, matched } = router.currentRoute.value; | ||
| 8 | + if (obj === undefined) { | ||
| 9 | + matched.forEach((m) => { | ||
| 10 | + if (m.components && m.components.default && m.components.default.name) { | ||
| 11 | + if (!['Layout', 'ParentView'].includes(m.components.default.name)) { | ||
| 12 | + obj = { name: m.components.default.name, path: path, query: query }; | ||
| 13 | + } | ||
| 14 | + } | ||
| 15 | + }); | ||
| 16 | + } | ||
| 17 | + return useTagsViewStore().delCachedView(obj).then(() => { | ||
| 18 | + const { path, query } = obj | ||
| 19 | + router.replace({ | ||
| 20 | + path: '/redirect' + path, | ||
| 21 | + query: query | ||
| 22 | + }) | ||
| 23 | + }) | ||
| 24 | + }, | ||
| 25 | + // 关闭当前tab页签,打开新页签 | ||
| 26 | + closeOpenPage(obj) { | ||
| 27 | + useTagsViewStore().delView(router.currentRoute.value); | ||
| 28 | + if (obj !== undefined) { | ||
| 29 | + return router.push(obj); | ||
| 30 | + } | ||
| 31 | + }, | ||
| 32 | + // 关闭指定tab页签 | ||
| 33 | + closePage(obj) { | ||
| 34 | + if (obj === undefined) { | ||
| 35 | + return useTagsViewStore().delView(router.currentRoute.value).then(({ lastPath }) => { | ||
| 36 | + return router.push(lastPath || '/index'); | ||
| 37 | + }); | ||
| 38 | + } | ||
| 39 | + return useTagsViewStore().delView(obj); | ||
| 40 | + }, | ||
| 41 | + // 关闭所有tab页签 | ||
| 42 | + closeAllPage() { | ||
| 43 | + return useTagsViewStore().delAllViews(); | ||
| 44 | + }, | ||
| 45 | + // 关闭左侧tab页签 | ||
| 46 | + closeLeftPage(obj) { | ||
| 47 | + return useTagsViewStore().delLeftTags(obj || router.currentRoute.value); | ||
| 48 | + }, | ||
| 49 | + // 关闭右侧tab页签 | ||
| 50 | + closeRightPage(obj) { | ||
| 51 | + return useTagsViewStore().delRightTags(obj || router.currentRoute.value); | ||
| 52 | + }, | ||
| 53 | + // 关闭其他tab页签 | ||
| 54 | + closeOtherPage(obj) { | ||
| 55 | + return useTagsViewStore().delOthersViews(obj || router.currentRoute.value); | ||
| 56 | + }, | ||
| 57 | + // 打开tab页签 | ||
| 58 | + openPage(url) { | ||
| 59 | + return router.push(url); | ||
| 60 | + }, | ||
| 61 | + // 修改tab页签 | ||
| 62 | + updatePage(obj) { | ||
| 63 | + return useTagsViewStore().updateVisitedView(obj); | ||
| 64 | + } | ||
| 65 | +} |
src/renderer/src/router/index.js
0 → 100644
| 1 | +import { createRouter, createWebHashHistory } from 'vue-router' | ||
| 2 | + | ||
| 3 | +// 公共路由 | ||
| 4 | +export const constantRoutes = [ | ||
| 5 | + { | ||
| 6 | + path: '/', | ||
| 7 | + name: '首页', | ||
| 8 | + component: () => import('@/views/index.vue'), | ||
| 9 | + hidden: true | ||
| 10 | + } | ||
| 11 | +] | ||
| 12 | + | ||
| 13 | +const router = createRouter({ | ||
| 14 | + history: createWebHashHistory(import.meta.env.BASE_URL), | ||
| 15 | + routes: constantRoutes, | ||
| 16 | +}) | ||
| 17 | + | ||
| 18 | +export default router |
src/renderer/src/store/index.ts
0 → 100644
src/renderer/src/store/modules/user.js
0 → 100644
| 1 | +import { defineStore } from 'pinia' | ||
| 2 | +import { login, logout, getInfo } from '@/api/login.js' | ||
| 3 | +import { getToken, setToken, removeToken } from '@/utils/auth' | ||
| 4 | + | ||
| 5 | +const useUserStore = defineStore('user', { | ||
| 6 | + state: () => ({ | ||
| 7 | + token: getToken(), | ||
| 8 | + name: '', | ||
| 9 | + avatar: '', | ||
| 10 | + roles: [], | ||
| 11 | + permissions: [] | ||
| 12 | + }), | ||
| 13 | + actions: { | ||
| 14 | + // 登录 | ||
| 15 | + login(userInfo) { | ||
| 16 | + const username = userInfo.username.trim() | ||
| 17 | + const password = userInfo.password | ||
| 18 | + return new Promise((resolve, reject) => { | ||
| 19 | + login(username, password) | ||
| 20 | + .then((res) => { | ||
| 21 | + setToken(res.token) | ||
| 22 | + this.token = res.token | ||
| 23 | + resolve() | ||
| 24 | + }) | ||
| 25 | + .catch((error) => { | ||
| 26 | + reject(error) | ||
| 27 | + }) | ||
| 28 | + }) | ||
| 29 | + }, | ||
| 30 | + // 获取用户信息 | ||
| 31 | + getInfo() { | ||
| 32 | + return new Promise((resolve, reject) => { | ||
| 33 | + getInfo() | ||
| 34 | + .then((res) => { | ||
| 35 | + const user = res.user | ||
| 36 | + const avatar = | ||
| 37 | + user.avatar == '' || user.avatar == null | ||
| 38 | + ? '' | ||
| 39 | + : import.meta.env.VITE_APP_BASE_API + user.avatar | ||
| 40 | + | ||
| 41 | + if (res.roles && res.roles.length > 0) { | ||
| 42 | + // 验证返回的roles是否是一个非空数组 | ||
| 43 | + this.roles = res.roles | ||
| 44 | + this.permissions = res.permissions | ||
| 45 | + } else { | ||
| 46 | + this.roles = ['ROLE_DEFAULT'] | ||
| 47 | + } | ||
| 48 | + this.name = user.userName | ||
| 49 | + this.avatar = avatar | ||
| 50 | + resolve(res) | ||
| 51 | + }) | ||
| 52 | + .catch((error) => { | ||
| 53 | + reject(error) | ||
| 54 | + }) | ||
| 55 | + }) | ||
| 56 | + }, | ||
| 57 | + // 退出系统 | ||
| 58 | + logOut() { | ||
| 59 | + return new Promise((resolve, reject) => { | ||
| 60 | + logout(this.token) | ||
| 61 | + .then(() => { | ||
| 62 | + this.token = '' | ||
| 63 | + this.roles = [] | ||
| 64 | + this.permissions = [] | ||
| 65 | + removeToken() | ||
| 66 | + resolve() | ||
| 67 | + }) | ||
| 68 | + .catch((error) => { | ||
| 69 | + reject(error) | ||
| 70 | + }) | ||
| 71 | + }) | ||
| 72 | + } | ||
| 73 | + } | ||
| 74 | +}) | ||
| 75 | + | ||
| 76 | +export default useUserStore |
src/renderer/src/utils/auth.js
0 → 100644
| 1 | +import Cookies from 'js-cookie' | ||
| 2 | + | ||
| 3 | +const TokenKey = 'Admin-Token' | ||
| 4 | + | ||
| 5 | +export function getToken() { | ||
| 6 | + return Cookies.get(TokenKey) | ||
| 7 | +} | ||
| 8 | + | ||
| 9 | +export function setToken(token) { | ||
| 10 | + return Cookies.set(TokenKey, token) | ||
| 11 | +} | ||
| 12 | + | ||
| 13 | +export function removeToken() { | ||
| 14 | + return Cookies.remove(TokenKey) | ||
| 15 | +} |
src/renderer/src/utils/base64toFormData.js
0 → 100644
| 1 | +// 在渲染进程中发送base64图片 | ||
| 2 | +import { uploadAvatar } from '@/api/login'; | ||
| 3 | + | ||
| 4 | +// 辅助函数:Base64转Blob | ||
| 5 | +function base64ToBlob(base64Data, contentType = 'image/png') { | ||
| 6 | + // 分割Base64字符串获取实际数据部分 | ||
| 7 | + const byteString = atob(base64Data.split(',')[1]); | ||
| 8 | + | ||
| 9 | + // 将字符串转换为字节数组 | ||
| 10 | + const arrayBuffer = new ArrayBuffer(byteString.length); | ||
| 11 | + const uint8Array = new Uint8Array(arrayBuffer); | ||
| 12 | + | ||
| 13 | + for (let i = 0; i < byteString.length; i++) { | ||
| 14 | + uint8Array[i] = byteString.charCodeAt(i); | ||
| 15 | + } | ||
| 16 | + | ||
| 17 | + // 创建Blob对象 | ||
| 18 | + return new Blob([arrayBuffer], { type: contentType }); | ||
| 19 | +} | ||
| 20 | + | ||
| 21 | +export async function uploadImage(base64Data) { | ||
| 22 | + // 1. 将Base64字符串转换为Blob对象 | ||
| 23 | + const blob = base64ToBlob(base64Data); | ||
| 24 | + console.log(blob) | ||
| 25 | + // 2. 创建FormData对象 | ||
| 26 | + const formData = new FormData(); | ||
| 27 | + formData.append('avatarfile', blob); // 'image'是后端要求的字段名 | ||
| 28 | + console.log(formData) | ||
| 29 | + // 3. 添加其他表单数据(可选) | ||
| 30 | + // formData.append('userId', '12345'); | ||
| 31 | + // formData.append('type', 'avatar'); | ||
| 32 | + | ||
| 33 | + try { | ||
| 34 | + // await fetch('https://xfwbzshd.crgx.net/system/user/profile/avatar', { | ||
| 35 | + // method: 'POST', | ||
| 36 | + // headers: { | ||
| 37 | + // // 如果需要,可以添加其他HTTP头,如Authorization等 | ||
| 38 | + // Authorization: 'Bearer eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjcxOWM1ZjE3LTNjMjYtNGFhNi04NWUwLThlOGZjZDBkYTAxMSJ9._gpSVz_UbRGTH0O0vMDlJEQ4uepf619ELGXgsdA1Ul0puNiWicQuddfwv-4c--qdaqN5hxSnKlJADNv7gfR64g', | ||
| 39 | + // }, | ||
| 40 | + // body: formData, | ||
| 41 | + // // 注意:不要手动设置Content-Type!FormData会自动设置正确的multipart/form-data和boundary | ||
| 42 | + // }); | ||
| 43 | + await uploadAvatar(formData); | ||
| 44 | + console.log('上传成功:'); | ||
| 45 | + } catch (error) { | ||
| 46 | + console.error('上传错误:', error); | ||
| 47 | + throw error; | ||
| 48 | + } | ||
| 49 | +} |
src/renderer/src/utils/common.js
0 → 100644
| 1 | +function objectToFormData(obj) { | ||
| 2 | + const fd = new FormData(); | ||
| 3 | + Object.keys(obj).forEach(key => { | ||
| 4 | + const value = obj[key]; | ||
| 5 | + fd.set(key, value); | ||
| 6 | + }); | ||
| 7 | + return fd; | ||
| 8 | +} | ||
| 9 | +function objectToQueryStr(obj, filterNull = false) { | ||
| 10 | + let queryStr = ""; | ||
| 11 | + Object.keys(obj).forEach(key => { | ||
| 12 | + if (filterNull && !obj[key]) { | ||
| 13 | + return; | ||
| 14 | + } | ||
| 15 | + queryStr += `&${key}=${obj[key] || ''}` | ||
| 16 | + }); | ||
| 17 | + console.log(queryStr); | ||
| 18 | + return queryStr.slice(1); | ||
| 19 | +} | ||
| 20 | +function openWindow(path) { | ||
| 21 | + const prefix = import.meta.env.VITE_APP_TAB_URL_PREFIX; | ||
| 22 | + window.open(`${prefix}${path}`); | ||
| 23 | +} | ||
| 24 | + | ||
| 25 | +// 运行实例 | ||
| 26 | +function listToTree(list) { | ||
| 27 | + const nodeMap = {}; | ||
| 28 | + const firstLevelList = list.filter(item => item.parentExecutionId === "0"); | ||
| 29 | + firstLevelList.forEach(item => { | ||
| 30 | + const {executionId} = item; | ||
| 31 | + nodeMap[executionId] = item; | ||
| 32 | + }); | ||
| 33 | + while (true) { | ||
| 34 | + list.forEach(item => { | ||
| 35 | + const {parentExecutionId, executionId} = item; | ||
| 36 | + if (nodeMap[executionId]) return; | ||
| 37 | + if (nodeMap[parentExecutionId]) { | ||
| 38 | + const parent = nodeMap[parentExecutionId]; | ||
| 39 | + if (parent.children) { | ||
| 40 | + parent.children.push(item); | ||
| 41 | + } else { | ||
| 42 | + parent.children = [item]; | ||
| 43 | + } | ||
| 44 | + nodeMap[executionId] = item; | ||
| 45 | + } | ||
| 46 | + }); | ||
| 47 | + if (Object.keys(nodeMap).length === list.length) { | ||
| 48 | + return firstLevelList; | ||
| 49 | + } | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | +} | ||
| 53 | + | ||
| 54 | +function normalizeDateTimeString(rawDatatimeStr) { | ||
| 55 | + function genNumStr(num) { | ||
| 56 | + return Number(num) < 10 ? "0" + num : ("" + num) | ||
| 57 | + } | ||
| 58 | + if (rawDatatimeStr) { | ||
| 59 | + const dateObj = new Date(rawDatatimeStr); | ||
| 60 | + | ||
| 61 | + const yyyy = dateObj.getFullYear(); | ||
| 62 | + const MM = dateObj.getMonth() + 1; | ||
| 63 | + const dd = dateObj.getDate(); | ||
| 64 | + | ||
| 65 | + const HH = dateObj.getHours(); | ||
| 66 | + const mm = dateObj.getMinutes(); | ||
| 67 | + const ss = dateObj.getSeconds(); | ||
| 68 | + | ||
| 69 | + return `${yyyy}-${genNumStr(MM)}-${genNumStr(dd)} ${genNumStr(HH)}:${genNumStr(mm)}:${genNumStr(ss)}`; | ||
| 70 | + } | ||
| 71 | +} | ||
| 72 | + | ||
| 73 | +export default { | ||
| 74 | + objectToFormData, | ||
| 75 | + objectToQueryStr, | ||
| 76 | + openWindow, | ||
| 77 | + listToTree, | ||
| 78 | + normalizeDateTimeString | ||
| 79 | +}; |
src/renderer/src/utils/dict.js
0 → 100644
| 1 | +import useDictStore from '@/store/modules/dict' | ||
| 2 | +import { getDicts } from '@/api/system/dict/data' | ||
| 3 | + | ||
| 4 | +/** | ||
| 5 | + * 获取字典数据 | ||
| 6 | + */ | ||
| 7 | +export function useDict(...args) { | ||
| 8 | + const res = ref({}); | ||
| 9 | + return (() => { | ||
| 10 | + args.forEach((dictType, index) => { | ||
| 11 | + res.value[dictType] = []; | ||
| 12 | + const dicts = useDictStore().getDict(dictType); | ||
| 13 | + if (dicts) { | ||
| 14 | + res.value[dictType] = dicts; | ||
| 15 | + } else { | ||
| 16 | + getDicts(dictType).then(resp => { | ||
| 17 | + res.value[dictType] = resp.data.map(p => ({ label: p.dictLabel, value: p.dictValue, elTagType: p.listClass, elTagClass: p.cssClass })) | ||
| 18 | + useDictStore().setDict(dictType, res.value[dictType]); | ||
| 19 | + }) | ||
| 20 | + } | ||
| 21 | + }) | ||
| 22 | + return toRefs(res.value); | ||
| 23 | + })() | ||
| 24 | +} |
src/renderer/src/utils/dynamicTitle.js
0 → 100644
| 1 | +import store from '@/store' | ||
| 2 | +import defaultSettings from '@/settings' | ||
| 3 | +import useSettingsStore from '@/store/modules/settings' | ||
| 4 | + | ||
| 5 | +/** | ||
| 6 | + * 动态修改标题 | ||
| 7 | + */ | ||
| 8 | +export function useDynamicTitle() { | ||
| 9 | + const settingsStore = useSettingsStore(); | ||
| 10 | + if (settingsStore.dynamicTitle) { | ||
| 11 | + document.title = settingsStore.title + ' - ' + defaultSettings.title; | ||
| 12 | + } else { | ||
| 13 | + document.title = defaultSettings.title; | ||
| 14 | + } | ||
| 15 | +} |
src/renderer/src/utils/errorCode.js
0 → 100644
src/renderer/src/utils/index.js
0 → 100644
| 1 | +import { parseTime } from './ruoyi' | ||
| 2 | + | ||
| 3 | +/** | ||
| 4 | + * 表格时间格式化 | ||
| 5 | + */ | ||
| 6 | +export function formatDate(cellValue) { | ||
| 7 | + if (cellValue == null || cellValue == "") return ""; | ||
| 8 | + var date = new Date(cellValue) | ||
| 9 | + var year = date.getFullYear() | ||
| 10 | + var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1 | ||
| 11 | + var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate() | ||
| 12 | + var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours() | ||
| 13 | + var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes() | ||
| 14 | + var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds() | ||
| 15 | + return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds | ||
| 16 | +} | ||
| 17 | + | ||
| 18 | +/** | ||
| 19 | + * @param {number} time | ||
| 20 | + * @param {string} option | ||
| 21 | + * @returns {string} | ||
| 22 | + */ | ||
| 23 | +export function formatTime(time, option) { | ||
| 24 | + if (('' + time).length === 10) { | ||
| 25 | + time = parseInt(time) * 1000 | ||
| 26 | + } else { | ||
| 27 | + time = +time | ||
| 28 | + } | ||
| 29 | + const d = new Date(time) | ||
| 30 | + const now = Date.now() | ||
| 31 | + | ||
| 32 | + const diff = (now - d) / 1000 | ||
| 33 | + | ||
| 34 | + if (diff < 30) { | ||
| 35 | + return '刚刚' | ||
| 36 | + } else if (diff < 3600) { | ||
| 37 | + // less 1 hour | ||
| 38 | + return Math.ceil(diff / 60) + '分钟前' | ||
| 39 | + } else if (diff < 3600 * 24) { | ||
| 40 | + return Math.ceil(diff / 3600) + '小时前' | ||
| 41 | + } else if (diff < 3600 * 24 * 2) { | ||
| 42 | + return '1天前' | ||
| 43 | + } | ||
| 44 | + if (option) { | ||
| 45 | + return parseTime(time, option) | ||
| 46 | + } else { | ||
| 47 | + return ( | ||
| 48 | + d.getMonth() + | ||
| 49 | + 1 + | ||
| 50 | + '月' + | ||
| 51 | + d.getDate() + | ||
| 52 | + '日' + | ||
| 53 | + d.getHours() + | ||
| 54 | + '时' + | ||
| 55 | + d.getMinutes() + | ||
| 56 | + '分' | ||
| 57 | + ) | ||
| 58 | + } | ||
| 59 | +} | ||
| 60 | + | ||
| 61 | +/** | ||
| 62 | + * @param {string} url | ||
| 63 | + * @returns {Object} | ||
| 64 | + */ | ||
| 65 | +export function getQueryObject(url) { | ||
| 66 | + url = url == null ? window.location.href : url | ||
| 67 | + const search = url.substring(url.lastIndexOf('?') + 1) | ||
| 68 | + const obj = {} | ||
| 69 | + const reg = /([^?&=]+)=([^?&=]*)/g | ||
| 70 | + search.replace(reg, (rs, $1, $2) => { | ||
| 71 | + const name = decodeURIComponent($1) | ||
| 72 | + let val = decodeURIComponent($2) | ||
| 73 | + val = String(val) | ||
| 74 | + obj[name] = val | ||
| 75 | + return rs | ||
| 76 | + }) | ||
| 77 | + return obj | ||
| 78 | +} | ||
| 79 | + | ||
| 80 | +/** | ||
| 81 | + * @param {string} input value | ||
| 82 | + * @returns {number} output value | ||
| 83 | + */ | ||
| 84 | +export function byteLength(str) { | ||
| 85 | + // returns the byte length of an utf8 string | ||
| 86 | + let s = str.length | ||
| 87 | + for (var i = str.length - 1; i >= 0; i--) { | ||
| 88 | + const code = str.charCodeAt(i) | ||
| 89 | + if (code > 0x7f && code <= 0x7ff) s++ | ||
| 90 | + else if (code > 0x7ff && code <= 0xffff) s += 2 | ||
| 91 | + if (code >= 0xDC00 && code <= 0xDFFF) i-- | ||
| 92 | + } | ||
| 93 | + return s | ||
| 94 | +} | ||
| 95 | + | ||
| 96 | +/** | ||
| 97 | + * @param {Array} actual | ||
| 98 | + * @returns {Array} | ||
| 99 | + */ | ||
| 100 | +export function cleanArray(actual) { | ||
| 101 | + const newArray = [] | ||
| 102 | + for (let i = 0; i < actual.length; i++) { | ||
| 103 | + if (actual[i]) { | ||
| 104 | + newArray.push(actual[i]) | ||
| 105 | + } | ||
| 106 | + } | ||
| 107 | + return newArray | ||
| 108 | +} | ||
| 109 | + | ||
| 110 | +/** | ||
| 111 | + * @param {Object} json | ||
| 112 | + * @returns {Array} | ||
| 113 | + */ | ||
| 114 | +export function param(json) { | ||
| 115 | + if (!json) return '' | ||
| 116 | + return cleanArray( | ||
| 117 | + Object.keys(json).map(key => { | ||
| 118 | + if (json[key] === undefined) return '' | ||
| 119 | + return encodeURIComponent(key) + '=' + encodeURIComponent(json[key]) | ||
| 120 | + }) | ||
| 121 | + ).join('&') | ||
| 122 | +} | ||
| 123 | + | ||
| 124 | +/** | ||
| 125 | + * @param {string} url | ||
| 126 | + * @returns {Object} | ||
| 127 | + */ | ||
| 128 | +export function param2Obj(url) { | ||
| 129 | + const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ') | ||
| 130 | + if (!search) { | ||
| 131 | + return {} | ||
| 132 | + } | ||
| 133 | + const obj = {} | ||
| 134 | + const searchArr = search.split('&') | ||
| 135 | + searchArr.forEach(v => { | ||
| 136 | + const index = v.indexOf('=') | ||
| 137 | + if (index !== -1) { | ||
| 138 | + const name = v.substring(0, index) | ||
| 139 | + const val = v.substring(index + 1, v.length) | ||
| 140 | + obj[name] = val | ||
| 141 | + } | ||
| 142 | + }) | ||
| 143 | + return obj | ||
| 144 | +} | ||
| 145 | + | ||
| 146 | +/** | ||
| 147 | + * @param {string} val | ||
| 148 | + * @returns {string} | ||
| 149 | + */ | ||
| 150 | +export function html2Text(val) { | ||
| 151 | + const div = document.createElement('div') | ||
| 152 | + div.innerHTML = val | ||
| 153 | + return div.textContent || div.innerText | ||
| 154 | +} | ||
| 155 | + | ||
| 156 | +/** | ||
| 157 | + * Merges two objects, giving the last one precedence | ||
| 158 | + * @param {Object} target | ||
| 159 | + * @param {(Object|Array)} source | ||
| 160 | + * @returns {Object} | ||
| 161 | + */ | ||
| 162 | +export function objectMerge(target, source) { | ||
| 163 | + if (typeof target !== 'object') { | ||
| 164 | + target = {} | ||
| 165 | + } | ||
| 166 | + if (Array.isArray(source)) { | ||
| 167 | + return source.slice() | ||
| 168 | + } | ||
| 169 | + Object.keys(source).forEach(property => { | ||
| 170 | + const sourceProperty = source[property] | ||
| 171 | + if (typeof sourceProperty === 'object') { | ||
| 172 | + target[property] = objectMerge(target[property], sourceProperty) | ||
| 173 | + } else { | ||
| 174 | + target[property] = sourceProperty | ||
| 175 | + } | ||
| 176 | + }) | ||
| 177 | + return target | ||
| 178 | +} | ||
| 179 | + | ||
| 180 | +/** | ||
| 181 | + * @param {HTMLElement} element | ||
| 182 | + * @param {string} className | ||
| 183 | + */ | ||
| 184 | +export function toggleClass(element, className) { | ||
| 185 | + if (!element || !className) { | ||
| 186 | + return | ||
| 187 | + } | ||
| 188 | + let classString = element.className | ||
| 189 | + const nameIndex = classString.indexOf(className) | ||
| 190 | + if (nameIndex === -1) { | ||
| 191 | + classString += '' + className | ||
| 192 | + } else { | ||
| 193 | + classString = | ||
| 194 | + classString.substr(0, nameIndex) + | ||
| 195 | + classString.substr(nameIndex + className.length) | ||
| 196 | + } | ||
| 197 | + element.className = classString | ||
| 198 | +} | ||
| 199 | + | ||
| 200 | +/** | ||
| 201 | + * @param {string} type | ||
| 202 | + * @returns {Date} | ||
| 203 | + */ | ||
| 204 | +export function getTime(type) { | ||
| 205 | + if (type === 'start') { | ||
| 206 | + return new Date().getTime() - 3600 * 1000 * 24 * 90 | ||
| 207 | + } else { | ||
| 208 | + return new Date(new Date().toDateString()) | ||
| 209 | + } | ||
| 210 | +} | ||
| 211 | + | ||
| 212 | +/** | ||
| 213 | + * @param {Function} func | ||
| 214 | + * @param {number} wait | ||
| 215 | + * @param {boolean} immediate | ||
| 216 | + * @return {*} | ||
| 217 | + */ | ||
| 218 | +export function debounce(func, wait, immediate) { | ||
| 219 | + let timeout, args, context, timestamp, result | ||
| 220 | + | ||
| 221 | + const later = function() { | ||
| 222 | + // 据上一次触发时间间隔 | ||
| 223 | + const last = +new Date() - timestamp | ||
| 224 | + | ||
| 225 | + // 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait | ||
| 226 | + if (last < wait && last > 0) { | ||
| 227 | + timeout = setTimeout(later, wait - last) | ||
| 228 | + } else { | ||
| 229 | + timeout = null | ||
| 230 | + // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用 | ||
| 231 | + if (!immediate) { | ||
| 232 | + result = func.apply(context, args) | ||
| 233 | + if (!timeout) context = args = null | ||
| 234 | + } | ||
| 235 | + } | ||
| 236 | + } | ||
| 237 | + | ||
| 238 | + return function(...args) { | ||
| 239 | + context = this | ||
| 240 | + timestamp = +new Date() | ||
| 241 | + const callNow = immediate && !timeout | ||
| 242 | + // 如果延时不存在,重新设定延时 | ||
| 243 | + if (!timeout) timeout = setTimeout(later, wait) | ||
| 244 | + if (callNow) { | ||
| 245 | + result = func.apply(context, args) | ||
| 246 | + context = args = null | ||
| 247 | + } | ||
| 248 | + | ||
| 249 | + return result | ||
| 250 | + } | ||
| 251 | +} | ||
| 252 | + | ||
| 253 | +/** | ||
| 254 | + * This is just a simple version of deep copy | ||
| 255 | + * Has a lot of edge cases bug | ||
| 256 | + * If you want to use a perfect deep copy, use lodash's _.cloneDeep | ||
| 257 | + * @param {Object} source | ||
| 258 | + * @returns {Object} | ||
| 259 | + */ | ||
| 260 | +export function deepClone(source) { | ||
| 261 | + if (!source && typeof source !== 'object') { | ||
| 262 | + throw new Error('error arguments', 'deepClone') | ||
| 263 | + } | ||
| 264 | + const targetObj = source.constructor === Array ? [] : {} | ||
| 265 | + Object.keys(source).forEach(keys => { | ||
| 266 | + if (source[keys] && typeof source[keys] === 'object') { | ||
| 267 | + targetObj[keys] = deepClone(source[keys]) | ||
| 268 | + } else { | ||
| 269 | + targetObj[keys] = source[keys] | ||
| 270 | + } | ||
| 271 | + }) | ||
| 272 | + return targetObj | ||
| 273 | +} | ||
| 274 | + | ||
| 275 | +/** | ||
| 276 | + * @param {Array} arr | ||
| 277 | + * @returns {Array} | ||
| 278 | + */ | ||
| 279 | +export function uniqueArr(arr) { | ||
| 280 | + return Array.from(new Set(arr)) | ||
| 281 | +} | ||
| 282 | + | ||
| 283 | +/** | ||
| 284 | + * @returns {string} | ||
| 285 | + */ | ||
| 286 | +export function createUniqueString() { | ||
| 287 | + const timestamp = +new Date() + '' | ||
| 288 | + const randomNum = parseInt((1 + Math.random()) * 65536) + '' | ||
| 289 | + return (+(randomNum + timestamp)).toString(32) | ||
| 290 | +} | ||
| 291 | + | ||
| 292 | +/** | ||
| 293 | + * Check if an element has a class | ||
| 294 | + * @param {HTMLElement} elm | ||
| 295 | + * @param {string} cls | ||
| 296 | + * @returns {boolean} | ||
| 297 | + */ | ||
| 298 | +export function hasClass(ele, cls) { | ||
| 299 | + return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)')) | ||
| 300 | +} | ||
| 301 | + | ||
| 302 | +/** | ||
| 303 | + * Add class to element | ||
| 304 | + * @param {HTMLElement} elm | ||
| 305 | + * @param {string} cls | ||
| 306 | + */ | ||
| 307 | +export function addClass(ele, cls) { | ||
| 308 | + if (!hasClass(ele, cls)) ele.className += ' ' + cls | ||
| 309 | +} | ||
| 310 | + | ||
| 311 | +/** | ||
| 312 | + * Remove class from element | ||
| 313 | + * @param {HTMLElement} elm | ||
| 314 | + * @param {string} cls | ||
| 315 | + */ | ||
| 316 | +export function removeClass(ele, cls) { | ||
| 317 | + if (hasClass(ele, cls)) { | ||
| 318 | + const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)') | ||
| 319 | + ele.className = ele.className.replace(reg, ' ') | ||
| 320 | + } | ||
| 321 | +} | ||
| 322 | + | ||
| 323 | +export function makeMap(str, expectsLowerCase) { | ||
| 324 | + const map = Object.create(null) | ||
| 325 | + const list = str.split(',') | ||
| 326 | + for (let i = 0; i < list.length; i++) { | ||
| 327 | + map[list[i]] = true | ||
| 328 | + } | ||
| 329 | + return expectsLowerCase | ||
| 330 | + ? val => map[val.toLowerCase()] | ||
| 331 | + : val => map[val] | ||
| 332 | +} | ||
| 333 | + | ||
| 334 | +export const exportDefault = 'export default ' | ||
| 335 | + | ||
| 336 | +export const beautifierConf = { | ||
| 337 | + html: { | ||
| 338 | + indent_size: '2', | ||
| 339 | + indent_char: ' ', | ||
| 340 | + max_preserve_newlines: '-1', | ||
| 341 | + preserve_newlines: false, | ||
| 342 | + keep_array_indentation: false, | ||
| 343 | + break_chained_methods: false, | ||
| 344 | + indent_scripts: 'separate', | ||
| 345 | + brace_style: 'end-expand', | ||
| 346 | + space_before_conditional: true, | ||
| 347 | + unescape_strings: false, | ||
| 348 | + jslint_happy: false, | ||
| 349 | + end_with_newline: true, | ||
| 350 | + wrap_line_length: '110', | ||
| 351 | + indent_inner_html: true, | ||
| 352 | + comma_first: false, | ||
| 353 | + e4x: true, | ||
| 354 | + indent_empty_lines: true | ||
| 355 | + }, | ||
| 356 | + js: { | ||
| 357 | + indent_size: '2', | ||
| 358 | + indent_char: ' ', | ||
| 359 | + max_preserve_newlines: '-1', | ||
| 360 | + preserve_newlines: false, | ||
| 361 | + keep_array_indentation: false, | ||
| 362 | + break_chained_methods: false, | ||
| 363 | + indent_scripts: 'normal', | ||
| 364 | + brace_style: 'end-expand', | ||
| 365 | + space_before_conditional: true, | ||
| 366 | + unescape_strings: false, | ||
| 367 | + jslint_happy: true, | ||
| 368 | + end_with_newline: true, | ||
| 369 | + wrap_line_length: '110', | ||
| 370 | + indent_inner_html: true, | ||
| 371 | + comma_first: false, | ||
| 372 | + e4x: true, | ||
| 373 | + indent_empty_lines: true | ||
| 374 | + } | ||
| 375 | +} | ||
| 376 | + | ||
| 377 | +// 首字母大小 | ||
| 378 | +export function titleCase(str) { | ||
| 379 | + return str.replace(/( |^)[a-z]/g, L => L.toUpperCase()) | ||
| 380 | +} | ||
| 381 | + | ||
| 382 | +// 下划转驼峰 | ||
| 383 | +export function camelCase(str) { | ||
| 384 | + return str.replace(/_[a-z]/g, str1 => str1.substr(-1).toUpperCase()) | ||
| 385 | +} | ||
| 386 | + | ||
| 387 | +export function isNumberStr(str) { | ||
| 388 | + return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str) | ||
| 389 | +} | ||
| 390 | + |
src/renderer/src/utils/intervalTime.js
0 → 100644
src/renderer/src/utils/jsencrypt.js
0 → 100644
| 1 | +import JSEncrypt from 'jsencrypt/bin/jsencrypt.min' | ||
| 2 | + | ||
| 3 | +// 密钥对生成 http://web.chacuo.net/netrsakeypair | ||
| 4 | + | ||
| 5 | +const publicKey = | ||
| 6 | + 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\n' + | ||
| 7 | + 'nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==' | ||
| 8 | + | ||
| 9 | +const privateKey = | ||
| 10 | + 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY\n' + | ||
| 11 | + '7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\n' + | ||
| 12 | + 'PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA\n' + | ||
| 13 | + 'kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow\n' + | ||
| 14 | + 'cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv\n' + | ||
| 15 | + 'DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\n' + | ||
| 16 | + 'YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\n' + | ||
| 17 | + 'UP8iWi1Qw0Y=' | ||
| 18 | + | ||
| 19 | +// 加密 | ||
| 20 | +export function encrypt(txt) { | ||
| 21 | + const encryptor = new JSEncrypt() | ||
| 22 | + encryptor.setPublicKey(publicKey) // 设置公钥 | ||
| 23 | + return encryptor.encrypt(txt) // 对数据进行加密 | ||
| 24 | +} | ||
| 25 | + | ||
| 26 | +// 解密 | ||
| 27 | +export function decrypt(txt) { | ||
| 28 | + const encryptor = new JSEncrypt() | ||
| 29 | + encryptor.setPrivateKey(privateKey) // 设置私钥 | ||
| 30 | + return encryptor.decrypt(txt) // 对数据进行解密 | ||
| 31 | +} |
src/renderer/src/utils/permission.js
0 → 100644
| 1 | +import useUserStore from '@/store/modules/user' | ||
| 2 | + | ||
| 3 | +/** | ||
| 4 | + * 字符权限校验 | ||
| 5 | + * @param {Array} value 校验值 | ||
| 6 | + * @returns {Boolean} | ||
| 7 | + */ | ||
| 8 | +export function checkPermi(value) { | ||
| 9 | + if (value && value instanceof Array && value.length > 0) { | ||
| 10 | + const permissions = useUserStore().permissions | ||
| 11 | + const permissionDatas = value | ||
| 12 | + const all_permission = "*:*:*"; | ||
| 13 | + | ||
| 14 | + const hasPermission = permissions.some(permission => { | ||
| 15 | + return all_permission === permission || permissionDatas.includes(permission) | ||
| 16 | + }) | ||
| 17 | + | ||
| 18 | + if (!hasPermission) { | ||
| 19 | + return false | ||
| 20 | + } | ||
| 21 | + return true | ||
| 22 | + } else { | ||
| 23 | + console.error(`need roles! Like checkPermi="['system:user:add','system:user:edit']"`) | ||
| 24 | + return false | ||
| 25 | + } | ||
| 26 | +} | ||
| 27 | + | ||
| 28 | +/** | ||
| 29 | + * 角色权限校验 | ||
| 30 | + * @param {Array} value 校验值 | ||
| 31 | + * @returns {Boolean} | ||
| 32 | + */ | ||
| 33 | +export function checkRole(value) { | ||
| 34 | + if (value && value instanceof Array && value.length > 0) { | ||
| 35 | + const roles = useUserStore().roles | ||
| 36 | + const permissionRoles = value | ||
| 37 | + const super_admin = "admin"; | ||
| 38 | + | ||
| 39 | + const hasRole = roles.some(role => { | ||
| 40 | + return super_admin === role || permissionRoles.includes(role) | ||
| 41 | + }) | ||
| 42 | + | ||
| 43 | + if (!hasRole) { | ||
| 44 | + return false | ||
| 45 | + } | ||
| 46 | + return true | ||
| 47 | + } else { | ||
| 48 | + console.error(`need roles! Like checkRole="['admin','editor']"`) | ||
| 49 | + return false | ||
| 50 | + } | ||
| 51 | +} |
src/renderer/src/utils/request.js
0 → 100644
| 1 | +import axios from 'axios' | ||
| 2 | +import { ElNotification, ElMessageBox, ElMessage } from 'element-plus' | ||
| 3 | +import { getToken } from '@/utils/auth' | ||
| 4 | +import errorCode from '@/utils/errorCode' | ||
| 5 | +import { tansParams } from '@/utils/ruoyi' | ||
| 6 | +import cache from '@/plugins/cache' | ||
| 7 | +import useUserStore from '@/store/modules/user' | ||
| 8 | + | ||
| 9 | +// 是否显示重新登录 | ||
| 10 | +export let isRelogin = { show: false } | ||
| 11 | +const server_base = localStorage.getItem('crgx_server') | ||
| 12 | + | ||
| 13 | +axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8' | ||
| 14 | +// 创建axios实例 | ||
| 15 | +const service = axios.create({ | ||
| 16 | + // axios中请求配置有baseURL选项,表示请求URL公共部分 | ||
| 17 | + baseURL: server_base, | ||
| 18 | + // 超时 | ||
| 19 | + timeout: 10000 | ||
| 20 | +}) | ||
| 21 | + | ||
| 22 | +// request拦截器 | ||
| 23 | +service.interceptors.request.use( | ||
| 24 | + (config) => { | ||
| 25 | + // 是否需要设置 token | ||
| 26 | + const isToken = (config.headers || {}).isToken === false | ||
| 27 | + // 是否需要防止数据重复提交 | ||
| 28 | + const isRepeatSubmit = (config.headers || {}).repeatSubmit === false | ||
| 29 | + if (getToken() && !isToken) { | ||
| 30 | + config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 | ||
| 31 | + } | ||
| 32 | + // get请求映射params参数 | ||
| 33 | + if (config.method === 'get' && config.params) { | ||
| 34 | + let url = config.url + '?' + tansParams(config.params) | ||
| 35 | + url = url.slice(0, -1) | ||
| 36 | + config.params = {} | ||
| 37 | + config.url = url | ||
| 38 | + } | ||
| 39 | + if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) { | ||
| 40 | + const requestObj = { | ||
| 41 | + url: config.url, | ||
| 42 | + data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data, | ||
| 43 | + time: new Date().getTime() | ||
| 44 | + } | ||
| 45 | + const sessionObj = cache.session.getJSON('sessionObj') | ||
| 46 | + if (sessionObj === undefined || sessionObj === null || sessionObj === '') { | ||
| 47 | + cache.session.setJSON('sessionObj', requestObj) | ||
| 48 | + } else { | ||
| 49 | + const s_url = sessionObj.url // 请求地址 | ||
| 50 | + const s_data = sessionObj.data // 请求数据 | ||
| 51 | + const s_time = sessionObj.time // 请求时间 | ||
| 52 | + const interval = 1000 // 间隔时间(ms),小于此时间视为重复提交 | ||
| 53 | + if ( | ||
| 54 | + s_data === requestObj.data && | ||
| 55 | + requestObj.time - s_time < interval && | ||
| 56 | + s_url === requestObj.url | ||
| 57 | + ) { | ||
| 58 | + const message = '数据正在处理,请勿重复提交' | ||
| 59 | + console.warn(`[${s_url}]: ` + message) | ||
| 60 | + return Promise.reject(new Error(message)) | ||
| 61 | + } else { | ||
| 62 | + cache.session.setJSON('sessionObj', requestObj) | ||
| 63 | + } | ||
| 64 | + } | ||
| 65 | + } | ||
| 66 | + return config | ||
| 67 | + }, | ||
| 68 | + (error) => { | ||
| 69 | + console.log(error) | ||
| 70 | + Promise.reject(error) | ||
| 71 | + } | ||
| 72 | +) | ||
| 73 | + | ||
| 74 | +// 响应拦截器 | ||
| 75 | +service.interceptors.response.use( | ||
| 76 | + (res) => { | ||
| 77 | + // 未设置状态码则默认成功状态 | ||
| 78 | + const code = res.data.code || 200 | ||
| 79 | + // 获取错误信息 | ||
| 80 | + const msg = errorCode[code] || res.data.msg || errorCode['default'] | ||
| 81 | + // 二进制数据则直接返回 | ||
| 82 | + if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') { | ||
| 83 | + return res.data | ||
| 84 | + } | ||
| 85 | + if (code === 401) { | ||
| 86 | + if (!isRelogin.show) { | ||
| 87 | + isRelogin.show = true | ||
| 88 | + ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { | ||
| 89 | + confirmButtonText: '重新登录', | ||
| 90 | + cancelButtonText: '取消', | ||
| 91 | + type: 'warning' | ||
| 92 | + }) | ||
| 93 | + .then(() => { | ||
| 94 | + isRelogin.show = false | ||
| 95 | + useUserStore() | ||
| 96 | + .logOut() | ||
| 97 | + .then(() => { | ||
| 98 | + console.log('重新登录') | ||
| 99 | + location.href = '/login' | ||
| 100 | + }) | ||
| 101 | + }) | ||
| 102 | + .catch(() => { | ||
| 103 | + isRelogin.show = false | ||
| 104 | + }) | ||
| 105 | + } | ||
| 106 | + return Promise.reject('无效的会话,或者会话已过期,请重新登录。') | ||
| 107 | + } else if (code === 500) { | ||
| 108 | + ElMessage({ message: msg, type: 'error' }) | ||
| 109 | + return Promise.reject(new Error(msg)) | ||
| 110 | + } else if (code === 601) { | ||
| 111 | + ElMessage({ message: msg, type: 'warning' }) | ||
| 112 | + return Promise.reject(new Error(msg)) | ||
| 113 | + } else if (code !== 200) { | ||
| 114 | + ElNotification.error({ title: msg }) | ||
| 115 | + return Promise.reject('error') | ||
| 116 | + } else { | ||
| 117 | + return Promise.resolve(res.data) | ||
| 118 | + } | ||
| 119 | + }, | ||
| 120 | + (error) => { | ||
| 121 | + console.log('err' + error) | ||
| 122 | + let { message } = error | ||
| 123 | + if (message == 'Network Error') { | ||
| 124 | + message = '后端接口连接异常' | ||
| 125 | + } else if (message.includes('timeout')) { | ||
| 126 | + message = '系统接口请求超时' | ||
| 127 | + } else if (message.includes('Request failed with status code')) { | ||
| 128 | + message = '系统接口' + message.substr(message.length - 3) + '异常' | ||
| 129 | + } | ||
| 130 | + ElMessage({ message: message, type: 'error', duration: 5 * 1000 }) | ||
| 131 | + return Promise.reject(error) | ||
| 132 | + } | ||
| 133 | +) | ||
| 134 | + | ||
| 135 | +// 通用下载方法 | ||
| 136 | +// export function download(url, params, filename, config) { | ||
| 137 | +// downloadLoadingInstance = ElLoading.service({ text: "正在下载数据,请稍候", background: "rgba(0, 0, 0, 0.7)", }) | ||
| 138 | +// return service.post(url, params, { | ||
| 139 | +// transformRequest: [(params) => { return tansParams(params) }], | ||
| 140 | +// headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, | ||
| 141 | +// responseType: 'blob', | ||
| 142 | +// ...config | ||
| 143 | +// }).then(async (data) => { | ||
| 144 | +// const isLogin = await blobValidate(data); | ||
| 145 | +// if (isLogin) { | ||
| 146 | +// const blob = new Blob([data]) | ||
| 147 | +// saveAs(blob, filename) | ||
| 148 | +// } else { | ||
| 149 | +// const resText = await data.text(); | ||
| 150 | +// const rspObj = JSON.parse(resText); | ||
| 151 | +// const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'] | ||
| 152 | +// ElMessage.error(errMsg); | ||
| 153 | +// } | ||
| 154 | +// downloadLoadingInstance.close(); | ||
| 155 | +// }).catch((r) => { | ||
| 156 | +// console.error(r) | ||
| 157 | +// ElMessage.error('下载文件出现错误,请联系管理员!') | ||
| 158 | +// downloadLoadingInstance.close(); | ||
| 159 | +// }) | ||
| 160 | +// } | ||
| 161 | + | ||
| 162 | +export default service |
src/renderer/src/utils/ruoyi.js
0 → 100644
| 1 | +/** | ||
| 2 | + * 通用js方法封装处理 | ||
| 3 | + * Copyright (c) 2019 ruoyi | ||
| 4 | + */ | ||
| 5 | + | ||
| 6 | +// 日期格式化 | ||
| 7 | +export function parseTime(time, pattern) { | ||
| 8 | + if (arguments.length === 0 || !time) { | ||
| 9 | + return null | ||
| 10 | + } | ||
| 11 | + const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}' | ||
| 12 | + let date | ||
| 13 | + if (typeof time === 'object') { | ||
| 14 | + date = time | ||
| 15 | + } else { | ||
| 16 | + if (typeof time === 'string' && /^[0-9]+$/.test(time)) { | ||
| 17 | + time = parseInt(time) | ||
| 18 | + } else if (typeof time === 'string') { | ||
| 19 | + time = time | ||
| 20 | + .replace(new RegExp(/-/gm), '/') | ||
| 21 | + .replace('T', ' ') | ||
| 22 | + .replace(new RegExp(/\.[\d]{3}/gm), '') | ||
| 23 | + } | ||
| 24 | + if (typeof time === 'number' && time.toString().length === 10) { | ||
| 25 | + time = time * 1000 | ||
| 26 | + } | ||
| 27 | + date = new Date(time) | ||
| 28 | + } | ||
| 29 | + const formatObj = { | ||
| 30 | + y: date.getFullYear(), | ||
| 31 | + m: date.getMonth() + 1, | ||
| 32 | + d: date.getDate(), | ||
| 33 | + h: date.getHours(), | ||
| 34 | + i: date.getMinutes(), | ||
| 35 | + s: date.getSeconds(), | ||
| 36 | + a: date.getDay() | ||
| 37 | + } | ||
| 38 | + const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { | ||
| 39 | + let value = formatObj[key] | ||
| 40 | + // Note: getDay() returns 0 on Sunday | ||
| 41 | + if (key === 'a') { | ||
| 42 | + return ['日', '一', '二', '三', '四', '五', '六'][value] | ||
| 43 | + } | ||
| 44 | + if (result.length > 0 && value < 10) { | ||
| 45 | + value = '0' + value | ||
| 46 | + } | ||
| 47 | + return value || 0 | ||
| 48 | + }) | ||
| 49 | + return time_str | ||
| 50 | +} | ||
| 51 | + | ||
| 52 | +// 表单重置 | ||
| 53 | +export function resetForm(refName) { | ||
| 54 | + if (this.$refs[refName]) { | ||
| 55 | + this.$refs[refName].resetFields() | ||
| 56 | + } | ||
| 57 | +} | ||
| 58 | + | ||
| 59 | +// 添加日期范围 | ||
| 60 | +export function addDateRange(params, dateRange, propName) { | ||
| 61 | + let search = params | ||
| 62 | + search.params = | ||
| 63 | + typeof search.params === 'object' && search.params !== null && !Array.isArray(search.params) | ||
| 64 | + ? search.params | ||
| 65 | + : {} | ||
| 66 | + dateRange = Array.isArray(dateRange) ? dateRange : [] | ||
| 67 | + if (typeof propName === 'undefined') { | ||
| 68 | + search.params['beginTime'] = dateRange[0] | ||
| 69 | + search.params['endTime'] = dateRange[1] | ||
| 70 | + } else { | ||
| 71 | + search.params['begin' + propName] = dateRange[0] | ||
| 72 | + search.params['end' + propName] = dateRange[1] | ||
| 73 | + } | ||
| 74 | + return search | ||
| 75 | +} | ||
| 76 | + | ||
| 77 | +// 回显数据字典 | ||
| 78 | +export function selectDictLabel(datas, value) { | ||
| 79 | + if (value === undefined) { | ||
| 80 | + return '' | ||
| 81 | + } | ||
| 82 | + var actions = [] | ||
| 83 | + Object.keys(datas).some((key) => { | ||
| 84 | + if (datas[key].value == '' + value) { | ||
| 85 | + actions.push(datas[key].label) | ||
| 86 | + return true | ||
| 87 | + } | ||
| 88 | + }) | ||
| 89 | + if (actions.length === 0) { | ||
| 90 | + actions.push(value) | ||
| 91 | + } | ||
| 92 | + return actions.join('') | ||
| 93 | +} | ||
| 94 | + | ||
| 95 | +// 回显数据字典(字符串数组) | ||
| 96 | +export function selectDictLabels(datas, value, separator) { | ||
| 97 | + if (value === undefined || value.length === 0) { | ||
| 98 | + return '' | ||
| 99 | + } | ||
| 100 | + if (Array.isArray(value)) { | ||
| 101 | + value = value.join(',') | ||
| 102 | + } | ||
| 103 | + var actions = [] | ||
| 104 | + var currentSeparator = undefined === separator ? ',' : separator | ||
| 105 | + var temp = value.split(currentSeparator) | ||
| 106 | + Object.keys(value.split(currentSeparator)).some((val) => { | ||
| 107 | + var match = false | ||
| 108 | + Object.keys(datas).some((key) => { | ||
| 109 | + if (datas[key].value == '' + temp[val]) { | ||
| 110 | + actions.push(datas[key].label + currentSeparator) | ||
| 111 | + match = true | ||
| 112 | + } | ||
| 113 | + }) | ||
| 114 | + if (!match) { | ||
| 115 | + actions.push(temp[val] + currentSeparator) | ||
| 116 | + } | ||
| 117 | + }) | ||
| 118 | + return actions.join('').substring(0, actions.join('').length - 1) | ||
| 119 | +} | ||
| 120 | + | ||
| 121 | +// 字符串格式化(%s ) | ||
| 122 | +export function sprintf(str) { | ||
| 123 | + var args = arguments, | ||
| 124 | + flag = true, | ||
| 125 | + i = 1 | ||
| 126 | + str = str.replace(/%s/g, function () { | ||
| 127 | + var arg = args[i++] | ||
| 128 | + if (typeof arg === 'undefined') { | ||
| 129 | + flag = false | ||
| 130 | + return '' | ||
| 131 | + } | ||
| 132 | + return arg | ||
| 133 | + }) | ||
| 134 | + return flag ? str : '' | ||
| 135 | +} | ||
| 136 | + | ||
| 137 | +// 转换字符串,undefined,null等转化为"" | ||
| 138 | +export function parseStrEmpty(str) { | ||
| 139 | + if (!str || str == 'undefined' || str == 'null') { | ||
| 140 | + return '' | ||
| 141 | + } | ||
| 142 | + return str | ||
| 143 | +} | ||
| 144 | + | ||
| 145 | +// 数据合并 | ||
| 146 | +export function mergeRecursive(source, target) { | ||
| 147 | + for (var p in target) { | ||
| 148 | + try { | ||
| 149 | + if (target[p].constructor == Object) { | ||
| 150 | + source[p] = mergeRecursive(source[p], target[p]) | ||
| 151 | + } else { | ||
| 152 | + source[p] = target[p] | ||
| 153 | + } | ||
| 154 | + } catch (e) { | ||
| 155 | + source[p] = target[p] | ||
| 156 | + } | ||
| 157 | + } | ||
| 158 | + return source | ||
| 159 | +} | ||
| 160 | + | ||
| 161 | +/** | ||
| 162 | + * 构造树型结构数据 | ||
| 163 | + * @param {*} data 数据源 | ||
| 164 | + * @param {*} id id字段 默认 'id' | ||
| 165 | + * @param {*} parentId 父节点字段 默认 'parentId' | ||
| 166 | + * @param {*} children 孩子节点字段 默认 'children' | ||
| 167 | + */ | ||
| 168 | +export function handleTree(data, id, parentId, children) { | ||
| 169 | + let config = { | ||
| 170 | + id: id || 'id', | ||
| 171 | + parentId: parentId || 'parentId', | ||
| 172 | + childrenList: children || 'children' | ||
| 173 | + } | ||
| 174 | + | ||
| 175 | + var childrenListMap = {} | ||
| 176 | + var nodeIds = {} | ||
| 177 | + var tree = [] | ||
| 178 | + | ||
| 179 | + for (let d of data) { | ||
| 180 | + let parentId = d[config.parentId] | ||
| 181 | + if (childrenListMap[parentId] == null) { | ||
| 182 | + childrenListMap[parentId] = [] | ||
| 183 | + } | ||
| 184 | + nodeIds[d[config.id]] = d | ||
| 185 | + childrenListMap[parentId].push(d) | ||
| 186 | + } | ||
| 187 | + | ||
| 188 | + for (let d of data) { | ||
| 189 | + let parentId = d[config.parentId] | ||
| 190 | + if (nodeIds[parentId] == null) { | ||
| 191 | + tree.push(d) | ||
| 192 | + } | ||
| 193 | + } | ||
| 194 | + | ||
| 195 | + for (let t of tree) { | ||
| 196 | + adaptToChildrenList(t) | ||
| 197 | + } | ||
| 198 | + | ||
| 199 | + function adaptToChildrenList(o) { | ||
| 200 | + if (childrenListMap[o[config.id]] !== null) { | ||
| 201 | + o[config.childrenList] = childrenListMap[o[config.id]] | ||
| 202 | + } | ||
| 203 | + if (o[config.childrenList]) { | ||
| 204 | + for (let c of o[config.childrenList]) { | ||
| 205 | + adaptToChildrenList(c) | ||
| 206 | + } | ||
| 207 | + } | ||
| 208 | + } | ||
| 209 | + return tree | ||
| 210 | +} | ||
| 211 | + | ||
| 212 | +/** | ||
| 213 | + * 参数处理 | ||
| 214 | + * @param {*} params 参数 | ||
| 215 | + */ | ||
| 216 | +export function tansParams(params) { | ||
| 217 | + let result = '' | ||
| 218 | + for (const propName of Object.keys(params)) { | ||
| 219 | + const value = params[propName] | ||
| 220 | + var part = encodeURIComponent(propName) + '=' | ||
| 221 | + if (value !== null && value !== '' && typeof value !== 'undefined') { | ||
| 222 | + if (typeof value === 'object') { | ||
| 223 | + for (const key of Object.keys(value)) { | ||
| 224 | + if (value[key] !== null && value[key] !== '' && typeof value[key] !== 'undefined') { | ||
| 225 | + let params = propName + '[' + key + ']' | ||
| 226 | + var subPart = encodeURIComponent(params) + '=' | ||
| 227 | + result += subPart + encodeURIComponent(value[key]) + '&' | ||
| 228 | + } | ||
| 229 | + } | ||
| 230 | + } else { | ||
| 231 | + result += part + encodeURIComponent(value) + '&' | ||
| 232 | + } | ||
| 233 | + } | ||
| 234 | + } | ||
| 235 | + return result | ||
| 236 | +} | ||
| 237 | + | ||
| 238 | +// 返回项目路径 | ||
| 239 | +export function getNormalPath(p) { | ||
| 240 | + if (p.length === 0 || !p || p == 'undefined') { | ||
| 241 | + return p | ||
| 242 | + } | ||
| 243 | + let res = p.replace('//', '/') | ||
| 244 | + if (res[res.length - 1] === '/') { | ||
| 245 | + return res.slice(0, res.length - 1) | ||
| 246 | + } | ||
| 247 | + return res | ||
| 248 | +} | ||
| 249 | + | ||
| 250 | +// 验证是否为blob格式 | ||
| 251 | +export async function blobValidate(data) { | ||
| 252 | + try { | ||
| 253 | + const text = await data.text() | ||
| 254 | + JSON.parse(text) | ||
| 255 | + return false | ||
| 256 | + } catch (error) { | ||
| 257 | + return true | ||
| 258 | + } | ||
| 259 | +} |
src/renderer/src/utils/scroll-to.js
0 → 100644
| 1 | +Math.easeInOutQuad = function(t, b, c, d) { | ||
| 2 | + t /= d / 2 | ||
| 3 | + if (t < 1) { | ||
| 4 | + return c / 2 * t * t + b | ||
| 5 | + } | ||
| 6 | + t-- | ||
| 7 | + return -c / 2 * (t * (t - 2) - 1) + b | ||
| 8 | +} | ||
| 9 | + | ||
| 10 | +// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts | ||
| 11 | +var requestAnimFrame = (function() { | ||
| 12 | + return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) } | ||
| 13 | +})() | ||
| 14 | + | ||
| 15 | +/** | ||
| 16 | + * Because it's so fucking difficult to detect the scrolling element, just move them all | ||
| 17 | + * @param {number} amount | ||
| 18 | + */ | ||
| 19 | +function move(amount) { | ||
| 20 | + document.documentElement.scrollTop = amount | ||
| 21 | + document.body.parentNode.scrollTop = amount | ||
| 22 | + document.body.scrollTop = amount | ||
| 23 | +} | ||
| 24 | + | ||
| 25 | +function position() { | ||
| 26 | + return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop | ||
| 27 | +} | ||
| 28 | + | ||
| 29 | +/** | ||
| 30 | + * @param {number} to | ||
| 31 | + * @param {number} duration | ||
| 32 | + * @param {Function} callback | ||
| 33 | + */ | ||
| 34 | +export function scrollTo(to, duration, callback) { | ||
| 35 | + const start = position() | ||
| 36 | + const change = to - start | ||
| 37 | + const increment = 20 | ||
| 38 | + let currentTime = 0 | ||
| 39 | + duration = (typeof (duration) === 'undefined') ? 500 : duration | ||
| 40 | + var animateScroll = function() { | ||
| 41 | + // increment the time | ||
| 42 | + currentTime += increment | ||
| 43 | + // find the value with the quadratic in-out easing function | ||
| 44 | + var val = Math.easeInOutQuad(currentTime, start, change, duration) | ||
| 45 | + // move the document.body | ||
| 46 | + move(val) | ||
| 47 | + // do the animation unless its over | ||
| 48 | + if (currentTime < duration) { | ||
| 49 | + requestAnimFrame(animateScroll) | ||
| 50 | + } else { | ||
| 51 | + if (callback && typeof (callback) === 'function') { | ||
| 52 | + // the animation is done so lets callback | ||
| 53 | + callback() | ||
| 54 | + } | ||
| 55 | + } | ||
| 56 | + } | ||
| 57 | + animateScroll() | ||
| 58 | +} |
src/renderer/src/utils/theme.js
0 → 100644
| 1 | +// 处理主题样式 | ||
| 2 | +export function handleThemeStyle(theme) { | ||
| 3 | + document.documentElement.style.setProperty('--el-color-primary', theme) | ||
| 4 | + for (let i = 1; i <= 9; i++) { | ||
| 5 | + document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, `${getLightColor(theme, i / 10)}`) | ||
| 6 | + } | ||
| 7 | + for (let i = 1; i <= 9; i++) { | ||
| 8 | + document.documentElement.style.setProperty(`--el-color-primary-dark-${i}`, `${getDarkColor(theme, i / 10)}`) | ||
| 9 | + } | ||
| 10 | +} | ||
| 11 | + | ||
| 12 | +// hex颜色转rgb颜色 | ||
| 13 | +export function hexToRgb(str) { | ||
| 14 | + str = str.replace('#', '') | ||
| 15 | + let hexs = str.match(/../g) | ||
| 16 | + for (let i = 0; i < 3; i++) { | ||
| 17 | + hexs[i] = parseInt(hexs[i], 16) | ||
| 18 | + } | ||
| 19 | + return hexs | ||
| 20 | +} | ||
| 21 | + | ||
| 22 | +// rgb颜色转Hex颜色 | ||
| 23 | +export function rgbToHex(r, g, b) { | ||
| 24 | + let hexs = [r.toString(16), g.toString(16), b.toString(16)] | ||
| 25 | + for (let i = 0; i < 3; i++) { | ||
| 26 | + if (hexs[i].length == 1) { | ||
| 27 | + hexs[i] = `0${hexs[i]}` | ||
| 28 | + } | ||
| 29 | + } | ||
| 30 | + return `#${hexs.join('')}` | ||
| 31 | +} | ||
| 32 | + | ||
| 33 | +// 变浅颜色值 | ||
| 34 | +export function getLightColor(color, level) { | ||
| 35 | + let rgb = hexToRgb(color) | ||
| 36 | + for (let i = 0; i < 3; i++) { | ||
| 37 | + rgb[i] = Math.floor((255 - rgb[i]) * level + rgb[i]) | ||
| 38 | + } | ||
| 39 | + return rgbToHex(rgb[0], rgb[1], rgb[2]) | ||
| 40 | +} | ||
| 41 | + | ||
| 42 | +// 变深颜色值 | ||
| 43 | +export function getDarkColor(color, level) { | ||
| 44 | + let rgb = hexToRgb(color) | ||
| 45 | + for (let i = 0; i < 3; i++) { | ||
| 46 | + rgb[i] = Math.floor(rgb[i] * (1 - level)) | ||
| 47 | + } | ||
| 48 | + return rgbToHex(rgb[0], rgb[1], rgb[2]) | ||
| 49 | +} |
src/renderer/src/utils/validate.js
0 → 100644
| 1 | +/** | ||
| 2 | + * 判断url是否是http或https | ||
| 3 | + * @param {string} path | ||
| 4 | + * @returns {Boolean} | ||
| 5 | + */ | ||
| 6 | + export function isHttp(url) { | ||
| 7 | + return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1 | ||
| 8 | +} | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * 判断path是否为外链 | ||
| 12 | + * @param {string} path | ||
| 13 | + * @returns {Boolean} | ||
| 14 | + */ | ||
| 15 | + export function isExternal(path) { | ||
| 16 | + return /^(https?:|mailto:|tel:)/.test(path) | ||
| 17 | +} | ||
| 18 | + | ||
| 19 | +/** | ||
| 20 | + * @param {string} str | ||
| 21 | + * @returns {Boolean} | ||
| 22 | + */ | ||
| 23 | +export function validUsername(str) { | ||
| 24 | + const valid_map = ['admin', 'editor'] | ||
| 25 | + return valid_map.indexOf(str.trim()) >= 0 | ||
| 26 | +} | ||
| 27 | + | ||
| 28 | +/** | ||
| 29 | + * @param {string} url | ||
| 30 | + * @returns {Boolean} | ||
| 31 | + */ | ||
| 32 | +export function validURL(url) { | ||
| 33 | + const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/ | ||
| 34 | + return reg.test(url) | ||
| 35 | +} | ||
| 36 | + | ||
| 37 | +/** | ||
| 38 | + * @param {string} str | ||
| 39 | + * @returns {Boolean} | ||
| 40 | + */ | ||
| 41 | +export function validLowerCase(str) { | ||
| 42 | + const reg = /^[a-z]+$/ | ||
| 43 | + return reg.test(str) | ||
| 44 | +} | ||
| 45 | + | ||
| 46 | +/** | ||
| 47 | + * @param {string} str | ||
| 48 | + * @returns {Boolean} | ||
| 49 | + */ | ||
| 50 | +export function validUpperCase(str) { | ||
| 51 | + const reg = /^[A-Z]+$/ | ||
| 52 | + return reg.test(str) | ||
| 53 | +} | ||
| 54 | + | ||
| 55 | +/** | ||
| 56 | + * @param {string} str | ||
| 57 | + * @returns {Boolean} | ||
| 58 | + */ | ||
| 59 | +export function validAlphabets(str) { | ||
| 60 | + const reg = /^[A-Za-z]+$/ | ||
| 61 | + return reg.test(str) | ||
| 62 | +} | ||
| 63 | + | ||
| 64 | +/** | ||
| 65 | + * @param {string} email | ||
| 66 | + * @returns {Boolean} | ||
| 67 | + */ | ||
| 68 | +export function validEmail(email) { | ||
| 69 | + const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ | ||
| 70 | + return reg.test(email) | ||
| 71 | +} | ||
| 72 | + | ||
| 73 | +/** | ||
| 74 | + * @param {string} str | ||
| 75 | + * @returns {Boolean} | ||
| 76 | + */ | ||
| 77 | +export function isString(str) { | ||
| 78 | + if (typeof str === 'string' || str instanceof String) { | ||
| 79 | + return true | ||
| 80 | + } | ||
| 81 | + return false | ||
| 82 | +} | ||
| 83 | + | ||
| 84 | +/** | ||
| 85 | + * @param {Array} arg | ||
| 86 | + * @returns {Boolean} | ||
| 87 | + */ | ||
| 88 | +export function isArray(arg) { | ||
| 89 | + if (typeof Array.isArray === 'undefined') { | ||
| 90 | + return Object.prototype.toString.call(arg) === '[object Array]' | ||
| 91 | + } | ||
| 92 | + return Array.isArray(arg) | ||
| 93 | +} |
src/renderer/src/views/.DS_Store
0 → 100644
不能预览此文件类型
src/renderer/src/views/index.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <div | ||
| 3 | + class="photo-container" | ||
| 4 | + :style="{ backgroundImage: `url(${bgImgUrl})`, backgroundSize: 'cover' }" | ||
| 5 | + @click="handleOther" | ||
| 6 | + > | ||
| 7 | + <!-- 相册展开大小 容器 --> | ||
| 8 | + <div id="dragBox"> | ||
| 9 | + <div | ||
| 10 | + v-for="(item, index) in spanBoxList" | ||
| 11 | + :key="index" | ||
| 12 | + class="spinBox" | ||
| 13 | + :id="`spinBox${index + 1}`" | ||
| 14 | + > | ||
| 15 | + <img | ||
| 16 | + v-for="(childen, number) in item" | ||
| 17 | + :src="childen" | ||
| 18 | + @click.stop="handleClick($event, childen)" | ||
| 19 | + /> | ||
| 20 | + </div> | ||
| 21 | + <div v-show="closeImg" class="fixed-img" :style="{ opacity: fixedOpacity }"> | ||
| 22 | + <img :src="fixedImg" /> | ||
| 23 | + </div> | ||
| 24 | + </div> | ||
| 25 | + </div> | ||
| 26 | + | ||
| 27 | + <!-- 翻页按钮 --> | ||
| 28 | + <button | ||
| 29 | + v-show="queryParams.pageNum > 1" | ||
| 30 | + type="button" | ||
| 31 | + class="slick-prev slick-arrow slick-prev-button" | ||
| 32 | + @click="PreviousPage" | ||
| 33 | + > | ||
| 34 | + Previous | ||
| 35 | + </button> | ||
| 36 | + <button type="button" class="slick-next slick-arrow slick-next-button" @click="NextPage"> | ||
| 37 | + Next | ||
| 38 | + </button> | ||
| 39 | +</template> | ||
| 40 | + | ||
| 41 | +<script setup> | ||
| 42 | +import { ref, onMounted, onUnmounted } from 'vue' | ||
| 43 | +import { ElMessage, ElMessageBox } from 'element-plus' | ||
| 44 | +import { getSignatureList, getUseBg } from '@/api/signature' | ||
| 45 | +let imgList = import.meta.glob('../assets/img/*.*', { eager: true }) | ||
| 46 | +let imgArr = Object.values(imgList).map((item) => item.default) | ||
| 47 | +let spanBoxList = ref([]) | ||
| 48 | +let aEls = ref([]) | ||
| 49 | +let radius = ref(760) | ||
| 50 | +let bgImgUrl = ref('') | ||
| 51 | +const closeImg = ref(false) // 是否关闭中间图片 | ||
| 52 | +let outDom = ref(null) | ||
| 53 | +let startX = ref(0) | ||
| 54 | +let startY = ref(0) | ||
| 55 | +let endX = ref(0) | ||
| 56 | +let endY = ref(0) | ||
| 57 | +let tX = ref(0) | ||
| 58 | +let tY = ref(30) | ||
| 59 | +let desX = ref(0) | ||
| 60 | +let desY = ref(0) | ||
| 61 | +const queryParams = ref({ | ||
| 62 | + pageNum: 1, | ||
| 63 | + pageSize: 60 | ||
| 64 | +}) | ||
| 65 | +const server_base = localStorage.getItem('crgx_server') | ||
| 66 | +const fixedImg = ref('') | ||
| 67 | +const fixedOpacity = ref(0) | ||
| 68 | +function randomGroupWithRepeats(arr, groupCount, groupSize) { | ||
| 69 | + // 参数校验 | ||
| 70 | + if (!Array.isArray(arr) || arr.length === 0) { | ||
| 71 | + throw new Error('输入必须是非空数组') | ||
| 72 | + } | ||
| 73 | + if (typeof groupCount !== 'number' || groupCount <= 0) { | ||
| 74 | + throw new Error('分组数必须是正整数') | ||
| 75 | + } | ||
| 76 | + if (typeof groupSize !== 'number' || groupSize <= 0) { | ||
| 77 | + throw new Error('每组大小必须是正整数') | ||
| 78 | + } | ||
| 79 | + | ||
| 80 | + const result = [] | ||
| 81 | + | ||
| 82 | + for (let i = 0; i < groupCount; i++) { | ||
| 83 | + const group = [] | ||
| 84 | + for (let j = 0; j < groupSize; j++) { | ||
| 85 | + // 随机选择元素(允许重复) | ||
| 86 | + const randomIndex = Math.floor(Math.random() * arr.length) | ||
| 87 | + group.push(arr[randomIndex]) | ||
| 88 | + } | ||
| 89 | + result.push(group) | ||
| 90 | + } | ||
| 91 | + | ||
| 92 | + return result | ||
| 93 | +} | ||
| 94 | +spanBoxList.value = randomGroupWithRepeats(imgArr, 3, 20) | ||
| 95 | + | ||
| 96 | +// 设置样式 | ||
| 97 | +function setStyle(delayTime, dom, i, len) { | ||
| 98 | + //给元素加动画 展开 | ||
| 99 | + dom.style.transform = 'rotateY(' + i * (360 / len) + 'deg) translateZ(' + radius.value + 'px)' | ||
| 100 | + dom.style.transition = 'all 1s' | ||
| 101 | + dom.style.opacity = 1 | ||
| 102 | + dom.style.transitionDelay = delayTime || (len - i) / 4 + 's' | ||
| 103 | +} | ||
| 104 | +// 初始化每一层图片动画 | ||
| 105 | +function init(delayTime) { | ||
| 106 | + closeImg.value = false | ||
| 107 | + for (let i = 0; i < aEls.value.length; i++) { | ||
| 108 | + for (let j = 0; j < aEls.value[i].length; j++) { | ||
| 109 | + setStyle(delayTime, aEls.value[i][j], j, aEls.value[i].length) | ||
| 110 | + } | ||
| 111 | + } | ||
| 112 | +} | ||
| 113 | +// 获取图片 | ||
| 114 | +function getSpinBoxDom() { | ||
| 115 | + for (let i = 0; i < spanBoxList.value.length; i++) { | ||
| 116 | + let spinDom = document.getElementById(`spinBox${i + 1}`) | ||
| 117 | + let aImg = spinDom.getElementsByTagName('img') | ||
| 118 | + aEls.value.push(aImg) | ||
| 119 | + } | ||
| 120 | +} | ||
| 121 | +// 改变整体的旋转角度 | ||
| 122 | +function changeRotate(obj) { | ||
| 123 | + // X轴旋转0-180度 | ||
| 124 | + if (tY.value > 180) tY.value = 180 | ||
| 125 | + if (tY.value < 0) tY.value = 0 | ||
| 126 | + // y轴旋转角度不限制 | ||
| 127 | + obj.style.transform = 'rotateX(' + -tY.value + 'deg) rotateY(' + tX.value + 'deg)' | ||
| 128 | +} | ||
| 129 | +// 点击图片展示 | ||
| 130 | +const handleClick = (event, childen) => { | ||
| 131 | + init(1) | ||
| 132 | + fixedImg.value = childen | ||
| 133 | + event.target.style.transform = 'rotateY(0deg) translateZ(0px) scale(3)' | ||
| 134 | + event.target.style.opacity = 0 | ||
| 135 | + fixedOpacity.value = 1 | ||
| 136 | + closeImg.value = true | ||
| 137 | +} | ||
| 138 | +// 点击空白处关闭中间图片并恢复初始 | ||
| 139 | +const handleOther = () => { | ||
| 140 | + if (closeImg.value) { | ||
| 141 | + radius.value = 760 | ||
| 142 | + init(1) | ||
| 143 | + } | ||
| 144 | +} | ||
| 145 | +// 获取图片列表 | ||
| 146 | +const getImgList = async () => { | ||
| 147 | + const signRes = await getSignatureList(queryParams.value) | ||
| 148 | + const imgList = signRes.rows?.map((item) => { | ||
| 149 | + return `${server_base}${item.path}` | ||
| 150 | + }) | ||
| 151 | + spanBoxList.value = randomGroupWithRepeats(imgList, 3, 20) | ||
| 152 | +} | ||
| 153 | +// 上一页 | ||
| 154 | +function PreviousPage() { | ||
| 155 | + queryParams.value.pageNum-- | ||
| 156 | + getImgList() | ||
| 157 | +} | ||
| 158 | +// 下一页 | ||
| 159 | +function NextPage() { | ||
| 160 | + queryParams.value.pageNum++ | ||
| 161 | + getImgList() | ||
| 162 | +} | ||
| 163 | +// 鼠标滚动函数 | ||
| 164 | +const handleMouseWheel = (e) => { | ||
| 165 | + e || e.window.event | ||
| 166 | + let d = e.wheelDelta / 20 || -e.detail | ||
| 167 | + radius.value += d // 旋转半径 | ||
| 168 | + init(1) | ||
| 169 | +} | ||
| 170 | + | ||
| 171 | +// 是否打开填写服务器地址 | ||
| 172 | +const openServer = () => { | ||
| 173 | + const server = localStorage.getItem('crgx_server') | ||
| 174 | + ElMessageBox.prompt('请填写服务器地址以便正常使用', '提示', { | ||
| 175 | + confirmButtonText: '确认', | ||
| 176 | + cancelButtonText: '取消', | ||
| 177 | + inputValue: server, | ||
| 178 | + inputPattern: /^http/i, | ||
| 179 | + inputErrorMessage: '请正常填写地址' | ||
| 180 | + }) | ||
| 181 | + .then(({ value }) => { | ||
| 182 | + localStorage.setItem('crgx_server', value) | ||
| 183 | + ElMessage({ | ||
| 184 | + type: 'success', | ||
| 185 | + message: `填写完成,即将刷新页面` | ||
| 186 | + }) | ||
| 187 | + window.location.reload() | ||
| 188 | + }) | ||
| 189 | + .catch(() => {}) | ||
| 190 | +} | ||
| 191 | + | ||
| 192 | +onMounted(async () => { | ||
| 193 | + if (!localStorage.getItem('crgx_server')) { | ||
| 194 | + return openServer(true) | ||
| 195 | + } | ||
| 196 | + | ||
| 197 | + const signRes = await getSignatureList() | ||
| 198 | + const bgRes = await getUseBg() | ||
| 199 | + bgImgUrl.value = server_base + bgRes.data.path | ||
| 200 | + const imgList = signRes.rows?.map((item) => { | ||
| 201 | + return `${server_base}${item.path}` | ||
| 202 | + }) | ||
| 203 | + spanBoxList.value = randomGroupWithRepeats(imgList, 3, 20) | ||
| 204 | + let spinDom1 = document.getElementById('spinBox1') | ||
| 205 | + getSpinBoxDom() | ||
| 206 | + //相册容器 | ||
| 207 | + outDom.value = document.getElementById('dragBox') | ||
| 208 | + // 开始旋转动画 | ||
| 209 | + setTimeout(() => { | ||
| 210 | + init() | ||
| 211 | + }, 100) | ||
| 212 | + //鼠标滚动事件 | ||
| 213 | + document.addEventListener('mousewheel', handleMouseWheel) | ||
| 214 | + //暂停开始旋转 | ||
| 215 | + function playSpin(yes) { | ||
| 216 | + spinDom1.style.animationPlayState = yes ? 'running' : 'paused' | ||
| 217 | + } | ||
| 218 | + | ||
| 219 | + //鼠标移动事件 | ||
| 220 | + document.onpointerdown = function (e) { | ||
| 221 | + //清除惯性定时器 | ||
| 222 | + clearInterval(outDom.value.timer) | ||
| 223 | + e = e || ewindow.event | ||
| 224 | + //鼠标点击位置 | ||
| 225 | + ;(startX.value = e.clientX), (startY.value = e.clientY) | ||
| 226 | + this.onpointermove = function (e) { | ||
| 227 | + playSpin(false) | ||
| 228 | + //鼠标点击时 停止自动旋转//鼠标点击时 停止自动旋转 | ||
| 229 | + e = e || window.event | ||
| 230 | + //记录结束时位置 | ||
| 231 | + ;(endX.value = e.clientX), (endY.value = e.clientY) | ||
| 232 | + //计算移动距离 并修改角度 | ||
| 233 | + desX.value = endX.value - startX.value | ||
| 234 | + desY.value = endY.value - startY.value | ||
| 235 | + tX.value += desX.value * 0.1 | ||
| 236 | + tY.value += desY.value * 0.1 | ||
| 237 | + changeRotate(outDom.value) | ||
| 238 | + startX.value = endX.value | ||
| 239 | + startY.value = endY.value | ||
| 240 | + } | ||
| 241 | + //鼠标离开时 开始自动旋转 | ||
| 242 | + this.onpointerup = function (e) { | ||
| 243 | + //惯性旋转 | ||
| 244 | + outDom.value.timer = setInterval(function () { | ||
| 245 | + desX.value *= 0.95 | ||
| 246 | + desY.value *= 0.95 | ||
| 247 | + tX.value += desX.value * 0.1 | ||
| 248 | + tY.value += desY.value * 0.1 | ||
| 249 | + changeRotate(outDom.value) | ||
| 250 | + playSpin(false) | ||
| 251 | + if (Math.abs(desX.value) < 0.5 && Math.abs(desY.value) < 0.5) { | ||
| 252 | + clearInterval(outDom.value.timer) | ||
| 253 | + playSpin(true) | ||
| 254 | + } | ||
| 255 | + }) | ||
| 256 | + this.onpointermove = this.onpointerup = null | ||
| 257 | + } | ||
| 258 | + return false | ||
| 259 | + } | ||
| 260 | +}) | ||
| 261 | + | ||
| 262 | +onUnmounted(() => { | ||
| 263 | + document.removeEventListener('mousewheel', handleMouseWheel) | ||
| 264 | +}) | ||
| 265 | +</script> | ||
| 266 | + | ||
| 267 | +<style scoped lang="scss"> | ||
| 268 | +@use 'sass:math'; // 引入 math 模块 | ||
| 269 | +.photo-container { | ||
| 270 | + min-height: 100vh; | ||
| 271 | + touch-action: none; | ||
| 272 | + overflow: hidden; | ||
| 273 | + display: flex; | ||
| 274 | + perspective: 1500px; | ||
| 275 | + background: #111; | ||
| 276 | +} | ||
| 277 | +* { | ||
| 278 | + margin: 0; | ||
| 279 | + padding: 0; | ||
| 280 | +} | ||
| 281 | + | ||
| 282 | +/* perspective指定了观察者与 Z=9 平面的距离,使具有三维位置变换的元素产生透视效果。 */ | ||
| 283 | +@property --d { | ||
| 284 | + syntax: '<angle>'; | ||
| 285 | + inherits: true; | ||
| 286 | + initial-value: 0deg; | ||
| 287 | +} | ||
| 288 | +.moveAni { | ||
| 289 | + transform: rotateY(0deg) translateZ(0px) !important; | ||
| 290 | +} | ||
| 291 | + | ||
| 292 | +#dragBox, | ||
| 293 | +.spinBox { | ||
| 294 | + position: relative; | ||
| 295 | + display: flex; | ||
| 296 | + margin: auto; | ||
| 297 | + transform-style: preserve-3d; | ||
| 298 | + transform: rotateX(-10deg); | ||
| 299 | +} | ||
| 300 | + | ||
| 301 | +.fixed-img { | ||
| 302 | + position: fixed; | ||
| 303 | + top: 0; | ||
| 304 | + background-color: #fff; | ||
| 305 | + width: 180px; | ||
| 306 | + height: 240px; | ||
| 307 | + opacity: 0; | ||
| 308 | + transform: scale(3) rotateY(180deg); | ||
| 309 | + transition: all 0.5s ease-in-out; | ||
| 310 | + img { | ||
| 311 | + transform: rotateY(180deg); | ||
| 312 | + } | ||
| 313 | +} | ||
| 314 | + | ||
| 315 | +#dragBox { | ||
| 316 | + transform: rotateX(-30deg); | ||
| 317 | +} | ||
| 318 | + | ||
| 319 | +.spinBox { | ||
| 320 | + width: 180px; | ||
| 321 | + height: 240px; | ||
| 322 | + animation: spin 100s infinite linear; | ||
| 323 | +} | ||
| 324 | + | ||
| 325 | +@for $i from 2 through 7 { | ||
| 326 | + $top-value: if($i % 2 == 0, 120% * math.div($i, 2), -120% * math.div($i - 1, 2)); | ||
| 327 | + | ||
| 328 | + #spinBox#{$i} { | ||
| 329 | + position: absolute; | ||
| 330 | + top: $top-value; | ||
| 331 | + } | ||
| 332 | +} | ||
| 333 | + | ||
| 334 | +#dragBox img { | ||
| 335 | + transform-style: preserve-3d; | ||
| 336 | + position: absolute; | ||
| 337 | + left: 0; | ||
| 338 | + top: 0; | ||
| 339 | + width: 100%; | ||
| 340 | + height: 100%; | ||
| 341 | + background: #fff; | ||
| 342 | + box-shadow: 0 0 8px #fff; | ||
| 343 | + object-fit: contain; | ||
| 344 | + /*倒影 */ | ||
| 345 | + --webkit-box-reflect: below 10px linear-gradient(transparent, transparent, #0005); | ||
| 346 | +} | ||
| 347 | + | ||
| 348 | +#dragBox img:hover { | ||
| 349 | + box-shadow: 0 0 15px #fff; | ||
| 350 | +} | ||
| 351 | + | ||
| 352 | +/*自动旋转 */ | ||
| 353 | +@keyframes spin { | ||
| 354 | + from { | ||
| 355 | + transform: rotateY(0deg); | ||
| 356 | + } | ||
| 357 | + | ||
| 358 | + to { | ||
| 359 | + transform: rotateY(360deg); | ||
| 360 | + } | ||
| 361 | +} | ||
| 362 | + | ||
| 363 | +.slick-prev-button, | ||
| 364 | +.slick-next-button { | ||
| 365 | + position: absolute; | ||
| 366 | + top: 50%; | ||
| 367 | + bottom: auto; | ||
| 368 | + z-index: 20; | ||
| 369 | + margin-top: -2rem; | ||
| 370 | + height: 4rem; | ||
| 371 | + width: 4rem; | ||
| 372 | + cursor: pointer; | ||
| 373 | + border-radius: 50%; | ||
| 374 | + border-style: none; | ||
| 375 | + background-color: rgb(0 0 23 / 0.1); | ||
| 376 | + padding: 0px; | ||
| 377 | + font-size: 0; | ||
| 378 | + color: transparent; | ||
| 379 | + outline: 2px solid transparent; | ||
| 380 | + outline-offset: 2px; | ||
| 381 | +} | ||
| 382 | +.slick-prev-button { | ||
| 383 | + left: 2.5rem; | ||
| 384 | +} | ||
| 385 | +.slick-next-button { | ||
| 386 | + right: 2.5rem; | ||
| 387 | +} | ||
| 388 | + | ||
| 389 | +.slick-prev:before { | ||
| 390 | + content: ''; | ||
| 391 | + width: 100%; | ||
| 392 | + height: 100%; | ||
| 393 | + background: url('https://www.guet.edu.cn/_upload/tpl/01/39/313/template313/images/leftarrow.png') | ||
| 394 | + center no-repeat; | ||
| 395 | +} | ||
| 396 | + | ||
| 397 | +.slick-next:before { | ||
| 398 | + content: ''; | ||
| 399 | + width: 100%; | ||
| 400 | + height: 100%; | ||
| 401 | + background: url('https://www.guet.edu.cn/_upload/tpl/01/39/313/template313/images/rightarrow.png') | ||
| 402 | + center no-repeat; | ||
| 403 | +} | ||
| 404 | + | ||
| 405 | +.slick-arrow:before { | ||
| 406 | + display: block; | ||
| 407 | + opacity: 0.3; | ||
| 408 | + transition: 0.4s; | ||
| 409 | +} | ||
| 410 | + | ||
| 411 | +.slick-arrow:hover::before { | ||
| 412 | + opacity: 1; | ||
| 413 | +} | ||
| 414 | +</style> |
-
请 注册 或 登录 后发表评论